webPack打包解析
- webPack实例化以及编译解析
- 对打包后的代码解析
- 在index.js中引入login.js
- 手写webpack功能函数
- `__webpack_require__`方法(用来替换 import require 加载操作)
- webpack_require的`r`方法(用于标识当前模块是 es6 类型)
- webpack_require的`o`方法(判断object上是否有property属性)
- webpack_require的`d`方法(为当前exports对象添加属性name并设置getter函数)
- webpack_require的`t`方法(加载指定模块id对应的内容)
- webpack_require的`e`方法(用于懒加载的实现,创建script标签并写入head中)
- Jsonp回调函数webpackJsonpCallback
- 采用esModal导出
webPack实例化以及编译解析
webpack是一个事件流打包工具,它用到了一个小型的库 tapable,tapable主要用于事件钩子的监听与触发.
实例化webpack(options)并得到一个compiler对象
实例化compiler对象
,compiler继承tapable 因此它具备钩子的操作能力,具体的操作项放在compilation中- 在实例化了compiler对象之后就往它的身上
挂载options
(配置项) 实例化NodeEnvironmentPlugin
,这个操作就是让它具备了文件读写的能力(node的fs模块)- 具备文件读写能力之后,如果options中有插件配置,那么通过调用apply
执行插件
- 触发webpack环境钩子,触发webpack环境初始化之后钩子
实例化WebpackOptionsApply
,将内部默认的插件跟当前的compiler建立联系. 这它内部EntryOptionPlugin插件处理了入口模块的id,又用SingleEntryPlugin插件监听了make和compilation钩子
-compilation钩子就是让compilation具备了利用normalModuleFactory工厂创建一个普通模块的能力. 说白了就是webpack要去打包一个模块,首先它得有自己的模块- make钩子的回调中会涉及到compilation.addEntry()的方法调用
- 走到这里就意味着执行打包之前的所有准备工作就完成了
- 最后
返回实例化的Compiler
调用compiler.run方法
- run方法其实就是一堆钩子按照顺序触发,最后调用了compile
- compile中又执行了对应的操作
参数准备
(其中normalModuleFactory是后续用于创建模块的)- 触发beforeCompile钩子
实例化compilation
触发make
,调用endEntry方法(这个时候就带着 context name entry等)开始编译
处理chunk
- 编译的过程其实就是将模块的内容polify之后插入到一个事先准备好的.ejs文件模板中,这就是thunk.最后用node的fs文件操作功能分局option.output输出至对应的文件.在这个过程中会触发对应的钩子函数.
对打包后的代码解析
我对一个空的index.js进行打包得到一个built.js文件这个文件内容如下:
打包后的文件
就是一个函数自调用,函数接收的参数是一个对象- 这个对象可以理解为是对模块的定义
- 它的
键
是当前模块的路径与文件名的拼接也就是后续模块的id - 它的
值
是函数会将被加载模块的内容包裹在函数中 - 这个函数在将来某一时间点会被调用,同时会接收一定的参数,利用这些参数可以实现模块的加载
在index.js中引入login.js
手写webpack功能函数
(function (modules) {
// 01 定义对象用于将来缓存被加载过的模块
let installedModules = {}
// 02 定义一个 __webpack_require__ 方法来替换 import require 加载操作
function __webpack_require__(moduleId) {
// 2-1 判断当前缓存中是否存在要被加载的模块内容,如果存在则直接返回
if (installedModules[moduleId]) {
return installedModules[moduleId].exports
}
// 2-2 如果当前缓存中不存在则需要我们自己定义{} 执行被导入的模块内容加载
let module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
}
// 2-3 调用当前 moduleId 对应的函数,然后完成内容的加载
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
// 2-4 当上述的方法调用完成之后,我们就可以修改 l 的值用于表示当前模块内容已经加载完成了
module.l = true
// 2-5 加载工作完成之后,要将拿回来的内容返回至调用的位置
return module.exports
}
// 03 定义 m 属性用于保存 modules
__webpack_require__.m = modules
// 04 定义 c 属性用于保存 cache
__webpack_require__.c = installedModules
// 05 定义 o 方法用于判断对象的身上是否存在指定的属性
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty(object, property)
}
// 06 定义 d 方法用于在对象的身上添加指定的属性,同时给该属性提供一个 getter
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter })
}
}
// 07 定义 r 方法用于标识当前模块是 es6 类型
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
}
Object.defineProperty(exports, '__esModule', { value: true })
}
// 08 定义 n 方法,用于设置具体的 getter
__webpack_require__.n = function (module) {
let getter = module && module.__esModule ?
function getDefault() { return module['default'] } :
function getModuleExports() { return module }
__webpack_require__.d(getter, 'a', getter)
return getter
}
// 09 定义 P 属性,用于保存资源访问路径
__webpack_require__.p = ""
// 10 调用 __webpack_require__ 方法执行模块导入与加载操作
return __webpack_require__(__webpack_require__.s = './src/index.js')
})
({
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _login_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./login.js */ "./src/login.js");
console.log('index.js 执行了')
console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__["default"], '<------')
console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__["age"], '<------')
}),
"./src/login.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "age", function () { return age; });
__webpack_exports__["default"] = ('zce是一个帅哥');
const age = 40
})
})
__webpack_require__
方法(用来替换 import require 加载操作)
- 判断模块对应的代码是否被加载过
- 如果被加载过,直接返回加载的结果
- 如果没被加载过,那么递归进行加载,最后返回其加载结果并标记缓存
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
webpack_require的r
方法(用于标识当前模块是 es6 类型)
- 如果当前环境满足es6语法那么使用
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
给exports对象设置成[object Module]
在将来方便判断.(可以用Object.prototype.toString.call(exports)去验证) - 然后给exports对象添加
__esModule
属性为true - Object.defineProperty默认情况enumerable为false,就是不可枚举(正常取能取得到但是JSON.stringify(exports)就拿不到了)
// define __esModule on exports
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
webpack_require的o
方法(判断object上是否有property属性)
- 判断object上是否有property属性
// Object.prototype.hasOwnProperty.call
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
webpack_require的d
方法(为当前exports对象添加属性name并设置getter函数)
- 用于在对象的身上添加指定的属性,同时给该属性提供一个 getter
- 利用Object.defineProperty为当前exports对象添加属性name并设置getter函数
// define getter function for harmony exports
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
webpack_require的t
方法(加载指定模块id对应的内容)
- 加载指定模块id对应的内容
- 然后对内容进行es标记,如果返回的值是对象,则写入get函数
__webpack_require__.t = function (value, mode) {
// 01 加载 value 对应的模块内容( value 一般就是模块 id )
// 加载之后的内容又重新赋值给 value 变量
if (mode & 1) {
value = __webpack_require__(value)
}
if (mode & 8) { // 加载了可以直接返回使用的内容
return value
}
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) {
return value
}
// 如果 8 和 4 都没有成立则需要自定义 ns 来通过 default 属性返回内容
let ns = Object.create(null)
__webpack_require__.r(ns)
Object.defineProperty(ns, 'default', { enumerable: true, value: value })
if (mode & 2 && typeof value !== 'string') {
for (var key in value) {
__webpack_require__.d(ns, key, function (key) {
return value[key]
}.bind(null, key))
}
}
return ns
}
webpack_require的e
方法(用于懒加载的实现,创建script标签并写入head中)
- 用于懒加载的实现,创建script标签并写入head中
- 把resolve, reject,promise保存在installedChunks中,用于JsonP的回调执行
var installedChunks = {
"main": 0
};
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
// JSONP chunk loading for javascript
var installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = jsonpScriptSrc(chunkId);
// create error before stack unwound to get useful stacktrace later
var error = new Error();
onScriptComplete = function (event) {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if(chunk !== 0) {
if(chunk) {
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
installedChunks[chunkId] = undefined;
}
};
var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
}
}
return Promise.all(promises);
};
Jsonp回调函数webpackJsonpCallback
- 合并模块定义,保存模块id和其内容到modules中
- 执行installedChunks中的resolves回调.改变promise状态,触发.then链式调用
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
var moduleId, chunkId, i = 0, resolves = [];
for(;i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && 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(parentJsonpFunction) parentJsonpFunction(data);
while(resolves.length) {
resolves.shift()();
}
};
// 定义变量存放数组
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
// 保存原生带push方法
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
// 重写原生的push方法。在将来login.built.js中调用的push就是这个重写的push
jsonpArray.push = webpackJsonpCallback;
// 浅拷贝
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = "./src/index.js");
login.built.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
["login"],
{
"./src/login.js": function (module, exports) {
module.exports = "学习学习";
},
},
]);
采用esModal导出
- 原login文件
export default 'zcegg'
export const age = 18
- 转译之后的内容
"./src/login.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
// 标记是__esModule
__webpack_require__.r(__webpack_exports__);
// export处理
__webpack_require__.d(__webpack_exports__, "age", function() { return age; });
// export default 'zcegg'
__webpack_exports__["default"] = ('zcegg');
// age
const age = 18
})
- 最后得到一个的结果(是一个对象)