webpack最终生成bundle原理(主要是js模块化的原理)

这是生成的依赖树

{
  './src/index.js': {
    code: '"use strict";\n' +
      '\n' +
      'var _add = _interopRequireDefault(require("./add.js"));\n' +
      '\n' +
      'var _count = _interopRequireDefault(require("./count.js"));\n' +
      '\n' +
      'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
      '\n' +
      'console.log((0, _add["default"])(1, 2));\n' +
      'console.log((0, _count["default"])(3, 1));',
    deps: {
      './add.js': 'D:\\webpack5进阶\\05.myWebpack\\src\\add.js',
      './count.js': 'D:\\webpack5进阶\\05.myWebpack\\src\\count.js'
    }
  },
  'D:\\webpack5进阶\\05.myWebpack\\src\\add.js': {
    code: '"use strict";\n' +
      '\n' +
      'Object.defineProperty(exports, "__esModule", {\n' +
      '  value: true\n' +
      '});\n' +
      'exports["default"] = void 0;\n' +
      '\n' +
      'var _new = _interopRequireDefault(require("./new.js"));\n' +
      '\n' +
      'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
      '\n' +
      'function add(x, y) {\n' +
      '  return x + y;\n' +
      '}\n' +
      '\n' +
      'console.log(_new["default"]);\n' +
      'var _default = add;\n' +
      'exports["default"] = _default;',
    deps: { './new.js': 'D:\\webpack5进阶\\05.myWebpack\\src\\new.js' }
  },
  'D:\\webpack5进阶\\05.myWebpack\\src\\count.js': {
    code: '"use strict";\n' +
      '\n' +
      'Object.defineProperty(exports, "__esModule", {\n' +
      '  value: true\n' +
      '});\n' +
      'exports["default"] = void 0;\n' +
      '\n' +
      'function count(x, y) {\n' +
      '  return x - y;\n' +
      '}\n' +
      '\n' +
      'var _default = count;\n' +
      'exports["default"] = _default;',
    deps: {}
  },
  'D:\\webpack5进阶\\05.myWebpack\\src\\new.js': {
    code: '"use strict";\n' +
      '\n' +
      'Object.defineProperty(exports, "__esModule", {\n' +
      '  value: true\n' +
      '});\n' +
      'exports["default"] = void 0;\n' +
      'var _default = 123;\n' +
      'exports["default"] = _default;',
    deps: {}
  }
}

这是简单版的bundle生成方法

  generate(depsGraph) {
    const bundle = `
      (function(depsGraph){
        // require目的:为了加载入口文件
        function require(module){
          // 定义模块内部的require函数
          function localRequire(relativePath){
            // 为了找到要引入模块的绝对路径,通过require加载
            return require(depsGraph[module].deps[relativePath]);
          }
          // 定义暴露对象(将来我们要暴露的内容)
          var exports = {};
          (function(require,exports,code){
            eval(code)
          })(localRequire, exports, depsGraph[module].code);
          // 作为require函数的返回值返回出去
          // 后面的require函数能得到暴露的内容
          return exports;
        }
        // 加载入口文件
        require('${this.options.entry}');
      })(${JSON.stringify(depsGraph)})
    `;
    // 生成输出文件的绝对路径
    const filePath = path.resolve(this.options.output.path, this.options.output.filename);
    // 写入文件
    fs.writeFileSync(filePath, bundle, 'utf-8');
  }

首先可以从依赖树里面看到,es6的模块语法都已经被转为了require和exports之类的。

关于require:

执行generate方法时,首先会从入口文件开始执行require('${this.options.entry}'),然后require函数里面,有一个立即执行方法,这个方法可以执行入口文件里面的代码:

          (function(require,exports,code){
            eval(code)
          })(localRequire, exports, depsGraph[module].code);

如果这些代码里面有模块引入(require),那么又会继续递归调用require函数,并且根据依赖树把模块引入的相对路径转为绝对路径,这样就可以在依赖树里面找到该模块内部的代码了,那么require函数的立即执行方法又可以继续执行引入模块内部的代码了。

关于exports:

但是仅仅递归调用require是不够的,因为require返回的是一个模块,外层模块必须基于require的返回值来进行下一步操作,所以要对exports进行处理,其实处理很简单,对于有exports语法的模块,在require方法内部先定义一个空的exports对象var exports = {};,也就是我们的暴露对象,然后再在这个对象上添加要暴露出去的属性或方法,最后返回这个对象即可。那么当我外层模块require时,就可以拿到内层模块的想要暴露出来的值了。

总结:

所谓的模块化其实最后都生成了一段代码,最重要的有三点,第一点是依赖树,第二点是require的递归调用,第三点是exports的返回值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值