# rollup 打包组件库和 react+ts+less 项目

# 一. 初始化项目

mkdir y-design
cd y-design
npm init -y

# 二. 实现 react+ts 打包组件库或者工具库

# 1. 安装 rollup 基础依赖

npm i -D rollup @rollup/plugin-commonjs @rollup/plugin-node-resolve
  • @rollup/plugin-commonjs: rollup收集依赖打包,是依赖esModule规范,而引入第三方commonjs规范的代码需要使用该插件进行转化为esModule才能使用

    rollup-plugin-commonjs

    rollup-plugin-commonjs已被弃用,所以使用@rollup/plugin-commonjs

  • @rollup/plugin-node-resolve: 如果没有该插件 import 引入的 第三包,会被转为 require 形式,不会将包打进入bundle.js

    rollup-plugin-node-resolve

    rollup-plugin-node-resolve已被弃用,所以使用@rollup/plugin-node-resolve

# 2.安装 react 相关依赖

npm i react react-dom && npm i -D @types/react @types/react-dom

# 3.安装 typescript 相关依赖

npm i -D typescript rollup-plugin-typescript2

# 4.安装 css 相关依赖

npm i -D less postcss postcss-less rollup-plugin-postcss classnames
  • rollup-plugin-postcss: 默认支持 less、scss,但是需要安装对应前置插件
  • classnames: 可以使用函数来组合 jsx 语法的 class 名

# 4.编辑以下文件

tsconfig.json
{
  "compilerOptions": {
    "jsx": "react", // 可以使用jsx或tsx
    "baseUrl": ".",
    "outDir": "./", // 输出目录
    "sourceMap": false, // 是否生成sourceMap
    "target": "esnext", // 编译目标
    "module": "esnext", // 模块类型
    "moduleResolution": "node",
    "allowJs": false, // 是否编辑js文件
    "strict": true, // 严格模式
    "noUnusedLocals": true, // 未使用变量报错
    "experimentalDecorators": true, // 启动装饰器
    "resolveJsonModule": true, // 加载json
    "esModuleInterop": true,
    "removeComments": false, // 删除注释
    "declaration": true, // 生成定义文件
    "declarationMap": false, // 生成定义sourceMap
    // "declarationDir": "./lib/types", // 定义文件输出目录
    "lib": ["esnext", "dom"], // 导入库类型定义
    "types": ["node"] // 导入指定类型包
  },
  "include": ["packages", "typings.d.ts"],
  "exclude": ["node_modules"]
}
rollup.config.js
import typescript from "rollup-plugin-typescript2";

import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import path from "path";

// import babel from "@rollup/plugin-babel";
//babel: 转换为es5语法,需要安装@babel/core,@babel/preset-env
// import { terser } from "rollup-plugin-terser";
//terser 压缩代码

import pkg from "./package.json";
// import cssnanoPlugin from "cssnano";
import postcss from "rollup-plugin-postcss";
// import autoprefixer from "autoprefixer";
// import clear from "rollup-plugin-clear";
const resolve = (p) => path.resolve(__dirname, p);


export default {
  input: resolve("packages/index.ts"),
  output: [
    {
      file: resolve("lib/cjs/index.js"),
      format: "cjs",
      name: pkg.name,
    },
    {
      file: resolve("lib/es/index.js"),
      format: "es",
      name: pkg.name,
    },
    {
      file: resolve("lib/umd/index.js"),
      format: "umd",
      name: pkg.name, //umd 模块 必须给name
    },
  ],
  plugins: [
    // clear({
    //   targets: ["lib"],
    // }),
    postcss(
      // {
      //   plugins: [autoprefixer(), cssnanoPlugin()],
      // }
    ),
    commonjs(),
    nodeResolve({
      extensions:  [".ts", ".tsx"]
    }),
    typescript(),
    // babel({
    //   exclude: "node_modules/**",
    //   extensions: [".ts", ".tsx"], //需要手动配置后缀,不然文件ts文件不会被转成es5
    // }),
    // terser(),
  ],
  // 指出应将哪些模块视为外部模块 就不会被打包
  external: ["react", "react-dom"],
};
packages/index.ts

import Button from "./Button/Button";

const count = (x: number,y: number) => {
  return x + y;
}

export {
  count,
  Button
}
packages/Button/Button.tsx
import React from "react";
import classnames from "classnames";
import './index.less';

const prefix = 'y';

export default (props: {
  children: React.ReactNode;
  type?: "default" | "primary";
}) => {
  const cName = `${prefix}-button`;
  return (
    <button
      className={classnames(cName, cName + '-' + (props.type || "default"))}
    >
      {props.children}
    </button>
  );
};
packages/Button/index.less
.y-button{
  position: relative;
  display: inline-block;
  font-weight: 400;
  white-space: nowrap;
  text-align: center;
  background-image: none;
  border: 1px solid transparent;
  box-shadow: 0 2px #00000004;
  cursor: pointer;
  transition: all .3s cubic-bezier(.645,.045,.355,1);
  user-select: none;
  touch-action: manipulation;
  height: 32px;
  padding: 4px 15px;
  font-size: 14px;
  border-radius: 2px;
  color: #000000d9;
  border-color: #d9d9d9;
  background: #fff;
  &-primary{
    color: #fff;
    border-color: #1890ff;
    background: #1890ff;
    text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
    box-shadow: 0 2px #0000000b;
  }
}

# package.json





 




{
  ...,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build:pro": "rollup -c"
  },
  ...
}

# 5.运行命令即可打包

npm run build:pro

# 6.优化代码,安装插件,编辑.babelrc,根据安装的插件将上面文件中的注释恢复

npm i -D @rollup/plugin-babel @babel/core @babel/preset-env rollup-plugin-terser cssnano autoprefixer rollup-plugin-clear
  • @rollup/plugin-babel: 将 es6 以上的代码装换为 es5,前置依赖@babel/core @babel/preset-env

    rollup-plugin-babel

    rollup-plugin-babel已被弃用,所以使用@rollup/plugin-babel

  • rollup-plugin-terser: js 压缩
  • cssnano: css 压缩
  • rollup-plugin-clear: 重新编译的时候清除上一次的文件
.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "ie": 10 
          //如果代码运行的浏览器支持 ES Modules,应当指定如下配置.不过,此时 browsers 选项将被忽略
          // "esmodules": true
        }
      }
    ]
  ]
}

# 三. 实现 react+ts 引用组件库或者工具库

# 1. 在以上基础下,再创建rollup.config.dev.js,examples/App.tsx,examples/index.tsx,

# examples/style.less,examples/typeings.d.ts,public/index.html

rollup.config.dev.js











 







 
 
 
 



 
 
 
 
 


 

 
 
 
 
 
 
 
 
 
 
 
 


 













 


import typescript from "rollup-plugin-typescript2";

import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import path from "path";

import babel from "@rollup/plugin-babel";
//babel: 转换为es5语法,需要安装@babel/core,@babel/preset-env
import { terser } from "rollup-plugin-terser";
//terser 压缩代码

// import pkg from "./package.json";
import cssnanoPlugin from "cssnano";
import postcss from "rollup-plugin-postcss";
import replace from "@rollup/plugin-replace";
import autoprefixer from "autoprefixer";
import clear from "rollup-plugin-clear";
const resolve = (p) => path.resolve(__dirname, p);

//预览
import htmlTemplate from "rollup-plugin-generate-html-template";//
import livereload from "rollup-plugin-livereload";//
import serve from "rollup-plugin-serve";//


export default {
  input: resolve("examples/index.tsx"),//
  output: {//
    file: "dist/main.js",//
    format: "cjs",//
  },//
  plugins: [
    clear({
      targets: ["dist"],//
    }),
    //
    htmlTemplate({
      template: "public/index.html",
      target: "dist/index.html",
    }),
    serve("dist"),
    livereload("examples"),
    replace({
      preventAssignment: true,
      "process.env.NODE_ENV": JSON.stringify("production"), // 否则会报:process is not defined的错
    }),
    //
    postcss({
      plugins: [autoprefixer(), cssnanoPlugin()],
      modules: true// 可以使用 import style from './style.less';
    }),
    commonjs(),
    nodeResolve({
      extensions:  [".ts", ".tsx"]
    }),
    typescript(),
    babel({
      exclude: "node_modules/**",
      extensions: [".ts", ".tsx"], //需要手动配置后缀,不然文件ts文件不会被转成es5
    }),
    terser(),
  ],
  // 指出应将哪些模块视为外部模块 就不会被打包
  // external: ["react", "react-dom"], 生产或开发的时候需要打包进去
};
examples/App.tsx
import React from "react";
import { Button } from "../lib/es";
import style from './style.less';

function App() {
  return (
    <div className={style.App}>
      <header className="App-header">header</header>
      <Button type="primary">primary</Button>
      <Button>default</Button>
    </div>
  );
}

export default App;
examples/index.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));
examples/style.less
.App{
  width: 800px;
  margin: 10px auto;
}
examples/typeings.d.ts
declare module '*.less' {
  const classes : { readonly [key: string]: string }
  export default classes
} 
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

注意一下 tsconfig.json

tsconfig.json这个文件需要注意一下,npm run build:pro的时候 "include": ["packages", "typings.d.ts"], npm run dev 的时候要加上"examples",即:"include": ["packages", "typings.d.ts","examples"]

# 2. 添加 dev 命令

# package.json






 




{
  ...,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build:pro": "rollup -c",
    "dev": "rollup -wc rollup.config.dev.js"
  },
  ...
}

# 3. 安装依赖

npm i -D rollup-plugin-generate-html-template rollup-plugin-livereload rollup-plugin-serve
  • rollup-plugin-generate-html-template: 使用 html 模板
  • rollup-plugin-livereload: 实时刷新页面
  • rollup-plugin-serve: 启动一个服务器

# 4.运行命令实现在线预览

npm run dev

# 四. 添加代码规范 eslint+prettier+stylelint+husky+lint-staged

# 1. eslint

npm i eslint -D

npx eslint --init
  • npx eslint --init 根据选择自定义安装依赖,完成以后生成.eslintrc.js
    .eslintrc.js
    module.exports = {
        env: {
          browser: true,
          es2021: true,
        },
        extends: [
          'eslint:recommended',
          'plugin:react/recommended',
          'plugin:@typescript-eslint/recommended',
        ],
        overrides: [],
        parser: '@typescript-eslint/parser',
        parserOptions: {
          ecmaFeatures: {
            jsx: true,
          },//这几行自己加的
          ecmaVersion: 'latest',
          sourceType: 'module',
        },
        plugins: ['react', '@typescript-eslint'],
        rules: {
          'no-undef': 2,//这几行自己加的
          '@typescript-eslint/no-unused-vars': 2,//这几行自己加的
        },
      };
      

# 2. prettier

npm i prettier eslint-config-prettier eslint-plugin-prettier -D
  • eslint-config-prettier解决eslintprettier的冲突配置,保证 prettier 格式不出错
  • eslint-plugin-prettier配置eslint --fix采用prettier的配置规则格式化
创建.prettierrc.js
'use strict';
/** @format */
module.exports = {
  singleQuote: true,
  trailingComma: 'all',
  printWidth: 100,
  proseWrap: 'never',
  endOfLine: 'lf',
  overrides: [
    {
      files: '.prettierrc',
      options: {
        parser: 'json',
      },
    },
    {
      files: 'document.ejs',
      options: {
        parser: 'html',
      },
    },
  ],
};

# 3.stylelint

npm i stylelint stylelint-config-css-modules stylelint-config-prettier stylelint-config-standard stylelint-declaration-block-no-ignored-properties -D
  • stylelint-config-prettier解决stylelintprettier的冲突配置,保证 prettier 格式不出错
创建.stylelintrc.js
'use strict';
/** @format */
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-css-modules',
    'stylelint-config-prettier',
  ],
  customSyntax: 'postcss-less',
  plugins: ['stylelint-declaration-block-no-ignored-properties'],
  rules: {
    'no-descending-specificity': null,
    //https://github.com/stylelint/stylelint/issues/4114
    // 'function-calc-no-invalid': null,
    'function-url-quotes': 'always',
    'selector-attribute-quotes': 'always',
    'font-family-no-missing-generic-family-keyword': null,
    'plugin/declaration-block-no-ignored-properties': true,
    'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
    // webcomponent
    'selector-type-no-unknown': null,
    'value-keyword-case': ['lower', { ignoreProperties: ['composes'] }],
    'declaration-block-no-shorthand-property-overrides': null,
  },
  ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};

# 4.husky + lint-staged

npm i husky lint-staged -D
  • husky: husky 是 Git hooks 工具,提供很多 git 操作的钩子

编辑package.json文件,并运行 prepare 命令,进行初始化 husky

# package.json







 

 
 
 
 
 
 
 
 
 
 
 
 



{
  ...,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build:pro": "rollup -c",
    "dev": "rollup -wc rollup.config.dev.js",
    "prepare": "husky install"
  },
  "lint-staged": {
    "**/**.{js,jsx,json,ts,tsx}": [
      "prettier --write",
      "eslint --fix",
      "git add"
    ],
    "**/*.less": [
      "prettier --write",
      "stylelint --fix",
      "git add"
    ]
  },
  ...
}
npm run prepare
npx husky add .husky/pre-commit "npx lint-staged"

配置完成,进行测试

git init
git add .
git commit -m "test"

# 五. babel 的优化配置策略

  1. 安装依赖
npm install @babel/runtime @babel/runtime-corejs3
  1. 编辑.babelrcrollup.config.jsrollup.config.dev.js

# .babelrc







 
 
 
 
 
 
 
 
 
 


{
  "presets": [
    [
      "@babel/preset-env",
    ]
  ],
  "plugins": [
    "@babel/plugin-external-helpers",// 把 helpers 收集到一个共享模块或共享文件
    [
      "@babel/plugin-transform-runtime",//依赖@babel/runtime 生产依赖
      {
        "corejs": 3,//配置安装 @babel/runtime-corejs3 生产依赖
        "useESModules": true,//不转换esm的代码,让其支持tree-shaking。交给rollup去转换esm。
      }
    ]
  ]
}

# rollup.config.js rollup.config.dev.js





 



  ...,
  babel({
      exclude: 'node_modules/**',
      extensions: [ '.ts', '.tsx'], //需要手动配置后缀,不然文件ts文件不会被转成es5
      babelHelpers: 'runtime',
  }),
  ...

完整代码YDesign (opens new window)

Last Updated: 8/14/2022, 8:51:33 PM