webpack 很强大,提供的能力选项配置也很多好让你满足各种不同的打包场景。
但学习成本也跟着上去了,其中一件让人头疼的是输出时的配置,特别容易让人迷惑;
平时用 webpack 可能不会有太大问题,可一旦你开发的包被别人引用的时候,就会存在问题;
我最近遇到这么一个场景耗费我很多时间去重新学习 webpack 的打包输出。
1、场景
当你开发的包依赖较大的第三方包(react
、react-dom
) 的时候,你一般是把这些大的第三方包 externals 出去:
{
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
}
假如你开发了 A、B 两个插件
- A 依赖 B 、
react
和react-dom
- B 只依赖
react
和react-dom
如果你想发布 A 的话,有两种策略,要么直接依赖(将 B 写到 dependencies
中),要么像 react
和 react-dom
一样 externals 掉 B 包(有可能 B 的包也很大)。
请问此时该如何 webpack 配置来支持常用的两种模式?
2、如何解决?
先看一下 webpack 官方在进行打包时候,是根据 package.json 中的 mainFields
字段指定依据哪个字段中的 路径 找到第三方包的,而 externals
字段则是指定以何种方式引入第三方包
客观情况如下:
- 我们通常的是会把打出来的包放在
browser
字段中; - webpack 打包时,选择依赖包是根据 resolve.mainFields 字段找到指定的路径把代码打进去的;默认配置是
['browser', 'module', 'main']
,也就是说会优先找browser
的字段指定的路径 libraryTarget
配置如何暴露library
。如果不设置library
, 那这个library就不暴露。就相当于一个自执行函数libraryTarget
决定了你的library
运行在哪个环境,哪个环境也就决定了你哪种模式去加载所引入的额外的包。也就是说,externals
应该和libraryTarget
保持一致。library
运行在浏览器中的,你设置externals
的模式为commonjs
,那代码肯定就运行不了了。
我们一般容易混淆的是 externals
的使用,比如对 react-dom
的 externals,经常会看到两种写法:
- {'react-dom': 'reactDom'}
- {'react-dom': 'react-dom'}
这两种的区别,其实是和你想将第三方以怎样的方式打入到你最终代码有关
还有一种 对象形式,那种是只应用在 umd 的打包方式中
2.1、externals 资料
- externals:官方文档
- webpack externals 深入理解:偏向总结
- 深入浅出webpack之externals的使用:以打包之后的源码拆解开来讲解
2.2、mainFields 参考资料
先通读一下官方文档中的 resolve.mainFields,想看中文的可以看这个链接 解析(resolve)
附其他参考资料:
- 深入浅出webpack学习(5)--Resolve:详细解读 webpack 中的这个
resolve
字段用法,相比官方文档多了举例 - package-browser-field-spec:package.json 中
browser
字段的标准写法。 - 深入理解webpack如何解析代码路径:掘金上的文章,讲解
webpack
的代码路径解析规则
顺带收集几个相关 issue,看看别人遇到的问题现在你是否可以解决:
browser
vsmodule
fields inpackage.json
:回答了当 package.json 同时包含browser
,module
&main fields
字段时候,如何指定我们 webpack 不用默认的browser
字段
3、解决方案
使用两份输出配置项,主要更改 webpack 的打包的配置项中的 externals
、output
这两个字段。
3.1、B 包的配置如下
第一份配置是针对 .umd.js
文件的(别人用于 externals ,然后通过 script 脚本标签)
{
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
},
output: {
filename: 'index.umd.js',
libraryTarget: 'umd',
library: 'extB',
path: path.resolve(__dirname, 'dist'),
umdNamedDefine: true
}
}
第二份配置是针对 .browser.js
文件的(别人使用的时候直接放在 dependencies
中,也会最终打包进去)
{
externals: {
'react': 'react', // 这里更改了
'react-dom': 'react-dom' // 这里更改了
},
output: {
filename: 'index.browser.js', // 这里更改了
libraryTarget: 'umd',
library: 'extB',
path: path.resolve(__dirname, 'dist'),
umdNamedDefine: true
}
}
同时还得更改当前文件夹的 package.json 的对应字段如下:
{
...
"main": "dist/index.umd.js",
"module": "dist/index.umd.js",
"browser": "dist/index.browser.js",
...
}
3.2、A 包的配置
首先,无论是否 externals,都需要在 package.json
中填完对 B 依赖的信息(可以根据实际情况放在 dependencies
字段或者 peerDependencies
字段)
情况 1 :A 包最终要把 B 包打入到最终代码中去,那么和 B 包的 webpack 配置是一样的;
情况 2:A 包最终要把 B 包 externals 掉,基本配置是一样的,只不过有额外的两部需要操作:
- 在上述的
externals
中新增 B 包的 externals 配置项(需要区别 {'B': 'B'} 和 {'B': 'extB'}) - 在页面中引入 cdn 资源
http://xxx/index.umd.js
(注意不是http://xxx/index.browser.js
)
4、简化的写法
我们看到上述这么写是能成功的,官方考虑到了这种情况,所以针对 umd
的打包方式,推出 对象形式](https://webpack.js.org/configuration/externals/#object),让你统一上述两种配置文件(但这种配置只能应用在 umd 的打包方式中);
最终我们把上述两份打包配置合并成一份:
{
externals: {
'react': 'react'
'react-dom': {
commonjs: 'react-dom', // 这里更改了
commonjs2: 'react-dom', // 这里更改了
amd: 'react-dom', // 这里更改了
root: 'reactDom'
}
},
output: {
filename: 'index.umd.js', // 这里更改了
libraryTarget: 'umd',
library: 'extB',
path: path.resolve(__dirname, 'dist'),
umdNamedDefine: true
}
}
这样就省去了 index.browser.js
这个文件,增加了兼容性。
5、总结
这篇文章是我一整天掉在 webpack 坑里,来回调试了半天所得出的经验总结,特此总结形成文章,方便以后查找。
具体案例参考 ide-context-menu 中的配置