使用 Rollup + TypeScript 编写库
本文的主题是一步一步建立 Rollup + TypeScript 代码模板。
前言
首先看看,我们需要做什么。通常一个库,在发布前他的目录树是这样的。
.
├── dist
├── esm
├── lib
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── rollup.config.js
├── src
├── tsconfig.json
├── vite.config.js
其中,dist 目录一般是通过 Rollup 等打包器打包后的入口文件,一般具有多种格式,以不同后缀命令,如: index.cjs.js
index.esm.js
。lib 和 esm 目录可以是 TypeScript 编译后生成的文件,目录下的结构基本和原项目结构相同,只是后缀变为 js,lib 一般为 CommonJS 格式,esm 为 ESModule 格式。而这些是一个库最基本的需要发布的文件。
初始化项目
通过如下命令快速开始:
npm init -y
npm i -D typescript rollup @rollup/plugin-typescript @rollup/plugin-commonjs @rollup/plugin-node-resolve rollup-plugin-terse rollup-plugin-peer-deps-external
npx run tsc --init
mkdir src
touch src/index.ts
echo '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。
//@ts-check
import commonjs from '@rollup/plugin-commonjs'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import typescript from '@rollup/plugin-typescript'
import peerDepsExternal from 'rollup-plugin-peer-deps-external'
import { terser } from 'rollup-plugin-terser'
const packageJson = require('./package.json')
const umdName = packageJson.name
const globals = {
...packageJson.devDependencies,
}
const dir = 'dist'
/**
* @type {import('rollup').RollupOptions[]}
*/
const config = [
{
input: 'src/index.ts',
// ignore lib
external: ['lodash', 'lodash-es', ...Object.keys(globals)],
output: [
{
file: dir + '/index.umd.js',
format: 'umd',
sourcemap: true,
name: umdName,
},
{
file: dir + '/index.umd.min.js',
format: 'umd',
sourcemap: true,
name: umdName,
plugins: [terser()],
},
{
file: dir + '/index.cjs.js',
format: 'cjs',
sourcemap: true,
},
{
file: dir + '/index.cjs.min.js',
format: 'cjs',
sourcemap: true,
plugins: [terser()],
},
{
file: dir + '/index.esm.js',
format: 'es',
sourcemap: true,
},
{
file: dir + '/index.esm.min.js',
format: 'es',
sourcemap: true,
plugins: [terser()],
},
],
plugins: [
nodeResolve(),
commonjs({ include: 'node_modules/**' }),
typescript({ tsconfig: './src/tsconfig.json', declaration: false }),
// @ts-ignore
peerDepsExternal(),
],
treeshake: true,
},
]
export default config
配置之后,使用 rollup -c,就可以编译打包 ts 文件到 dist 目录了。
但是这才刚刚开始。
Path Alias
一般的也会用 Path Alias 方便方法的引入。
在 tsconfig.json 配置 paths
,如
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"~/*": [
"*"
]
},
}
}
就可以用 import foo from '~/'
的形式了。为什么要讲这个。因为这个是巨坑。请看下节。
TSC 编译与 Path Alias
上面说了 Rollup 的打包,再来说说 TSC,其实也比较简单。一般的,我会在根目录下新建一个 tsconfig.json 作为基本 tsconfig,然后在建立 build 用 tsconfig,开发用 tsconfig,都是从根目录的 extends 出来。这样做的好处就是不同环境用不同配置比较灵活。
大概这就像这样:
.
├── package.json
├── pnpm-lock.yaml
├── readme.md
├── renovate.json
├── rollup.config.js
├── src
│ ├── tsconfig.build.json # build
│ ├── tsconfig.cjs.json # cjs build
│ ├── tsconfig.json # dev
├── tsconfig.json # base
├── vite.config.js
└── yarn-error.log
前面说了 tsc 编译也要两种格式一个是 ESM,另一个是 CJS。就需要编写两个配置了,唯一的不同其实就是 outDir
和 module
。然后编译跑这行就行了。
{
"scripts": {
"build": "tsc --build src/tsconfig.build.json && tsc --build src/tsconfig.cjs.json"
}
}
好像没有什么不对,但是仔细一看人傻了,tsc 编译之后的产物没有把 Path Alias 转换过来。但是这个库被调包侠调过来之后,它的环境咋知道 ~
alias 是个啥,况且 js 也不读 tsconfig 的配置。
这可咋整。
一查发现别的 CLI 都用了一个工具叫 tsconfigs-paths,但是这玩意好像只是个库,用起来比较麻烦。在这之后有大佬写一个 TypeScript 的插件叫 @zerollup/ts-transform-paths
。用于解决这个问题。由于目前 TypeScript 还不支持自定义 transformer 所以得用 ttypescript 替换 TypeScript。
npm i -g @zerollup/ts-transform-paths ttypescript
// tsconfig.json
{
"compilerOptions": {
"outDir": "./dist",
"baseUrl": "./src",
"paths": {
"~/*": [
"*"
]
},
"plugins": [
{
"transform": "@zerollup/ts-transform-paths",
}
]
}
}
把 tsc
全换成 ttsc
,
ttsc --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-typescript
和 rollup-plugins-typescript2
都没有这一功能。
之后就发现了一个神器 dts-bundle-generator。可以做到这个需求,同时它也支持 Path Alias 的转换。
使用也非常的简单。
dts-bundle-generator -o build/index.d.ts src/index.ts --project tsconfig.json --no-check
一些不能工作的点
- ttsc 在 typescript 4.5.2 的环境可能会报错。
TypeError: Cannot read properties of undefined (reading 'impliedNodeFormat')
。 解决方式:降级到 4.4.4 - dts-bundle-generator 不能支持没有提前引入的泛型的值的解析(也可能是 目前 TS 的 bug)参考:https://github.com/timocov/dts-bundle-generator/issues/178
Package.json
注明需要发布的文件,以及入口文件、类型文件。
{
"main": "build/index.cjs.js",
"module": "build/index.esm.js",
"types": "build/index.d.ts",
"unpkg": "build/index.umd.min.js"
}