rollup打包基于antd的react组件库

起因

  • 前段时间公司在做公共组件的抽取,打算做一个公共的组件库,本来的想法是直接把封装好的各个公共组件直接引入到项目中就可以使用,后来发现直接引用会报错,大概意思是直接引入不能识别jsx语法之类的错误。
  • 遂参考各个现成类库antdelementUI等UI库的做法经过打包生成可供其他项目引入使用的eslibumd等格式。
  • 决定引入同样的打包机制自动生成不同环境使用的代码格式

解决过程

调研使用哪种打包方案webpackorrollup

Webpack

  • Webpack 由 Tobias Koppers 在 2012年创建,用于解决当时的工具不能处理的问题:构建复杂的单页应用(SPA)
  • 代码分割Webpack可以将你的 app 分割成许多个容易管理的分块,这些分块能够在用户使用你的 app 时按需加载。这意味着你的用户可以有更快的交互体验
  • 静态资源导入:图片、CSS 等静态资源可以直接导入到你的 app 中,就和其它的模块、节点一样能够进行依赖管理。因此,我们再也不用小心翼翼地将各个静态文件放在特定的文件夹中,然后再去用脚本给文件 URL 加上哈希串了。Webpack 已经帮你完成了这一切。

rollup

  • Tree Shaking:这是rollup提出的一个特性,利用的es6模块的静态特性对导入的模块进行分析,只抽取使用到的方法,从而减小打包体积。
  • 配置使用简便,生成的代码相对于Webpack更简洁。
  • 可以指定生成生产中使用的各种不同的模块(amd,commonjs,es,umd)。

异同

  • Webpack更适用于我们实际业务项目的打包,将代码和静态资源进行打包、分割、动态引入无需我们手动处理。
  • rollup小巧使用配置方便可以对lib类库进行打包,代码运行效率更高,从vue,react等流行框架拥抱rollup即可看出端倪。
  • Webpack打包配置相对繁琐,打包出的代码体积相对较大,不同依赖之间执行会有依赖查找的情况,效率不如rollup
  • 给一个粗暴的结论:对于日常单页应用来说有代码和各种静态资源Webpack更适合,对于一些纯js/ts类库项目rollup更适合,rollup打包输入可以指定不同的格式(amd,commonjs,es,umd)应对各个场景引入使用。

打包实现

  • 综上所述本次打包是应用于其他项目中的UI库,虽然本项目中存在样式文件和图片文件等静态资源,但在rollup中使用了@rollup/plugin-imagerollup-plugin-postcss两个插件来处理css/lessimg进行处理。
  • 配置步骤如下(会根据实际配置做一个注释来解释各个配置和依赖的作用):
    • 安装rollup以及依赖
    yarn add -D rollup rollup-plugin-babel rollup-plugin-commonjs rollup-plugin-node-resolve rollup-plugin-replace  @rollup/plugin-image rollup-plugin-terser rollup-plugin-uglify rollup-plugin-postcss cssnano postcss-cssnext postcss-nested postcss-simple-vars
    复制代码
    • 安装好rollup后还需要配置babel,也需要安装babel配置需要的相关插件依赖
    yarn add -D @babel/cli @babel/core @babel/plugin-external-helpers @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-proposal-object-rest-spread @babel/plugin-transform-react-display-name @babel/plugin-transform-react-jsx @babel/plugin-transform-runtime @babel/preset-env
    复制代码
    • 安装好以上开发依赖后进行rollupbabel的配置,在项目根目录下新建rollup.config.js.babelrc.js两个配置文件用于rollupbabel执行时读取。
    • rollup.config.js配置如下:
      // Rollup plugins
      // babel插件用于处理es6代码的转换,使转换出来的代码可以用于不支持es6的环境使用
      import babel from 'rollup-plugin-babel';
      // resolve将我们编写的源码与依赖的第三方库进行合并
      import resolve from 'rollup-plugin-node-resolve';
      // 解决rollup.js无法识别CommonJS模块
      import commonjs from 'rollup-plugin-commonjs';
      // 全局替换变量比如process.env
      import replace from 'rollup-plugin-replace';
      // 使rollup可以使用postCss处理样式文件less、css等
      import postcss from 'rollup-plugin-postcss';
      // 可以处理组件中import图片的方式,将图片转换成base64格式,但会增加打包体积,适用于小图标
      import image from '@rollup/plugin-image';
      // 压缩打包代码(这里弃用因为该插件不能识别es的语法,所以采用terser替代)
      // import { uglify } from 'rollup-plugin-uglify';
      // 压缩打包代码
      import { terser } from 'rollup-plugin-terser';
      // import less from 'rollup-plugin-less';
      // PostCSS plugins
      // 处理css定义的变量
      import simplevars from 'postcss-simple-vars';
      // 处理less嵌套样式写法
      import nested from 'postcss-nested';
      // 可以提前适用最新css特性(已废弃由postcss-preset-env替代,但还是引用进来了。。。)
      // import cssnext from 'postcss-cssnext';
      // 替代cssnext
      import postcssPresetEnv from 'postcss-preset-env';
      // css代码压缩
      import cssnano from 'cssnano';
      
      const env = process.env.NODE_ENV;
      
      export default {
        // 入口文件我这里在components下统一导出所有自定义的组件
        input: 'src/components/index.js',
        // 输出文件夹,可以是个数组输出不同格式(umd,cjs,es...)通过env是否是生产环境打包来决定文件命名是否是.min
        output: [{
          file: `dist/dna-ui-react-umd${env === 'production' ? '.min' : ''}.js`,
          format: 'umd',
          name: 'geneUI',
        }, {
          file: `dist/dna-ui-react-es${env === 'production' ? '.min' : ''}.js`,
          format: 'es'
        }],
        // 注入全局变量比如jQuery的$这里只是尝试 并未启用
        // globals: {
        //   react: 'React',                                         // 这跟external 是配套使用的,指明global.React即是外部依赖react
        //   antd: 'antd'
        // },
        // 自定义警告事件,这里由于会报THIS_IS_UNDEFINED警告,这里手动过滤掉
        onwarn: function (warning) {
          if (warning.code === 'THIS_IS_UNDEFINED') {
            return;
          }
        },
        // 将模块视为外部模块,不会打包在库中
        external: ['antd', '@ant-design/icons', 'react', 'prop-types', 'gojs'],
        // 插件
        plugins: [
          image(),
          postcss({
            plugins: [
              simplevars(),
              nested(),
              // cssnext({ warnForDuplicates: false, }),
              postcssPresetEnv(),
              cssnano(),
            ],
            // 处理.css和.less文件
            extensions: [ '.css', 'less' ],
          }),
          resolve(),
          // babel处理不包含node_modules文件的所有js
          babel({
            exclude: '**/node_modules/**',
            runtimeHelpers: true,
            plugins: [
              "@babel/plugin-external-helpers"
            ]
          }),
          // 这里有些引入使用某个库的api但报未导出改api通过namedExports来手动导出
          commonjs({
            'namedExports': {
              'node_modules/react-is/index.js': ['isFragment'],
              'node_modules/react/index.js': ['Fragment', 'cloneElement', 'isValidElement', 'Children', 'createContext', 'Component', 'useRef', 'useImperativeHandle', 'forwardRef', 'useState', 'useEffect', 'useMemo'],
              'node_modules/react-dom/index.js': ['render', 'unmountComponentAtNode', 'findDOMNode'],
              'node_modules/gojs/release/go.js': ['Diagram', 'GraphLinksModel', 'Overview', 'Spot']
            }
          }),
          // 全局替换NODE_ENV,exclude表示不包含某些文件夹下的文件
          replace({
            // exclude: 'node_modules/**',
            'process.env.NODE_ENV':  JSON.stringify(env || 'development'),
          }),
          // 生产环境执行terser压缩代码
          (env === 'production' && terser()),
        ],
      }
    复制代码
    • .babelrc.js配置如下:
      // 这里通过cross-env注入不同执行变量来确定babel转码成不同的格式es和commonjs
      const { NODE_ENV, BABEL_ENV } = process.env
      const cjs = NODE_ENV === 'test' || BABEL_ENV === 'commonjs'
      const loose = true
      
      module.exports = {
        // 设置modules:false来避免babel转换成commonjs之后rollup执行会报错
        presets: [['@babel/env', { loose, modules: false }]],
        plugins: [
          ['@babel/proposal-decorators', { legacy: true }],
          ['@babel/proposal-object-rest-spread', { loose }],
          // 对jsx语法进行转换
          '@babel/transform-react-jsx',
          cjs && ['@babel/transform-modules-commonjs', { loose }],
          [
            '@babel/transform-runtime',
            {
              useESModules: !cjs,
              version: require('./package.json').dependencies[
                '@babel/runtime'
              ].replace(/^[^0-9]*/, '')
            }
          ],
          ["@babel/plugin-proposal-class-properties"]
        ].filter(Boolean)
      }
    复制代码
    • 以上安装好rollupbabel依赖,配置好相应配置之后可以配置执行命令在package.json中:
     "rollup-build": "cross-env BABEL_ENV=rollup rollup -c",
     "rollup-production-build": "cross-env NODE_ENV=production rollup -c",
    复制代码

    rollup -c指的默认执行根目录下的rollup.config.js

    • 执行以上命令yarn rollup-buildyarn rollup-production-build后如rollup.config.jsoutput配置在dist目录下生成对应的打包文件。生成的文件可以用于对应的环境直接引入使用该组件库。

番外

  1. 以上配置可以把代码打包成一个js文件用于引入使用,同时细心的同学应该能发现一些类库均有lib,es文件夹用来表示commonjs写法和es6写法,我们可以用babel直接转码生成。
  • 我们在根目录下有一个.babelrc.js的配置用于执行babel的配置。
  • 我们只需要在package.jsonscripts增加如下命令即可:
  "build:commonjs": "rimraf lib  && cross-env BABEL_ENV=commonjs babel src --out-dir lib",
  "build:es": "rimraf es && cross-env BABEL_ENV=es babel src --out-dir es",
复制代码
  • 执行以上命令即可生成对应的模块代码。
  1. 看到antdelementUI等UI库的文档站结合源码发现文档站是通过markdown写法写的文档,然后通过相应的转换工具转换成的静态文档站。这里推荐两个收集文档的静态网站生成器:
  • docz是针对react的文档收集站,开发者可以将自己的组件引入到对应的markdown.mdx文件,底层是使用的gatsby的静态网站生成器,但是使用doczreact脚手架略坑,需要做好心理准备。
  • storybook是一个功能更强大的文档生成器,可以支持jsx,.vue等各种组件,也支持.mdx文件编写。

搬砖

分类:
前端
标签: