写在前面的

1. 版本说明

使用的 webpack 版本 3.0.0。

2. webpack 的核心思想

  • 一切皆“模块(module)”
  • 按需加载

一、开始

1. 准备

当前只创建一个文件 index.js,该文件作为入口文件,代码如下。

console.log('hello, world');

接着使用 webpack 来进行打包,执行的命令如下。

webpack index.js index.bundle.js

2. 分析

打包文件生成的一大堆代码,实际上就是一个自执行函数,仅传入一个参数为 modules,且该对象为一个数组。

(function (modules) {
// ...
})([function (module, exports) {
function sayHello () {
console.log('hello');
}
}])

该函数的作用就是管理模块,它的内部定义了两个主要的对象 installedModules 对象和 __webpack_require__(moduleId) 函数对象。

installedModules 对象初始化代码如下。

var installedModules = {};

来看一下函数对象 __webpack_require__ 的定义,它的作用与 require 函数相同。

// require 函数
function __webapck_require__(moduleId) {
// 如果模块在缓存中
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}

// 创建一个新的模块(并将它放在缓存中)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,// l是loaded的缩写,指代是否已经加载
exports: {}
};

// 执行模块的函数
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

// 标记模块已经加载
module.l = true;

// 返回模块的 exports 对象
return module.exports;
}

看到这里,我们可以看出 installedModules 对象其实是被当作字典使用,key 是模块的 id,value 是代表模块状态和导出的一个对象,如下。

{
i: moduleId, // 模块的 id
l: false, // l是loaded的缩写,指代是否已经加载
exports: {} // 模块的导出对象
}

__webpack_require__ 还被定义了许多的静态方法和静态对象。

// 外放 modules 对象(__webpack_modules__)
__webpack_require__.m = modules; // 外放模块的缓存
__webpack_require__.c = installedModules; // 定义 getter 函数以便友好的加载模块
__webpack_require__.d = function (exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
});
}
};
__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) { Object.prototype.hasOwnProperty.call(object, property); }; // __webpack_public_path__
__webpack_require__.p = "";

在函数的最后,结果是导入 moduleId = 0 的模块,就是执行了入口文件。

return __webpack_require__(__webpack_require__.s = 0);

你定义的入口文件中的内容被转换为。

(function (module, __webpack_exports__, __webpack_require__) {
console.log('hello, world!');
});

传入立即函数表达式中的参数为一个数组对象,而入口文件转换的函数为数组中的第一个元素。注意 webpack 添加的注释 /* 0 */ 代表该模块的 moduleId 为 0,而 webpack 其实是将每一个资源文件看作一个模块,并将其指定一个标识 moduleId。

[
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
// ...
})
]

 二、添加依赖

1. 准备

添加一个新的 js 文件,命名为 util.js,代码如下。

export function add (a, b) {
return a + b;
}

入口文件 index.js 代码修改如下。

import util from './util.js'

console.log('1 + 2 = ' + util.add(1, 2));

执行之前的命令,使用 webpack 构建工具进行打包。

2. 分析

此时查看 index.bundle.js 下的代码,之后生成的立即执行函数的传入参数发生变化,还记得之前说过该参数为一个数组,此时的数组如下。

[
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util_js__ = __webpack_require__(1); console.log('1 + 2 = ' + __WEBPACK_IMPORTED_MODULE_0__util_js__["a" /* default */].add(1 + 2)); }),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony default export */ __webpack_exports__["add"] = add;
function (a, b) {
return a + b;
}
})
]

可见,webpack 将每个文件视为一个独立的模块,它分析模块之间的依赖关系,将模块中 import export 相关的内容做一次替换,比如。

import b from './b'

export default {
name: 'c'
} // 最后被转化为
var __WEBPACK_IMPORTED_MODULE_0_b__ = __webpack_require__(0) // 这里需要特别注意一点,webpack 将 a 属性作为某块的 default 值
__webpack_exports__["a"] = ({
name: 'c'
})

再给所有模块外面加一层包装函数,使其成为模块初始化函数,接着把所有模块初始化函数合成一个数组,赋值给 modules 变量。

三、模块的动态导入

1. 准备

修改入口文件 index.js 的代码如下。

import('./util.js').then(function (util) {
console.log('1 + 2 = ' + util.add(1 + 2));
})

执行之前的命令,使用 webpack 构建工具进行打包。

2. 分析

这次打包后不仅获取了 index.bundle.js 文件,还产生了一个 0.index.bundle.js 文件,接下来先分析 o.index.bundle.js。

webpackJsonp([0],[
/* 0 */,
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (immutable) */ __webpack_exports__["add"] = add;
function add (a, b) {
return a + b;
} /***/ })
]);

之后可以看到该块代码以 JSONP 的形式被加载,这里可以看出该代码被加载后立即执行 webpackJsonp 这个方法,这个方法为 index.bundle.js 中新增的方法。在看这个方法之前,先看一下 index.bundle.js 中新增的一个对象。

var installedChunks = {
1: 0
};

该对象记录 chunk 的安装状态,而不记录 chunk 相关的具体内容,具体内容仍保存到 installedModules 对象中。installedChunks 也是被当作一个字典使用,key 为 chunk 的 id(此处要注意与 mouleId 的区别,每个 chunk 中至少会拥有一个 module),value 可为 0(已加载)、[resolve, reject](正在加载)、undefined(未加载),可以在 requireEnsure(chunkId) 方法中观察到这种状态的变化。

__webpack_require__.e = function requireEnsure(chunkId) {
var installedChunkData = installedChunks[chunkId];
if (installedChunkData === 0) {
return new Promise(function(resolve) { resolve(); });
} // 一个 Promise 意味“正在加载”
if (installedChunkData) {
return installedChunkData[2];
} var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
installedChunkData[2] = promise; var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.async = true;
script.timeout = 120000; if(__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".bundle.js";
var timeout = setTimeout(onScriptComplete, 120000);
script.onerror = script.onload = onScriptComplete;
function onScriptComplete() {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk !== 0) {
if(chunk) {
chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
}
installedChunks[chunkId] = undefined;
}
};
head.appendChild(script); return promise;
};

最后看 webpackJsonp 函数,如下。

var parentJsonpFunction = window["webpackJsonp"];
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
// 添加 “moreModules”到 modules 对象上
// 然后所有的 “chunkIds” 已经加载并调用 callback
var moduleId, chunkId, i = 0, resolves = [], result;
for(; i<chunkIds.length; i++) {
chunkId = chunkIds[i];
if(installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if(parentJsonFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
while(resolves.length) {
resolves.shift()();
}
};

该方法有三个参数,chunkIds 为该模块的先行 chunk,moreModules 为此次新添加的模块,executeModules 暂时不知道其作用。该方法先对正在加载的先行 chunk 进行处理,还记的么?正在加载的 chunk 代表其状态的对象的值为 [resolve, reject],这里将它们的 resolve 方法保存到局部定义的 resolves 数组中。还记得 modules 对象么,该对象为 webpack 生成的最外层立即执行函数的参数,这里继续将 moreModules 安装到 modules 对象中。最后执行保存在 resolves 数组中的 resolve 方法。

可见异步加载模块时主要是使用 Promise 来包装 jsonp 请求需要的 chunk 文件,之后将其添加到 modules 参数中,随后的调用与同步的步骤的相同的。

分析 webpack 打包后的代码的更多相关文章

  1. 简要分析webpack打包后代码

    开门见山 1.打包单一模块 webpack.config.js module.exports = { entry:"./chunk1.js", output: { path: __ ...

  2. webpack: webpack简单打包后的代码(1)

    源码 本文研究的源码地址为:https://github.com/collect-webpack/practice/tree/master/webpack-01 在本研究的前提是 entry 的配置为 ...

  3. 为何webpack打包后的文件要放在服务器上才能运行

    为何会有此问: 在刚开始使用vue-cli时还不知道打包后的文件要在服务中才能运行,直接点开后发现页面白板,请教大神后得知要起一个服务才能运行起来,当时我脑子中的逻辑是这样的: 因为:js代码是由浏览 ...

  4. 性能优化 - 查看 webpack 打包后所有的依赖关系(webpack 可视化工具)

    查看 webpack 打包后所有组件与组件间的依赖关系,针对多余的包文件过大, 剔除首次影响加载的效率问题进行剔除修改,本次采用的是 ==webpack-bundle-analyzer(可视化视图查看 ...

  5. vue 使用webpack打包后路径报错以及 alias 的使用

    一.vue 使用webpack打包后路径报错(两步解决) 1. config文件夹 ==> index.js ==> 把assetsPublicPath的 '/ '改为 './' 2. b ...

  6. vue webpack打包后 iconfont引入路径不对

    vue webpack打包后 iconfont引入路径不对 { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', option ...

  7. webpack打包后的文件

    用了webpack打包工具,你是不是有时会疑惑,写了一个很简单的函数,结果生成那么多东西,而且还没有问题?下面,我从三种情况来分析打包后的入口文件,帮助自己理解webpack打包,也为平时定位产出目录 ...

  8. webpack编译后的代码如何在浏览器执行

    浏览器是无法直接使用模块之间的commonjs或es6,webpack在打包时做了什么处理,才能让浏览器能够执行呢,往下看吧. 使用commonjs语法 先看下写的代码, app.js minus.j ...

  9. webpack打包后访问不到json文件

    一.问题描述 在vue中,前端写ajax假数据,用axios将json数据渲染到组件中,开发期间一切正常,webpack打包压缩后,json文件的路径错误,页面访问不到数据,导致渲染失败. 二.预期结 ...

随机推荐

  1. HI3531ARM调试printf的实现

    void UART_Putc(char c) {     if (c == '\n')   *(unsigned int *)(UART0_ADDR  + 0X00) =  '\r';     del ...

  2. VTK显示mhd,mha格式文件

    下一篇文章将详细介绍mhd,mha文件的构成以及如何制作void renderMhd () { // read input image vtkSmartPointer<vtkMetaImageR ...

  3. Caused by: java.lang.ClassNotFoundException: org.jbpm.pvm.internal.processengine.SpringHelper

    1.错误描述 usage: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -help ...

  4. Linux显示服务器完整的状态信息

    Linux显示服务器完整的状态信息 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ apachectl [fullstatus] Usage: /usr/sbi ...

  5. 关于这个该死的报错:TypeError - 'undefined' is not a function (evaluating '_getTagName(currWindow).toLowerCase()')

    在利用Selenium爬取页面信息的时候突然报错,第一条信息爬取的时候还好好的,第二条就不行了. 请参考网上的爬取代码: # coding=utf-8"""Created ...

  6. class-感知机Perception

    1 感知机模型1.1 模型定义2 感知机学习策略2.1 数据的线性可分性2.2 学习策略3 学习算法3.1 算法原始形式3.2 收敛性3 学习算法的对偶形式 1 感知机模型 感知机perceptron ...

  7. JavaScript设计模式(8)-装饰者模式

    装饰者模式 1. 作用: 可用来透明地把对象包装在具有同样接口的另一对象之中,这样可以给一个方法添加一些行为,然后将方法调用传递给原始对象. 可用于为对象增加功能,用来代替大量子类. 装饰者对其组件进 ...

  8. Filter的过滤链理解

    一.Filter过滤链 web.xml配置了filter过滤器,在容器启动的时候执行了init()方法进行了初始化,然后在容器关闭的时候执行了destroy()方法销毁过滤器,在每次服务器接受请求的时 ...

  9. 记录一个前端bug的解决过程

    人在江湖飘,哪能不挨刀. 我挨了重重一bug.严格来讲这可能是我职业生涯以来的首个悲惨经历,因为凭我的知识储备和经验,基本上任何可重现的bug都是可解的.然而这个bug却困扰了我三个月之久,它具有以下 ...

  10. git本地项目关联远程仓库

    应用场景: 当你在开发一个项目的时候,不想只在本地存储,想用git来管理代码时候的. 1.在你的项目根目录打开git命令窗口,通过 git init 命令把这个目录变成Git可以管理的仓库: git ...