以前一篇文章分析了Webpack打包JS模块的基本原理,所介绍的案例是最多见的一种状况,即多个JS模块和一个入口模块,打包成一个bundle文件,能够直接被浏览器或者其它JavaScript引擎执行,至关于直接编译生成一个完整的可执行的文件。不过还有一种很常见的状况,就是咱们要构建发布一个JavaScript的库,好比你在npm社区发布本身的库,这时Webpack就须要相应的配置,编译生成的代码也会略有不一样。javascript
和以前一篇文章同样,本文主要分析的是Webpack的生成代码,并结合它来讲明编译库时Webpack那些关于library的配置选项的具体做用,相应的官方文档在这里。vue
咱们仍是从简单的案例开始,咱们随便编写一个简单的库util.js
:java
import $ from 'jquery' function sayHello() { console.log("Hello"); } function hideImages() { $('img').hide(); } export default { sayHello: sayHello, hideImages: hideImages }
提供了两个函数,固然它们之间毫无关系,也实际上没有任何卵用,纯粹是仅供教学参考。。。jquery
接下来写Webpack的配置:webpack
// 入口文件 entry: { util: './util.js', } // 输出文件 output: { path: './dist', filename: '[name].dist.js' }
但仅仅这样是不够的,这样输出的文件就是一个当即执行的函数,最后会返回util.js
的exports,参照上一篇文章中分析,最后生成的bundle代码结构大体是这样的:web
(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
的库。因此咱们但愿获得的编译文件应该是这样的:npm
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信息:segmentfault
// 入口文件 output: { path: './dist', filename: '[name].dist.js', library: 'util', libraryTarget: commonjs2 }
这里最重要的就是libraryTarget
,咱们如今采用commonjs2
的格式,就会获得上面的编译结果,也就是说Webpack会library把最后的输出以CommonJS的形式export出来,这样就实现了一个库的发布。浏览器
除了commonjs2,libraryTarget
还有其它选项:ide
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的方式加载Vue
,你会发现Vue没法识别这个Component,由于你获得的这个对象多了一层没必要要的default
。
你可能会问若是我把输出内容改为了default
,会不会影响这个模块在ES6环境下的使用?通常来讲是不会的。以前一篇文章里已经谈到,Webpack的生成代码在引入一个模块时,会经过一个叫__esModule
的值来设置和判断它是否是ES6格式的export,如今若是只导出default部分,那么这个对象是被视为非ES6的,由于它不含__esModule
。这样其它模块经过import
来引入这个模块时,会引入整个对象,这实际上变相地就等价于只引入原模块的export default
部分。
固然以上讨论的前提是,你全部须要export的内容所有都在export default
里。若是你既有default,又有正常的export,那编译后的文件只导出default部分显然是不行的。