blog.itcode.devblog.itcode.dev

My Experience Developing a React Component Library with Rollup.js

As one of the tasks assigned to me at work, I was given the job of turning components into a library and publishing them to npm. In other words, I needed to develop a component library like react-bootstrap. My only prior experience with code publishing was deploying a JAVA open-source library to Maven once, so publishing in this new development environment inevitably led to trial and error. What I felt while developing was that there just weren't many deep references to look at, and I couldn't find suitable code I could just reuse. Fortunately, by grinding through it over time, I was able to establish some kind of foundation. It was fairly fun, seemed worth digging into, and since there didn't seem to be a widely-known reference for it either, I decided to try making one myself.

My Experience Developing a React Component Library with Rollup.js

As one of the tasks assigned to me at work, I was given the job of turning components into a library and publishing them to npm. In other words, I needed to develop a component library like react-bootstrap. My only prior experience with code publishing was deploying a JAVA open-source library to Maven once, so publishing in this new development environment inevitably led to trial and error. What I felt while developing was that there just weren't many deep references to look at, and I couldn't find suitable code I could just reuse. Fortunately, by grinding through it over time, I was able to establish some kind of foundation. It was fairly fun, seemed worth digging into, and since there didn't seem to be a widely-known reference for it either, I decided to try making one myself.
RWB0104
@RWBwritten at 2022-06-10 14:51:25

As one of the tasks assigned to me at work, I was given the job of turning components into a library and publishing them to npm. In other words, I needed to develop a component library like react-bootstrap.

My only prior experience with code publishing was deploying a JAVA open-source library to Maven once, so publishing in this new development environment inevitably led to trial and error.

What I felt while developing was that there just weren't many deep references to look at, and I couldn't find suitable code I could just reuse. Fortunately, by grinding through it over time, I was able to establish some kind of foundation.

It was fairly fun, seemed worth digging into, and since there didn't seem to be a widely-known reference for it either, I decided to try making one myself.




  • Not using Create React App, for minimal bundling
  • Build a TypeScript-based React library development environment
  • Use SCSS for style code (CSS-in-CSS)
  • Test components with Storybook
  • Publish to npm and verify usability in other projects

These are the goals. The ultimate goal is to provide a development environment that's usable to a reasonable degree, even if it doesn't reach a professional-grade level.




I named this project React Components Library Starter. I wanted the fact that it provides a React library development environment to come through intuitively.

Unless it's productized software or a solution, I think it's best for this kind of library that, just by looking at the name, you can roughly guess "oh, this is a library that does X."

Since I'm not using CRA, I build it entirely from scratch.

This is written based on yarn.



BASH

mkdir react-components-library-starter

cd react-components-library-starter

yarn init
 # question name (react-components-library-starter):
 # question version (1.0.0):
 # question description:
 # question entry point (index.js):
 # question repository url (https://github.com/itcode-dev/react-components-library-starter):
 # question author (RWB0104 <psj2716@gmail.com>):
 # question license (MIT):
 # question private: false

mkdir src
  • Create the folder.
  • Initialize the project.
  • Create the src folder. This will be the top-level folder for the source code.


BASH

yarn add -D react @types/react
NamePurpose
reactReact library
@types/reactReact library types
  • Install the React-related libraries.
  • Most libraries not directly related to running this library are installed as devDependencies by specifying the -D option.


BASH

yarn add -D typescript
NamePurpose
typescriptTypeScript library
  • Install the TypeScript-related library.

BASH

vim tsconfig.json
  • To configure the TypeScript build settings, create a config file tsconfig.json.

JSON

{
	"compilerOptions": {
		"target": "es5",
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"strict": true,
		"skipLibCheck": true,
		"jsx": "react",
		"module": "ESNext",
		"declaration": true,
		"declarationDir": "./dist",
		"sourceMap": false,
		"outDir": "./dist",
		"moduleResolution": "node",
		"allowSyntheticDefaultImports": true,
		"emitDeclarationOnly": true,
		"removeComments": true
	},
	"include": [
		"./src"
	],
	"exclude": [
		"./dist",
		"./node_modules",
		"./src/**/*.test.tsx",
		"./src/**/*.stories.tsx",
	]
}
  • An example of tsconfig.json is shown above.
    • declaration - whether to generate *.d.ts type files
    • declarationDir - the output path for *.d.ts. Must be the same as, or a subpath of, output.
    • sourceMap - whether to generate source map code for bundle analysis
    • outDir - the output path. The library's build artifacts are generated in dist/.
  • Unless you need special settings, it's fine to just use it as-is.


BASH

yarn add classnames style-inject
yarn add -D postcss sass
NamePurpose
classnamesLibrary for joining the className class attribute
style-injectStyle tag header injector
postcssCSS post-processor
sassSASS/SCSS library


BASH

yarn add -D rollup @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-typescript rollup-plugin-peer-deps-external rollup-plugin-postcss
NamePurpose
rollupRollup.js core
@rollup/plugin-babelPlugin for integrating Rollup.js with Babel
@rollup/plugin-commonjsPlugin that converts CommonJS -> ES6 code
@rollup/plugin-node-resolveA plugin that, when using an external library, converts references to resolve against the node_modules of the project the library is installed in
@rollup/plugin-typescriptPlugin for integrating Rollup.js with TypeScript
rollup-plugin-peer-deps-externalA plugin that avoids bundling peerDependencies modules and instead converts them to resolve against the node_modules of the project the library is installed in
rollup-plugin-postcssPlugin for integrating Rollup.js with PostCSS
  • Install the Rollup.js-related libraries.
  • You can add various plugins depending on the developer's needs.

BASH

vim rollup.config.js
  • To configure Rollup.js, create rollup.config.js.

JS

/**
 * Rollup configuration module
 *
 * @author RWB
 * @since 2022.06.06 Mon 17:44:31
 */

import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';

const extensions = [ 'js', 'jsx', 'ts', 'tsx', 'mjs' ];

const pkg = require('./package.json')

const config = [
	{
		external: [ /node_modules/ ],
		input: './src/index.ts',
		output: [
			{
				dir: './dist',
				format: 'cjs',
				preserveModules: true,
				preserveModulesRoot: 'src'
			},
			{
				file: pkg.module,
				format: 'es'
			}
			,
			{
				name: pkg.name,
				file: pkg.browser,
				format: 'umd'
			}
		],
		plugins: [
			nodeResolve({ extensions }),
			babel({
				exclude: 'node_modules/**',
				extensions,
				include: [ 'src/**/*' ]
			}),
			commonjs({ include: 'node_modules/**' }),
			peerDepsExternal(),
			typescript({ tsconfig: './tsconfig.json' }),
			postcss({
				extract: false,
				inject: (cssVariableName) => `import styleInject from 'style-inject';\nstyleInject(${cssVariableName});`,
				modules: true,
				sourceMap: false,
				use: [ 'sass' ]
			})
		]
	}
];

export default config;
  • The config example is shown above.
  • Output the result based on the ./src/index.ts file
    • CJS (default)
    • ESM
    • UMD
  • The CJS module supports Tree Shaking via preserveModules
  • @rollup/plugin-babel must run before @rollup/plugin-commonjs
    • Due to the nature of plugins, ordering can matter significantly


BASH

npx storybook init --builder webpack5

yarn add -D @storybook/preset-scss css-loader sass-loader style-loader react-dom
NamePurpose
storybookStorybook CLI
@storybook/preset-scssStorybook's webpack SCSS configuration addon
css-loaderCSS resolver
sass-loaderLibrary that builds SASS/SCSS into CSS
style-loaderLibrary that injects CSS code into the DOM
react-domReact DOM handler
  • Install the libraries needed to run Storybook.
    • .storybook/ - Storybook config folder
    • src/stories/ - Storybook demo folder
  • Since the latest versions of most style-related loaders are compatible with webpack5, you must set Storybook's builder to webpack5.
    • The default builder is webpack4, in which case the loader versions need to be downgraded to match the builder.

Installation alone isn't enough; some simple configuration is also required.

Running npx storybook init --builder webpack5 automatically installs Storybook into the project. During this process, it creates a .storybook folder at the top-level path.

Add the following code to .storybook/main.js.

JS

module.exports = {
	"stories": [
		"../src/**/*.stories.mdx",
		"../src/**/*.stories.@(js|jsx|ts|tsx)"
	],
	"addons": [
		"@storybook/addon-links",
		"@storybook/addon-essentials",
		"@storybook/addon-interactions",
		// Added
		"@storybook/preset-scss"
	],
	"framework": "@storybook/react",
	"core": {
		"builder": "@storybook/builder-webpack5"
	}
}
  • Apply @storybook/preset-scss by adding it to the addon list.


Consistent code rules improve code readability. Of course, a developer could manually follow code conventions entirely by hand, but since it's still humans doing the work, mistakes happen, and manually hunting down code that doesn't match conventions requires a lot of effort with almost no correlation to code performance. On top of that, when there are multiple developers, code conventions can easily break down due to each person's subjective judgment.

Using ESLint lets you follow code conventions without developers needing to think about it.

However, this is purely for maintaining code quality, has little to no connection to code performance, and having or not having ESLint is completely unrelated to development. If you'd rather not bother with this at all, feel free to skip this section — it won't affect the library's development going forward in any way.

BASH

yarn add -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-sort-keys-fix eslint-plugin-storybook
NamePurpose
eslintESLint core
@typescript-eslint/eslint-pluginPlugin for applying ESLint to a TypeScript environment
@typescript-eslint/parserESLint TypeScript parser plugin
eslint-config-airbnbAirbnb rule configuration
eslint-plugin-importimport/export rules plugin
eslint-plugin-jsx-a11yJSX element rules plugin
eslint-plugin-reactReact rules plugin
eslint-plugin-react-hooksReact Hook rules plugin
eslint-plugin-sort-keys-fixObject key sorting rules plugin
eslint-plugin-storybookStorybook rules plugin
  • Install ESLint along with its related configuration and plugins.

JS

module.exports = {
	env: {
		browser: true,
		node: true
	},
	extends: [ 'airbnb', 'airbnb/hooks', 'eslint:recommended', 'plugin:react/recommended', 'plugin:import/recommended', 'plugin:storybook/recommended' ],
	ignorePatterns: [ '.storybook', '*.d.ts', 'node_modules', 'build', 'dist', '**/env/*.js' ],
	overrides: [
		{
			files: [ '*.ts', '*.tsx' ],
			rules: { 'no-undef': 'off' }
		}
	],
	parser: '@typescript-eslint/parser',
	parserOptions: { warnOnUnsupportedTypeScriptVersion: false },
	plugins: [ '@typescript-eslint', 'sort-keys-fix', 'prettier' ],
	rules: {
		'@typescript-eslint/ban-ts-comment': [
			'error',
			{ 'ts-ignore': 'allow-with-description' }
		],
		'@typescript-eslint/no-explicit-any': 'warn',
		'@typescript-eslint/no-unused-vars': 'error',
		'array-bracket-spacing': [
			'error',
			'always',
			{
				arraysInArrays: false,
				objectsInArrays: false
			}
		],
		'brace-style': [ 'error', 'allman' ],
		'comma-dangle': [ 'error', 'never' ],
		'eol-last': [ 'error', 'never' ],
		'import/extensions': 'off',
		'import/named': 'off',
		'import/no-anonymous-default-export': 'off',
		'import/no-cycle': 'off',
		'import/no-extraneous-dependencies': 'off',
		'import/no-named-as-default': 'off',
		'import/no-unresolved': 'off',
		'import/order': [
			'error',
			{
				alphabetize: {
					caseInsensitive: true,
					order: 'asc'
				},
				groups: [ 'external', 'builtin', 'internal', 'sibling', 'parent', 'index' ],
				'newlines-between': 'always'
			}
		],
		indent: [ 'error', 'tab' ],
		'jsx-a11y/control-has-associated-label': 'off',
		'jsx-quotes': [ 'error', 'prefer-single' ],
		'linebreak-style': 'off',
		'max-len': 'off',
		'no-restricted-exports': 'off',
		'no-tabs': [ 'error', { allowIndentationTabs: true }],
		'no-unused-vars': 'off',
		'object-curly-newline': [ 'error', {
			ExportDeclaration: 'never',
			ImportDeclaration: 'never',
			ObjectExpression: {
				minProperties: 3,
				multiline: true
			},
			ObjectPattern: 'never'
		}],
		'react-hooks/exhaustive-deps': 'warn',
		'react/button-has-type': 'off',
		'react/destructuring-assignment': 'off',
		'react/function-component-definition': 'off',
		'react/jsx-curly-brace-presence': [
			'error',
			{
				children: 'never',
				props: 'never'
			}
		],
		'react/jsx-filename-extension': 'off',
		'react/jsx-indent': [ 'error', 'tab' ],
		'react/jsx-props-no-spreading': 'off',
		'react/jsx-sort-props': [
			'error',
			{
				callbacksLast: true,
				ignoreCase: true,
				multiline: 'last',
				noSortAlphabetically: false,
				reservedFirst: false,
				shorthandFirst: false,
				shorthandLast: true
			}
		],
		'react/prop-types': 'off',
		'react/react-in-jsx-scope': 'off',
		'react/require-default-props': 'off',
		'require-jsdoc': 'off',
		'sort-keys-fix/sort-keys-fix': 'error'
	},
	settings: {
		'import/parsers': { '@typescript-eslint/parser': [ '.ts', '.tsx', '.js' ] },
		react: { version: 'detect' }
	}
};
  • You can manage ESLint's configuration in .eslintrc.js, and an example is shown above.
  • You can add whatever rules you want to rules.


  • .npmignore
    • .npmignore is similar to .gitignore. The difference is that it declares which files to exclude when publishing to npm.
    • Files and folders matching the rules in this list are not included when publishing to npm.

TXT

.storybook/
src/
rollup.config.js
tsconfig.json
yarn.lock
  • package.json
    • name - when publishing to npm, publishing is done under this name.
      • If publishing under an organization, enter it in the form @org/name.
    • version - the library version. A version that's already been published cannot be republished, so the version must be managed appropriately with each publish.
    • main - the default (CJS) script for this library
    • module - the ESM script for this library
    • browser - the UMD script for this library
    • types - the types for this library
    • private - whether it's public on npm
      • This is unrelated to the GitHub repository.
    • script - the list of the project's script commands
      • Add the rollup -c command for building.

JSON

{
	"name": "@itcode-dev/react-components-library-starter",
	"version": "3.0.1",
	"main": "./dist/index.js",
	"module": "./dist/index.es.js",
	"browser": "./dist/index.umd.js",
	"types": "./dist/index.d.ts",
	"private": false,
	"script": {
		"build": "rollup -c"
	}
}



Publish the library.

BASH

npm login
 # username
 # password
 # email
 # email otp

yarn publish --access public
  • Log in with npm login.
    • If you don't have an account, create one at the npm homepage.
  • Publish with yarn publish --access public.



Let's actually install the published project and try using it.

BASH

npm i @itcode-dev/react-components-library-starter

yarn add @itcode-dev/react-components-library-starter

You can install the library with the commands above.

TSX

import Button from '@itcode-dev/react-components-library-starter/dist/atom/Button';
import Input from '@itcode-dev/react-components-library-starter/dist/atom/Input';

You can use the library as shown above.




This project was small in scale, but there was a lot to research. There was a lot to look into despite the small scale, which I think made it fun.

Thanks to this project, I was able to learn a lot of new things.

  • Storybook
  • classnames
  • The npm publishing flow
  • rollup.js

I think I picked up a lot of other little things too. It was a rewarding project in many ways.


Come to think of it, I think I published a Java library once before too... unlike npm, I remember Maven's publishing process being complicated.

I'm not sure I could do it again right now if asked to... I should probably write that up again sometime when I have time too.

# React# Rollup.js# npm# Library
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08