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);
};
posted @ 2024-08-04 15:58  kongshu  阅读(25)  评论(0编辑  收藏  举报