写在前面的

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. ffmpeg入门之 Tutorial01

    #include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libswscale/ ...

  2. stm32 fsmc 功能讲解(转)

    LCD有如下控制线:CS:Chip Select 片选,低电平有效RS:Register Select 寄存器选择WR:Write 写信号,低电平有效RD:Read 读信号,低电平有效RESET:重启 ...

  3. Java引用变量的类型

    Java引用变量的类型 1.编译时类型:由声明该变量时使用的类型决定 2.运行时类型:由实际赋给该变量的对象决定    如果编译时类型和运行时类型不一致,就可能出现多态性

  4. web开发性能优化---代码优化篇

    1.合理使用缓存使用 提高性能最好最快的办法当然是通过缓存来改善,对于任何一个web开发者都应该善用缓存.Asp.net下的缓存机制十分强大,用好缓存机制可以让我们极大的改善web应用的性能. 1.页 ...

  5. 求助:关于sql如何统计时间的问题

    三.现在我们假设应用计时分为app应用和web应用,需要考虑如下几个方面: (1)多时间段(2)表中有冗杂数据 (3)用户是在web端和app端都登陆,这种类型的重复时间段只能取其一 存在数据: 存在 ...

  6. freemarker之include指令(九)

    freemarker之include指令 1.父页面ftl <html> <head> <meta http-equiv="content-type" ...

  7. 深究ASP.NET Session

    Session 本质 & 访问方法 Session 本质 是 HttpSessionState 类 link Session 访问方法 HttpContext.Session Page.Ses ...

  8. Codeforces Round #446 (Div. 2)

    Codeforces Round #446 (Div. 2) 总体:rating涨了好多,虽然有部分是靠和一些大佬(例如redbag和ShichengXiao)交流的--希望下次能自己做出来2333 ...

  9. 【BZOJ2132】圈地计划(最小割)

    [BZOJ2132]圈地计划(最小割) 题面 BZOJ 题解 对我而言,不可做!!! 所以我膜烂了ZSY大佬 他的博客写了怎么做... 这,,...太强啦!! 完全想不到黑白染色之后反着连边 然后强行 ...

  10. LightOJ1370 Bi-shoe and Phi-shoe

    题意 给出一些数字,对于每个数字找到一个欧拉函数值大于等于这个数的数,求找到的所有数的最小和. Solution 线性筛出phi,把询问数组排序搞就行了 # include <bits/stdc ...