react-lib-tutorial
v2.0.0
Published
Example repo on how to create a React library
Downloads
11
Readme
How to create a React library
Walkthrough for setting up a React Library repo with Github, React, Typescript, Rollup and Emotion.
Sources:
- Creating a React Component Library using Rollup, Typescript, Sass and Storybook
- 3 Ways to Build Your Own React Component Library
- create-react-library - Github
Development:
yarn install
yarn watch
- To run the build process of the component library in watch mode
cd example
yarn install
yarn start
- To run the example React app in development mode
- This way the component library will re-build and the example React app will re-compile every time a file changes.
Walkthrough:
1. Create a new repository on Github
- Initialize repo with a README
- Add .gitignore with Node.js presets
- Add an MIT License
2. Initialize package
- Run
yarn init
3. Install react
and react-dom
as peerDependencies
yarn add -D react react-dom @types/react
- Set them as
peerDependencies
inpackage.json
(use the version you want to support):
...
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
...
4. Install and configure typescript
yarn add -D typescript
- Add the following
tsconfig.json
file in root:
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": ["es6", "dom", "esnext"],
"target": "es5",
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"declaration": true,
"declarationDir": "dist",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noEmit": true
},
"include": ["src/**/*"],
"exclude": [
"node_modules",
"dist",
"example/build",
"src/**/*.stories.tsx",
"src/**/*.test.tsx"
]
}
5. Create example component and install emotion
yarn add -D @emotion/core @emotion/styled
- Add the following folder structure:
src/
TestComponent/
index.ts
TestComponent.tsx
index.ts
TestComponent.tsx
:
/** @jsx jsx */
import { css, jsx } from '@emotion/core';
export interface TestComponentProps {
color: 'darkgreen' | 'lightgreen';
}
const TestComponent: React.FC<TestComponentProps> = ({ color }) => (
<div
css={css`
background-color: hotpink;
&:hover {
color: ${color};
}
`}
>
This is a {color} component with a hotpink background.
</div>
);
export default TestComponent;
- In
src/index.ts
, make sure to use named exports (Source):
export { default as TestComponent } from './TestComponent';
6. Install rollup
and plugins
yarn add -D rollup rollup-plugin-typescript2 @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-peer-deps-external
- Add the following
rollup.config.js
file in root:
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from 'rollup-plugin-typescript2';
const packageJson = require('./package.json');
export default {
input: 'src/index.ts',
output: [
{
file: packageJson.main,
format: 'cjs',
sourcemap: true
},
{
file: packageJson.module,
format: 'esm',
sourcemap: true
}
],
plugins: [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({ useTsconfigDeclarationDir: true })
]
};
- Add the following config variables and scripts in
package.json
:
...
"main": "dist/index.js",
"module": "dist/index.es.js",
"files": ["dist"],
"types": "dist/index.d.ts",
"scripts": {
"build": "rollup -c",
"watch": "rollup -cw"
},
...
- Run
yarn build
8. Generate example React app with create-react-app
npx create-react-app example --template typescript
9. Install the example app's dependencies in the parent package
yarn add -D @testing-library/jest-dom @testing-library/react @testing-library/user-event @types/jest @types/node @types/react-dom react-scripts
10. Link the example app's dependencies to the ones installed to the parent
...
"dependencies": {
...
"react": "link:../node_modules/react",
"react-dom": "link:../node_modules/react-dom",
"react-scripts": "link:../node_modules/react-scripts",
...
},
...
"scripts": {
"start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
...
},
...
- Add a link to the development package in
package.json
:
...
"dependencies": {
...
"how-to-create-a-react-lib": "link:.."
},
...
11. Use the development package in the example app
- Run
yarn install
in the example app - Import the development package in the
App.tsx
:
import { TestComponent } from 'how-to-create-a-react-lib';
12. Develop the library and run the example app
yarn watch
cd example && yarn start
- Change the code in the
src/
folder
Deploying the example app to Github Pages (Source)
- Add a
homepage
variable to both./package.json
and./example/package.json
(Optional)- Per CRA docs: we need to set the
homepage
variable if we use routing in our app - Use the base path where you will be serving the React app from. (More info)
- Per CRA docs: we need to set the
...
"homepage": "http://andrewszucs.github.io/how-to-create-a-react-lib",
...
yarn add -D gh-pages
Add the following
scripts
topackage.json
in root:- I needed the
clean
script because I ran into this issue saying:fatal: A branch named 'gh-pages' already exists.
- I needed the
...
"scripts": {
...
"clean" : "gh-pages-clean",
"predeploy": "cd example && yarn install && yarn build",
"deploy": "gh-pages -d example/build"
...
}
...
- Run
yarn deploy
- This will create a new branch named
gh-pages
commit theexample/build
folder to that branch, push it to our Github repo and set it as the base for Github Pages.
- This will create a new branch named
Setting up ESLint, Prettier, Husky and Lint-staged for code quality
1. Add ESLint with the eslint-config-react-app
preset
yarn add -D eslint-config-react-app @typescript-eslint/[email protected] @typescript-eslint/[email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected] [email protected]
- Create an
.eslintrc.json
file and add the following:
{
"extends": ["react-app"],
/** These are optional: */
"rules": {
/** Warn if console.log are added */
"no-console": "warn",
/** Enforcing rules about import ordering */
"import/order": [
"warn",
{
"groups": [["builtin", "external"], "internal", ["parent", "sibling", "index"]],
"newlines-between": "always-and-inside-groups"
}
]
}
}
2. Add Prettier and integrate it with ESLint
yarn add -D prettier eslint-config-prettier eslint-plugin-prettier
- Add
plugin:prettier/recommended
to theextends
array ineslintrc.json
:
{
"extends": [..., "plugin:prettier/recommended"],
...
}
- Add a
.prettierrc
file to configure Prettier:
{
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"trailingComma": "none",
"tabWidth": 2,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"printWidth": 90
}
3. Add husky
and lint-staged
to lint and format at each commit
yarn add -D husky lint-staged
Add a
lint
and aformat
script topackage.json
:
"scripts": {
"lint": "eslint . --max-warnings=0",
"format": "prettier --write \"**/*.+(js|jsx|ts|tsx|json|yml|yaml|css|md|vue)\"",
}
- Add the
husky
andlint-staged
config topackage.json
:
"husky": {
"hooks": {
"pre-commit": "yarn lint && yarn format"
}
},
"lint-staged": {
"*.+(js|jsx|ts|tsx)": [
"eslint --fix",
"git add"
],
"*.+(json|css|md)": [
"prettier --write",
"git add"
]
},
4. Add a script for running Typescript check
- Add the following to
scripts
inpackage.json
:
...
"scripts": {
...
"typecheck": "tsc -p ./tsconfig.json",
"typecheck:watch": "yarn typecheck --watch"
}
...
Adding Minification to the build process
yarn add -D rollup-plugin-uglify rollup-plugin-terser
- Change
rollup.config.js
Setting up semantic-release and Github Actions workflow
npx semantic-release-cli setup
- Add this to
package.json
:
{
"publishConfig": {
"registry": "https://npm.pkg.github.com/",
"pkgRoot": "build"
}
}
- Change back
version
inpackage.json
:
"version":"1.0.0"
- Add
.releaserc.json
to configuresemantic-release
:
{
"branches": "master",
"repositoryUrl": "https://github.com/andrewszucs/how-to-create-a-react-lib",
"debug": "true",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
"@semantic-release/github",
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
[
"@semantic-release/git",
{
"assets": ["package.json", "package-lock.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}
yarn add -D @semantic-release/git @semantic-release/changelog
Need to add a bot user with zero vulnerabilities to push
GITHUB_TOKEN
is not defined
[11:08:54 PM] [semantic-release] › ✖ Failed step "prepare" of plugin "@semantic-release/git"
[11:08:54 PM] [semantic-release] › ✖ An error occurred while running semantic-release: Error: Command failed with exit code 1: git push --tags https://github.com/andrewszucs/how-to-create-a-react-lib HEAD:master
remote: error: GH006: Protected branch update failed for refs/heads/master.
remote: error: Required status check "Test code" is expected. At least 1 approving review is required by reviewers with write access.
To https://github.com/andrewszucs/how-to-create-a-react-lib
! [remote rejected] HEAD -> master (protected branch hook declined)
error: failed to push some refs to 'https://github.com/andrewszucs/how-to-create-a-react-lib'
at makeError (/home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/@semantic-release/git/node_modules/execa/lib/error.js:59:11)
at handlePromise (/home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/@semantic-release/git/node_modules/execa/index.js:114:26)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async push (/home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/@semantic-release/git/lib/git.js:51:3)
at async module.exports (/home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/@semantic-release/git/lib/prepare.js:69:5)
at async prepare (/home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/@semantic-release/git/index.js:28:3)
at async validator (/home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/semantic-release/lib/plugins/normalize.js:34:24)
at async /home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/semantic-release/lib/plugins/pipeline.js:37:34
at async /home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/semantic-release/lib/plugins/pipeline.js:31:3
at async Object.pluginsConf.<computed> [as prepare] (/home/runner/work/how-to-create-a-react-lib/how-to-create-a-react-lib/node_modules/semantic-release/lib/plugins/index.js:80:11) {
shortMessage: 'Command failed with exit code 1: git push --tags https://github.com/andrewszucs/how-to-create-a-react-lib HEAD:master',
command: 'git push --tags https://github.com/andrewszucs/how-to-create-a-react-lib HEAD:master',
exitCode: 1,
signal: undefined,
signalDescription: undefined,
stdout: '',
stderr: 'remote: error: GH006: Protected branch update failed for refs/heads/master. \n' +
'remote: error: Required status check "Test code" is expected. At least 1 approving review is required by reviewers with write access. \n' +
'To https://github.com/andrewszucs/how-to-create-a-react-lib\n' +
' ! [remote rejected] HEAD -> master (protected branch hook declined)\n' +
"error: failed to push some refs to 'https://github.com/andrewszucs/how-to-create-a-react-lib'",
failed: true,
timedOut: [secure],
isCanceled: [secure],
killed: [secure],
pluginName: '@semantic-release/git'
}
Setting up commitizen
yarn add -D commitizen
npx commitizen init cz-conventional-changelog --yarn --dev --exact