7.8.webpack编译require、import

275 阅读4分钟

1 require

1.1用法

//index.js
const add = require('./a.js')
console.log(add(2,5))

//a.js
const add = function (a, b) {
  return a + b
}
module.exports = add

1.2打包后代码

(() => {
  // webpackBootstrap
  var __webpack_modules__ = {
    "./src/a.js": (module) => {
      eval(
        "var add = function add(a, b) {\n  return a + b;\n};\nmodule.exports = add;\n\n//# sourceURL=webpack://my-webpack-project/./src/a.js?"
      );
    },
    "./src/index.js": (
      __unused_webpack_module,
      __unused_webpack_exports,
      __webpack_require__
    ) => {
      eval(
        'var add = __webpack_require__(/*! ./a.js */ "./src/a.js");\nconsole.log(add(2, 5));\n\n//# sourceURL=webpack://my-webpack-project/./src/index.js?'
      );
    },
  };
  /************************************************************************/
  // The module cache
  var __webpack_module_cache__ = {};
  function __webpack_require__(moduleId) {
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = (__webpack_module_cache__[moduleId] = {
      exports: {},
    });
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    return module.exports;
  }

  /************************************************************************/

  var __webpack_exports__ = __webpack_require__("./src/index.js");
})();

1.3 分析

__webpack_modules__

使用立即执行函数将 编译后的源代码 存储在 __webpack_modules__变量,格式为

__webpack_modules__ = { 
    文件1路径:(
      module,
      exports,
      require
    ) => {
      eval(源代码字符串,);
    }
    ...
  }

__webpack_require__ webpack 封装的require方法

  • webpack_module_cache[moduleId] = {exports: {源代码对象},},已经加载了直接返回
  • 根据 moduleId 即 文件名作为缓存的key
  • __webpack_modules__[moduleId](module, module.exports, __webpack_require__) 执行源代码模块,因为每个reqiure的模块都往exports对象上赋值了,所有也就是挂载到了全局 __webpack_module_cache__变量上

2 import 静态加载

2.1用法

//index.js
import add from './a.js'
console.log(add(2,5))

//a.js
const add = function (a, b) {
  return a + b
}
export default add

打包后代码dist目录下只有一个文件 index.js

2.2 打包后代码

(() => {
  // webpackBootstrap

  var __webpack_modules__ = {
    "./src/a.js":
      (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        eval(
          '__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   "default": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nvar add = function add(a, b) {\n  return a + b;\n};\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (add);\n\n//# sourceURL=webpack://my-webpack-project/./src/a.js?'
        );
      },

    "./src/index.js":
      (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        eval(
          '__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a.js */ "./src/a.js");\n\nconsole.log((0,_a_js__WEBPACK_IMPORTED_MODULE_0__["default"])(2, 5));\n\n//# sourceURL=webpack://my-webpack-project/./src/index.js?'
        );
      },
  };
  /************************************************************************/
  // The module cache
  var __webpack_module_cache__ = {};
  function __webpack_require__(moduleId) {
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = (__webpack_module_cache__[moduleId] = {
      exports: {},
    });

    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    return module.exports;
  }

  /************************************************************************/
  /* webpack/runtime/define property getters */
  (() => {
    // define getter functions for harmony exports
    __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/runtime/hasOwnProperty shorthand */
  (() => {
    __webpack_require__.o = (obj, prop) =>
      Object.prototype.hasOwnProperty.call(obj, prop);
  })();

  /* webpack/runtime/make namespace object */
  (() => {
    __webpack_require__.r = (exports) => {
      if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
      }
      Object.defineProperty(exports, "__esModule", { value: true });
    };
  })();

  // startup
  var __webpack_exports__ = __webpack_require__("./src/index.js");
})();

2.3 分析

📌 相同:

可以看出 webpack在编译 import和require后的代码逻辑差不多,都是使用自己封装的__webpack_require__方法

区别:

多了__webpack_require__.d、__webpack_require__.o、__webpack_require__.r几个方法

  • __webpack_require__.desm export的对象挂载在 modules.exports,其实就抹平了与require的区别
  • __webpack_require__.o 公共方法:执行对象原型上的方法
  • __webpack_require__.r 给import的exports对象增加__esModule属性,用以区分是否为import导入

3 import 动态加载 -文件名为常量

3.1用法

//index.js
import('./bundle.js').then((add) => {
    console.log(add(1+2))
})

//bundle.js
const add = function (a, b) {
  return a + b
}
export default add

打包后代码dist目录下分两个文件 index.js 和 0.bundle.js

3.2 打包后核心代码

(1)index.js

(function(modules){     
   var installedModules = {};  
   function __webpack_require__(moduleId) {
     ...
   }
   ...
   function webpackJsonpCallback(data){...}
   var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
   var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
   jsonpArray.push = webpackJsonpCallback;  //---异步加载用到
   ...
  return __webpack_require__(__webpack_require__.s = './index.js');
})
({
'./index.js':(function(module, exports, __webpack_require__) {
                  __webpack_require__.e(/*! import() */ 0)
                  .then(__webpack_require__.bind(null,"./bundle.js"))
                  .then((add) => {
                     console.log(add(1+2)) 
                   })")
               })
});

webpack_require.e 函数 返回一个Promise.all

installedChunks[chunkId]:

  • 0 已经加载;
  • undefined记载失败;
  • [resolve, reject,promise]正在加载
// 当前模块依赖的chuncks子模块
var installedChunks = {
  a: 0
};
__webpack_require__.e = function requireEnsure(chunkId) {
    var promises = [];
    var installedChunkData = installedChunks[chunkId];
    if(installedChunkData !== 0) { 
	if(installedChunkData) {
            promises.push(installedChunkData[2]);
	} else {
            // step 1,新建一个promise
	    var promise = new Promise(function(resolve, reject) {
		  installedChunkData = installedChunks[chunkId] = [resolve, reject];});
  	  promises.push(installedChunkData[2] = promise);
          
            // step2,新增script标签加载 import里面的文件
            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);
            document.head.appendChild(script);
             
            // step3,监听script.onerror和定时120s后,执行reject
            var error = new Error();
            onScriptComplete = function (event) {
              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] = reject
                     chunk[1](error);
                  }
                  installedChunks[chunkId] = undefined;
              }
            };
         var timeout = setTimeout(function(){
                onScriptComplete({ type: 'timeout', target: script });
         }, 120000);
         script.onerror = script.onload = onScriptComplete;
     }
     
   }
   
   return Promise.all(promises);
};

(2) 0.bundle.js

script.src = jsonpScriptSrc(chunkId) 相当于执行 webpackJsonpCallback加载异步js文件

webpackJsonp.push([chunkIds=[0],moreModules={"./bundle.js":(function(){/*立即执行函数*/})}])
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
 "./bundle.js":(function(module, __webpack_exports__, __webpack_require__) {
   "use strict";
    eval("__webpack_require__.r(__webpack_exports__);
     const add = function (a, b) {\n  return a + b\n}\n\n
     __webpack_exports__[\"default\"] = (add);
     \n\n//sourceURL=webpack:///./bundle/a.js?"
   );
 })
}]);

window["webpackJsonp"].push([]) = webpackJsonpCallback([])

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;

webpackJsonpCallback

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]) {
            // 返回resolve
            resolves.push(installedChunks[chunkId][0]);
       }
       // 用于判断 加载成功
       installedChunks[chunkId] = 0;
    }
    for(moduleId in moreModules) {
        if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
             // 当前模块内容 赋值给 modules['./bundle.js']
             modules[moduleId] = moreModules[moduleId];
        }
    }
    if(parentJsonpFunction) parentJsonpFunction(data);
    
    // step3: 加载成功,执行resolve()
    while(resolves.length) {
        resolves.shift()();
    }
};

3.3 分析

总结为5点

  1. import__webpack_require__.e函数 返回一个Promise.all
  2. 新增script标签 src 加载 import里面的文件
  3. 监听script.onerror和定时120s后,执行reject installedChunks[chunkId] = undefined
  4. 异步加载文件执行 resolve installedChunks[chunkId] = 1;
    并把当前模块内容导入modules变量上
  5. webpack_require.e().then里再执行__webpack_require__

4 import 动态加载-文件名为变量

4.1 基本用法

function loadModule(moduleName) {
  return import(`./com/${moduleName}.js`)
    .then(module => {
      console.log('Module loaded:', module);
    })
    .catch(error => {
      console.error('Error loading module:', error);
    });
}

var input = document.getElementById('myInput');
input.addEventListener('input', function(event) {
    const path = event.target.value
    loadModule(path)
});

4.2 打包后核心代码

dist\mian.js

(() => {
  // 定义启动处文件
  var __webpack_modules__ = {
    "./src/com lazy recursive ^\\.\\/.*\\.js$": (
      module,
      __unused_webpack_exports,
      __webpack_require__
    ) => {
        var map = {"./a.js": ["./src/com/a.js","src_com_a_js"],"./b.js": ["./src/com/b.js","src_com_b_js"],"./c.js": ["./src/com/c.js","src_com_c_js"]};
        function webpackAsyncContext(req) {
          var ids = map[req], id = ids[0];
          return __webpack_require__.e(ids[1]).then(() => {
            return __webpack_require__(id);
          });
        }
        webpackAsyncContext.keys = () => (Object.keys(map));
        webpackAsyncContext.id = "./src/com lazy recursive ^\\\\.\\\\/.*\\\\.js$";
        module.exports = webpackAsyncContext;
    },
    "./src/index.js": (
      __unused_webpack_module,
      __unused_webpack_exports,
      __webpack_require__
    ) => {
       function loadModule(moduleName) { 
          return __webpack_require__("./src/com lazy recursive ^\\\\.\\\\/.*\\\\.js$\")(`./${moduleName}.js`)
            .then(module => {     console.log('Module loaded:', module);   })   
            .catch(error => {     console.error('Error loading module:', error);   
          })
        }
        var input = document.getElementById('myInput');
        input.addEventListener('input', function(event) {   
        const path = event.target.value   
        loadModule(path)
      });
    }
  };

  // 重新封装require函数
  function __webpack_require__(moduleId) {
    var module = (__webpack_module_cache__[moduleId] = {
      exports: {},
    });
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    return module.exports;
  }


  // 创建script标签加载异步js
  __webpack_require__.l = (url, done, key, chunkId) => {
    //...
    script = document.createElement("script");
    script.setAttribute("data-webpack", dataWebpackPrefix + key);
    script.src = url;
    script.onerror = onScriptComplete.bind(null, script.onerror);
    script.onload = onScriptComplete.bind(null, script.onload);
    needAttach && document.head.appendChild(script);
  };


  // 定义 chund 文件路径
  __webpack_require__.u = (chunkId) => {return "" + chunkId + ".js";};

  // 定义 publicPath
  __webpack_require__.p = __webpack_require__.g.location //...
  

  // 创建new Promise类型的jsonp 回调函数
  var installedChunks = {main: 0};
  __webpack_require__.e = (chunkId, promises) => {
    var installedChunkData = installedChunks[chunkId]
    if (installedChunkData !== 0) {
      if (installedChunkData) {
        promises.push(installedChunkData[2]);
      } else {
        var promise = new Promise((resolve, reject) =>(installedChunkData = installedChunks[chunkId] =[resolve, reject]));
        promises.push((installedChunkData[2] = promise));

        var url = __webpack_require__.p + __webpack_require__.u(chunkId);
        __webpack_require__.l(url,loadingEnded,"chunk-" + chunkId,chunkId);
        
      }
    }
  };

  // 运行 JSONP callback
  var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
    var [chunkIds, moreModules, runtime] = data;
    var moduleId,chunkId,i = 0;
    if (chunkIds.some((id) => installedChunks[id] !== 0)) {
      for (moduleId in moreModules) {
          __webpack_modules__[moduleId] = moreModules[moduleId];
      }
    }
    if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
    for (; i < chunkIds.length; i++) {
      chunkId = chunkIds[i];
      installedChunks[chunkId][0]();
      installedChunks[chunkId] = 0;
    }
  };

  // 定义webpackChunkimport_path 函数,异步js会用到
  var chunkLoadingGlobal = (self["webpackChunkimport_path"] = self["webpackChunkimport_path"] || []);
  chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
  chunkLoadingGlobal.push = webpackJsonpCallback.bind(null,
    chunkLoadingGlobal.push.bind(chunkLoadingGlobal)
  );

  
  //运行 启动处文件
  var __webpack_exports__ = __webpack_require__("./src/index.js");
})();

dist\src_com_a_js.js

主要就是webpackChunkimport_path.push(),也就是运行 webpackJsonpCallback函数,将异步模块注册到__webpack_modules__

(self["webpackChunkimport_path"] = self["webpackChunkimport_path"] || []).push([
  ["src_com_a_js"],
  {
    "./src/com/a.js":
    (__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, { "default": () => (__WEBPACK_DEFAULT_EXPORT__)}); 
        const __WEBPACK_DEFAULT_EXPORT__ = ('1111');
   }
])

4.3 分析

和import 文件名为常量相比,多了 ./src/com lazy recursive ^\\.\\/.*\\.js$ ,map对象封装动态模块下的所有代码文件、并返回webpackAsyncContext函数用于加载制定的动态模块。
所以当 import($path) 对应路径为变量时,webpack会通过 require.context(dirname,useSubdirectories,RegExp)的模式遍历获取当前目录下所有的文件,将之封装为map对象

(() => {
  // 定义启动处文件
  var __webpack_modules__ = {
    "./src/com lazy recursive ^\\.\\/.*\\.js$": (
      module,
      __unused_webpack_exports,
      __webpack_require__
    ) => {
        var map = {"./a.js": ["./src/com/a.js","src_com_a_js"],"./b.js": ["./src/com/b.js","src_com_b_js"],"./c.js": ["./src/com/c.js","src_com_c_js"]};
        function webpackAsyncContext(req) {
          var ids = map[req], id = ids[0];
          return __webpack_require__.e(ids[1]).then(() => {
            return __webpack_require__(id);
          });
        }
        webpackAsyncContext.keys = () => (Object.keys(map));
        webpackAsyncContext.id = "./src/com lazy recursive ^\\\\.\\\\/.*\\\\.js$";
        module.exports = webpackAsyncContext;
    }
    ...

欢迎关注我的前端自检清单,我和你一起成长