1. 前言
落魄前端打字员小李下班后,觉得有点饿,于是买了一份炒粉,分量实在太足,吃不完决定打包:
小李:
老板,麻烦打包
老板:
好的,你要哪种包装盒,有 UMD, CMD, AMD, ES modules 等等,由本店金牌打包员 webpack, babel, rollup 为你服务
小李:
…
好吧,有点尬,今天主要想说下前端普遍的打包方式
2. yarn add xx 之后都下载了啥
以 组件库
antd
为例子
1 | yarn add antd |
下载下来目录结构长这样子
抛去俄罗斯套娃的 node_modules
外,可以看到有三个目录,他们分别对应主流的三个模块规范 (AMD,CMD 远古模块规范现在基本没人使用了)
目录 | 模块规范 | 含义 |
---|---|---|
dist | UMD | 兼容 AMD,CMD 的模块,可以直接在浏览器中使用 |
lib | CommonJS | 一个文件即一个模块,Node.js 采用的就是这个规范 |
es | ES modules | ES6 模块规范 |
3. 模块的使用方式
UMD
此时,antd
变成了浏览器的全局变量,可以直接使用,好处是无需构建,直接在浏览器使用,缺点是不好维护,无法按需加载
1 | <script src="https://unpkg.com/antd"/> |
ES modules
推荐的方式,由于 import 的 module 都是静态的 , rollup 和 webpack 可以很好的去除无用代码,也就是传说中的 tree shaking
, 确定就是第三方包质量参吃不齐,很多没有提供 es 的代码包
1 | import { Button } from 'antd'; |
CommonJS
缺点是 可以 require 一个 动态的 module, 所以打包工具不能 tree shaking
1 | const { Button } = require('antd'); |
4. 模块的构建
上面说了不同模块规范的使用方式,那么怎样发布 npm
的 时候 打包不同规范的模块呢?前端什么都缺,就是不缺轮子,这里主要讲解 babel
, webpack
和 tsc
这三种方式
使用 webpack 打包 UMD
1 | const fs = require('fs'); |
这里的重点 主要是 output.libraryTarget
和 externals
, 其他的都是比较常规的配置,如果代码是 js
编写的,替换对应的 loader 即可
使用 babel 打包
编写 .babelrc
1 | const env = process.env.BABEL_ENV || process.env.NODE_ENV; |
这里的 modules
可选项为 “amd” | “umd” | “systemjs” | “commonjs” | “cjs” | “auto” | false
如果代码是 js
编写的,在命令行执行如下命令
1 | // umd `-d` 是输出到指定目录 |
直接 改变环境变量 OUTPUT_MODULE
切换不同的输出格式即可,可以看到,在输入 ES modules
的时候,没有指定环境变量,相当于是 modules: false
, 因为我们开发的时候,就是采用的 这种规范,所以不需要做额外的转换,原样输出到 es
目录即可
如果代码是 ts
编写的,问题也不大,加上 ts
的 presets
即可
1 | const env = process.env.BABEL_ENV || process.env.NODE_ENV; |
使用 tsc 打包
tsc 是 TypeScript
提供的 打包工具
首先编写 tsconfig.json
1 | { |
指定不同的模块规范,和 babel 类似,不要忘记 --declaration
, 也就是输出 types
定义,这样别人使用你的包的时候,才有友好的提示
1 | // umd |
将打包脚本集成到 package.json
为了方便的进行打包,我们可以将写好的打包脚本集成到 npm scripts
里面,最后再配置 npm
的钩子 prepublishOnly
在每次发布前自动打包,这样不会担心代码更新没有重新构建,美滋滋!
1 | // package.json |
1 | npm publish |
5. 指定不同模块的路径
代码也打包了,也发布了,是不是就完事呢?当然不是,这会有个问题,回想下前面说到的 antd
1 | import { Button } from 'antd'; |
antd
提供了三种模块代码包,我们在使用的时候却并没有指明具体的路径,那我们到底使用的是 dist
, es
还是 lib
呢?
查看 antd
代码包下面的 package.json
可以看到这几个字段:
- main 表示
Node.js
默认会加载的路径 - module 表示
ES modules
的路径 - unpkg 表示
umd
的路径 (unpkg
是一个免费的 cdn)
所以这两句代码是等价的:
1 | import { Button } from 'antd'; |
所以没有指定具体的路径的时候,默认是加载 lib
目录,也就是 cjs
模块 , 但是这样会引入全量的 antd
组件,结果这个问题也很简单,加载 es
目录 或者使用 babel-plugin-import 即可
1 | import { Button } from 'antd/es'; |
6. 让 webpack 优先加载 ES modules
如果依赖的库提供了 es
目录,我们可以手动指定,但会有些麻烦,我们需要手动查看每个库有没有提供 es
目录,幸运的是 webpack 可以帮我们处理,优先去寻找依赖的 es
目录,如果没找到,再降级为 lib
目录
1 | // webpack.config.js |
7. 结语
配置工程师还真滴不好当,现如今,还出现了 snowpack, vite 之类的打包工具,也非常值得学习,以上是我做一些开源项目学习到的一些打包方面的知识点,希望能对你有帮助。