我们知道不管原来什么模块规范,最后全变成了 commonjs。
所以在浏览器中,我们只需要模拟 commonjs 就行。
我们设计一个打包的例子。

  1. |-dist
  2. |-index.html //html 打包文件
  3. |-main.js //html 引用的打包文件
  4. |-src
  5. |-index.js //入口文件
  6. |-title.js //入口文件引入的例子

假设 dist 就是打包后的文件夹,src 就是打包前的文件夹。我们今天的目的就是要实现一个 dist 的 main.js

/** ./src/index.js 入口文件 **/ 
let title = require('./title.js');
console.log(title);
/** ./src/title.js 引用文件 **/ 
module.exports = 'title';

就这样简简单单的两个文件,打包后的代码是怎么实现的呢。下面来讲解一下。

自执行函数

在 commonjs 规范中,基本都是自执行函数,防止作用域被污染。

;(()=>{
  //自执行函数体
  //...
})();

声明模块定义

在自执行函数中,必定有一模块对象来存放我们导入的函数体。
这个模块对象符合以下几个特性:

  • key 是模块的id,不管什么模块,总的模块 id 都是相对于项目根目录的相对路径。
  • commonjs 的文件,在 webpack 打包之后仍然是 commonjs。
  • value 是一个方法,方法体就是导入的 title.js 中的内容,并且有以下几个入参:

    • module 代表当前模块。
    • exports = module.exports 代表当前模块的导出对象。
    • require 是加载别的模块的加载方法。
      ;(()=>{
      //模块定义
      var modules = {
      // key 是模块的id,不管什么模块,总的模块 id 都是相对于项目根目录的相对路径
      // commonjs 的文件,在 webpack 打包之后仍然是 commonjs
      // module 代表当前模块,exports = module.exports 代表当前模块的导出对象,require 是加载别的模块的加载方法 
      './src/title.js':(module, exports, require) => {
       module.exports = 'title';
      }
      }
      //....
      })();
      

      require 方法定义

      这个方法实现两个最基本的功能
  • 接收模块id为入参。

  • 创建一个新的模块。
  • 返回模块的导出对象。
    ;(()=>{
    var modules = {
      './src/title.js':(module, exports, require) => {
        module.exports = 'title';
      }
    }
    function require(moduleId){
      // 创建一个新的模块
      var module = {
        exports: {}
      };
      //执行模块定义方法,给导出对象赋值
      modules[moduleId](module, module.exports, require);
      //返回导出对象
      return module.exports;
    }
    })();
    

    设置缓存

    如果模块被加载过了,就不需要再加载了。
    ;(()=>{
    var modules = {
      './src/title.js':(module, exports, require) => {
        module.exports = 'title';
      }
    }
    var cache = {};
    function require(moduleId){
      //如果缓存中有此模块对应的缓存数据
      if(cache[moduleId]){
        //直接返回模块的结果
        return cache[moduleId].exports
      }
      var module = cache[moduleId] = {
        exports: {}
      };
      modules[moduleId](module, module.exports, require);
      return module.exports;
    }
    })();
    
    接下来就是正文内容
    ;(()=>{
    var modules = {
      './src/title.js':(module, exports, require) => {
        module.exports = 'title';
      }
    }
    var cache = {};
    function require(moduleId){
      //如果缓存中有此模块对应的缓存数据
      if(cache[moduleId]){
        //直接返回模块的结果
        return cache[moduleId].exports;
      }
      var module = cache[moduleId] = {
        exports: {}
      };
      modules[moduleId](module, module.exports, require);
      return module.exports;
    }
    // index.js 中的内容
    let title = require('./src/title.js');
    console.log(title);
    })();
    

    结果

    运行之后会发现和 webpack 打包后的代码是一样的。
    image.png

    webpack 打包源码

    下面是 webpack 实际生成的代码,和我们写的基本上相差不大。我们可以参考下。 ```javascript /**/ (() => { // webpackBootstrap /**/ var webpack_modules = ({

// “./src/title.js”: /!**!\ !** ./src/title.js ! ***/ /*/ ((module) => {

module.exports = ‘title’;

/*/ })

/**/ }); /**/ /**/ // The module cache /**/ var webpack_module_cache = {}; /**/
/**/ // The require function /**/ function webpack_require(moduleId) { /**/ // Check if module is in cache /**/ var cachedModule = webpack_module_cache[moduleId]; /**/ if (cachedModule !== undefined) { /**/ return cachedModule.exports; /**/ } /**/ // Create a new module (and put it into the cache) /**/ var module = webpack_module_cache[moduleId] = { /**/ // no module.id needed /**/ // no module.loaded needed /**/ exports: {} /**/ }; /**/
/**/ // Execute the module function /**/ webpack_modulesmoduleId; /**/
/**/ // Return the exports of the module /**/ return module.exports; /**/ } /**/
/**/ var webpack_exports = {}; // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. (() => { /!**!\ ! ./src/index.js ! **/ let title = webpack_require(/! ./title / “./src/title.js”); console.log(title) })();

/**/ })() ; ```