webpack 打包实战解析
Webpack 打包实战
本文从一个简单的例子出发,比较一下,我们的代码经过webpack 打包后会变成啥样,带有HMR的情况下,会有什么不同
我们的代码
// index.js
import {greeting} from './moduleA'
let cleanup=null;
function render(){
const node = document.getElementById('mount');
const text = document.createElement('div');
text.className='red';
node.appendChild(text);
text.innerHTML=greeting();
return ()=>node.removeChild(text);
}
cleanup=render();
if(module.hot){
module.hot.accept('./moduleA',()=>{
cleanup?.();
cleanup=render();
});
module.hot.accept();
}
// moduleA.js
function getName(){
return 'user3'
}
export function greeting(){
return `<div>${getName()} said: Hello World! chafd fd</div>`
}
经过webpack 打包,我们通过bundle.js, 我们可以看到如下代码
- 整个bundle.js是在一个IIFE里面执行的,避免了对于全局的污染。
__webpack_modules__
,这个对象里面是{moduleName:(module,module.exports,webpackrequire)=>{}}
这样的结构。它将我们的模块转换成成对象。同时它拦截了我们对于代码的导入。代码片段如下。
var __webpack_modules__ = ({
"./index.js": ((module, __webpack_exports__, __webpack_require__) => {
"use strict";
// 这个方法是为了定义这个属性: module.export.__esmodule=true
__webpack_require__.r(__webpack_exports__);
// 这个方法就是我们源码里面的import('./moduleA.js').它被webpack拦截了,通过__webpack_require__来导入到本地变量。注意,这个方法里面是有缓存的。一个模块只会导入依次。后面介绍它的实现
/* harmony import */ var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleA */ "./moduleA.js");
// 下面就是我们代码的主逻辑。
let cleanup=null;
function render(){
const node = document.getElementById('mount');
const text = document.createElement('div');
text.className='red';
node.appendChild(text);
text.innerHTML=(0,_moduleA__WEBPACK_IMPORTED_MODULE_0__.greeting)();// 这个是为了在全局作用域中执行。这样的好处是greeting()里面的this,会是window,然而由于我们开启了严格模式,所以,它应该是undefined.
return ()=>node.removeChild(text);
}
cleanup=render();
if(true){
module.hot.accept(/*! ./moduleA */ "./moduleA.js",__WEBPACK_OUTDATED_DEPENDENCIES__ => {
// 这端代码就是webpack hmr插入的,当依赖模块被更新了,它会自动的更新moduleA.js.
/* harmony import */ _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleA */ "./moduleA.js");
(()=>{
cleanup?.();
cleanup=render();
})(__WEBPACK_OUTDATED_DEPENDENCIES__); });
// 这个是我们自己定义的自更新的代码
module.hot.accept();
}
}),
"./moduleA.js":((module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
// 这个是将greeting这个方法定义到module.exports中。
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ greeting: () => (/* binding */ greeting)
/* harmony export */ });
// 这个方法是为了使得module.exports 不能被修改。module.exports是从__webpack_exports__中读取的。
/* module decorator */ module = __webpack_require__.hmd(module);
function getName(){
return 'user3'
}
function greeting(){
return `<div>${(0,getName)()} said: Hello World! chafd</div>`
}
if(true){
console.log(module);
module.hot.accept(/*! ./moduleB.js */ "./moduleB.js",
__WEBPACK_OUTDATED_DEPENDENCIES__ => {
/* harmony import */ _moduleB_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./moduleB.js */ "./moduleB.js");
(()=>{
module.hot.invalidate();
})(__WEBPACK_OUTDATED_DEPENDENCIES__); })
}
})
});
__webpack_require__
,这个方法是webpack拦截我们代码里面的import用的,import都被换成了这个。它的定义如下
function __webpack_require__(moduleId) {
// Check if module is in cache
// ================================导入模块优先从缓存里面读取==============================
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
if (cachedModule.error !== undefined) throw cachedModule.error;
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
// =====================以模块名为key创建缓存对象,这个module对象也就是我们所理解的module,module.exports===============
var module = __webpack_module_cache__[moduleId] = {
id: moduleId,
loaded: false,
exports: {} //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! module.exports
};
// Execute the module function
try {
var execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: __webpack_require__ };
//====================这个是模块加载拦截用的===============================
__webpack_require__.i.forEach(function(handler) { handler(execOptions); });
//===================这个就是module, module.exports=====================
module = execOptions.module;
//=====================执行我们在__webpack_modules__里面定义的模块工程方法,第一个是this,后面三个是它所需的参数========================================
execOptions.factory.call(module.exports, module, module.exports, execOptions.require);
} catch(e) {
module.error = e;
throw e;
}
// Flag the module as loaded
// 模块加载完成。
module.loaded = true;
// Return the exports of the module
// 返回模块的exports.
return module.exports;
}
- 在
__webpack_require__
上面还定义了一些其他静态方法__webpack_require__.d
这个是Object.defineProperty
的封装,它是为了将module.exprts里面属性设置成只读。
__webpack_require__.d = (exports, definition) => {
for(var key in definition) {
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
}
}
};
- `__webpack_require__.hmd` 这个其实是为了创建**module**对象实例,最重要的就是将**module.exports**设置成不能修改。
// hmd: harmony module decorator
__webpack_require__.hmd = (module) => {
module = Object.create(module);
if (!module.children) module.children = [];
Object.defineProperty(module, 'exports', {
enumerable: true,
set: () => {
throw new Error('ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: ' + module.id);
}
});
return module;
};
- `__webpack_require__.r` 在module中定义**__esmodule**属性
__webpack_require__.r = (exports) => {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
- `__webpack_require__.m = __webpack_modules__; __webpack_require__.c = __webpack_module_cache__; __webpack_require__.i = [];` 设置一些快捷属性
下面的API是与hmr有关的
__webpack_require__.h
这个是获取当前代码的hash码的__webpack_require__.h = () => ("333c9438eb92e79fd271")
__webpack_require__.hmrF
这个是hmr 在check的时候向后端检查是否有代码更新的路由名__webpack_require__.hmrF = () => ("main." + __webpack_require__.h() + ".hot-update.json");
__webpack_require__.hu
这个是hmr向后端请求代码更新的路由名,发送在prepare阶段。
__webpack_require__.hu = (chunkId) => {
// return url for filenames based on template
return "" + chunkId + "." + __webpack_require__.h() + ".hot-update.js";
};
__webpack_require__.l
这个是hmr向后端发送请求的方法。前面的两个方法只是产生url,最终交给这个方法去发请求。
var inProgress = {};
var dataWebpackPrefix = "hmr:";
// loadScript function to load a script via script tag
// url 是请求js的路由,done,是回调方法。它是借助script标签来完成js文件的请求的。
__webpack_require__.l = (url, done, key, chunkId) => {
if(inProgress[url]) { inProgress[url].push(done); return; }
var script, needAttach;
if(key !== undefined) {
var scripts = document.getElementsByTagName("script");
for(var i = 0; i < scripts.length; i++) {
var s = scripts[i];
if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
}
}
if(!script) {
needAttach = true;
script = document.createElement('script');
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.setAttribute("data-webpack", dataWebpackPrefix + key);
script.src = url;
}
inProgress[url] = [done];
var onScriptComplete = (prev, event) => {
// avoid mem leaks in IE.
script.onerror = script.onload = null;
clearTimeout(timeout);
var doneFns = inProgress[url];
delete inProgress[url];
script.parentNode && script.parentNode.removeChild(script);
doneFns && doneFns.forEach((fn) => (fn(event)));
if(prev) return prev(event);
}
var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
script.onerror = onScriptComplete.bind(null, script.onerror);
script.onload = onScriptComplete.bind(null, script.onload);
needAttach && document.head.appendChild(script);
};