19

使用 Rollup + TypeScript 编写库

 2 years ago
source link: https://innei.ren/posts/programming/build-rollup-typescript-lib
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
使用 Rollup + TypeScript 编写库
本文的主题是一步一步建立 Rollup + TypeScript 代码模板。
首先看看,我们需要做什么。通常一个库,在发布前他的目录树是这样的。
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
其中,dist 目录一般是通过 Rollup 等打包器打包后的入口文件,一般具有多种格式,以不同后缀命令,如: index.cjs.js index.esm.js。lib 和 esm 目录可以是 TypeScript 编译后生成的文件,目录下的结构基本和原项目结构相同,只是后缀变为 js,lib 一般为 CommonJS 格式,esm 为 ESModule 格式。而这些是一个库最基本的需要发布的文件。

初始化项目

通过如下命令快速开始:
bash
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
注:基本配置不再过多赘述,@rollup/plugin-commonjs 为 ES6 转换插件,@rollup/plugin-node-resolve 为 Node 模块解析插件,rollup-plugin-terse 为代码压缩插件,rollup-plugin-peer-deps-external 为打包时使用外部库插件(就是说,打包的时候不把依赖库打包进去,node_modules 依赖链你也知道)。
建立 rollup.config.js,编写基本配置以支持 TypeScript。
js
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
配置之后,使用 rollup -c,就可以编译打包 ts 文件到 dist 目录了。
但是这才刚刚开始。

Path Alias

一般的也会用 Path Alias 方便方法的引入。
在 tsconfig.json 配置 paths,如
json
1{
2  "compilerOptions": {
3    "baseUrl": "./src",
4    "paths": {
5      "~/*": [
6        "*"
7      ]
8    },
9  }
10}
就可以用 import foo from '~/' 的形式了。为什么要讲这个。因为这个是巨坑。请看下节。

TSC 编译与 Path Alias

上面说了 Rollup 的打包,再来说说 TSC,其实也比较简单。一般的,我会在根目录下新建一个 tsconfig.json 作为基本 tsconfig,然后在建立 build 用 tsconfig,开发用 tsconfig,都是从根目录的 extends 出来。这样做的好处就是不同环境用不同配置比较灵活。
大概这就像这样:
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
前面说了 tsc 编译也要两种格式一个是 ESM,另一个是 CJS。就需要编写两个配置了,唯一的不同其实就是 outDirmodule。然后编译跑这行就行了。
json
1{
2  "scripts": {
3    "build": "tsc --build src/tsconfig.build.json && tsc --build src/tsconfig.cjs.json"
4  }
5}
好像没有什么不对,但是仔细一看人傻了,tsc 编译之后的产物没有把 Path Alias 转换过来。但是这个库被调包侠调过来之后,它的环境咋知道 ~ alias 是个啥,况且 js 也不读 tsconfig 的配置。
这可咋整。
一查发现别的 CLI 都用了一个工具叫 tsconfigs-paths,但是这玩意好像只是个库,用起来比较麻烦。在这之后有大佬写一个 TypeScript 的插件叫 @zerollup/ts-transform-paths。用于解决这个问题。由于目前 TypeScript 还不支持自定义 transformer 所以得用 ttypescript 替换 TypeScript。
bash
1npm i -g @zerollup/ts-transform-paths ttypescript
json
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}
tsc 全换成 ttsc,
bash
1ttsc --build src/tsconfig.build.json && ttsc --build src/tsconfig.cjs.json"

打包 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
1dts-bundle-generator -o build/index.d.ts src/index.ts --project tsconfig.json  --no-check

一些不能工作的点

  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
    https://github.com/timocov/dts-bundle-generator/issues/178

Package.json

注明需要发布的文件,以及入口文件、类型文件。
json
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}
完整模板:
rollup-typescript-lib
https://github.com/Innei/rollup-typescript-lib

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK