TypeScript + React 构建组件库 -- 配置篇(一)

目标

使用 react + typescript 写组件库,发布至 npm,支持按需加载,有文档,单元测试和 CI

项目地址

项目地址 文档

本文主要记录开发过程中遇到的一些配置问题,以及如何解决的。可能不是最好的解决方案,但总归还是解决了吧,如果有人遇到相同问题的可以参考一下。

webpack 配置

我使用的是 create-react-app 来生成项目,后面觉得非常不合适,因为打包库和打包应用的配置是有很多差别的,并且生成的配置很多,反而增加了修改的难度。而且现在我认为打包库最好用 rollup,原因和按需加载有关

yarn eject

运行 yarn eject 修改配置

去掉多余的的配置

把 WorkboxWebpackPlugin 干掉,这个是用于 PWA 的,会导致最终打包的文件多一个 service-worker.js 和一个 precache-manifest.[hash].js

把 optimization 下的 splitChunks 和 runtimeChunk 干掉,因为要生成一个 index.js

umd 打包

output: {
  libraryTarget: 'umd',
}

按需加载

组件库要提供按需加载的功能,有两种方式。

  • 一种是将组件库的每个组件都打包至单独的目录下,比如

    // 增加 entry.js
    const path = require("path");
    const fs = require("fs");
    
    function getEntries() {
      function isDir(dir) {
        return fs.lstatSync(dir).isDirectory();
      }
    
      const entries = {
        index: path.join(__dirname, `../src/index.tsx`),
      };
      const dir = path.join(__dirname, "../src/components");
      const files = fs.readdirSync(dir);
      files.forEach((file) => {
        const absolutePath = path.join(dir, file);
        if (isDir(absolutePath)) {
          entries[file] = path.join(
            __dirname,
            `../src/components/${file}/index.tsx`
          );
        }
      });
      return entries;
    }
    
    const entryies = getEntries();
    
    module.exports = entryies;
    // webpack.config.js
    const entryies = require('./entries');
    
    {
    ...
      entry: entryies,
      filename: (chunkData) => {
        return chunkData.chunk.name === 'index' ? 'lib/[name].js': 'lib/components/[name]/index.js';
      },
    ...
    }

    这样,使用者就可以直接引用单个组件而不是整个的 index.js 了。并且他们也可以使用 babel-plugin-import 或者其他插件来简化写法,只不过需要加点配置。使用者如何配置可见文档

  • 另一种方式就是将组件库以 es6 模块的方式导出,并在 package.json 增加 module 字段,那么使用者用的 webpack 或者其他打包工具就可以自动识别,去加载组件库导出的 es 模块的代码了,也可以支持 tree-shaking 了。另外,有写文章说到 tree-shaking 可能没用什么的,我觉得有没有用是一回事,支不支持是另一回事了。目前我是没找到 webpack 可以方便的支持导出 es6 模块的方法,rollup 是天生就支持的。所以我个人觉得还是用 rollup 打包组件库比较好。

另外,MiniCssExtractPlugin 里的 filename 字段也改一下

使用 svg-sprite-loader

这是因为写 Icon 组件时要用到 svg,这个插件可以将多个 svg 文件整合到一个 svg 元素下,每个 svg 文件对应一个 symbol 元素,并且把整个 svg 元素插入到页面中。这样我们想使用某个 svg 元素时用相应的 symbol 就可以了。

npm 配置

private

private: true,否则发布不了

main

"main": "./build/lib/index.js", 指定使用者默认引入的文件

type

"types": "./build/lib/index.d.ts",指定使用者引入的 TS 类型文件

files

 "files": [
    "/build/lib/**/*"
  ],

需要发布的目录

TypeScript 配置

配置别名

一般我们在 webpack 里会配置别名,所以使用了 TS 之后也要配置相应别名

  "baseUrl": "./",
  "paths": {
    "@components/*": ["src/components/*"],
    "@utils/*": ["src/common/utils/*"]
  },

这个 baseUrl 一定要有

自动生成类型定义文件

  "isolatedModules": false,
  "declaration": true,
  "declarationDir": "build/lib",
  "emitDeclarationOnly": true,

这里有一个巨坑,按照文档的说法,首先要把 onEmit: true 删掉,然后只要配置了 "declaration": true, 应该就可以自动生成 .d.ts 文件了。但是我这里不行,最初我修改 npm script 的 build 命令为 "build": "node scripts/build.js & npx tsc -p tsconfig.json --emitDeclarationOnly"。当时确实是解决了,但是在之后的 circleCI 环节又出了问题,circleCI 上最后 build 出来的文件里只有 .d.ts 文件了,其他的所有 .js .css 文件都消失了。但是在我本地是没问题的。经过多次测试发现用 && 就可以了。虽然我知道 & 是并行执行 && 是串行执行,但是还是不清楚为什么我本地没问题但是 circleCI 上就不行了。。。然后我把 emitDeclarationOnly 放到 tsconfig.js 里了,发现也不影响 node scripts/build.js 打包出组件代码,所以最终配置就是如上所示。package.json 里配置 "build": "node scripts/build.js && npx tsc -p tsconfig.json"

以上就是打包组件库最重要的三个配置,接下来还有文档,单元测试以及 CI

阅读 469

推荐阅读