使用 Rollup + TypeScript 编写库

2021-12-28 10:38:06 浏览数 (1)

本文的主题是一步一步建立 Rollup TypeScript 代码模板。

前言

首先看看,我们需要做什么。通常一个库,在发布前他的目录树是这样的。

代码语言:javascript复制
1.
2├── dist
3├── esm
4├── lib
5├── node_modules
6├── package.json
7├── pnpm-lock.yaml
8├── rollup.config.js
9├── src
10├── tsconfig.json
11├── vite.config.js

COPY

其中,dist 目录一般是通过 Rollup 等打包器打包后的入口文件,一般具有多种格式,以不同后缀命令,如: index.cjs.js index.esm.js。lib 和 esm 目录可以是 TypeScript 编译后生成的文件,目录下的结构基本和原项目结构相同,只是后缀变为 js,lib 一般为 CommonJS 格式,esm 为 ESModule 格式。而这些是一个库最基本的需要发布的文件。

初始化项目

通过如下命令快速开始:

bash

代码语言:javascript复制
1npm init -y
2npm i -D typescript rollup @rollup/plugin-typescript @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-terse rollup-plugin-peer-deps-external
3npx run tsc --init
4mkdir src
5touch src/index.ts
6echo 'export {}' >> src/index.ts

COPY

注:基本配置不再过多赘述,@rollup/plugin-commonjs 为 ES6 转换插件,@rollup/plugin-node-resolve 为 Node 模块解析插件,rollup-plugin-terse 为代码压缩插件,rollup-plugin-peer-deps-external 为打包时使用外部库插件(就是说,打包的时候不把依赖库打包进去,node_modules 依赖链你也知道)。

建立 rollup.config.js,编写基本配置以支持 TypeScript。

js

代码语言:javascript复制
1//@ts-check
2import commonjs from '@rollup/plugin-commonjs'
3import { nodeResolve } from '@rollup/plugin-node-resolve'
4import typescript from '@rollup/plugin-typescript'
5import peerDepsExternal from 'rollup-plugin-peer-deps-external'
6import { terser } from 'rollup-plugin-terser'
7
8const packageJson = require('./package.json')
9
10const umdName = packageJson.name
11
12const globals = {
13  ...packageJson.devDependencies,
14}
15
16const dir = 'dist'
17
18/**
19 * @type {import('rollup').RollupOptions[]}
20 */
21const config = [
22  {
23    input: 'src/index.ts',
24    // ignore lib
25    external: ['lodash', 'lodash-es', ...Object.keys(globals)],
26
27    output: [
28      {
29        file: dir   '/index.umd.js',
30        format: 'umd',
31        sourcemap: true,
32        name: umdName,
33      },
34      {
35        file: dir   '/index.umd.min.js',
36        format: 'umd',
37        sourcemap: true,
38        name: umdName,
39        plugins: [terser()],
40      },
41      {
42        file: dir   '/index.cjs.js',
43        format: 'cjs',
44        sourcemap: true,
45      },
46      {
47        file: dir   '/index.cjs.min.js',
48        format: 'cjs',
49        sourcemap: true,
50        plugins: [terser()],
51      },
52      {
53        file: dir   '/index.esm.js',
54        format: 'es',
55        sourcemap: true,
56      },
57      {
58        file: dir   '/index.esm.min.js',
59        format: 'es',
60        sourcemap: true,
61        plugins: [terser()],
62      },
63    ],
64    plugins: [
65      nodeResolve(),
66      commonjs({ include: 'node_modules/**' }),
67      typescript({ tsconfig: './src/tsconfig.json', declaration: false }),
68
69      // @ts-ignore
70      peerDepsExternal(),
71    ],
72
73    treeshake: true,
74  },
75]
76
77export default config

COPY

配置之后,使用 rollup -c,就可以编译打包 ts 文件到 dist 目录了。

但是这才刚刚开始。

Path Alias

一般的也会用 Path Alias 方便方法的引入。

在 tsconfig.json 配置 paths,如

json

代码语言:javascript复制
1{
2  "compilerOptions": {
3    "baseUrl": "./src",
4    "paths": {
5      "~/*": [
6        "*"
7      ]
8    },
9  }
10}

COPY

就可以用 import foo from '~/' 的形式了。为什么要讲这个。因为这个是巨坑。请看下节。

TSC 编译与 Path Alias

上面说了 Rollup 的打包,再来说说 TSC,其实也比较简单。一般的,我会在根目录下新建一个 tsconfig.json 作为基本 tsconfig,然后在建立 build 用 tsconfig,开发用 tsconfig,都是从根目录的 extends 出来。这样做的好处就是不同环境用不同配置比较灵活。

大概这就像这样:

代码语言:javascript复制
1.
2├── package.json
3├── pnpm-lock.yaml
4├── readme.md
5├── renovate.json
6├── rollup.config.js
7├── src
8│   ├── tsconfig.build.json # build
9│   ├── tsconfig.cjs.json # cjs build
10│   ├── tsconfig.json # dev
11├── tsconfig.json  # base
12├── vite.config.js
13└── yarn-error.log

COPY

前面说了 tsc 编译也要两种格式一个是 ESM,另一个是 CJS。就需要编写两个配置了,唯一的不同其实就是 outDirmodule。然后编译跑这行就行了。

json

代码语言:javascript复制
1{
2  "scripts": {
3    "build": "tsc --build src/tsconfig.build.json && tsc --build src/tsconfig.cjs.json"
4  }
5}

COPY

好像没有什么不对,但是仔细一看人傻了,tsc 编译之后的产物没有把 Path Alias 转换过来。但是这个库被调包侠调过来之后,它的环境咋知道 ~ alias 是个啥,况且 js 也不读 tsconfig 的配置。

这可咋整。

一查发现别的 CLI 都用了一个工具叫 tsconfigs-paths,但是这玩意好像只是个库,用起来比较麻烦。在这之后有大佬写一个 TypeScript 的插件叫 @zerollup/ts-transform-paths。用于解决这个问题。由于目前 TypeScript 还不支持自定义 transformer 所以得用 ttypescript 替换 TypeScript。

bash

代码语言:javascript复制
1npm i -g @zerollup/ts-transform-paths ttypescript

COPY

json

代码语言:javascript复制
1// tsconfig.json
2{
3  "compilerOptions": {
4    "outDir": "./dist",
5    "baseUrl": "./src",
6    "paths": {
7      "~/*": [
8        "*"
9      ]
10    },
11    "plugins": [
12      {
13        "transform": "@zerollup/ts-transform-paths",
14      }
15    ]
16  }
17}

COPY

tsc 全换成 ttsc,

bash

代码语言:javascript复制
1ttsc --build src/tsconfig.build.json && ttsc --build src/tsconfig.cjs.json"

COPY

之后。

打包 DTS

DTS 就是 tsc 生成 d.ts,我们要做的就是把 dts 也打包一份,全部扔到一个 index.d.ts。这样的话如果引用的是任何一个 dist 下的 index.js (比如dist/index.esm.js)都会识别到 type definition。

但是,@rollup/plugin-typescriptrollup-plugins-typescript2 都没有这一功能。

之后就发现了一个神器 dts-bundle-generator。可以做到这个需求,同时它也支持 Path Alias 的转换。

使用也非常的简单。

bash

代码语言:javascript复制
1dts-bundle-generator -o build/index.d.ts src/index.ts --project tsconfig.json  --no-check

COPY

一些不能工作的点

  1. ttsc 在 typescript 4.5.2 的环境可能会报错。TypeError: Cannot read properties of undefined (reading 'impliedNodeFormat')。 解决方式:降级到 4.4.4
  2. dts-bundle-generator 不能支持没有提前引入的泛型的值的解析(也可能是 目前 TS 的 bug)参考:https://github.com/timocov/dts-bundle-generator/issues/178

Package.json

注明需要发布的文件,以及入口文件、类型文件。

json

代码语言:javascript复制
1{
2  "main": "build/index.cjs.js",
3  "module": "build/index.esm.js",
4  "types": "build/index.d.ts",
5  "unpkg": "build/index.umd.min.js"
6}

COPY

完整模板:

rollup-typescript-lib

0 人点赞