之前一篇文章分析了 webpack 打包 JS 模块的基本原理, 所介绍的案例是最常见的一种情况, 即多个 JS 模块和一个入口模块, 打包成一个 bundle 文件, 可以直接被浏览器或者其它 JavaScript 引擎执行, 相当于直接编译生成一个完整的可执行的文件不过还有一种很常见的情况, 就是我们要构建发布一个 JavaScript 的库, 比如你在 npm 社区发布自己的库, 这时 Webpack 就需要相应的配置, 编译生成的代码也会略有不同
和之前一篇文章一样, 本文主要分析的是 Webpack 的生成代码, 并结合它来说明编译库时 Webpack 那些关于 library 的配置选项的具体作用, 相应的官方文档在这里
编写 JS 的库
我们还是从简单的案例开始, 我们随便编写一个简单的库 util.js:
- import $ from 'jquery'
- function sayHello() {console.log("Hello");
- }
- function hideImages() {
- $('img').hide();
- }
- export default {
- sayHello: sayHello,
- hideImages: hideImages
- }
提供了两个函数, 当然它们之间毫无关系, 也实际上没有任何卵用, 纯粹是仅供教学参考
接下来写 Webpack 的配置:
- // 入口文件
- entry: {
- util: './util.js',
- }
- // 输出文件
- output: {
- path: './dist',
- filename: '[name].dist.js'
- }
但仅仅这样是不够的, 这样输出的文件就是一个立即执行的函数, 最后会返回 util.js 的 exports, 参照上一篇文章中分析, 最后生成的 bundle 代码结构大致是这样的:
- (function(modules) {
- var installedModules = {};
- function webpack_require(moduleId) {
- // ...
- }
- return webpack_require('./util.js');
- }) ({
- './util.js': generated_util,
- '/path/to/jquery.js': generated_jquery
- });
它如果执行完, 那就结束了, 将 util.js 的 export 部分返回而已, 而我们需要的是将这个返回值交给编译后的文件的 module.export, 这样编译后的文件就成了一个可以被别人 import 的库所以我们希望得到的编译文件应该是这样的:
- module.exports = (function(modules) {
- var installedModules = {};
- function webpack_require(moduleId) {
- // ...
- }
- return webpack_require('./util.js');
- }) ({
- './util.js': generated_util,
- '/path/to/jquery.js': generated_jquery
- });
要得到这样的结果, Webpack 配置 output 部分需要加入 library 信息:
- // 入口文件
- output: {
- path: './dist',
- filename: '[name].dist.js',
- library: 'util',
- libraryTarget: commonjs2
- }
这里最重要的就是 libraryTarget, 我们现在采用 commonjs2 的格式, 就会得到上面的编译结果, 也就是说 Webpack 会 library 把最后的输出以 CommonJS 的形式 export 出来, 这样就实现了一个库的发布
其它发布格式
除了 commonjs2,libraryTarget 还有其它选项:
- var (默认值, 发布为全局变量)
- commonjs
- commonjs2
- amd
- umd
使用不同的选项, 编译出来的文件就能够在不同 JavaScript 执行环境中使用在这里我们直接看万金油 umd 格式的输出是怎么样的:
- (function webpackUniversalModuleDefinition(root, factory) {
- if(typeof exports === 'object' && typeof module === 'object') // commonjs2
- module.exports = factory();
- else if(typeof define === 'function' && define.amd)
- define("util", [], factory); // amd
- else if(typeof exports === 'object')
- exports["util"] = factory(); // commonjs
- else
- root["util"] = factory(); // var
- }) (window, function() {
- return (function(modules) {
- var installedModules = {};
- function webpack_require(moduleId) {
- // ...
- }
- return webpack_require('./util.js');
- }) ({
- './util.js': generated_util,
- '/path/to/jquery.js': generated_jquery
- });
- }
比之前的 commonjs2 的情况要复杂得多, 因为它需要处理各种不同的 case, 但其实后面的部分都是一样的, 最重要的就是最前面的几行, 这是 umd 模块的标准写法它运行传入的 factory 函数, 实际上就是加载模块的函数, 然后把返回的结果根据不同的运行环境交给相应的对象例如 var, 那就会把结果设置为一个全局变量, 这用于浏览器通过 < script > 标签直接导入该 JS 文件; 如果是 CommonJS, 它则交给 exports 对象; 如果是 AMD 环境, 它也是用标准的 AMD 的写法这样这个发布出来的 JS 库就可以在任意的环境中都能被其他人使用
targetExport 控制输出内容
如果用 umd 格式打包, 可能会有一个坑需要注意, 如果你的库的源代码是用 ES6 格式 export default 来输出的, 正如上面的例子 util.js, 你直接把编译后的 JS 库文件放到浏览器中使用, 可以是 < script>, 或者 RequireJS, 可能得不到你想要的结果这是因为你的 JS 文件返回给你的对象是这样的:
- {
- 'default': {
- sayHello: sayHello,
- hideImages: hideImages
- }
- }
而不是你所期望的:
- {
- sayHello: sayHello,
- hideImages: hideImages
- }
不仅是浏览器, 在不支持 ES6 的模块系统中同样会出这个问题, 就是因为它们并不认识 default 所以你编译后的 JS 文件实际上应当只输出 default, 这就需要在 Webpack 配置里用 targetExport 来控制:
- library: 'util',
- libraryTarget: umd,
- targetExport: 'default'
这样上面的模块加载函数 factory 会在返回值后面加一个 ['default'], 这样就只返回 exports 的 default 部分
这个坑在 umd 格式下其实还是挺容易踩到的, 例如你发布一个 vue 组件,.vue 文件中的 JavaScript 部分一般都是把 Component 对象以 export default 的格式导出的, 就像这样:
- export default {
- name: 'xxx',
- data: {
- return // ...
- },
- props: {
- // ...
- }
- methods: {
- // ...
- }
- }
如果你把编译后的 JS 文件直接放在浏览器里运行, 并且用 CDN 的方式通过 < script > 加载 Vue, 你会发现 Vue 无法识别这个 Component, 因为你得到的这个对象多了一层不必要的 default
你可能会问如果我把输出内容改成了 default, 会不会影响这个模块在 ES6 环境下的使用? 一般来说是不会的之前一篇文章里已经谈到, Webpack 的生成代码在引入一个模块时, 会通过一个叫__esModule 的值来设置和判断它是不是 ES6 格式的 export, 现在如果只导出 default 部分, 那么这个对象是被视为非 ES6 的, 因为它不含__esModule 这样其它模块通过 import 来引入这个模块时, 会引入整个对象, 这实际上变相地就等价于只引入原模块的 export default 部分
当然以上讨论的前提是, 你所有需要 export 的内容全部都在 export default 里如果你既有 default, 又有正常的 export, 那编译后的文件只导出 default 部分显然是不行的
来源: http://www.jb51.net/article/136187.htm