分析 webpack 打包后的代码
写在前面的
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 打包后的代码的更多相关文章
- 简要分析webpack打包后代码
开门见山 1.打包单一模块 webpack.config.js module.exports = { entry:"./chunk1.js", output: { path: __ ...
- webpack: webpack简单打包后的代码(1)
源码 本文研究的源码地址为:https://github.com/collect-webpack/practice/tree/master/webpack-01 在本研究的前提是 entry 的配置为 ...
- 为何webpack打包后的文件要放在服务器上才能运行
为何会有此问: 在刚开始使用vue-cli时还不知道打包后的文件要在服务中才能运行,直接点开后发现页面白板,请教大神后得知要起一个服务才能运行起来,当时我脑子中的逻辑是这样的: 因为:js代码是由浏览 ...
- 性能优化 - 查看 webpack 打包后所有的依赖关系(webpack 可视化工具)
查看 webpack 打包后所有组件与组件间的依赖关系,针对多余的包文件过大, 剔除首次影响加载的效率问题进行剔除修改,本次采用的是 ==webpack-bundle-analyzer(可视化视图查看 ...
- vue 使用webpack打包后路径报错以及 alias 的使用
一.vue 使用webpack打包后路径报错(两步解决) 1. config文件夹 ==> index.js ==> 把assetsPublicPath的 '/ '改为 './' 2. b ...
- vue webpack打包后 iconfont引入路径不对
vue webpack打包后 iconfont引入路径不对 { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', option ...
- webpack打包后的文件
用了webpack打包工具,你是不是有时会疑惑,写了一个很简单的函数,结果生成那么多东西,而且还没有问题?下面,我从三种情况来分析打包后的入口文件,帮助自己理解webpack打包,也为平时定位产出目录 ...
- webpack编译后的代码如何在浏览器执行
浏览器是无法直接使用模块之间的commonjs或es6,webpack在打包时做了什么处理,才能让浏览器能够执行呢,往下看吧. 使用commonjs语法 先看下写的代码, app.js minus.j ...
- webpack打包后访问不到json文件
一.问题描述 在vue中,前端写ajax假数据,用axios将json数据渲染到组件中,开发期间一切正常,webpack打包压缩后,json文件的路径错误,页面访问不到数据,导致渲染失败. 二.预期结 ...
随机推荐
- 织梦dedecms列表序号从0到1开始的办法 autoindex,itemindex标签
自增1 arclist 标签下使用 [field:global.autoindex/] 默认从1开始 channel 标签下使用 [field:global.au ...
- 图像处理------透明混合 - Alpha Blending效果
基本原理: 图像的透明混合有个专属名词– Alpha Blending 对任意两张图像可以合成为一张图像,合成图像的像素取值根据数学公式: RGB3 = (1- a) * RGB1 + a * RGB ...
- Flex父子窗口相互调用
Flex父子窗口相互调用 1.设计思路 (1)子窗口调用父窗口的方法 (2)子窗口做了修改后,返回父窗口,父窗口调用子窗口函数 2.设计源码 (1)父窗口 ParentWindow.mxml: < ...
- VME总线
简介 诞生于25年前的VME(VersaModule Eurocard)总线是一种通用的计算机总线,结合了Motorola公司Versa总线的电气标准和在欧洲建立的Eurocard标准的机械形状因子, ...
- JavaScript禁止浏览器默认行为
JavaScript禁止浏览器默认行为 1.实现源码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&quo ...
- 关于游览器网页标题logo的设置
<link rel="shortcut icon"type="image/x-icon" href="images/favicon.ico&qu ...
- jsonp的案例
一,转发一 首先基于安全的原因,浏览器是存在同源策略这个机制的,同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性.看起来不知道什么意思,实践一下就知道了. 1.随便建两个网页 一 ...
- 【CF17E】Palisection(回文树)
[CF17E]Palisection(回文树) 题面 洛谷 题解 题意: 求有重叠部分的回文子串对的数量 所谓正难则反 求出所有不重叠的即可 求出以一个位置结束的回文串的数量 和以一个位置为开始的回文 ...
- [BZOJ1000] A+B Problem
Description Calculate a+b Input Two integer a,b (0<=a,b<=10) Output Output a+b Sample Input 1 ...
- iptables 学习
本博客是学习慕课网课程 用iptables搭建一套强大的安全防护盾 整理而成 iptables相当于在ip层挂载一个hook point对用户进行控制 组成: 四张表+ 五条链(hook point) ...