If you are managing multiple React applications and want consistency across your user interfaces, sooner or later you'll find that you need a compo...
For further actions, you may consider blocking this person and/or reporting abuse
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.
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 thepackage.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:
As Global CSS.
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?
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'
orinjectStyles
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.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:
Do you have more info like a github issue on this?
Can you ellaborate on this?
I guess the issue answers the latter question: github.com/vitejs/vite/issues/7504
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.
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…
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.
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.
@merri If you are interested I have written an article on CSS order of appearance. And in the meantime I have started to use CSS Layers to prevent these issues.
Looks like it works since Next.js 13.4:
github.com/vercel/next.js/discussi...
github.com/vercel/next.js/discussi...
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
: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:
packages
).apps
).page.tsx
. This file doesn't contain the 'use client' directive, so I believe it's SSR.Results:
Question:
Are there cases in which we would consume the build files and still encounter this issue?
dev.to/receter/comment/2ag7m
I added a faq section about using Storybook: dev.to/receter/how-to-create-a-rea...
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
.The guide discusses essential technical details, such as TypeScript integration, CSS modules, tree shaking, and peer dependencies. It covers these aspects while keeping the content concise and understandable.
Great article really helpfull! 🥳
Keep up the awesome work 💪!
But my .stories files gets included in the bundle.
I tried to exluded them by extending the glob.sync:
glob.sync('stories/**/*{!(*.stories).ts,!(*.stories).tsx,!(*.stories).js,!(*.stories).jsx}')
But it did not work.. 🤔
Any idea why this is happening?
Thx!
Edit:
Fixed it, I was stupid and included the * in the begining:
glob.sync('stories/**/{!(*.stories).ts,!(*.stories).tsx,!(*.stories).js,!(*.stories).jsx}')
But it still includes the stories.d.ts... 😞
Edit 2:
Sorry I'm asking to fast 😅, working glob that exclude stories files:
'stories/**/{!(*.stories|*.stories.d).ts,!(*.stories).tsx,!(*.stories).js,!(*.stories).jsx}'
Edit 3:
I initially thought it worked. But stories.d.ts still included.. :/
I was successful with this:
glob.sync('lib/**/*.{ts,tsx}', { ignore: 'lib/**/*.stories.tsx'})
Thx man!
That worked great 🤩!
I have added a FAQ section about using Storybook: dev.to/receter/how-to-create-a-rea...
Hi Js4,
why do you have your stories in the library? I think it would be better to place them outside the
lib
folder. Then you shouldn't have to exclude them.Andreas, thank you for sharing such a well thought out and detailed explanation of how to set up a component library. This subject isn't easy, but you did a great job explaining everything. I took inspiration from your setup and expanded it to include support for
esm
andcjs
bundles in addition to subpath exports setup for explicit import references for anyone interested. You can find it here.Awesome, thanks for sharing!
Not sure why you want a cjs version that imports css files. Do you have a use case where a bundler can import css files but not esm? If not it wouldn't make sense to me.
Good question, this was more about compatibility between our legacy and newer codebases. The goal was to implement a bundler config that would operate for both situations so that when we do migrate to a fully esm system, the conversion would be painless. Probably not necessary in most cases considering cjs doesn’t gain any tree shaking benefits typically, but an activity I enjoyed doing to see if I could get both esm and cjs bundle outputs working.
Hello!
I found and issue with the tsconfig files in the latest vite version (5.4 at the time of this comment). While previous versions of create vite would generate a tsconfig.json and a tsconfig.node.json, the latest version creates an additional tsconfig.app.json making it no longer possible to make the steps in this tutorial work by extending any of these files in the tsconfig-build.json. What solved the issue for me was deleting all three files and copying tsconfig.json and tsconfig.node.json from an older vite project. Hope this helps!
I created a new
tsconfig.build.json
file and extended tsconfig.app.json, seems to work fine:{
"extends": "./tsconfig.app.json",
"include": ["lib"]
}
Also, with the new vite version with tsconfig.app.json, for building type definitions I had to add tsconfigPath:
dts({ include: ['lib'], rollupTypes: true, tsconfigPath: 'tsconfig.app.json' }),
I would suggest this:
Do not include the
lib
folder intsconfig.app.json
, instead create atsconfig.lib.json
like so:Then add
tsconfig.lib.json
to thereferences
array in yourtsconfig.json
.And set the build script to:
I made a branch with the latest vite version (5.4.4 at the time of writing): github.com/receter/my-component-li...
Here is a summary of what I had to change for the building the types:
update to vite-plugin-dts@4
add tsconfig path to dts config
This outputs types to a
lib
subfolder in dist. So you need to update your package.json to point to the correct type entry: "./dist/lib/main.d.ts" (or you could use rollupTypes: true)Also I manually had to install
ajv
because of an issue: stackoverflow.com/questions/787352...In this branch I also use the new package.json
exports
feature and "self reference" the library in App.tsx.I will try to find time to update my article accordingly, but for now this branch might help you.
I just found out that the issue with
ajv
was because I (unintentionally) hadlegacy-peer-deps=true
in my global npm config on my other computer. I have updated the FAQs of this article with more details.Thank you for your work. It helped me to move from Rollup to Vite.
I just had to make some changes related to my implementation, but it worked with most of your lessons.
If you are interested, here is the link to the issue where I collected all the knowledge from this experience.
Cool, glad to hear it helped you.
And I am very interested in your component library and architecture, I will have a closer look. I already noticed you use Hygen for code generation, I didn't know about that but I need this :)
I'm also working on a component library at the moment, if you're interested in sharing thoughts and experiences I'd be up for it, maybe in Discord or something.
Thanks for sharing!
Thank you for your work and your words, Andreas. My Discord is
nicolasomar
if you want to talk sometime.I am glad to help in any endeavor you may need a hand.
Hy thank you so much for the guide. I do have a question though.
if in my library i'd like to achieve an import like: d
how can i do it.
do i have to pass an array of entries like?
my structors is
lib/assest
lib/hooks
etc
So sorry for the dumb question
Hi Federico,
You should be able to do that with subpath exports.
First create a file
assets.ts
alongsidemain.ts
and export what you want:Then in
package.json
you can define the exports like so:You can read more about this here: nodejs.org/api/packages.html
Let me know if this works for you.
For multiple entry points like this, do we need to update vite config lib.entry as well as the rollupOptions.output?
Yes, either that or you could also do multiple build steps. Like a different vite config file for each export and then run
vite --config ./vite-build-main.config.ts build && vite --config ./vite-build-assets.config.ts build
for example.Thank you so much @receter. I do have another questions. I'm using this set(more or less) for my RN lib.
I having trouble with react-native-reanimated library. I created my animated Comp in the library and set reanimated as a external lib and peer, i can build without problems my lib but then when i try to import my comp the app crash. I don't get the problem to be honest. Do you have any suggest? thank you in advance
Hi Frederico,
do you get any kind of error message? If you can provide a repo where I can reproduce your issue, I am happy to help you find it.
That's my biggest problem, i don't get an error. the app just crash. i gonna create a simple repo and share it with you. Really appreciate your help
@receter i'll add here the link for the repo example
👍 Let me know when it is ready!
Hey, i added in the comment the link for the lib repo. do you need also a simple repo app example? Thank you so much
@receter Sorry for the ping but maybe you didn't get a notification
I did, will do that today.
Just finished writing a guide to automatically publish packages if your are interested. And I will now read the code in your repo 😉
@federbeije I opened an issue in your repo, we can continue to discuss there.
GREAT post. I was struggling with this. I especially overlooked
react/jsx-runtime
as an external dep as was getting frustrated why it was being included in the build. So you saved me a fair bit of time there.The CSS splitting I would have figured out.... eventually. But after a lot of frustration and maybe hours of searching. So, again, big thanks. Followed.
Hi Ben, very glad I saved you some time and thanks for letting me know. That was the main reason for me to write this article. It took me some time and frustration to get it all working and I wanted to share my learnings so that others have a better starting point.
If you find anything that can be improved or needs to be updated let me know!
Hi Andreas, thank you for this really useful post.
I wanted to comment that I am having an issue when compiling, on the dist folder, instead of getting "main.js", "main.d.ts", and "components", I am getting this structure:
Here, under "dist/components/Button" for example, I have the index.js with the compiled code. And under "dist/lib/components/Button" I have the index.d.ts with the declarations
So I have to do nested imports to get to the correct main.d.ts file and that isn't ideal.
Do you have any thoughts on what may be causing this problem? thanks in advance
It was due to me importing files from outside lib
good to know, glad you found the issue
Hi Andreas,
thanks for excellent article and sample code! I followed your entire article and everything works perfectly then I tried npm link on the project root and ran npm link @username/my-component-library on a new project that uses the library. On this project I tried to debug the Button component (with devTools of Chrome) but I only see the compiled code (dist/components/Button index.js). Is it possible to view the source code in debug?
Thanks :)
You're welcome, I'm glad you like the article. First, if you run
npm run dev
you can start the dev server and debug your components locally. Further you can install something like react-cosmos or storybook locally to test and debug your components.If you really need/want to install your package with
npm link
to test it in another project you can try if building with a sourcemap solves your problem: vitejs.dev/config/build-options#bu...Let me know if that helps you, cheers!
Thank you, that really helped me ❤️
Really great tutorial! Thanks!
I'd like to add StoryBook to the component library. Is this straight-forward or is there anything special I have to consider?
I used
npx storybook@latest init
to install Storybook, which seemed to work fine. I can start Storybook in dev mode withnpm run storybook
, howevernpm run build-storybook fails with an error:
`=> Failed to build the preview
TypeError: Cannot convert undefined or null to object
at Function.values (<anonymous>)
at configResolved (file:///C:/prj/react/cp-web-react-components/node_modules/vite-plugin-dts/dist/index.mjs:582:100)
I looks like it has to do with the modified vite setup...
Hi Peter,
The issue comes from using the plugin
libInjectCss
with Storybook. You should not need this plugin to build Storybook. You can remove it from the config when building Storybook.Add this to
.storybook/main.ts
add this:Hi Andreas,
that did it! Thanks a lot!
This is really a nice setup for our company component library!
Here is the branch with Storybook: github.com/receter/my-component-li...
Hi Peter, thanks for your comment. I will try to find some time over the weekend and create a branch with storybook.
I have problem that the generated main.js has imports to 3rd party libs.
Generated main.js:
import { Button as i } from "./components/Button/Button.js";
import "react/jsx-runtime";
import "tailwind-variants";
import "@nextui-org/react";
export {
i as Button
};
But my main.ts looks like this:
export {Button} from './components/Button/Button'
I have all three of them in the exclude block:
external: ['react','react/jsx-runtime',"tailwind-variants","framer-motion",RegExp("^(@nextui-org/).+")],
Why do Vite add them to the final build?!
I had removed this plugin in the Vite.config:
import {libInjectCss} from 'vite-plugin-lib-inject-css'
Because I dont use css files I use Tailwind classes.
But when I add this lib back the imports are not added.. 🤯
I have no idea why.. but at least it works....
Interesting, I will investigate this. Do you have a public repo to reproduce?
Hi man!
I do not have a repo, this package is for internal use only.
But I created a sandbox for you to test with :). I tested and when you remove the libInjectCss from vite.config main.js gets the imports.. you run
pnpm build-sb
to build it andpnpm sb
to start the project.Sandbox Project
could u guys fix this issue?
Tailwind needs Postcss using Vite so add it like so to your vite.config.ts
Put it in the external and output of rollup based on your needs.
Thanks for this article, I followed each step and works perfectly!!
Is there a possibility to make this work for sveltekit? I am trying to adapt this to it, but it is not working as spected.
I would like to try Svelte… What did not work as expected?
When I import a component in another project, I get the this error with vitest when running tests:
I am facing same issue, anyone have solution??
This was super helpful!
I'm still deciding between emotion or styled-components or regular sass but this had everything I needed to set up my vite component library
Thank you Andreas!
how do exclude .test / .spec files from the output?
Figured it out. You can add this to dts:
and this to the rollupOptions:
Thank you this is very helpful. I cloned your latest repo revision 1.
What I would like to do is create my own custom MUI based components for this library. I tried npm install @mui/material, added a new folder lib/components/MuiButton with an index.tsx of just a very simple example:
import Button from '@mui/material/Button'
export function MuiButton() {
return(
<Button>asdf</Button>
)}
then your main.ts
export { Button } from './components/Button'
export { Input } from './components/Input'
export { Label } from './components/Label'
export { MuiButton } from './components/MuiButton'
and import MuiButton into the App.tsx
After an
npm run build
andnpm run dev
, I cannot get my MuiButton into the App. It gives a nonsense error in the console.DefaultPropsProvider-BfsOEd9N.js:1837 Uncaught TypeError: _n is not a function
at Fn (DefaultPropsProvider-BfsOEd9N.js:1837:13)
Any advice here would be VERY MUCH appreciated!!
thanks
J
Does it work if you add "@mui/material/Button" to rollupOptions/external? (and have it in the dependencies)
Do you have your code in a public repo?
Hi Andreas,
Thanks for the reply. In creating an example to go into a public repo, I started over from scratch and somehow managed to get it working without needing your suggested change above. shrug I'm still not sure yet what I did differently, still investigating.
NOW this has given me a good example to solve the main problem I am trying to solve, which is passing a MaterialUI theme from the "parent" App to the "child" 3rd library ui elements. But this is a problem outside of the scope of your article :)
thanks,
Jon
Are you sure it is correct to use
peerDependencies
for react here? Based on the npm doc, the package is expected to not import a peerDependency.peerDependencies
should be read as: "dependencies if this project is a plugin". And a component plugs in to another project--it's not a project itself. So it relies on its host's plugins. peerDependencies says "as a plugin I want my host to have these deps" (which is why it's important to test with a variety of versions and be very lenient with the sem-version control).Yes, using
peerDependencies
for React is appropriate here.peerDependencies
should be used for packages that the consumer is expected to provide. This ensures that React is not automatically installed by the library, preventing multiple versions of React from existing in the same project, which is essential for React to work.use rollupTypes: true in your vite.config.ts and the typescript version 5.4.2 if you are doing this now. As rollupTypes will give you the required types file and the dts package work with the older typescript
`
import react from '@vitejs/plugin-react'
import { resolve } from 'path';
import dts from 'vite-plugin-dts'
// vitejs.dev/config/
export default defineConfig({
plugins: [
react(), dts({ include: ['lib'], rollupTypes: true })
],
build: {
copyPublicDir: false,
lib: {
entry: resolve(__dirname, 'lib/main.ts'),
formats: ['es']
}
}
})
`
So 3 questions.
Hi Bica,
Thanks for you comment!
copyDtsFiles
and all your d.ts files will get copied to the output.I also noticed that for each d.ts file an empty d.js file is created. This is not wanted and you can fix it by changing the glob to
lib/**/!(*.d).{ts,tsx}
:Here is a branch that uses global types: github.com/receter/my-component-li...
You need to export all components that you want to expose to the outside. If you have a component that is only used internally you don't have to include it in
main.ts
.But not all components will end up in you final application bundle, only the components that you import will.
To use SASS you need to first install it
And then you can just rename files to for example
styles.module.sass
and everything should compile fine.If you need a global stylesheet you can create a
lib/global.sass
and import it inmain.ts
:Here is a branch that uses global sass for the Button and a global stylesheet: github.com/receter/my-component-li...
Does this answer your questions sufficiently?
I had noticed my index.d.ts becoming converted to index.d.js and being empty.
On the export question, our setup differs a bit from the standard react component folder structure....i.e how I'm doing it is basically..
So in main.ts I guess I have to do
Yes? I get an error if I don't specify as default because I don't have my components as index.tsx files.
Then we are using Bootstrap. They haven't updated their sass to support @use and @forward, they still use @import so I have to be careful when pulling it in due to increased file size and duplication. What I was doing was running the sass command on build so a css was compiling and added to dist so the end developer could pull it in if they chose (from a theme perspective). However I want to play around with your solution inside the main.ts
Per the above I have to be careful adding a sass import to the top of individual components because if an input is used multiple times per page, I fear the reference to the styles will be repeated....I am not sure if that is valid in react. But if that file has to reference our bootstrap sass for their utility methods (i.e. color or fontsize) I think it will try to pull in bootstrap again due to the import issue I mentioned.
I want to have a playground locally to test the package but I don't want my local testing to be a part of our commit, just the package library.
For exporting your components you should be able to:
But inside of toggleWhatever.tsx you need to have a named export like:
If you for some reason need to export the components as default you can also do it like this which is easier to read in my opinion:
Not 100% sure what you mean with the SASS, but if you import a button multiple times the styles will be included just once.
CSS duplication issues can also be handled downstream by your bundler.
If you use a global CSS library you should be fine by just adding an import in
main.tsx
In this case the CSS library will end up in your application as soon as one component of the library is used. You could also add an import for the library to every component that needs the library, so the CSS library is only bundled if one of these components is imported.Yes it does! I'll check out your fixes and if they will work with my changes.
This is a fantastic article and gave me the confidence to rebuild my personal component library, cross-country, with vite and upgrade to storybook 7!
I also wanted to consume the library in a NextJS app but instead of separating the CSS and JS, I wanted to compile them into a single JS file which also eliminates the global import css error and in theory one wouldn't have to worry importing any third-party css into the _app file.
NextJS Update
I'm now using this recommended rollup-plugin-css-only approach, and have decided that I'm ok with including simple instructions to simply import the single bundled css file into the NextJS _app.js file since you only have to do this once.
Instead of vite-plugin-lib-inject-css, I'm experimenting with vite-plugin-css-injected-by-js which seems to be what I want but isn't quite working yet. While I have no css errors I still don't see the css being applied when I consume the published library components. They appear unstyled which definitely isn't good. This is the recommended setup:
When I try
vite-plugin-lib-inject-css
with my library, publish it, and then consume it in a non-nextjs app like the Vite react stackblitz example, it works! I can import my components and their css style is maintained.This is the ChatGPT explanation:
The vite-plugin-css-injected-by-js and vite-plugin-lib-inject-css are both Vite plugins that help manage CSS in your JavaScript bundles, but they serve slightly different purposes and work in different ways.
The vite-plugin-css-injected-by-js takes all the CSS generated by the build process and adds it via JavaScript. This results in a single JavaScript file, as the CSS file is not generated separately. This plugin is useful for those who want a single JavaScript file and can be configured to execute the CSS injection before or after your app code
On the other hand, vite-plugin-lib-inject-css is designed to work specifically in library mode. It injects CSS at the top of each chunk file using an import statement. This plugin supports multi-entries build and is especially helpful when building component libraries
Hm, I think injecting css has it's own problems like no control over the order of appearance. Also no further CSS processing is possible downstream... But this might be no problem in you use case.
What do you mean with "you don't have to worry importing any third-party css into the _app file."?
Thank you very much for this tutorial. I am using this to build a production app in my workplace, The issue I am going through is something like this.
Think that a button components is using types which are coming from icon component also.
So in the button component there will be a import like
import iconProps from "../icons/types"
but after building the project all the t.ds files are in root level but still the button.js build file has a import import iconProps from "../icons/types" which is not resolving since there is no icon folder inside the dist folder now. Seems like even I managed to add all the d.ts files in root level the js files using them are not resolving the path properly.
I am using multiple entry points for dts plugin as well.
So I managed to add d.ts files of some components on the relevant folder without ading them to root, which is not the ideal way I guess
Hi Lanka,
Thanks, glad you like the article. Could you create a repo with a minimal example to reproduce this? Can you ellaborate on why you need this config for dts? Why don't you exclude the story files in the rollup config?
And here is a branch on how I would recommend to setup storybook: github.com/receter/my-component-li...
Thank you for this amazing tutorial on setting up a React component library. I created a component library, but component props were not showing in IntelliSense. After reading your post, I found my mistake: I exported both the es and umd builds, which caused the issue. After using only the es build, the problem was resolved.
Thanks again.
Thank you very much for the detailed tutorial - it is the best I could find in the internet for this subject.
Vite 5 introduces changes that does not align perfectly with this tutorial (that uses vite 4), most of the things do match to what is written here, but there are few minor changes.
So I created an updated version that uses Vite 5, for the public to enjoy:
Npm:
npmjs.com/package/template-react-c...
GitHub:
github.com/ben-sembira-1/react-npm...
Note: It does not contain css modules and storybook, but I assume those hasn't changed between vite versions. If someone tries and faces a difference, please make a pull request or at least let me know!
Hey! This is great and got us almost there in terms of building a new standalone consumable component library, so much thanks!!!
The one issue I'm running into is react compatibility - we spun up a new react library using react 18, however, the consumers are react 17, and unknown if/when we can bump those versions up.
Would we need to pin the react version on the library to 17? I'm not sure how other libraries do it, tbh, but running into a roadblock here, unless we revert the version to react 17 on the library; how would we set this up so that it's compatible with both React 17 and React 18 applications?
H Victor,
glad to hear my article was of help. I would try to go with:
I don't know if removing
react/jsx-runtime
is necessary, did you try if it works without removing?And of course you need to take care your component code works with both, React 17 and 18.
If you can, I would advise you to update your consumers to React 18 instead, if possible.
I ended up doing this instead (I saw this range used in another popular library)
and yes, tested it without including
react/jsx-runtime
and this is the error i got:adding it back in resolved this error
FYI - updating the
peerDependencies
to something compatible to 17, as well as removing thereact/jsx-runtime
from the external rollup options ended up fixing this for me. not sure if there's a better way!Any ideas on why I would get an error TS2307: Cannot find module './styles.module.css' or its corresponding type declarations?
Do you have a repo to reproduce? Sometimes I get this in VSCode and in this case a restart of VSCode helps 😅…
It's not finding the vite environment types. In the repo, this is performed by referencing the vite-env.d.ts file. In the comment I just added above I have suggested another, cleaner way to reference the types: -
dev.to/u4ea/comment/2eo2i
Hello!
Thanks for the tutorial! It is great! Unfortunately I was not able fully finish it. After I added glob config to my vite.config.ts, the "npm run build" started to return an error:
failed to load config from ..../vite.config.ts
error during build:
file:///..../vite.config.ts.timestamp-1694619764411-22e8f4327550d.mjs:7
import glob from "file:///..../node_modules/glob/dist/mjs/index.js";
^^^^
SyntaxError: The requested module 'file:///..../node_modules/glob/dist/mjs/index.js' does not provide an export named 'default'
at ModuleJob._instantiate (node:internal/modules/esm/module_job:122:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:188:5)
at async DefaultModuleLoader.import (node:internal/modules/esm/loader:228:24)
at async loadConfigFromBundledFile (file:///..../node_modules/vite/dist/node/chunks/dep-df561101.js:66235:21)
at async loadConfigFromFile (file:///..../node_modules/vite/dist/node/chunks/dep-df561101.js:66086:28)
at async resolveConfig (file:///..../node_modules/vite/dist/node/chunks/dep-df561101.js:65682:28)
at async build (file:///..../node_modules/vite/dist/node/chunks/dep-df561101.js:47852:20)
at async CAC. (file:///..../node_modules/vite/dist/node/cli.js:822:9)
I use npm version 9.8.0, nvm version 20.5.1, vite version
Best Regards,
Andrei
Hi Andrei,
thanks for your comment, I am glad you like the tutorial!
I guess you need to do a named import:
It was a "typo" in the article, I have updated it accordingly, thanks for your feedback!
Hello Andreas! Thanks a lot for the update! now everything works just perfectly!
I tried installing the
vite-plugin-lib-inject-css
on my Mac M1 (Sonoma 14.4.1) but got error@ast-grep/napi-darwin-x64@npm:0.21.4: No candidates found
For those with the same error, I've found this one that worked for me: npmjs.com/package/vite-plugin-css-...
Great post, thx
One question I have:
If my component depends on another npm module (say lodash), when I run vite...the dependency would be bundled into my index.js file....
So do I have lodash listed as a dependency in package.json? If so, wouldn't that mean consumers would transitively install lodash unnecessarily?
So seems like I should either:
external
Hi Kyle, sorry for my late reply. Here is an answer to a similar question if you are still interested: reddit.com/r/reactjs/comments/15o4...
My recommendation for your case would be to add it to dependencies and define it as external.
I made a branch to show how I would do it here:
github.com/receter/my-component-li...
thanks for excellent article and sample code. The import CSS does not get transpiled to JS script. Thus I get run time error (when I use the library in another project).
For eg. Button/index.js has the following:
import "../../assets/index4.css";
Obvisiouly, this will not work in the brower. Should'nt this get transpiled as CSS Module ?
If the consuming environment does not have a bundler setup that supports CSS imports please see this answer: dev.to/receter/comment/2a198
You can disable these CSS imports and generate a CSS file that you can import sepparately.
thank you. Got it!
I have a interface i export in a component. When i run build i get a error that it can not import the interface in the main.ts. Why is that?
Hi, can you provide a repo to reproduce this? What is the error message?
Have a component in lib/components/date-input/
It has a date-input.component.tsx file that includes a interface:
It also has a index.ts file with the following code:
When I run build I get this error:
Is it maybe related to this issue? github.com/rollup/plugins/issues/71
If you can share a repo to reproduce the issue, I can have a look.
Terrific article, thank you!
I had an issue with changes in my library not being reflected in the consuming project. It turned out it was because there was a cached version in node_modules/.vite
Deleting that folder solved the problem. For more information on the cache, see Vite: dep-pre-bundling - caching.
Hope that helps someone else!
I have some .ts files which contains interfaces and types in it. After I build library those are getting converted into .d.ts files (which is expected). But I'm not able to import types from these d.ts files into my Client Application. getting below error.
[plugin:vite:import-analysis] Failed to resolve import "../dist/types/IHeaderConfigs" from "client/main.tsx". Does the file exist?
Hi Pankaj, I can help you if you provide me with a repo to reproduce this issue.
I would need more detailed information to give you an helpful answer. Do you have a repo were this happens? Does it also happen when you install my demo npm library? (link at the bottom of the article) What bundler setup are you using?
Hi Andreas, great article!
I have an issue with the global css import, I have imported global styles in my main.ts file and when the library is compiled I can see those clases added into my /assets/main.css file that is imported into the compiled main.js.
The problem is when I import the library in a create-react-app project, it ignores everything that is inside main.css, It only works if I directly import it into the project like: import "mylibrary/dist/assets/main.css".
Do you know how to solve this issue?
Hi Jonn,
do you import anything in the create-react-app project or did you just install the plugin? The import will only work if the
main.js
file is somehow included in the build. For example when you import anything frommain.js
.Do you have an example project on GitHub in which I can replicate the issue?
Hey! Amazing tutorial thanks for sharing.
Im having kind of an issue where the build is creating an additional file and Im not sure what could be happening.
Any ideas? Not sure where i should look
Wow, @receter ! Absolutely awesome stuff! That's exactly what I needed. Thank you! Can I buy you a coffee? :D
I have my library as a part of nx monorepo with Vite as bundler and my consuming app is in Next.js v13.5.6 - works flawlessly! I used plain old sass with BEM and had css code split up per component and loaded per component - Next.js deals with that easily - no css / font 'jumps'.
Cool, glad my article did help you! If you ever make it to Austria/Graz you can buy me a real coffee 😉
Hello, after adding tailwind to my project, the imports are not working when the developed lib is used in another project. It is generating a different structure in the "dist" directory
For the
main.d.ts
to generate, I've had to calldts
as follows invite.config.ts
:Which version of dts did you use/install?
"vite": "^5.4.9"
"vite-plugin-dts": "^4.3.0"
Great post I've been looking for something like this for a while now...is it possible to test this by using npm link with another react app locally before deploying to npm?
Hi, yes you can totally do that. There are some peculiarities when using
npm link
. Here is an older article of mine, the section about npm link might help you: dev.to/receter/the-minimal-setup-t...In most cases it might be better to just install the package via a file path, see stackoverflow.com/questions/143818...
Like so:
Thank you for this article its been extremely helpful! Quick question, how much refactor would it be to substitute css modules with tailwind? Do you have any resources you could point me too?
I think that should be fairly easy. I never worked with tailwind but always wanted to try using it. If I have time today, I will give it a try and push a branch with tailwind instead of CSS Modules.
@receter , thanks so much for this. Fantastic walkthrough!
How can you add global styles to the library and also the Storybook docs? Global styles like: * { box-sizing: border-box; }
I would recommend to ship a css file as part of the package and import in manually in the consuming applications. Other than that you should be able to just import a CSS file in
main.ts
. Just be aware that it will then be only imported if any of the components exported inmain.ts
are imported in the consuming application.Thank you. That's actually the behavior I want, so the components' styles don't conflict with any other style the end user may have in their consuming app.
Thank you
Fantastic Explanation. Thank you.
how to write styles using tailwind
it would be nice if you include that to
I think you can just use tailwind classes an make sure tailwind is set up in the consuming application and your test environment.
What other steps do I need for developing component library for React, Vue, HTML all at the same time?
I think this library only supports React.
You can look into developing WebComponents developer.mozilla.org/en-US/docs/W...
Another option is to create a monorepo with individual packages for each library.
Is there a reason why my package.json is not being included when my library is built?
Hi, your
package.json
is not copied to thedist
folder. It stays where it is and is shipped as it is when you runnpm publish
in the root directory. Just take a look at npmjs.com/package/@receter/my-comp... to see what is published in my example.Hi, i have implemented the structure suggested. Thanks for sharing. While debugging the consumer app i noticed the components i added are minimized. Can i debug the lib content somehow?
Hi Andrey, thanks for sharing. I followed the steps and its working great. I have one concern regarding tailwind css. How the consumer app will add the css as it building in build time
I heard Vite library doesnt do tree shake, how do you know your import is being tree shaked?
Thanks for your comment. I will investigate this.
Here is a SO question I found regarding this topic: stackoverflow.com/questions/743626...
And this linked issue in particular: github.com/vitejs/vite/issues/5174
It depends on the bundler I guess, seems that when using webpack you might need to do additional work.
To find out if tree shaking works, take a look at the final bundle.
Using some unique strings in your code can help you to quickly find if something made it to the bundle.
I will do some tests to find out what works and will share the repos in f I do so.
If you have a public repo to reproduce tree shaking issues I am also happy to have a look at it.
Hey Andreas! Nice work dude, u really help me! Is really a great article.
You have something about make a lib with more performance? My lib is getting bigger and bigger.
With performance you mean build performance?