At work we wanted to build some simple web components to publish to NPM.
I was thinking about writing plain web components, but there is a lot of boiler plate, therefor I chose Lit for the job. And Vite for development and packaging.
Setup new Vite project
We start by setting up a new Vite project.
npm create vite@latest my-webcomponents -- --template lit-ts
Install Storybook
npx sb@latest init --builder storybook-builder-vite
Storybook uses webpack by default, but since we are using Vite, we would rather use the storybook-builder-vite
plugin.
Storybook 7 is going to be better and more modular when it comes to which build system it uses under the hood, be it vite, esbuild, swc...
Vite config changes
In our case we wanted to be able to use the web components as a stand alone package. the default config externalizes lit
. so we have to comment that out.
// vite.config.js
import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
build: {
lib: {
entry: 'src/my-element.ts',
formats: ['es'],
},
rollupOptions: {
// If we want to publish standalone components we don't externalize lit,
// if you are going to use lit in your own project, you can make it a dep instead.
// external: /^lit/, <-- comment this line
},
},
})
Typescript config changes
Because we have added storybook we need to exclude .stories.ts
files from the typescript compilation, otherwise when we publish to NPM they will be included.
// tsconfig.json
{
"compilerOptions": {
"module": "esnext",
"lib": ["es2017", "dom", "dom.iterable"],
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./types",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": false
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.stories.ts"], // <-- this is the line we need to add
"references": [{ "path": "./tsconfig.node.json" }]
}
Storybook config changes
The storybook config needs to tweak the vite config for storybook-builder-vite
to work. We need to include and exclude some packages from the vite config.
// .storybook/main.js
module.exports = {
stories: ['../src/ **/*.stories.mdx', '../src/** /*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
framework: '@storybook/web-components',
core: {
builder: 'storybook-builder-vite',
},
async viteFinal(config, { configType }) {
// customize the Vite config here
config.optimizeDeps.include = [...(config.optimizeDeps?.include ?? []), '@storybook/web-components']
config.optimizeDeps.exclude = [...(config.optimizeDeps?.exclude ?? []), 'lit', 'lit-html']
// return the customized config
return config
},
}
Now we have a working Storybook
There is a downside to using web components (lit) with storybook, and that is that it does not support hot reload. so each change will cause Storybook to do a full reload. Hopefully this will be mitigated in the future.
Publish to NPM
This section covers the steps needed to publish to NPM, if you are not planning to publish to NPM, you can skip this section.
First off, depending if you want to publish standalone web components you will need to move lit
to become a devDependency in your package.json
.
- Also instead of only exporting the single
dist/my-element.es.js
file we will change it so we have a barrel file that exports all components. - and change both
index.html
andvite.config.js
to pointsrc/index.ts
- We then also need to change
package.json
to export the correct files. - And we also want to make the package public by removing
"private": true
and choosing a name and license.
after this we should be good to go by running
npm run build
then checking /dist/
to see that the correct files are there, and that our package.json
points to these files.
then we can run
npm publish
If all has gone well you should be able to use your new package by importing it
<script type="module" src="https://cdn.skypack.dev/my-element"></script>
<my-element name="Lit"></my-element>
Example Repo
If you don't want to copy paste everything, I've created a demo project on github. you can find it here https://github.com/leon/blog-vite-lit-storybook
Top comments (0)