commonjs 组件 cmd 化
commonjs 组件 cmd 化只需要加个 define 的头部。
define(function(require, exports, module) {
const result = // code here ...
module.exports = result;
});
一般操作是在组件内部重新打包,并配合插件 wrapper-webpack-plugin 添加 define 头部。webpack 配置如下:
const webpackConfig = {
....
plugins: [
test: /\.(js|jsx)/,
header: function header(fileName){
return `define("componentName", function(require, exports, module){ \n var a =`
},
footer: '\n module.exports = a \n});'
]
}
组件外部 umd
因为我们是优化需求,得在组件外部,实现 cmd 化。
这个解决方法并不麻烦,创建一个入口文件 xxx-umd.js 将需要打包的组件引入即可。
const xxx = require('xxx')
module.exports = xxxx
此时单纯的我并没有意识到,即将进入一个大坑。

问题初现
某个组件升级为 umd 包之后,页面渲染非常慢。原本 1s 即触发完成 FP,升级后时间延长到了 8s。严重影响用户体验。
通过 performance 面板的分析,发现大部分时间在解析 cmd 加载的包。定睛一看包的体积。。。 同样依赖包的情况下,cmd 方式下,组件包的总体积比之前大了 20 倍。
包体积太大了
看了webpack 的配置,以及 webkpack-bundle-analyze 主要是两个问题:
- 没有设置 mode:'production'
- 公共组件被反复打包。比如 antd,在多个包里出现,还占据了多个包的大部分体积。
问题二的解法很简单,设置 webpack.externals ,使其不再打包 antd.
externals: {
antd: 'var window.antd'
}
出人意料的 externals
配置之后,antd 依旧被打包... 使用 webkpack-bundle-analyze,可以看到 antd 的压缩包,以及它的大量依赖 rc-xxx 。

externals 原理
externals 允许用户所创建的 bundle 依赖于那些存在于用户环境(consumer's environment)中的依赖。打包文件中少了对应的包,相匹配地 html 也需要引入这些包。externals 具体做的是就是 改写依赖。
webpack 文件打包后的文件,由源文件与依赖集合组成,使用 externals 之前,呈现如下:
(function(modules) {
// webpackBootstrap 源文件具体内容
})({
"antd": (function(module, exports) {
// antd 文件内容
}),
});
使用了 externals 把依赖改写了,变成了 externals 里配置的外部引用。 externals 配置:
{ antd: 'var window.antd', }
重新打包后的文件,antd 模块的引入变成了 window.antd.
(function(modules) {
// webpackBootstrap 源文件具体内容
})({
"antd": (function(module, exports) {
eval("module.exports = window.antd;")
}
})
只为成功找方法,不为失败找理由
重新翻阅 webapck externals 的文档,一个包引起了我的兴趣——Webpack node modules externals 。这个包可以将所有来自 node_modules 的包都不做打包处理,与我的需求非常契合。
翻看它的源码,发现它使用了 externals 的函数方法:
module.exports = {
//...
externals: [
function(context, request, callback) {
if (/^yourregex$/.test(request)){
return callback(null, 'commonjs ' + request);
}
callback();
}
]
};
于是我把 externals 改写,当 request 包含了 antd 就依赖外部的 window.antd.
module.exports = {
//...
externals: [
function(context, request, callback) {
if (/^antd$/.test(request)){
return callback(null, 'var window.antd');
}
callback();
}
]
};
我内心窃喜,运行了打包程序。结果现实又是一记重拳。运行时报错:
cannot find _button
重新翻看打包后的源码,之前的 externals 设置做了错误的改写
// externals 设置前
var _button = __webpack_require__(/*! antd/lib/button */ \"./node_modules/_antd@3.26.15@antd/lib/button/index.js\");
// externals 设置后,引用错误
var _button = __webpack_require__(\"antd\");
剩下的尽管难以置信,但那就是真相
喝了一杯茶,冷静了一下。发现压缩后的源码中,依赖包的 antd 的引用比我想象地复杂 antd/lib/xx, antd。
当前的配置强制改写了 antd/lib/xxxx 为 antd, 而我们要做的就是保留 antd/lib/xxxx 的依赖,使其从 window.antd.xxx 中获取
修改了配置为:
externals: [
function(context, request, callback) {
if(request === 'antd'){
return callback(null, 'var window.antd')
}
// 保留 antd/lib/xxxx 的依赖, 改写来源
const [,component] = request.match( /\/lib\/([^\/]+)/) || []
if(component){
return callback(null, 'var window.antd.' + getModuleName(component))
} else {
return callback();
}
}]
结果
处理之后效果惊人,包体积缩减回了原来的体积!
招人时间
这波操作有意思吗?支付宝体验技术部招人啦!校招、社招均有,岗位涉及中后台、to c、小程序、cloud IDE ,有意者投简历 anqi.anqifeng@antfin.com.