webpack组织模块打包Library的原理及实现


Posted in Javascript onMarch 10, 2018

之前一篇文章分析了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部分显然是不行的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
在chrome浏览器中,防止input[text]和textarea在聚焦时出现黄色边框的解决方法
May 24 Javascript
Ext JS 4官方文档之三 -- 类体系概述与实践
Dec 16 Javascript
JavaScript将数据转换成整数的方法
Jan 04 Javascript
原生JS和JQuery动态添加、删除表格行的方法
May 28 Javascript
JSON与XML优缺点对比分析
Jul 17 Javascript
JS实现放大、缩小及拖拽图片的方法【可兼容IE、火狐】
Aug 23 Javascript
vue中的非父子间的通讯问题简单的实例代码
Jul 19 Javascript
Bootstrap fileinput 上传新文件移除时触发服务器同步删除的配置
Oct 08 Javascript
vue-froala-wysiwyg 富文本编辑器功能
Sep 19 Javascript
jquery插件实现轮播图效果
Oct 19 jQuery
token 机制和实现方式
Dec 15 Javascript
CocosCreator入门教程之网络通信
Apr 16 Javascript
浅谈webpack组织模块的原理
Mar 10 #Javascript
Vuex实现计数器以及列表展示效果
Mar 10 #Javascript
在vue中使用css modules替代scroped的方法
Mar 10 #Javascript
redux-saga 初识和使用
Mar 10 #Javascript
JS获取input[file]的值并显示在页面的实现方法
Mar 09 #Javascript
vue获取当前点击的元素并传值的实例
Mar 09 #Javascript
vue.js获得当前元素的文字信息方法
Mar 09 #Javascript
You might like
PHP解析html类库simple_html_dom的转码bug
2014/05/22 PHP
利用谷歌 Translate API制作自己的翻译脚本
2014/06/04 PHP
ThinkPHP字符串函数及常用函数汇总
2014/07/18 PHP
codeigniter显示所有脚本执行时间的方法
2015/03/21 PHP
php如何实现不借助IDE快速定位行数或者方法定义的文件和位置
2017/01/17 PHP
延时重复执行函数 lLoopRun.js
2007/05/08 Javascript
Javascript 遍历对象中的子对象
2009/07/03 Javascript
javascript 防止刷新,后退,关闭
2010/08/07 Javascript
NodeJS 模块开发及发布详解分享
2012/03/07 NodeJs
JavaScript显示当然日期和时间即年月日星期和时间
2013/10/29 Javascript
js 判断js函数、变量是否存在的简单示例代码
2014/03/04 Javascript
基于NodeJS的前后端分离的思考与实践(四)安全问题解决方案
2014/09/26 NodeJs
基于BootStrap的图片轮播效果展示实例代码
2016/05/23 Javascript
如何使用angularJs
2017/05/08 Javascript
easyui datagrid 表格中操作栏 按钮图标不显示的解决方法
2017/07/27 Javascript
npm 更改默认全局路径以及国内镜像的方法
2018/05/16 Javascript
JS数组去重常用方法实例小结【4种方法】
2018/05/28 Javascript
layui问题之模拟select点击事件的实例讲解
2018/08/15 Javascript
小程序组件之仿微信通讯录的实现代码
2018/09/12 Javascript
微信小程序中如何使用flyio封装网络请求
2019/07/03 Javascript
[01:04:14]OG vs Winstrike 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
web.py中调用文件夹内模板的方法
2014/08/26 Python
python中实现指定时间调用函数示例代码
2017/09/08 Python
利用OpenCV和Python实现查找图片差异
2019/12/19 Python
pytorch 修改预训练model实例
2020/01/18 Python
为什么称python为胶水语言
2020/06/16 Python
Flask-SocketIO服务端安装及使用代码示例
2020/11/26 Python
Matplotlib中rcParams使用方法
2021/01/05 Python
美国女性奢华品牌精品店:INTERMIX
2017/10/12 全球购物
工业学校毕业生自荐书
2014/01/03 职场文书
大学生实习证明范本
2014/01/15 职场文书
2014年安全生产目标责任书
2014/07/23 职场文书
政风行风评议心得体会
2014/10/21 职场文书
优秀班主任主要事迹材料
2014/12/16 职场文书
高中社区服务活动报告
2015/02/05 职场文书
MySQL读取JSON转换的方式
2022/03/18 MySQL