前言
之前写过一个测网速的小工具和一个Dom2Img的工具库,最终通过npm发布。由于这两个工具开发成本低,代码量比较小,也就没有进行工程化,没有打包过程,没有代码风格检测,更没有代码压缩.... 用tsc编译下就完事了。正好笔者将要开发一个实现思维导图的类库,于时便搭建了一个相对完善的js库的开发环境(TS+Rollup+ESLint)。
代码打包工具
- webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。(webpack存在大量引导代码和模块函数)
- rollup仅仅是一个代码打包器,将一个个小的代码块整合为一个大块且复杂的代码
如果使用场景中包含css、js、html和静态资源,那么使用webpack打包会更好;如果只是对js代码模块进行打包,可以使用rollup。
总之,rollup更加适合用来打包js库,而webpack适合用于构建前端应用程序
笔者选用rollup作为代码打包工具,相较于webpack它更加轻量化,且支持ES Module标准,充分利用esmd的优势构建出性能更优的类库。
bash
# 项目初始化
mkdir lib-demo
cd lib-demo
npm init
pnpm i -D rollup
配置Rollup
创建rollup配置文件rollup.config.js
,这里贴出我的配置文件,由于rollup除了打包代码块外没有任何功能,所以需要一些插件来做其他的操作,我的配置文件中需要安装以下插件,插件看起来很多,但每个都有重要的作用!
shell
pnpm i -D @rollup/plugin-babel @rollup/plugin-typescript @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-terser
javascript
// rollup.config.js
// 导出defineConfig方法可以让编辑器(VSCode)智能提示所有的rollup的配置项,很方便
import { defineConfig } from "rollup";
// 这里是babel的插件,用来处理es的转换,当然会用一个.babelrc配置文件,下面也会简单列出来
import { babel } from "@rollup/plugin-babel";
// rollup转译typescript的插件
import typescript from "@rollup/plugin-typescript";
// 使得rollup能够加载node_modules里的第三方模块
import { nodeResolve } from "@rollup/plugin-node-resolve";
// rollup编译源码中默认是esm,所以rollup无法识别CommonJS模块。比如某个依赖包里用到了cjs标准的导入语句,这个插件将他们转为esm的导入语句
import commonjs from "@rollup/plugin-commonjs";
// 为了使rollup能够导入json中的数据,需要这个插件
import json from "@rollup/plugin-json";
// 压缩代码插件
import terser from "@rollup/plugin-terser";
// 引入package.json
import pkg from "./package.json" assert { type: "json" };
// 拿到package.json的name属性来动态设置打包名称
const libName = pkg.name;
export default defineConfig({
input: "index.ts",
output: [
{
file: `dist/${libName}.cjs.js`,
// commonjs格式
format: "cjs",
},
{
file: `dist/${libName}.es.js`,
// es module
format: "es",
},
{
file: `dist/${libName}.umd.js`,
// 通用格式可以用于node和browser等多个场景
format: "umd",
// 配合external使用,指出第三方库在具名导入时的名称
// globals: {
// lodash: "_",
// },
// 注意如果是umd格式的bundle的话name属性是必须的,这时可以在script标签引入后window下会挂载该属性的变量来使用你的类库方法
name: libName,
},
{
file: `dist/${libName}.min.js`,
format: "iife",
name: libName,
extend: true,
plugins: [terser()],
},
],
plugins: [
babel({ babelHelpers: "bundled" }), // 默认参数也是这个,这里显式调用是为了消除命令行窗口提示文字
typescript(),
nodeResolve(),
commonjs(),
json(),
],
// 保持某些库的外部引用状态,将第三方库通过导入语句进行导入,而不是直接将其代码打包进模块中
// external: ["lodash"],
});
这里有一点需要注意:rollup.config
文件的扩展名可以是.js
、.mjs
、.cjs
。
- 扩展名为
.js
时,rollup.config
会被node当作 esm还是cmj模块取决与package.json
中的type
字段(默认为"commonjs"),type
值为"module"
则.js
文件解释为es模块,反之亦然。 - 扩展名为
.mjs
时,无论package.json
的type
字段为什么值都将按照es模块语法进行处理 - 扩展名为
.cjs
时,无论package.json
的type
字段为什么值都将按照commonjs模块语法进行处理
所以当文件中使用了import/export
时,一旦被当作commonjs标准解释就会报错了。
支持TypeScript
rollup中存在与ts相集成的插件@rollup/plugin-typescript
,可以将ts代码转译为js代码。不过需要注意该插件依赖于typescript
和tslib
这两这个包才能正常工作
shell
pnpm i -D typescript tslib
# 初始化tsconfig.json
tsc --init
json
{
"compilerOptions": {
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"module": "ESNext", /* Specify what module code is generated. */
"moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
"declarationDir": "dist/type", /* Specify the output directory for generated declaration files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src/**/*.ts",
"index.ts",
".eslintrc.cjs" // 重点,缺少它.eslintrc.cjs会报错
]
}
Babel配置
在根目录创建babel配置文件.babelrc
json
// .babelrc
// 这里就不过多解释了,主要是modules:false这个配置项配置成false
// 否则 Babel 会在 Rollup 有机会做处理之前,将我们的模块转成 CommonJS ,导致 Rollup 的一些处理失败。
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": "3.6.4",
"modules": false
},
"@babel/preset-typescript"
]
],
"exclude": "node_modules/**"
}
创建打包命令
在package.json
中的"script"
中添加"build"
命令:rimraf -rf dist && rollup --config
。
然后执行 npm run build
即可进行打包。
ts
// 测试代码
// pnpm i -D lodash @types/lodash
import concat from 'lodash/concat'
console.log(concat([1, 2], 3, [4, 5]))
class Human {
height: string
weight: string
constructor() {
this.height = ''
this.weight = ''
}
}
class Student extends Human {
private _study: number
_sleep: number
constructor() {
super()
this._study = 0
this._sleep = 0
}
set study(val: number) {
this._study = val
}
get study(): number {
return this._study
}
setSleep(val: number): undefined {
this._sleep = val
}
setHeight(val: string): void {
this.height = val
}
}
const stu = new Student()
stu.study = 10
stu.setHeight('178')
stu.setSleep(6)
打包输出为四个文件,分别对应commonjs、esm、umd以及压缩后的代码
配置ESLint
其实也有rollup和eslint集成的插件,但是我配置一直出错,索性直接用eslint好了
shell
pnpm i -D eslint
# 初始化配置.eslintrc.js文件
npm init @eslint/config
eslint配置文件初始化的引导过程:
这样就生成了一份基础的eslint配置文件,基本可以满足平常所需的代码风格约束规则。
js
// .eslintrc.cjs
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'standard-with-typescript',
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
overrides: [
{
env: {
node: true
},
files: ['.eslintrc.{js,cjs}'],
parserOptions: {
sourceType: 'script'
}
}
],
plugins: ['@typescript-eslint'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {}
}
要想及时的修复检测代码,在跟目录创建文件夹.vscode
,在该文件夹下创建settings.json
,内容为:
json
{
"prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
}
这样每次保存代码都会对出错代码进行自动修复。
最后再配置下eslint的忽略文件.eslintignore
bash
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
Dockerfile