探索webpack模块及webpack3新特性


Posted in Javascript onSeptember 18, 2017

本文从简单的例子入手,从打包文件去分析以下三个问题:webpack打包文件是怎样的?如何做到兼容各大模块化方案的?webpack3带来的新特性又是什么?

一个简单的例子

webpack配置

// webpack.config.js
module.exports = {
 entry: './src/index.js',
 output: {
 filename: 'bundle.js',
 path: path.resolve(__dirname, 'dist')
 },
};

简单的js文件

// src/index.js
 console.log('hello world');

webpack打包后的代码

一看你就会想,我就一行代码,你给我打包那么多???(黑人问号)

// dist/bundle.js
 /******/ (function(modules) { // webpackBootstrap
/******/  // The module cache
/******/  var installedModules = {};
/******/
/******/  // The require function
/******/  function __webpack_require__(moduleId) {
/******/
/******/   // Check if module is in cache
/******/   if(installedModules[moduleId]) {
/******/    return installedModules[moduleId].exports;
/******/   }
/******/   // Create a new module (and put it into the cache)
/******/   var module = installedModules[moduleId] = {
/******/    i: moduleId,
/******/    l: false,
/******/    exports: {}
/******/   };
/******/
/******/   // Execute the module function
/******/   modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/   // Flag the module as loaded
/******/   module.l = true;
/******/
/******/   // Return the exports of the module
/******/   return module.exports;
/******/  }
/******/
/******/
/******/  // expose the modules object (__webpack_modules__)
/******/  __webpack_require__.m = modules;
/******/
/******/  // expose the module cache
/******/  __webpack_require__.c = installedModules;
/******/
/******/  // define getter function for harmony exports
/******/  __webpack_require__.d = function(exports, name, getter) {
/******/   if(!__webpack_require__.o(exports, name)) {
/******/    Object.defineProperty(exports, name, {
/******/     configurable: false,
/******/     enumerable: true,
/******/     get: getter
/******/    });
/******/   }
/******/  };
/******/
/******/  // getDefaultExport function for compatibility with non-harmony modules
/******/  __webpack_require__.n = function(module) {
/******/   var getter = module && module.__esModule ?
/******/    function getDefault() { return module['default']; } :
/******/    function getModuleExports() { return module; };
/******/   __webpack_require__.d(getter, 'a', getter);
/******/   return getter;
/******/  };
/******/
/******/  // Object.prototype.hasOwnProperty.call
/******/  __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/  // __webpack_public_path__
/******/  __webpack_require__.p = "";
/******/
/******/  // Load entry module and return exports
/******/  return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
console.log('hello world');
/***/ })
/******/ ]);

我们来分析一下这部分代码,先精简一下,其实整体就是一个自执行函数,然后传入一个模块数组

(function(modules) { 
  //...
 })([function(module, exports) {
  //..
 }])

好了,传入模块数组做了什么(其实注释都很明显了,我只是大概翻译一下)

/******/ (function(modules) { // webpackBootstrap
/******/  // The module cache 缓存已经load过的模块
/******/  var installedModules = {};
/******/
/******/  // The require function 引用的函数
/******/  function __webpack_require__(moduleId) {
/******/
/******/   // Check if module is in cache 假如在缓存里就直接返回
/******/   if(installedModules[moduleId]) {
/******/    return installedModules[moduleId].exports;
/******/   }
/******/   // Create a new module (and put it into the cache) 构造一个模块并放入缓存
/******/   var module = installedModules[moduleId] = {
/******/    i: moduleId, //模块id
/******/    l: false, // 是否已经加载完毕
/******/    exports: {} // 对外暴露的内容
/******/   };
/******/
/******/   // Execute the module function 传入模块参数,并执行模块
/******/   modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/   // Flag the module as loaded 标记模块已经加载完毕
/******/   module.l = true;
/******/
/******/   // Return the exports of the module 返回模块暴露的内容
/******/   return module.exports;
/******/  }
/******/
/******/
/******/  // expose the modules object (__webpack_modules__) 暴露模块数组
/******/  __webpack_require__.m = modules;
/******/
/******/  // expose the module cache 暴露缓存数组
/******/  __webpack_require__.c = installedModules;
/******/
/******/  // define getter function for harmony exports 为ES6 exports定义getter
/******/  __webpack_require__.d = function(exports, name, getter) {
/******/   if(!__webpack_require__.o(exports, name)) { // 假如exports本身不含有name这个属性
/******/    Object.defineProperty(exports, name, {
/******/     configurable: false,
/******/     enumerable: true,
/******/     get: getter
/******/    });
/******/   }
/******/  };
/******/
/******/  // getDefaultExport function for compatibility with non-harmony modules 解决ES module和Common js module的冲突,ES则返回module['default']
/******/  __webpack_require__.n = function(module) {
/******/   var getter = module && module.__esModule ?
/******/    function getDefault() { return module['default']; } :
/******/    function getModuleExports() { return module; };
/******/   __webpack_require__.d(getter, 'a', getter);
/******/   return getter;
/******/  };
/******/
/******/  // Object.prototype.hasOwnProperty.call
/******/  __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/  // __webpack_public_path__ webpack配置下的公共路径
/******/  __webpack_require__.p = "";
/******/
/******/  // Load entry module and return exports 最后执行entry模块并且返回它的暴露内容
/******/  return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
console.log('hello world');
/***/ })
/******/ ]);

整体流程是怎样的呢

  • 传入module数组
  • 调用__webpack_require__(__webpack_require__.s = 0)

构造module对象,放入缓存

调用module,传入相应参数modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); (这里exports会被函数内部的东西修改)

标记module对象已经加载完毕

返回模块暴露的内容(注意到上面函数传入了module.exports,可以对引用进行修改)

  • 模块函数中传入module, module.exports, __webpack_require__
  • 执行过程中通过对上面三者的引用修改,完成变量暴露和引用

webpack模块机制是怎样的

我们可以去官网看下webpack模块

doc.webpack-china.org/concepts/mo…

webpack 模块能够以各种方式表达它们的依赖关系,几个例子如下:

  • ES2015 import 语句
  • CommonJS require() 语句
  • AMD define 和 require 语句
  • css/sass/less 文件中的 @import 语句。
  • 样式(url(...))或 HTML 文件()中的图片链接(image url)

强大的webpack模块可以兼容各种模块化方案,并且无侵入性(non-opinionated)

我们可以再编写例子一探究竟

CommonJS

修改src/index.js

var cj = require('./cj.js');
console.log('hello world');
cj();

新增src/cj.js,保持前面例子其他不变

// src/cj.js
function a() {
 console.log("CommonJS");
}
module.exports = a;

再次运行webpack

/******/ (function(modules) { // webpackBootstrap
 //... 省略代码
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
let cj = __webpack_require__(1);
console.log('hello world');
cj();
/***/ }),
/* 1 */
/***/ (function(module, exports) {
function a() {
 console.log("CommonJS");
}
module.exports = a;
/***/ })
/******/ ]);

我们可以看到模块数组多了个引入的文件,然后index.js模块函数多了个参数__webpack_require__,去引用文件(__webpack_require__在上一节有介绍),整体上就是依赖的模块修改了module.exports,然后主模块执行依赖模块,获取exports即可

ES2015 import

新增src/es.js

// src/es.js
export default function b() {
 console.log('ES Modules');
}

修改src/index.js

// src/index.js
import es from './es.js';
console.log('hello world');
es();
webpack.config.js不变,执行webpack
/******/ (function(modules) { // webpackBootstrap
// ... 省略代码
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__es_js__ = __webpack_require__(1);
console.log('hello world');
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["a" /* default */])();
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = b;
function b() {
 console.log('ES Modules');
}
/***/ })
/******/ ]);

我们可以看到它们都变成了严格模式,webpack自动采用的

表现其实跟CommonJS相似,也是传入export然后修改,在主模块再require进来,

我们可以看到这个

Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

这个干嘛用的?其实就是标记当前的exports是es模块,还记得之前的__webpack_require__.n吗,我们再拿出来看看

/******/  // getDefaultExport function for compatibility with non-harmony modules 解决ES module和Common js module的冲突,ES则返回module['default']
/******/  __webpack_require__.n = function(module) {
/******/   var getter = module && module.__esModule ?
/******/    function getDefault() { return module['default']; } :
/******/    function getModuleExports() { return module; };
/******/   __webpack_require__.d(getter, 'a', getter);
/******/   return getter;
/******/  };

为了避免跟非ES Modules冲突?冲突在哪里呢?

其实这部分如果你看到babel转换ES Modules源码就知道了,为了兼容模块,会把ES Modules直接挂在exports.default上,然后加上__esModule属性,引入的时候判断一次是否是转换模块,是则引入module['default'],不是则引入module

我们再多引入几个ES Modules看看效果

// src/es.js
export function es() {
 console.log('ES Modules');
}
export function esTwo() {
 console.log('ES Modules Two');
}
export function esThree() {
 console.log('ES Modules Three');
}
export function esFour() {
 console.log('ES Modules Four');
}

我们多引入esTwo和esFour,但是不使用esFour

// src/index.js
import { es, esTwo, esFour} from './es.js';
console.log('hello world');
es();
esTwo();

得出

/******/ (function(modules) { // webpackBootstrap
// ...
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__es_js__ = __webpack_require__(1);
console.log('hello world');
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["a" /* es */])();
Object(__WEBPACK_IMPORTED_MODULE_0__es_js__["b" /* esTwo */])();
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = es;
/* harmony export (immutable) */ __webpack_exports__["b"] = esTwo;
/* unused harmony export esThree */
/* unused harmony export esFour */
function es() {
 console.log('ES Modules');
}
function esTwo() {
 console.log('ES Modules Two');
}
function esThree() {
 console.log('ES Modules Three');
}
function esFour() {
 console.log('ES Modules Four');
}
/***/ })
/******/ ]);

嗯嗯其实跟前面是一样的,举出这个例子重点在哪里呢,有没有注意到注释中

/* unused harmony export esThree */
/* unused harmony export esFour */

esThree是我们没有引入的模块,esFour是我们引用但是没有使用的模块,webpack均对它们做了unused的标记,其实这个如果你使用了webpack插件uglify,通过标记,就会把esThree和esFour这两个未使用的代码消除(其实它就是tree-shaking)

AMD

我们再来看看webpack怎么支持AMD

新增src/amd.js

// src/amd.js
define([
],function(){
 return {
  amd:function(){
   console.log('AMD');
  }
 };
});

修改index.js

// src/index.js
define([
 './amd.js'
],function(amdModule){
 amdModule.amd();
});

得到

/******/ (function(modules) { // webpackBootstrap
// ... 省略代码
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
 __webpack_require__(1)
], __WEBPACK_AMD_DEFINE_RESULT__ = function(amdModule){
 amdModule.amd();
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
    __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [
], __WEBPACK_AMD_DEFINE_RESULT__ = function(){
 return {
  amd:function(){
   console.log('AMD');
  }
 };
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
    __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
/***/ })
/******/ ]);

先看amd.js整理一下代码

function(module, exports, __webpack_require__) {
 var __WEBPACK_AMD_DEFINE_ARRAY__,
  __WEBPACK_AMD_DEFINE_RESULT__;
 !(
  __WEBPACK_AMD_DEFINE_ARRAY__ = [],
  __WEBPACK_AMD_DEFINE_RESULT__ = function() {
   return {
    amd: function() {
     console.log('AMD');
    }
   };
  }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
  __WEBPACK_AMD_DEFINE_RESULT__ !== undefined &&
  (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)
 );
})

简单来讲收集define Array然后置入返回函数,根据参数获取依赖

apply对数组拆解成一个一个参数

再看index.js模块部分

function(module, exports, __webpack_require__) {
 var __WEBPACK_AMD_DEFINE_ARRAY__,
  __WEBPACK_AMD_DEFINE_RESULT__;
 !(
  __WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(1)],
  __WEBPACK_AMD_DEFINE_RESULT__ = function(amdModule) {
    amdModule.amd();
  }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__),
  __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && 
  (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)
 );
}

其实就是引入了amd.js暴露的{amd:[Function: amd]}

css/image?

css和image也可以成为webpack的模块,这是令人震惊的,这就不能通过普通的hack commonjs或者函数调用简单去调用了,这就是anything to JS,它就需要借助webpack loader去实现了

像css就是转换成一段js代码,通过处理,调用时就是可以用js将这段css插入到style中,image也类似,这部分就不详细阐述了,有兴趣的读者可以深入去研究

webpack3新特性

我们可以再顺便看下webpack3新特性的表现

具体可以看这里medium.com/webpack/web…

Scope Hoisting

我们可以发现模块数组是一个一个独立的函数然后闭包引用webpack主函数的相应内容,每个模块都是独立的,然后带来的结果是在浏览器中执行速度变慢,然后webpack3学习了Closure Compiler和RollupJS这两个工具,连接所有闭包到一个闭包里,放入一个函数,让执行速度更快,并且整体代码体积也会有所缩小

我们可以实际看一下效果(要注意的是这个特性只支持ES Modules,是不支持CommonJs和AMD的)

使用上面的例子,配置webpack.config.js,增加new webpack.optimize.ModuleConcatenationPlugin()

const path = require('path');
const webpack = require('webpack');
module.exports = {
 entry: './src/index.js',
 output: {
 filename: 'bundle.js',
 path: path.resolve(__dirname, 'dist')
 },
 module: {
 },
 plugins: [
 new webpack.optimize.ModuleConcatenationPlugin(),
 ]
};

打包

/******/ (function(modules) { // webpackBootstrap
// ... 省略代码
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
// CONCATENATED MODULE: ./src/es.js
function es() {
 console.log('ES Modules');
}
function esTwo() {
 console.log('ES Modules Two');
}
function esThree() {
 console.log('ES Modules Three');
}
function esFour() {
 console.log('ES Modules Four');
}
// CONCATENATED MODULE: ./src/index.js
// src/index.js
console.log('hello world');
es();
/***/ })
/******/ ]);

我们可以惊喜的发现没有什么require了,它们拼接成了一个函数,good!?

Magic Comments

code splitting是webpack一个重点特性之一,涉及到要动态引入的时候,webpack可以使用 require.ensure去实现,后来webpack2支持使用了符合 ECMAScript 提案 的 import() 语法,但是它有个不足之处,无法指定chunk的名称chunkName,为了解决这个问题,出现了Magic Comments,支持用注释的方式去指定,如下

import(/* webpackChunkName: "my-chunk-name" */ 'module');

小结

webpack是一个强大的模块打包工具,在处理依赖、模块上都很优秀,本文从bundle.js文件分析出发去探索了不同模块方案的加载机制,初步去理解webpack,并且对webpack3特性进行阐述,当然,webpack还有很多地方需要去探索深究,敬请期待以后的文章吧~关于本文如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
用jscript实现列出安装的软件列表
Jun 18 Javascript
基于JQuery的Pager分页器实现代码
Jul 17 Javascript
让浏览器非阻塞加载javascript的几种方法小结
Apr 25 Javascript
jquery动态添加删除一行数据示例
Jun 12 Javascript
javascript定义变量时有var和没有var的区别探讨
Jul 21 Javascript
复制网页内容,粘贴之后自动加上网址的实现方法(脚本之家特别整理)
Oct 16 Javascript
zepto.js中tap事件阻止冒泡的实现方法
Feb 12 Javascript
jquery实现初次打开有动画效果的网页TAB切换代码
Sep 06 Javascript
JavaScrip调试技巧之断点调试
Oct 22 Javascript
jQuery+AJAX实现遮罩层登录验证界面(附源码)
Sep 13 Javascript
VUE中使用MUI方法
Feb 12 Javascript
javascript网页随机点名实现过程解析
Oct 15 Javascript
浅谈JavaScript作用域和闭包
Sep 18 #Javascript
详解用函数式编程对JavaScript进行断舍离
Sep 18 #Javascript
深入浅析JavaScript中的RegExp对象
Sep 18 #Javascript
JavaScript 数组的进化与性能分析
Sep 18 #Javascript
JavaScript实现HTML5游戏断线自动重连的方法
Sep 18 #Javascript
Node.JS中快速扫描端口并发现局域网内的Web服务器地址(80)
Sep 18 #Javascript
BetterScroll 在移动端滚动场景的应用
Sep 18 #Javascript
You might like
php使用timthumb生成缩略图的方法
2016/01/22 PHP
通过chrome浏览器控制台(Console)进行PHP Debug的方法
2016/10/19 PHP
PHP设计模式之命令模式示例详解
2020/12/20 PHP
老鱼 浅谈javascript面向对象编程
2010/03/04 Javascript
JavaScript字符串对象fromCharCode方法入门实例(用于把Unicode值转换为字符串)
2014/10/17 Javascript
javascript实现切换td中的值
2014/12/05 Javascript
js实现的tab标签切换效果代码分享
2015/08/25 Javascript
基于JavaScript实现鼠标箭头移动图片跟着移动
2016/08/30 Javascript
AngularJS中watch监听用法分析
2016/11/04 Javascript
JQuery validate 验证一个单独的表单元素实例
2017/02/17 Javascript
原生JS仿QQ阅读点击展开、收起效果
2017/03/08 Javascript
javascript计算对象长度的方法
2017/10/25 Javascript
nodejs中art-template模板语法的引入及冲突解决方案
2017/11/07 NodeJs
在Vue中使用echarts的方法
2018/02/05 Javascript
JS插入排序简单理解与实现方法分析
2019/11/25 Javascript
浅谈vue中get请求解决传输数据是数组格式的问题
2020/08/03 Javascript
Python简单删除列表中相同元素的方法示例
2017/06/12 Python
Python 多进程和数据传递的理解
2017/10/09 Python
PyQt实现界面翻转切换效果
2018/04/20 Python
Django代码性能优化与Pycharm Profile使用详解
2018/08/26 Python
Python面向对象之类和实例用法分析
2019/06/08 Python
python取余运算符知识点详解
2019/06/27 Python
在python中用url_for构造URL的方法
2019/07/25 Python
django 解决自定义序列化返回处理数据为null的问题
2020/05/20 Python
Pandas对DataFrame单列/多列进行运算(map, apply, transform, agg)
2020/06/14 Python
pytho matplotlib工具栏源码探析一之禁用工具栏、默认工具栏和工具栏管理器三种模式的差异
2021/02/25 Python
全球度假村:Club Med
2017/11/27 全球购物
2014年大学生四年规划书范文
2014/04/03 职场文书
机关党员公开承诺书
2014/08/30 职场文书
2014年四风问题自我剖析材料
2014/09/15 职场文书
银行职员工作失误检讨书
2014/10/14 职场文书
企业开业庆典答谢词
2015/01/20 职场文书
全国爱眼日活动总结
2015/02/27 职场文书
销售经理岗位职责范本
2015/04/02 职场文书
王亚平太空授课观后感
2015/06/12 职场文书
话题作文之财富(600字)
2019/12/03 职场文书