• webpack code split实现原理


    动态导入(dynamic import)

    当涉及到动态代码拆分时,webpack 提供了两个类似的技术。

    • 第一种,也是推荐选择的方式是,使用符合[ECMAScript 提案](https://github.com/tc39/proposal-dynamic-import) 的 import() 语法 来实现动态导入。
    • 第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure。

    通过webpack打包后(mode=development也一样,只不过文件内容在内存中),会将import() 语法和webpack 特定的 require.ensure方法转换为__webpack_require__.e函数
    当执行代码时,源代码中的import()或require.ensure方法调用已变成__webpack_require__.e函数调用。通过这个__webpack_require__.e => webpack_require.f.j => webpack_require.l调用链,最终使用script标签加载相应的js文件。

    加载流程

    1. 加载html页面,页面内容包含

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Webpack Code Split</title>
      <meta name="viewport" content="width=device-width, initial-scale=1"><script defer src="runtime.bundle.js"></script><script defer src="index.bundle.js"></script></head>
      <body>
      </body>
    </html>
    

    2. 加载runtime.bundle.js并执行代码

    // 文件底部代码
    
    // - chunkLoadingGlobal为常规数组
    var chunkLoadingGlobal = self["webpackChunkgetting_started_using_a_configuration"] = self["webpackChunkgetting_started_using_a_configuration"] || [];
    
    chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
    
    // 1. 为chunkLoadingGlobal数组的push方法绑定执行上下文为chunkLoadingGlobal
    // 2. 为webpackJsonpCallback函数绑定第一个参数为上一步的push方法
    // 3. 将上一步的webpackJsonpCallback函数赋值给chunkLoadingGlobal.push
    // 这样,webpackJsonpCallback的第一个参数已经绑定为常规数组的push方法,而chunkLoadingGlobal.push函数就变成了webpackJsonpCallback方法
    chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
    

    3. 加载index.bundle.js

    /*
     * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
     * This devtool is neither made for production nor for readable output files.
     * It uses "eval()" calls to create a separate source file in the browser devtools.
     * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
     * or disable the default devtool with "devtool: false".
     * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
     */
    // 这里实际上是webpackJsonpCallback的函数调用,即push === webpackJsonpCallback
    // 在webpackJsonpCallback函数内部,参数为该push的3个参数。
    // runtime为第三个参数,在函数的底部调用这个这个函数,参数为moduleId="./src/index.js"即第二个参数的键,
    // 然后通过__webpack_require__的内部代码__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__)去调用
    // 第二个参数的moduleId所对应值的函数。
    
    // 遇到__webpack_require__.e这个函数,它会去动态加载通过import导入的文件。即所谓的code split。
    (self["webpackChunkgetting_started_using_a_configuration"] = self["webpackChunkgetting_started_using_a_configuration"] || []).push([["index"],{
    
    /***/ "./src/index.js":
    /*!**********************!*
      !*** ./src/index.js ***!
      **********************/
    /***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
    
    eval("async function getComponent() {
      const element = document.createElement('div');
      const { default: _ } = await __webpack_require__.e(/*! import() */ "vendors-node_modules_lodash_lodash_js").then(__webpack_require__.t.bind(__webpack_require__, /*! lodash */ "./node_modules/lodash/lodash.js", 23));
    
      element.innerHTML = _.join(['Hello', 'webpack'], ' ');
      return element;
    }
    
    getComponent().then((component) => {
      document.body.appendChild(component);
    });
    
    //# sourceURL=webpack://getting-started-using-a-configuration/./src/index.js?");
    
    /***/ })
    
    },
    /******/ __webpack_require__ => { // webpackRuntimeModules
    /******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
    /******/ var __webpack_exports__ = (__webpack_exec__("./src/index.js"));
    /******/ }
    ]);
    

    4. 加载js文件

    webpack_require.e => webpack_require.f.j => webpack_require.l通过这个调用链,然后script标签去加载而外的js文件。
    通过结合Promise实现异步回调。

    /* webpack/runtime/load script */
    /******/ 	(() => {
    /******/ 		var inProgress = {};
    /******/ 		var dataWebpackPrefix = "getting-started-using-a-configuration:";
    /******/ 		// loadScript function to load a script via script tag
    /******/ 		__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);
    /******/ 		};
    /******/ 	})();
    

    参考

    [-] https://webpack.docschina.org/guides/code-splitting/
    [-] https://github.com/YYvanYang/webpack-code-split
    [-] https://github.com/webpack/webpack/blob/d28592e9daf1f483c621708451534fc1ec7240c6/examples/code-splitting/README.md

  • 相关阅读:
    最近很火的GAN应用
    pose项目里我遇到的问题
    pose的初体验
    Ubuntu 移动硬盘不能用
    深度学习中参数量与计算量的理解
    GAN的流程-cyclegan为例
    The version of SOS does not match the version of CLR you are debugging
    mnist 手写数字识别
    计算模型-图、数据模型-张量、运算模型-会话
    tensorflow环境安装
  • 原文地址:https://www.cnblogs.com/joe-yang/p/15518055.html
Copyright © 2020-2023  润新知