WebPack 模块化原理解析 之 CommonJS原理

615 阅读4分钟

Webpack 模块化原理解析

Webpack 允许我们使用各种模块化代码 我们最常用的就是 CommonJs 和 ES Module,目前大部分浏览器 还是不支持 模块化的

ES Module

image.png

那么webpack是如何支持我们使用模块化的呢

目录结构

image.png

webpack 配置

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')


module.exports = {
  mode: 'development',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'js/bundle.js',
    clean: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ]
}

接下来 我们就开始研究一下

  • Commonjs模块化实现原理

  • ES Modulel模块化实现原理

  • Commonjs加载ES Module实现原理

  • ES Module 加载 Commonjs 实现原理

Commonjs模块化实现原理

js代码

1. math.js

const sum = (num1, num2) => {
  return num1 + num2
}

const mut = (num1, num2) => {
  return num1 * num2
}

module.exports = {
  sum,
  mut
}

2. index.js

const { sum, mut } = require('./src/math')

console.log(sum(20, 30));
console.log(mut(20, 30));

打包之后的bundle.js (删除没有必要的代码 和 注释)

因为我们设置development 环境 默认 devtool 为 eval 打包后 会有 eval函数 不便于 理解所以我们在config 中 指定devtool 为source-map

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')


module.exports = {
  mode: 'development',
  devtool: 'source-map',
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'js/bundle.js',
    clean: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ]
}


bundle.js

// 定义一个对象 我们可以把他就当成一个对象 返回的这个对象 key 就是我们的模块路径
// value 是一个函数,函数里面的代码 就是我们模块里的代码 放在一个 函数 里面 作为 路径的value
// 因为我们只有一个模块  所以 只有 一个键值对 多个模块的话 就是多个键值对
var __webpack_modules__ = ({ 
  "./src/math.js": ((module) => {
    const sum = (num1, num2) => {
      return num1 + num2
    }
    const mut = (num1, num2) => {
      return num1 * num2
    }
    // 将我们要导出的变量 放入我们的module对象下的 exports对象中
    module.exports = {
      sum,
      mut
    }
  })
});
// 定义一个缓存对象 作为加载模块的缓存
var __webpack_module_cache__ = {};
// 定义一个函数 在我们加载模块的时候 需要调用这个函数来加载 这个moduleId就是我们存放的上面的对象的一个字符串
function __webpack_require__(moduleId) {
// 定义一个缓存模块 拿到我们在上面定义的对象
  var cachedModule = __webpack_module_cache__[moduleId];
// 判断我们的缓存对象中是否已经有了这个模块(就是这个字符串对应的模块) 我们第一次是不会有缓存的所以不会
// 执行这段下面的代码
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
// 定义一个module 对象 这个连续的赋值 的含义 就是 
// 我们拿到一个对象 对象的属性是exports: 值是一个空对象
// 赋值给我们的缓存对象(__webpack_module_cache__)中的我们的math模块 就是
// __webpack_module_cache__ = {
//   './src/math.js': {
//      exports: {}
//   }
// }
// 然后这个module 也等于我们的 { exports: {} };
// 连续赋值的意义 就是 我们现在的 module 和 这个缓存对象 指向的 是同一个对象 同一个内存地址
  var module = __webpack_module_cache__[moduleId] = {
    exports: {}
  };
// 执行我们最开始的对象 拿到字符串 __webpack_modules__[moduleId]这句话就是拿到我们对应模块路径的函数
// 然后我们执行这个函数 传入了个函数因为我们当前这个模块没有 再去引用其他模块 所以我们只用到module这个参数
// module 这个参数现在就是一个对象里面有一个exports 对应一个对象 我们执行这个函数 相当于
// var module = __webpack_module_cache__[moduleId] = { exports: {sum, mut} };
// 现在我们的module 和 缓存对象 都有了 这个 个 exports对象 里面存放得 我们真正要导出的函数
// 等我我们下次再去调用这个模块的时候 我们的缓存对象 拿到我们 模块路径这个字符串 就可以直接判断出来是否
// 有我们的一个缓存 这时候就可以直接调用export了
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// 导出module.exports 现在这个对象 就存放这我们模块路径字符串('./src/math.js')需要导出的函数了
// { sum: function, mut: function }
  return module.exports;
}
var __webpack_exports__ = {};
//  这个立即执行函数 才是我们最后执行代码的地方
(() => {
// 开始加载我们的 /src/math.js模块 
// 最后一步 对我们刚才内个函数拿到的对象 进行一个结构  获取到sum  和 mut 函数 然后我们就可以调用函数了
  const {
    sum,
    mut
  } = __webpack_require__( /*! ./src/math */ "./src/math.js") // 执行 __webpack_require__() 这个函数
  console.log(sum(20, 30));
  console.log(mut(20, 30));
})();

这就是我们在使用require的时候 webpack 帮助我们转换的代码