使用rollup和storybook构建react+typescript组件库

组件库日益流行,尤其是在拥有多个产品和团队的组织中。组织正在专门设立团队来维护组件库。这里的最终目标可能是一个设计系统,我们的原则和实践经过深思熟虑。但是,一个好的设计系统需要数月甚至数年的研究和一个专门的团队,这是许多组织无法承受的。Google 的Material design和 Atlassian 的Design system 还有蚂蚁金服的ant-design是我想到的一些优秀的。对于大多数团队来说,一个好的起点是组件库。一组常用组件,有助于在应用程序之间实现一致性。我们可以从简单的组件开始,例如buttoninputs,modal并在此过程中添加更多内容。下面我们会使用 React、Typescript 和 Rollup 从头构建一个简单的组件库,并在此过程中学习rollup和storybook相关的知识。

初始化项目

mkdir rollup-react-lib // 创建项目文件夹
cd rollup-react-lib
npm init // 初始化项目
npm i -D react react-dom typescript @types/react // 添加依赖项
npx tsc --init // 添加tsconfig.json

现在StoryBook还不支持react18,所以可以携带版本号,避免装到最新的react,导致启动问题。

npm i -D react@^17.0.2 react-dom@^17.0.2 typescript @types/react

// 添加17的依赖依赖项

我们所有的包裹都将列在devDependencies. 此外,将使用该库的应用程序将附带 react,我们不必捆绑 react。因此,我们将添加react为peerDependency,我们package.json现在的样子。

{
  "name": "rollup-react-lib",
  "version": "1.0.0",
  "description": "react component lib",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "types": "dist/esm/index.d.ts",
  "scripts": {
    "build": "rollup -c"
  },
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "@rollup/plugin-commonjs": "^21.0.3",
    "@rollup/plugin-node-resolve": "^13.1.3",
    "@rollup/plugin-typescript": "^8.3.1",
    "@types/react": "^17.0.43",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "rollup": "^2.70.1",
    "rollup-plugin-dts": "^4.2.0",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-terser": "^7.0.2",
    "typescript": "^4.6.3"
  },
  "peerDependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "dependencies": {}
}

tsconfig.json如下:

{
  "compilerOptions": {
    "strict": true,
    "strictPropertyInitialization": false,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitAny": false,
    "outDir": "dist",
    "module": "esnext",
    "target": "ES5",
    "lib": ["es2019", "DOM"],
    "declaration": true,
    "declarationDir": "dist/esm/types",
    "emitDeclarationOnly": false,
    "jsx": "react",
    "allowJs": false,
    "checkJs": false,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  },
  "include": ["src/components/**/*.tsx", "src/components/**/*.ts"],
  "exclude": ["node_modules", "src/components/**/*.spec.(ts|tsx)", "dist"]
}

src目录如下:

如果我们运行npm run build,我们应该会看到一个dist文件夹,其中包含我们所有的 ts 文件被转译成 js 文件。如果您注意到,其中没有 css 文件。让我们使用Rollup来实现。

安装rollup及一些插件

  • @rollup/plugin-node-resolve- 解决第三方依赖node_modules
  • @rollup/plugin-commonjs- 打包成commonjs格式
  • rollup-plugin-typescript2 在 JS 中转译我们的 Typescript 代码
  • rollup-plugin-peer-deps-external- 防止打包peerDependencies
  • rollup-plugin-postcss- 处理我们的 CSS
  • rollup-plugin-terser- 缩小我们的包
  • rollup-plugin-dts - 它获取我们所有的.d.ts文件并输出一个单一类型的文件
npm i -D rollup
npm i -D @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-typescript2 rollup-plugin-peer-deps-external rollup-plugin-postcss rollup-plugin-terser rollup-plugin-dts

根目录添加rollup.config.js

import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from 'rollup-plugin-typescript2';
import { terser } from 'rollup-plugin-terser';
import external from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';
import dts from 'rollup-plugin-dts';

const packageJson = require('./package.json');

export default [{
  input: 'src/index.ts',
  output: [
    {
      file: packageJson.main,
      format: 'cjs',
      sourcemap: true,
      name: 'rollup-react-lib'
    },
    {
      file: packageJson.module,
      format: 'esm',
      sourcemap: true
    }
  ],
  plugins: [
    external(),
    resolve(),
    commonjs(),
    typescript({ useTsconfigDeclarationDir: true }),
    postcss(),
    terser()
  ]
}, {
  input: 'dist/esm/types/index.d.ts',
  output: [{ file: 'dist/esm/index.d.ts', format: "esm" }],
  external: [/\.css$/],
  plugins: [dts()],
}]

完善我们的package.json

{
  "name": "rollup-react-lib",
  "version": "1.0.0",
  "description": "react component lib",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "types": "dist/esm/index.d.ts",
  "scripts": {
    "build": "rollup -c"
  },
  "author": "Joseph",
  "license": "MIT",
  "devDependencies": {
    "@rollup/plugin-commonjs": "^21.0.3",
    "@rollup/plugin-node-resolve": "^13.1.3",
    "@types/react": "^17.0.43",
    "postcss": "^8.4.12",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "rollup": "^2.70.1",
    "rollup-plugin-dts": "^4.2.0",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.31.2",
    "typescript": "^4.6.3"
  },
  "peerDependencies": {
    "react": "^17.0.2"
  },
  "dependencies": {}
}

安装rimraf

npm i -D rimraf

可以帮我们在打包前删除文件夹

安装babel

npm i -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript

创建.babelrc

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-typescript",
    ["@babel/preset-react", { "runtime": "automatic" }]
  ]
}

安装eslint和prettier

npm i -D eslint
npx eslint --init

vscode中的settings.json

"editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
 }

如何我们使用VScode开发,加上上面的配置,可以保存时自动格式化。

Prettier强化:

npm i prettier -D

配置.prettierrc

{
  "useTabs": false,
  "tabWidth": 2,
  "printWidth": 100,
  "singleQuote": true,
  "trailingComma": "none",
  "bracketSpacing": true,
  "semi": false
}
// 常用配置相关解释
printWidth: 100, // 超过最大值换行
tabWidth: 4, // 缩进字节数
useTabs: false, // 缩进不使用tab,使用空格
semi: true, // 句尾添加分号
singleQuote: true, // 使用单引号代替双引号
proseWrap: "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
arrowParens: "avoid", //  (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号
bracketSpacing: true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
disableLanguages: ["vue"], // 不格式化vue文件,vue文件的格式化单独设置
endOfLine: "auto", // 结尾是 \n \r \n\r auto
eslintIntegration: false, //不让prettier使用eslint的代码格式进行校验
htmlWhitespaceSensitivity: "ignore",
ignorePath: ".prettierignore", // 不使用prettier格式化的文件填写在项目的.prettierignore文件中
jsxBracketSameLine: false, // 在jsx中把'>' 是否单独放一行
jsxSingleQuote: false, // 在jsx中使用单引号代替双引号
parser: "babylon", // 格式化的解析器,默认是babylon
requireConfig: false, // Require a 'prettierconfig' to format prettier
stylelintIntegration: false, //不让prettier使用stylelint的代码格式进行校验
trailingComma: "es5", // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
tslintIntegration: false // 不让prettier使用tslint的代码格式进行校验
"eslint": "^8.12.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-prettier": "^4.0.0",
 
eslint-plugin-prettier插件会调用prettier对你的代码风格进行检查,其原理是先使用prettier对你的代码进行格式化,然后与格式化之前的代码进行对比,如果过出现了不一致,这个地方就会被prettier进行标记。

解决ESlint与Prettier的冲突

npm i eslint-config-prettier -D

.eslintrc.js

module.exports = {
  env: {
    commonjs: true,
    es2021: true,
    node: true
  },
  extends: ['airbnb-base', 'prettier'], // 覆盖eslint格式配置,写在最后
  parserOptions: {
    ecmaVersion: 13
  },
  rules: {}
}

安装husky

npm i -D husky

// 给script加个prepare:
// prepare: "husky install"

npm run prepare

npx husky add .husky/pre-commit "npm run prettier-format && npm run lint"

git add .husky/pre-commit

最新版会在根目录生成.husky文件夹,.git里面的pre-commit钩子,回触发此文件夹里面的pre-commit的shell,然后进行lint修复。最终的package.json参考最终版(下面)。

安装StoryBook

npx sb init

会在根目录创建.storybook文件夹,会在src底下创建stories文件夹,我们删除stories底下的文件,就可以写自己的stories了。

具体参考:https://storybook.js.org/docs/react/get-started/introduction

修改一下package.json里面的scripts

  "scripts": {
    "build": "rimraf build/* && rollup -c",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "prepare": "husky install",
    "lint": "npx eslint src/**/*.{ts,tsx}",
    "lint-fix": "npx eslint src/**/*.{ts,tsx} --fix",
    "prettier-format": "prettier --write src/components/**/*.{ts,tsx}"
  },

完整package.json

{
  "name": "rollup-react-lib",
  "version": "1.0.0",
  "description": "react component lib",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "types": "dist/esm/index.d.ts",
  "scripts": {
    "build": "rimraf build/* && rollup -c",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "prepare": "husky install",
    "lint": "npx eslint src/**/*.{ts,tsx}",
    "lint-fix": "npx eslint src/**/*.{ts,tsx} --fix",
    "prettier-format": "prettier --write src/components/**/*.{ts,tsx,less}"
  },
  "author": "Joseph",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.17.8",
    "@babel/preset-env": "^7.16.11",
    "@babel/preset-react": "^7.16.7",
    "@babel/preset-typescript": "^7.16.7",
    "@rollup/plugin-commonjs": "^21.0.3",
    "@rollup/plugin-image": "^2.1.1",
    "@rollup/plugin-node-resolve": "^13.1.3",
    "@storybook/addon-actions": "^6.4.19",
    "@storybook/addon-essentials": "^6.4.19",
    "@storybook/addon-interactions": "^6.4.19",
    "@storybook/addon-links": "^6.4.19",
    "@storybook/react": "^6.4.19",
    "@storybook/testing-library": "0.0.9",
    "@testing-library/jest-dom": "^5.16.3",
    "@testing-library/react": "^12.1.4",
    "@testing-library/user-event": "^14.0.0",
    "@types/lodash": "^4.14.181",
    "@types/react": "^17.0.43",
    "@typescript-eslint/eslint-plugin": "^5.17.0",
    "@typescript-eslint/parser": "^5.17.0",
    "babel-loader": "^8.2.4",
    "babel-preset-react-app": "^10.0.1",
    "eslint": "^8.12.0",
    "eslint-config-google": "^0.14.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-react": "^7.29.4",
    "husky": "^7.0.4",
    "postcss": "^8.4.12",
    "prettier": "^2.5.1",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "rimraf": "^3.0.2",
    "rollup": "^2.70.1",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-dts": "^4.2.0",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.31.2",
    "typescript": "^4.6.3"
  },
  "peerDependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "lodash": "^4.17.21"
  }
}

项目结构图:

StoryBook运行:

gitee地址:https://gitee.com/cary123/rollup-react-lib

上面只是基本的一些配置,如果需要sass,less,svg等还需要额外配置。还有很多内容值得我们学习。

相关标签:
  • rollup
  • typescript
1人点赞

发表评论

当前游客模式,请登陆发言

所有评论(1)

用户头像

多谢分享,nice~