问题描述
在一次运营后台的常规更新时,发现有一个外部依赖包不能正常工作。经排查发现,
react-split-pane
在前几天发了一个新版0.1.81,该版本同时提供commonjs和es module两种包导入方式:
原来我们采用的CMD写法
const ReactSplitPane = require('react-split-pane')
失效了。
原因分析
翻查webpack官方文档后,发现webpack会对包导入会进行解析,不管是采用CMD方式还是ES6 import方式。 解析的依据是配文件的
resolve.mainFields
属性, 该属性用于配置采用package.json的哪个字段作为模块的入口文件。
以react-split-pane 模块为例,当
resolve.mainFields = ['browser', 'module', 'main']
时 , webpack在解析 如 const ReactSplitPane = require('react-split-pane')
或
import ReactSplitPane from 'react-split-pane'
的时候,会优先使用 dist/index.esm.js
。 由于采用
export default 导出的模块必须使用 import x from 'xx' 的方式(或 使用 const x = require('x').default的方式) 导入, 否则导入后的模块会引用不正确.
如果 resolve.mainFiels 没有显式指定,则根据webpack的
target 的取值来决定其默认值
-
当 target的值为
webworker
,web
或者没有指定时,mainFields = ['browser', 'module', 'main']
-
当 target 为其他任意值(包括
node
),mainFields = ['module', 'main']
后续
随着ES6的普及,相信会有越来越多的第三方模块提供 es module 版本,而mainFields的默认值中,
module
总是排在 main
之前 ,因此类似的问题还会发生。要从根本上解决这个问题,有以下的办法
-
借助 js-codemod等工具,将所有对第三方模块的导入方式 从commonjs 方式改成 ES6 import方式。 因为采用
module.exports= ..
导出的包均可以使用import x from '...'
的方式导入,但export default x
导出的包不能直接用require('x')
导入. -
显式指定
mainFields
, 把main的优先级提高 或干脆不使用es module。但这种做法无法利用webpack提供的tree shaking 能力减少构建后bundle的体积, 也无法用上一些浏览器已支持的es6特性 - 利用npm shrinkwrap锁定包版本,缺点是无法及时享受依赖bugfix更新