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 相关文章推荐
基于jQuery的为attr添加id title等效果的实现代码
Apr 20 Javascript
移动节点的jquery代码
Jan 13 Javascript
jQuery 顶部导航跟随滚动条滚动固定浮动在顶部
Jun 06 Javascript
js实现class样式的修改、添加及删除的方法
Jan 20 Javascript
体验jQuery和AngularJS的不同点及AngularJS的迷人之处
Feb 02 Javascript
Angular懒加载机制刷新后无法回退的快速解决方法
Aug 30 Javascript
Vue-router 中hash模式和history模式的区别
Jul 24 Javascript
浅谈Vue render函数在ElementUi中的应用
Sep 06 Javascript
JS与SQL方式随机生成高强度密码示例
Dec 29 Javascript
详解vue-router导航守卫
Jan 19 Javascript
基于three.js实现的3D粒子动效实例代码
Apr 09 Javascript
vue 实现小程序或商品秒杀倒计时
Apr 14 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变量作用域的深入解析
2013/06/03 PHP
[企业公众号]升级到[企业微信]之后发送消息失败的解决方法
2017/06/30 PHP
PHP+redis实现的悲观锁机制示例
2018/06/12 PHP
php实现多站点共用session实现单点登录的方法详解
2019/09/18 PHP
二行代码解决全部网页木马
2008/03/28 Javascript
iphone safari不支持position fixed的解决方法
2012/05/04 Javascript
纯javascript实现四方向文本无缝滚动效果
2015/06/16 Javascript
jQuery实现图片上传和裁剪插件Croppie
2015/11/29 Javascript
自学实现angularjs依赖注入
2016/12/20 Javascript
jquery uploadify隐藏上传进度的实现方法
2017/02/06 Javascript
Angular使用$http.jsonp发送跨站请求的方法
2017/03/16 Javascript
JS判断用户用的哪个浏览器实例详解
2018/10/09 Javascript
vue+canvas实现拼图小游戏
2020/09/18 Javascript
python批量下载图片的三种方法
2013/04/22 Python
Python中用PIL库批量给图片加上序号的教程
2015/05/06 Python
详解使用python的logging模块在stdout输出的两种方法
2017/05/17 Python
浅谈python函数之作用域(python3.5)
2017/10/27 Python
python 自动去除空行的实例
2018/07/24 Python
Python实现针对json中某个关键字段进行排序操作示例
2018/12/25 Python
详解django+django-celery+celery的整合实战
2019/03/19 Python
Python使用Pickle模块进行数据保存和读取的讲解
2019/04/09 Python
Python 操作mysql数据库查询之fetchone(), fetchmany(), fetchall()用法示例
2019/10/17 Python
pytorch方法测试——激活函数(ReLU)详解
2020/01/15 Python
python实现高斯投影正反算方式
2020/01/17 Python
python日期与时间戳的各种转换示例
2020/02/12 Python
使用python matplotlib 画图导入到word中如何保证分辨率
2020/04/16 Python
Python多线程:主线程等待所有子线程结束代码
2020/04/25 Python
matplotlib subplot绘制多个子图的方法示例
2020/07/28 Python
SQL里面如何插入自动增长序列号字段
2012/03/29 面试题
室内设计自我鉴定
2013/10/15 职场文书
行政部工作岗位职责范本
2014/03/05 职场文书
竞选劳动委员演讲稿
2014/04/28 职场文书
大学生就业自荐书
2014/06/16 职场文书
幼儿园中班教育随笔
2015/08/14 职场文书
低门槛开发iOS、Android、小程序应用的前端框架详解
2021/10/16 Javascript
Vue深入理解插槽slot的使用
2022/08/05 Vue.js