npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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:

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 in package.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)
...
"homepage": "http://andrewszucs.github.io/how-to-create-a-react-lib",
...
  • yarn add -D gh-pages

  • Add the following scripts to package.json in root:

    • I needed the clean script because I ran into this issue saying: fatal: A branch named 'gh-pages' already exists.
...
"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 the example/build folder to that branch, push it to our Github repo and set it as the base for Github Pages.

Setting up ESLint, Prettier, Husky and Lint-staged for code quality

1. Add ESLint with the eslint-config-react-app preset

{
  "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 the extends array in eslintrc.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 a format script to package.json:

"scripts": {
  "lint": "eslint . --max-warnings=0",
  "format": "prettier --write \"**/*.+(js|jsx|ts|tsx|json|yml|yaml|css|md|vue)\"",
}
  • Add the husky and lint-staged config to package.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 in package.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 in package.json:
"version":"1.0.0"
  • Add .releaserc.json to configure semantic-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