DEV Community

Discussion on: Create a Component Library Fast🚀(using Vite's library mode)

Collapse
 
merri profile image
Vesa Piittinen

Please note that this will not work with Next.js. You cannot provide CSS import in JS for SSR.

This is the issue you will face: nextjs.org/docs/messages/css-npm

For Next.js the solution would be to import the CSS on Next's side so that it can be loaded in an appropriate location. So you would need to make boilerplate file for every entry in a Next app so that the CSS would be only loaded when the component is going to be used to get the benefit of only loading what is needed.

Collapse
 
receter profile image
Andreas Riedmüller

Looks like it works since Next.js 13.4:

github.com/vercel/next.js/discussi...

Following up on this: the App Router is now stable with 13.4!
CSS files can now be imported inside any layout or page, including styles from external npm packages.
nextjs.org/blog/next-13-4

github.com/vercel/next.js/discussi...

Collapse
 
receter profile image
Andreas Riedmüller • Edited

Update
Importing CSS from external npm packages works since Next.js 13.4:
github.com/vercel/next.js/discussi...


Hi Vesa,

this is very interesting, thank you! I did not yet try to use this approach within a Next.js project.

You can also remove the inject-css plugin (and the sideEffects from the package.json)

Having done that you should be able to just import the generated css file (dist/assets/style.css) inside your Next.js project. But of course you will not have the css treeshaking advantages with this approach.

I published a branch with this approach here: github.com/receter/my-component-li...

Generally there are answers for the quetions raised in the linked issue:

Should the file be consumed as Global CSS or CSS Modules?

As Global CSS.

If Global, in what order does the file need to be injected?

The order of the individual files is determined by the order they are imported inside the libraries main file. And the order in the consuming application should not matter.

Maybe it would be possible to write a Next.js plugin enabling this.

What do you think?

Collapse
 
merri profile image
Vesa Piittinen • Edited

You are incorrect about the order in consuming application not mattering. Because people tend to write overrides. Or they may use things from multiple sources and mix their use together. And then loading order, or order of the styles as they exist in the document, does matter.

For an example, in my past experience Styled Components was notorious for becoming a true troublemaker with style issues. Because if you had multiple different components from different libraries + your own local Styled Components + SC was primarily designed for the React SPA world, but we entered the "SSR is a better idea" era, and then you would just end up having awfully hard to debug style issues. CJS to ESM transition also caused it's own share of issues.

And Styled Components is all about style injection. I think they've now got it somewhat in order with their v6 release, but we're of course on our way away from Styled Components as runtime CSS-in-JS is fundamentally bad for performance.

Then there are even issues with CSS Modules in Vite. Vite does not keep the modules on their own but instead mashes composes into the consuming output file. So this means the same class name definition can be loaded again later, and if you do any property overrides then BOOM now you have issues that flicker depending on which order the CSS is loaded. So you just have to know not to ever write property overrides when using CSS Modules.

Anyway, I think doing any sort of import './style.css' or injectStyles will always be fundamentally wrong. You must be able to control the loading order of the CSS and the only thing that can reliably do that is the consuming app / framework.

Thread Thread
 
receter profile image
Andreas Riedmüller • Edited

Yes you are absolutely right about overrides. If you assign classes to the exported components it makes a difference if these classes come before or after the classes provided by the component library.

In this case the component library styles have to be imported before to have a lower specificity. And this is only guaranteed if the library is imported before any other styles.

As you correctly mention, the consuming application is responsible for ensuring the correct order of css.

You should be able to ensure this if:

  • Your libraries CSS is imported before any other local js or css
  • You never assign a CSS class imported from a js library to a component
  • Only one component library is allowed to influence global styles
Thread Thread
 
receter profile image
Andreas Riedmüller

Then there are even issues with CSS Modules in Vite. Vite does not keep the modules on their own but instead mashes composes into the consuming output file.

Do you have more info like a github issue on this?

So you just have to know not to ever write property overrides when using CSS Modules.

Can you ellaborate on this?

Thread Thread
 
merri profile image
Vesa Piittinen

I guess the issue answers the latter question: github.com/vitejs/vite/issues/7504

Thread Thread
 
receter profile image
Andreas Riedmüller

Thanks for the link!

The reason I am working on this topic and wrote the article is that I am trying to find the best solution to build a component library. I don't like CSS in JS that much and I am convinced that a stylesheet based approach is the way I want to go. I will think about the style ordering and might publish another article on this soon.

I wrote you on LinkedIn, if you are interested in having a discussion about this topic I would be more then happy to speak/write to you.

Thread Thread
 
receter profile image
Andreas Riedmüller

The advantage of handling the style imports inside the library is (obviously) that you don't need to manually import styles. This is not a big issue if it is just one stylesheet for a library. But if you only want to import the styles for components you actually use I see no other really satisfying solution.

I do have some rough ideas though…

Thread Thread
 
merri profile image
Vesa Piittinen

Yeah, the more you want to provide benefits to the user (= well splitting code, tree shaking, only styles you need) the nastier the management comes for the component library consumer.

We are in transition to CSS Modules based component library at work and the design of that is basically a bunch of createXYZ() functions with the sole role of passing in the CSS Modules as loaded by the consuming app. It could be a little better by instead just creating the components and making them use a hook that would provide the styles from context, and then have a context provider. Although I guess then you'd end up with the issue that all the CSS would be loaded at once.

One reason for doing things like that is we also use some of the very same CSS Modules directly. It does provide benefits as you can choose to work without a component just using the styles to classes, use composes to extend in a local module, get unified breakpoints for both CSS and JS, or use the convenient components when you don't need that much power (although our main layout component and text component are both very versatile).

The problem I have is that only our team really needs the full intelligent splitting. Other teams work on what are more like in-house SPAs, so for them there is a friction in moving from Styled Components to CSS Modules. They don't like the boilerplate and they don't need to consider app boot times. So this is one reason I found your article.

Thread Thread
 
receter profile image
Andreas Riedmüller

I have the feeling that a fundamental problem is that the order of CSS is defined by the order it is imported in javascript. Which is kind of by chance because sometimes a component is imported earlier and sometimes later.

There are also not so easy to fix problems with dynamic importing github.com/vitejs/vite/issues/3924

I created a branch with a very simple example that demonstrates the order issue if anyone is interested in an example: github.com/receter/my-component-li...

What I did not expect: When importing ANY component from the library, all other imported CSS, even for components imported later on, will be at the same position.

Collapse
 
jamestbaker profile image
James Baker

Hi, Vesa,

I'm not having this problem with Next. The doc you provided says that this issue occurs when the source files are consumed instead of the build files. Are there other circumstances that produce this problem that we should watch out for?

My steps, in a monorepo with NPM workspaces:

  1. Followed instructions here (in a directory inside packages).
  2. Installed Next 13.5.5 (in a directory inside apps).
  3. Added a very simple component from the custom library to Next's page.tsx. This file doesn't contain the 'use client' directive, so I believe it's SSR.
  4. Tested locally only — didn't deploy.

Results:

  • The admittedly simplistic component renders as expected.
  • No messages in the Terminal or the browser console.
  • (Storybook set up in the component library also renders the component as expected.)

Question:
Are there cases in which we would consume the build files and still encounter this issue?

Collapse
 
merri profile image
Vesa Piittinen

I don't have experience of having a library as a part of a monorepo setup, but I would guess it will be handled more like a part of the local project, not as an external dependency.

The problem exists when you release the built library as a npm package and then try to import it, so it will be within node_modules.

Collapse
 
receter profile image
Andreas Riedmüller

I added a faq section about using Storybook: dev.to/receter/how-to-create-a-rea...

Collapse
 
receter profile image
Andreas Riedmüller
Collapse
 
receter profile image
Andreas Riedmüller • Edited

I just created a new nextjs project and it seems to work quite fine.

Here is the my repo: github.com/receter/my-nextjs-compo...

What version of nextjs were you using? I would like to reproduce.

Also can you try if this works for you:

In next.config.js:

+module.exports = {
+  transpilePackages: ['awesome_module'],
+};
Enter fullscreen mode Exit fullscreen mode