What is Vite? Most of you're probably familiar with what Vite is, but why not start with a quick introduction to get the flow going.
Vite (read as vit
) is actually a combination of two great frontend tools - an immensely fast development server and a build command for shipping heavily optimized static assets using Rollup. Many developers have encountered the process of setting up a project using Create React App. While CRA can be useful for beginners due to its simplicity and abstraction of configuration, it has some drawbacks that outweigh its benefits, particularly its tendency to be bloated. Don't get me wrong, Vite is opinionated as well, but it's highly extensible through its Plugin API.
The main reason for writing this post is that although Vite is an excellent tool, it is still relatively new and sometimes lacks extensive documentation. When I initially had to set up a Vite npm package, it took me more time than I'd care to admit to get it working properly. Therefore, I've decided to create this post to help individuals who may be facing a similar situation and are looking for an alternative to Webpack.
Setting up the project:
yarn create vite
Since we want our project to be leveraging TypeScript's power, we will choose TypeScript + SWC
as a variant. SWC is a new transpiler written in Rust which advertises itself to be even quicker than Esbuild
(which as you know, it's quicker than the old kid on the block - Babel
)
Since I've already set up a git repo for my package, I need to move all of the files from within the initialized project to the repo.
Directory structure
Let's clean up the project a little bit before creating a folder for our package.
In the picture above you can see I've removed the following files/directories from /src
:
-
*.css
andassets
If you find the predefined CSS styling to be beneficial, you can keep it as it is. However, in the majority of cases, I personally prefer to start with a clean slate, but this is purely a matter of personal preference.
Now, create a libs
folder within the src
directory.
Through this approach, we can encapsulate our package within the libs
directory and test it locally within App.tsx
(or any other file as a matter of fact). When I last worked on creating a component library, I utilized the src directory to establish an environment that closely resembled the one I intended to develop the library for.
IMPORTANT By utilizing yarn link (npm link), you can directly test the library in other projects and skip the above mentioned setup.
If you're getting the following exception, run yarn add --dev @types/react
Now, we should define an index.ts
file which will be used to tell the package what to export (e.g. make available to the end user). In my case, I've created a React component defined in the file exampleComponent.tsx
and I will be exporting it from index.ts as a default export. Alternatively, you have the option to export the component as a regular export. However, in this particular case, I am treating it as the primary export.
And index.ts
looks like:
import ExampleComponent from "./exampleComponent";
export default ExampleComponent
Now, we can locally test the component by importing it in App.tsx
and starting the development server by running yarn dev
:
Cool, cool, but now what?
We should play around a bit with Vite's configuration.
vite.config.ts
:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: resolve(__dirname, './src/libs/index.ts'),
name: 'Example NPM Package',
fileName: 'example-npm-package'
},
rollupOptions: {
external: ['react'],
output: {
globals: {
react: 'React'
}
}
}
}
})
As you can guess, in the above configuration we're defining the entry for our package (e.g. our index.ts
file). The reason for specifying react
as an external dependency is to exclude it from being bundled in the final distribution build. This decision is based on the assumption that since the package is intended for use in a React environment, 'react' is likely to be already installed and available in the consumer's project.
Add this to your package.json
and rename all occurrences of example-npm-package
to the value of fileName
you have defined in the Vite config above:
"name": "example-npm-package",
"private": false,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"files": [
"dist"
],
"main": "./dist/example-npm-package.umd.cjs",
"module": "./dist/example-npm-package.js",
"exports": {
".": {
"import": {
"default": "./dist/example-npm-package.js",
"types": "./dist/index.d.ts"
},
"require": {
"default": "./dist/example-npm-package.umd.cjs",
"types": "./dist/index.d.ts"
}
}
},
"types": "./dist/index.d.ts",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Now, due to us using "bundler" as a module resolution method (this has been default for Vite for some time now), we need to explicitly point to the types when generating the exports.
Otherwise, you will get the following exception when loading the package:
There are types at '.../node_modules/example-npm-package/dist/index.d.ts', but this result could not be resolved when respecting package.json "exports". The 'example-npm-package' library may need to update its package.json or typings.
Now, let's try building our package.
vite build && tsc src/libs/index.ts --declaration --emitDeclarationOnly --jsx react --esModuleInterop --outDir dist
Here we use the typescript compiler to generate type definitions for all of our exports. You may add the command as a script in your package.json
file to make your life easier.
What should happen now is that a new folder called dist
should have been generated with your bundled javascript code and the generated type definitions.
If your build fails with some of the following exceptions:
node_modules/@types/react/index.d.ts:246:10 - error TS2456: Type alias 'ReactFragment' circularly references itself.
246 type ReactFragment = Iterable<ReactNode>;
~~~~~~~~~~~~~
node_modules/@types/react/index.d.ts:246:26 - error TS2304: Cannot find name 'Iterable'.
246 type ReactFragment = Iterable<ReactNode>;
~~~~~~~~
node_modules/@types/react/index.d.ts:254:10 - error TS2456: Type alias 'ReactNode' circularly references itself.
254 type ReactNode =
~~~~~~~~~
node_modules/@types/react/index.d.ts:446:23 - error TS2583: Cannot find name 'Set'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2015' or later.
446 interactions: Set<SchedulerInteraction>,
~~~
You may need to do yarn add @types/node
Let's push the package to NPM
Run yarn publish
and you will be asked for your npm credentials.
You're all set!
You can now fetch your React npm package with type definitions!
Here's a link to the template repo. Feel free to ask me any questions or open Pull Requests if there is anything you would like to improve to the template!
Top comments (1)
In fact, this is an extremely lousy guide.
Firstly, itβs not clear why there is yarn in this chain at all? Everything described can be done by the vite + tsc combination. Secondly, how do I get vite to export CSS?