- Published on
Publishing a React component to npm using Vite
- Authors
- Name
- Tom Southall
- @tomsouthall
Overview
This guide is designed to help you build a React component using Vanilla JS and then publish it to npm as a package for re-use by other React applications.
Like, me you may have found that publishing a React component is not quite as straightforward as you may have first imagined. After much tinkering I've ended up with a very handy setup which I can re-use for any project.
We are going to use Vite as our build tool and local development environment. If you're not familiar with Vite, it's a super-fast, super-lean build tool that leverages the ability to use native ES modules in modern browsers. In short, it's great.
Guide
All the code in this guide is available in the accompanying repository on Github.
Pre-requisites
- You have Git installed
- You have Node.js and npm installed
- You have an npm account and are logged in
Initial Set Up
Create your project folder and
cd
into it. For the sake of this guide, we'll assume your component is calledmy-component
$ mkdir my-component $ cd my-component
Create a new git repository
$ git init .
Create the following folders and files. All the files can be left empty for now:
Your component files that will be bundled and packaged ready to be published to npm live in the /src/lib
folder. The other files in /src
are only needed for the development environment. They will be ignored at build time.
My preference for test files is to have them live alongside the file that they are testing, in this case in /src/lib
but these could be placed, for example, in a separate __tests__
folder if you prefer.
Note that in Vite, jsx files must have a .jsx
file extension and not .js
.
Configure .gitignore
In your editor, open
.gitignore
Paste the following and add any other files or folders you may wish to ignore.
# dependencies /node_modules # testing /coverage # component build /dist # misc .DS_Store
Configure package.json
In your editor, open
/package.json
and paste in the following:{ "name": "my-component", "version": "0.0.0", "description": "", "license": "MIT", "author": "", "keywords": [], "files": [ "dist", "README.md" ], "main": "./dist/my-component.umd.js", "module": "./dist/my-component.es.js", "exports": { ".": { "import": "./dist/my-component.es.js", "require": "./dist/my-component.umd.js" } }, "scripts": { "dev": "vite", "build": "vite build", "test": "vitest run", "watch": "vitest", "coverage": "vitest run --coverage" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "eslintConfig": { "env": { "browser": true, "node": true, "es2020": true }, "extends": [ "eslint:recommended", "plugin:react/recommended" ], "parserOptions": { "sourceType": "module" } } }
Replace all instances of
my-component
with your component/package name. This must not already be a name taken by another npm package as this is what users will type to install your component in their projects, for example:npm install my-component
. You can check whether the name is already taken by searching for it on the npm website.Add your own values for
description
,author
andkeywords
Change the license to your preference
Install React as a development dependency. We do not install it as a production dependency because the React app that uses your component package is responsible for the React product dependency.
$ npm install react react-dom --save-dev
Install Vite as a development dependency.
$ npm install vite @vitejs/plugin-react --save-dev
Install all the development dependencies required for testing and linting
$ npm install @testing-library/dom @testing-library/react c8 eslint eslint-plugin-react jsdom react-test-renderer vitest --save-dev
Configure Vite
In your editor, open
/vite.config.js
and paste in the following:import path from 'path' import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ build: { lib: { entry: path.resolve(__dirname, 'src/lib/index.jsx'), name: 'My Component', fileName: (format) => `my-component.${format}.js` }, rollupOptions: { external: ['react', 'react-dom'], output: { globals: { react: 'React' } } } }, plugins: [react()] })
Make sure you replace
name: 'My Component'
with the name of your componentLikewise, make sure that
my-component
in thefileName
value is changed to the name of your component
Configure Testing and Linting
In your editor, open
/vitest.config.js
and paste in the following:import { defineConfig } from 'vite' export default defineConfig({ test: { globals: false, environment: 'jsdom' } })
If you prefer different ESLint rules, configure these in
eslintConfig
inpackage.json
Write your Component along with Snapshot Tests
The entrypoint for your component is /src/lib/index.jsx
. You can of course create a whole set of child components too within /src/lib
as you would in any React app.
For now you may want to create a dummy HelloWorld component as in the examples below, then once the development environment is set up, come back and build your component.
Create your component in
/src/lib/index.jsx
. Here's a very simple example:import React from 'react' export default function HelloWorld(props) { const { greetee = 'World' } = props return ( <div>Hello, {greetee}!</div> ) }
Add snapshot tests to
/src/lib/index.test.jsx
. For example:import React from 'react' import renderer from 'react-test-renderer' import { describe, expect, test } from 'vitest' import HelloWorld from './index' describe('HelloWorld', () => { test('HelloWorld component renders correctly', () => { const component = renderer.create( <HelloWorld /> ) const tree = component.toJSON() expect(tree).toMatchSnapshot() }) test('The greetee prop works', () => { const component = renderer.create( <HelloWorld greetee={'Universe'} /> ) const tree = component.toJSON() expect(tree).toMatchSnapshot() }) })
You can run your tests from the command line by typing:
$ npm test
Or you can run tests and set a watch to re-run tests when anything changes.
$ npm run watch
Note: Running snapshot tests will create a /src/lib/__snapshots__
folder. Do not gitignore this folder. Snapshot tests are intended to be committed to your Git repository.
Set Up the Development App
We need to create a development React app in order to test your component library.
Open
/index.html
and paste in the following. Change the<title>
element to whatever you choose.<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My React Component</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.jsx"></script> </body> </html>
Open
/src/main.jsx
and paste in the following:import React from 'react' import ReactDOM from 'react-dom' import './index.css' import App from './App' ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') )
Open
/src/index.css
and enter any CSS you want to be applied to the local development page, for example:body { font-family: sans-serif; }
Open
/src/main.jsx
and paste in the following, replacing theHelloWorld
component with your own.import React from 'react' import HelloWorld from './lib' const App = () => { return ( <HelloWorld greetee={'Universe'} /> ) } export default App
To launch the development environment from the command line enter:
$ npm run dev
You can then navigate to http://localhost:3000
in your browser to see your component.
Validate Props
(Optional)
I recommend typechecking your component's props. This will help developers who are using your component in their projects to ensure they are passing the expected types. This only applies if you are using Vanilla JS as assumed by this guide. Typescript does away with this need.
Install the prop-types package as a production dependency:
$ npm install prop-types
Add validation rules, for example in our
/src/lib/index.jsx
file, we would make the following additions:import React from 'react' import PropTypes from 'prop-types' export default function HelloWorld(props) { const { greetee = 'World' } = props return ( <div>Hello, {greetee}!</div> ) } HelloWorld.propTypes = { greetee: PropTypes.string }
Integration Testing
(Optional)
If your component is at all complex and has child components, then writing integration tests is highly recommended so that you can test your component as an end-user. Snapshot and unit tests can even be replaced in many instances by good integration tests using Testing Library.
Here is an example /src/lib/integration.test.jsx
for our dummy HelloWorld
component:
import * as React from 'react'
import { render, screen, cleanup } from '@testing-library/react'
import { describe, expect, test, afterEach } from 'vitest'
import HelloWorld from './index'
describe('Integration test', () => {
afterEach(cleanup)
test('Minimal render display expected text', () => {
render(<HelloWorld />)
expect(screen.getByText('Hello, World!'))
})
test('Expected greetee is displayed', () => {
const greetee = 'Universe'
render(<HelloWorld greetee={greetee} />)
expect(screen.getByText(`Hello, ${greetee}!`))
})
})
Write a README
If you want other developers to use your component, it's really important to write a top-notch README file including detailed documentation and examples, as well a the features and benefits of using your component.
This will be published to npm.
Open
/src/README.md
Create your README file using markdown.
Publishing to npm
We are finally ready to publish our component to npm.
Here is our publishing checklist.
Firstly ensure that the version number in your
package.json
file is correct and uses semantic versioning rules. Each time you publish to npm, you must do so with a new version number.Ensure all tests pass:
$ npm test
Create the build files:
$ npm run build
Both UMD and ESM module formats are created and placed in the
/dist
folder. Again, note that React is not bundled in alongside your component. Just your component code and any dependencies.Ensure you are logged into npm. If not type:
$ npm login
Publish your component
$ npm publish
And all done!
A useful thing to do next is to create a test application that pulls in your component from npm. Then you can check that it is working as intended.