谈谈webpack对npm模块导入的解析

问题描述

在一次运营后台的常规更新时,发现有一个外部依赖包不能正常工作。经排查发现,react-split-pane在前几天发了一个新版0.1.81,该版本同时提供commonjses module两种包导入方式:
谈谈webpack对npm模块导入的解析
 
原来我们采用的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的值为 webworkerweb 或者没有指定时,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更新