webpack处理UMD及ES6的类
我们在开发一个插件时,通常希望它既可以被服务端引用,也能被客户端浏览器使用,即适用多个平台。于是,在模块加载的处理上,我们通常会采用 UMD(Universal Module Definition)的写法。
所谓的 UMD,主要是针对 CommonJS 或 AMD 规范加载模块的差异,而特意封装的一种通用引入模块的解决方案。
UMD 的基本代码如下:
;(function (context, name, definition) { |
上面的代码,也是我前段时间写的一个倒计时插件(Countdown
)最前面的部分代码。当插件在本地开发完成,要开启定时器,只需要在页面里实例化 Countdown 构造函数即可,如:
new Countdown(); |
一切都进行的很顺利,但当我用webpack编译打包准备发布时,发现编译出错了,提示 Countdown is not defined
。怎么回事?经过排查,发现原因出在模块加载上。我们来细看下 UMD:
if (typeof module != 'undefined' && module.exports) { |
首先,判断是否处于node服务器环境,如果是,则把 插件 挂载到 module.exports。
如果不是,再判断当前环境有没有使用 AMD 规范,如果有,则将 插件 传入到第三方模块加载器(如RequireJS)定义方法中。
如果也没检测到使用 AMD 规范来加载模块,则直接将 插件 挂载到当前环境的全局变量(浏览器为 window),context[name]
中的 context
为 window,而 name
为插件名称,这个名称是包裹 umd 匿名函数调用时传入的参数(倒计时插件为 Countdown
)。
而 webpack 编译时,可能是运行在node环境的原因,typeof module != 'undefined' && module.exports
的值竟然为 true
,这就导致本该挂载在浏览器 window 对象的 插件,却挂载到了 module.exports。如此一来,浏览器自然获取不到 Countdown 这个构造函数。报 Countdown is not defined
也合乎情理。
那么,如何既保证代码以 UMD 形式输出,又可以让webpack编译通过呢?
一、webpack 处理 UMD
经翻阅文档发现,webpack 在 ouput
输出选项上,提供了 libraryTarget
设置,如果用户添加了该选项,并把它的值设置为 umd
,像这样:
output: { |
那么,打包后输出的模块,外部会套一层 umd 代码。
于是,我们需要删除原来插件里 umd 代码,并使用如下方式输出模块:
module.exports = Countdown; |
再设置 libraryTarget
选项后,再用 webpack 编译打包一次。果然,打包后的代码前面被 umd 处理了,如下图:

与之类似的,还有一个 umdNamedDefine
选项,可参见这里。
虽然解决了 umd 的输出问题,但控制台还是报 Countdown is not defined
。
前面说到,针对浏览器环境,我们可以会直接将 插件 挂载到当前环境的全局变量(浏览器为 window)上,即 context[name] = definition()
。 而这里的 name
需要你手动指定,比如文章开头代码里的 'Countdown'
。
类似的,在webpack这里,光指定模块的输出形式是不够的,还需要指定模块的名称,同样的,webpack提供了 library
选项,它的值为模块名称。所以,将配置修改为:
output: { |
设置完成后,再webpack编译打包一次,倒计时正常运行了。
二、处理 ES6 的类
在前面处理中,通过 module.exports = Countdown
这种方式来输出模块。我们尝试改为 ES6 的语法,即:
export default class Countdown { |
当webpack再次编译运行时,又报错了 Countdown is not a constructor
。
Countdown 不是一个构造函数?
在控制台打印 Countdown,发现它是一个包含 default 属性的对象,而 default 的值是一个函数,该函数正是我们所定义的构造函数。
于是,我们得先在页面存储 default 属性,再进行实例化才能正常运行倒计时:
var Countdown = Countdown.default; |
之所以会出现这种情况,是因为我在 webpack 中使用了 Babel 来编译 ES6模块,而 Babel 会将模块编译成一个对象,并把模块指定在该对象的 default
属性。
三、总结
webpack可通过配置 libraryTarget: 'umd'
选项来输出 umd 形式的模块,同时,你需要使用 library
属性来指定模块名称,这种配置一般出现在通用模块或插件中。
使用 Babel 编译的 ES6模块,输出的内容并非模块本身,它被包含在同名对象的 default
属性中。