写在前面的

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. R语言︱情感分析—词典型代码实践(最基础)(一)

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 笔者寄语:词典型情感分析对词典要求极高,词典中 ...

  2. 从VGA到GPU!细数二十年显卡发展历程

    VGA有很多层涵义,本来是用于代表一个分辨率(您可能不了解VGA,但应该知道QVGA代表什么),随后被普遍称为显示输出接口.为了输出VGA分辨 率.提供VGA输出接口,显卡和VGA就有了不解之缘,显卡 ...

  3. 芝麻HTTP:PhantomJS的安装

    PhantomJS是一个无界面的.可脚本编程的WebKit浏览器引擎,它原生支持多种Web标准:DOM操作.CSS选择器.JSON.Canvas以及SVG. Selenium支持PhantomJS,这 ...

  4. 芝麻HTTP:TensorFlow LSTM MNIST分类

    本节来介绍一下使用 RNN 的 LSTM 来做 MNIST 分类的方法,RNN 相比 CNN 来说,速度可能会慢,但可以节省更多的内存空间. 初始化 首先我们可以先初始化一些变量,如学习率.节点单元数 ...

  5. Django学习-25-图片验证码实例

    处理流程 用户请求网页 --> 后台发送登录界面的静态页面 --> 后台在内存中生成验证码 --> 验证码保存在用户对应的Session中 --> 返回验证码图片到前端 用户登 ...

  6. HihoCoder - 1139

    在上一回和上上回里我们知道Nettle在玩<艦これ>,Nettle在整理好舰队之后终于准备出海捞船和敌军交战了.在这个游戏里面,海域是N个战略点(编号1..N)组成,如下图所示其中红色的点 ...

  7. 使用 LINQPad 助力 LINQ 学习

    简介一图示意 简介 LINQPad 是一款学习 LINQ,优化 SQL 的好助手. 它的一大特点是内置了新版<C# in a Nutshell>的全部 LINQ 示例,不管是配合原书进行练 ...

  8. [LOJ2230][BJOI2014]大融合

    题面戳我 sol LCT维护子树size. 开一个数组\(sz_i\)表示一个节点的所有虚儿子的size和,\(sum_i\)表示以一个节点为根的子树的\(size\)和,可见\(sz_u=\sum_ ...

  9. [BZOJ1543] 生成树计数 (Kruskal)

    Description 给定一个连通的带边权的图(允许自环和重边),求不同的最小生成树个数.两个生成树不同当它们所用的边的序号不同,换句话说,重边算多次. Input 第一行n,m,表示点数和边数(1 ...

  10. Python 终端输出字体颜色

    终端的字符颜色是用转义序列控制的,是文本模式下的系统显示功能,和具体的语言无关.             转义序列是以ESC开头,即用\033来完成(ESC的ASCII码用十进制表示是27,用八进制表 ...