简单易懂的webpack打包后JS的运⾏过程
hello~亲爱的看官⽼爷们⼤家好~ 最近⼀直在学习 webpack 的相关知识,当清晰地领悟到 webpack 就是不同 loader 和 plugin 组合起来打包之后,只作为⼯具使⽤⽽⾔,算是⼊门了。当然,在过程中碰到数之不尽的坑,也产⽣了想要深⼊⼀点了解 webpack 的原理(主要是掉进坑能靠⾃⼰爬出来)。因⽽就从简单的⼊⼿,先看看使⽤ webpack 打包后的 JS ⽂件是如何加载吧。
友情提⽰,本⽂简单易懂,就算没⽤过 webpack 问题都不⼤。如果已经了解过相关知识的朋友,不妨快速阅读⼀下,算是温故知新 ,其实是想请你告诉我哪⾥写得不对。
简单配置
既然需要⽤到 webpack,还是需要简单配置⼀下的,这⾥就简单贴⼀下代码,⾸先是 fig.js:
const path = require('path');
如何下载javascriptconst webpack = require('webpack');
//⽤于插⼊html模板
const HtmlWebpackPlugin = require('html-webpack-plugin');
//清除输出⽬录,免得每次⼿动删除
const CleanWebpackPlugin = require('clean-webpack-plugin');
entry: {
index: path.join(__dirname, 'index.js'),
},
output: {
path: path.join(__dirname, '/dist'),
filename: 'js/[name].[chunkhash:4].js'
},
module: {},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
}),
//持久化moduleId,主要是为了之后研究加载代码好看⼀点。
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
})
]
};
复制代码
这是我能想到近乎最简单的配置,⽤到的两个额外下载的插件都是⼗分常⽤的,也已经在注释中简单说明了。
之后是两个简单的 js ⽂件:
// test.js
const str = 'test is loaded';
// index.js
const test = require('./src/js/test');
console.log(test);
复制代码
这个就不解释了,贴⼀下打包后,项⽬的⽬录结构应该是这样的:
⾄此,我们的配置就完成了。
从 index.js 开始看代码
先从打包后的 index.html ⽂件看看两个 JS ⽂件的加载顺序:
<body>
<script type="text/javascript" src="js/manifest.2730.js"></script>
<script type="text/javascript" src="js/index.5f4f.js"></script>
</body>
复制代码
可以看到,打包后 js ⽂件的加载顺序是先 manifest.js,之后才是 index.js,按理说应该先看 manifest.js 的内容的。然⽽这⾥先卖个关⼦,我们先看看 index.js 的内容是什么,这样可以带着问题去了解 manifest.js,也就是主流程的逻辑到底是怎样的,为何能做到模块化。
// index.js
webpackJsonp([0], {
"JkW7": (function(module, exports, __webpack_require__) {
const test = __webpack_require__("zFrx");
console.log(test);
}),
"zFrx": (function(module, exports) {
const str = 'test is loaded';
})
}, ["JkW7"]);
复制代码
删去各种奇怪的注释后剩下这么点内容,⾸先应该关注到的是 webpackJsonp 这个函数,可以看见是不在任何命名空间下的,也就是manifest.js 应该定义了⼀个挂在 window 下的全局函数,index.js 往这个函数传⼊三个参数并调⽤。
第⼀个参数是数组,现在暂时还不清楚这个数组有什么作⽤。
第⼆个参数是⼀个对象,对象内都是⽅法,这些⽅法看起来⾄少接受两个参数(名为 zFrx 的⽅法只有两个形参)。看⼀眼这两个⽅法的内部,其实看见了⼗分熟悉的东西, ports,尽管看不见 require, 但有⼀个样⼦类似的 __webpack_require__,这两个应该是模块化的关键,先记下这两个函数。
第三个参数也是⼀个数组,也不清楚是有何作⽤的,但我们观察到它的值是 JkW7,与参数2中的某个⽅法的键是⼀致的,这可能存在某种逻辑关联。
⾄此,index.js 的内容算是过了⼀遍,接下来应当带着问题在 manifest.js 中寻答案。
manifest.js 代码阅读
由于没有配置任何压缩 js 的选项,因此 manifest.js 的源码⼤约在 150 ⾏左右,简化后为 28 ⾏(已经跑过代码,实测没问题)。鉴于精简后的代码真的不多,因⽽先贴代码,⼤家带着刚才提出的问题,先看看能到⼏个答案:
(function(modules) {
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
var moduleId, result;
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
if (executeModules) {
for (i = 0; i < executeModules.length; i++) {
result = __webpack_require__(executeModules[i]);
}
}
return result;
};
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
exports: {}
};
modules[moduleId].ports, module, ports, __webpack_require__);
ports;
}
})([]);
复制代码
⾸先应该看到的是,manifest.js 内部是⼀个 IIFE,就是⾃执⾏函数咯,这个函数会接受⼀个空数组作
为参数,该数组被命名为 modules。之后看到我们在 index.js 中的猜想,果然在 window 上挂了⼀个名为 webpackJsonp 的函数。它接受的三个参数,分别名为chunkIds, moreModules, executeModules。对应了 index.js 中调⽤ webpackJsonp 时传⼊的三个参数。⽽ webpackJsonp 内究竟是有怎样的逻辑呢?
先不管定义的参数,webpackJsonp 先是 for in 遍历了⼀次 moreModules,将 moreModules 内的所有⽅法都存在 modules, 也就是⾃执⾏函数执⾏时传⼊的数组。
之后是⼀个条件判断:
if (executeModules) {
for (i = 0; i < executeModules.length; i++) {
result = __webpack_require__(executeModules[i]);
}
}
复制代码
判断 executeModules, 也就是第三个参数是否存在,如存在即执⾏ __webpack_require__ ⽅法。在 index.js 调⽤ webpackJsonp ⽅法时,这个参数当然是存在的,因⽽要看看 __webpack_require__ ⽅法是什么了。
__webpack_require__ 接受⼀个名为 moduleId 的参数。⽅法内部⾸先是⼀个条件判断,先不管。接下来看到赋值逻辑
var module = installedModules[moduleId] = {
exports: {}
};
复制代码
结合刚才的条件判断,可以推测出 installedModules 是⼀个缓存的容器,那么前⾯的代码意思就是如果缓存中有对应的 moduleId,那么直接返回它的 exports,不然就定义并赋值⼀个吧。接着先偷看⼀下 __webpack_require__ 的最后的返回值,可以看到函数返回的是ports,那么 ports ⼜是如何被赋值的呢? 看看之后的代码:
modules[moduleId].ports, module, ports, __webpack_require__);
复制代码
刚才我们知道 modules[moduleId] 就是 moreModules 中的⽅法,此处就是将 this 指定为 ports,再把module, ports, __webpack_require__ 传⼊去作为参数调⽤。这三个参数是不是很熟悉?之前我们看 index.js ⾥⾯代码时,有⼀个疑问就是模块化是如何实现的。这⾥我们已经看出了眉⽬。
其实 webpack 就是将每⼀个 js ⽂件封装成⼀个函数,每个⽂件中的 require ⽅法对应的就是 __webpack_require__,
__webpack_require__ 会根据传⼊的 moduleId 再去加载对应的代码。⽽当我们想导出 js ⽂件的值时,要么⽤ ports,要么⽤exports,这就对应了module, ports两个参数。少接触这块的童鞋,应该就能理解为何导出值时,直接使⽤ exports = xxx 会导出失败了。简单举个例⼦:
const module = {
exports: {}
};
function demo1(module) {
}
demo1(module);
console.ports); // 1
function demo2(exports) {
exports = 2;
}
ports);
console.ports); // 1
复制代码
粘贴这段代码去浏览器跑⼀下,可以发现两次打印出来都是1。这和 wenpack 打包逻辑是⼀模⼀样的。
梳理⼀下打包后代码执⾏的流程,⾸先 minifest.js 会定义⼀个 webpackJsonp ⽅法,待其他打包后的⽂件(也可称为 chunk)调⽤。当调⽤ chunk 时,会先将该 chunk 中所有的 moreModules, 也就是每⼀个依赖的⽂件也可称为 module (如 test.js)存起来。之后通过executeModules 判断这个⽂件是不是⼊⼝⽂件,决定是否执⾏第⼀次 __webpack_require__。⽽ __webpack_require__ 的作⽤,就是根据这个 module 所 require 的东西,不断递归调⽤ __webpack_require__,__webpack_require__函数返回值后供 require 使⽤。当然,模块是不会重复加载的,因为 installedModules 记录着 module 调⽤后的 exports 的值,只要命中缓存,就返回对应的值⽽不会再次调⽤module。webpack 打包后的⽂件,就是通过⼀个个函数隔离 module 的作⽤域,以达到不互相污染的⽬的。
⼩结
以上就是 webpack 打包后 js ⽂件是加载过程的简单描述,其实还是有很多细节没有说完的,⽐如如何异步加载对应模块, chunkId 有什么作⽤等,但由于篇幅所限 (还没研究透),不再详述。相关代码会放置 中,欢迎随时查阅顺便点star。
感谢各位看官⼤⼈看到这⾥,知易⾏难,希望本⽂对你有所帮助~谢谢!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论