使用 Lerna、Rollup、TypeScript 打造自己的 JavaScript 工具包
目标
- 使用 Rollup、TypeScript 构建三个工具包,分别为:仅支持浏览器环境(使用了 BOM、DOM)、仅支持 nodejs 环境(使用了 nodejs API)、纯 JavaScript 环境;
- 自动生成 *.d.ts 文件,使用 @microsoft/api-documenter、 @microsoft/api-extractor 生成 API Doc;
- 加入 Jest 测试工具,测试覆盖率达标后方可发包;
- 使用 Lerna 进行多 package 管理与 npm 发布。
初始化
创建 Lerna repo
1 | npm install --global lerna |
目录如下:
1 | lerna-repo/ |
lerna.json
1 | { |
package.json
1 | { |
启用 Yarn Workspaces
package.json
1 | { |
lerna.json
1 | { |
创建 package
1 | lerna create tool |
安装依赖
安装 typescript 及相关工具
1
2
3
4lerna add typescript --dev
lerna add tslib --dev
lerna add @microsoft/api-extractor --dev
lerna add @microsoft/api-documenter --dev安装 babel 及相关插件
1
2
3
4
5nodejs 环境无需 babel
lerna add @babel/core packages/tool packages/tool-browser --dev
lerna add @babel/preset-env packages/tool packages/tool-browser --dev
lerna add @babel/preset-typescript packages/tool packages/tool-browser --dev
lerna add @babel/plugin-transform-runtime packages/tool packages/tool-browser --dev安装 rollup 及相关插件
1
2
3
4
5
6lerna add rollup --dev
lerna add @rollup/plugin-babel packages/tool packages/tool-browser --dev
lerna add @rollup/plugin-commonjs --dev
lerna add @rollup/plugin-node-resolve --dev
lerna add @rollup/plugin-typescript --dev
lerna add rollup-plugin-clear --dev安装 eslint 、prettier 及相关插件
1
2
3
4
5
6
7
8
9lerna add eslint --dev
lerna add prettier --dev
lerna add eslint-config-airbnb-base --dev
lerna add eslint-config-airbnb-typescript --dev
lerna add eslint-config-prettier --dev
lerna add eslint-plugin-import --dev
lerna add eslint-plugin-prettier --dev
lerna add @typescript-eslint/eslint-plugin --dev
lerna add @typescript-eslint/parser --dev
生成/编写相关配置文件
生成 tsconfig.json 文件,并添加自定义配置
1
./node_modules/.bin/tsc --init
配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20{
"compilerOptions": {
"target": "ES5", // tool-node: ES2021
"module": "ESNext",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": [
"src"
],
"exclude": [
"dist"
]
}编写 .eslintrc.js 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24module.exports = {
env: {
browser: true, // tool-browser 启用
es2021: true,
node: true, // tool-node 启用
},
extends: [
'airbnb-base',
'airbnb-typescript/base',
'prettier',
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
},
ignorePatterns: ['.eslintrc.js','rollup.config.js','package.json','tsconfig.json','node_modules'],
plugins: [
'@typescript-eslint',
'prettier',
],
rules: {
},
};编写 .eslintignore 文件
1
2
3
4dist/
ems/
node_modules/
config/编写 rollup.config.js 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import typescript from '@rollup/plugin-typescript'
import babel from '@rollup/plugin-babel' // tool-node 无需
import clear from 'rollup-plugin-clear'
const shareConfig = {
input: 'src/index.ts',
external: [
/@babel\/runtime/,
], // tool-node 无需
plugins: [
clear({
targets: ['dist', 'esm'],
}),
resolve(),
commonjs(),
],
}
export default [
{
...shareConfig,
plugins: [
...shareConfig.plugins,
typescript({
outDir: 'esm',
}),
babel({
babelHelpers: 'runtime',
extensions: ['.ts']
})
],
output: [
{
dir: 'esm',
format: 'esm',
sourcemap: true,
preserveModules: true,
exports: 'auto',
},
]
},
{
...shareConfig,
plugins: [
...shareConfig.plugins,
typescript({
outDir: 'lib',
}),
babel({
babelHelpers: 'runtime',
extensions: ['.ts']
})
],
output: [
{
dir: 'dist',
format: 'cjs',
sourcemap: true,
preserveModules: true,
exports: 'auto',
// tool-node 启用 generatedCode
// generatedCode: {
// arrowFunctions: true,
// constBindings: true,
// objectShorthand: true,
// preset: 'es2015',
// }
},
]
}
]编写 .babelrc
tool-node 无需
1
2
3
4
5
6
7
8
9
10
11
12
13
14{
"presets": [
[
"@babel/preset-env",
{
"modules": false
}
],
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}生成 api-extractor.json
1
npx api-extractor init
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts",
"bundledPackages": [],
"compiler": {},
"apiReport": {
"enabled": false
},
"docModel": {
"enabled": true,
"apiJsonFilePath": "../../../temp/<unscopedPackageName>.api.json"
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts",
"publicTrimmedFilePath": "<projectFolder>/esm/<unscopedPackageName>.d.ts"
},
"tsdocMetadata": {
"enabled": false
},
"messages": {
"compilerMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"extractorMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
}
}
}
}api json 文件放在根目录下,方便生成 Doc
编写 npm script
package
1 | { |
root
1 | { |
加入 Jest
安装 jest 及相关插件
1
2
3
4lerna add jest --dev
lerna add @types/jest --dev
lerna add ts-jest --dev
lerna add babel-jest packages/tool packages/tool-browser --dev编写 jest.config.js 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16module.exports = {
collectCoverage: true, // 是否收集测试时的覆盖率信息
coverageDirectory: "coverage", // 覆盖率信息输出目录
coverageProvider: "v8", // 用哪个提供程序来检测代码以进行覆盖。允许的值为babel(默认)或v8
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
}, // 配置覆盖结果的最小阈值强制执行
preset: "ts-jest", // 用作 Jest 配置基础的预设
testEnvironment: "jsdom", // 将用于测试的测试环境。 Jest 中的默认环境是 Node.js 环境。 如果你正在构建一个 web 应用程序,你可以通过 jsdom 来使用类似浏览器的环境
verbose: true, // 是否应在运行期间报告每个单独的测试。
};编写测试文件
type.test.ts 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import { type } from "../../src";
const { isUndefined, isNull } = type;
describe("type:", () => {
describe("isUndefined", () => {
test(" undefined => true ", () => {
expect(isUndefined(undefined)).toBe(true);
});
test(" null => false ", () => {
expect(isUndefined(null)).toBe(false);
});
test(" 0 => false ", () => {
expect(isUndefined(0)).toBe(false);
});
test(' "" => false ', () => {
expect(isUndefined("")).toBe(false);
});
test(" {} => false ", () => {
expect(isUndefined({})).toBe(false);
});
test(" [] => false ", () => {
expect(isUndefined([])).toBe(false);
});
test(" () => false ", () => {
expect(isUndefined(() => {})).toBe(false);
});
});
});配置 npm script
package1
2
3
4
5{
"scripts": {
"test": "jest"
}
}root
1
2
3
4
5{
"scripts": {
"test": "lerna exec -- yarn test"
}
}
配置 pre-commit git-hook
安装 husky
1
npx husky-init && yarn
它将设置 husky,修改 package.json 并创建一个 pre-commit 的示例挂钩。默认情况下,它将 npm test 在您提交时运行。
安装相关依赖
根目录安装即可1
yarn add @commitlint/cli @commitlint/config-conventional commitizen lint-staged
配置相关文件
- commitizen 配置文件
.czrc 1
2
3{
"path": "cz-conventional-changelog"
} - commitlint 配置文件
commitlint.config.js 1
2
3module.exports = {
extends: ['@commitlint/config-conventional']
} - lint-staged 配置文件(各 package 内)
.lintstagedrc.json 1
2
3
4{
"**/*.{js,ts}": "npm run lint-staged:js",
"**/*.{js,ts,md,json}": ["prettier --write"]
}
- commitizen 配置文件
添加 husky hook
已经存在的pre-commit
1
2
3
4
5!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint-staged
yarn test新增 commit-msg
1
yarn husky add .husky/commit-msg 'yarn commitlint --edit $1'
npm script
package 添加1
2
3
4
5{
"scripts": {
"lint-staged:js": "eslint --ext .js,.ts"
}
}root 添加
1
2
3
4
5{
"scripts": {
"commit": "cz"
}
}
发布 npm
1 | lerna publish |
更多查看:lerna publish
完整的 npm script
package
1 | { |
root
1 | { |