zp1112 / blog

地址
http://issue.suzper.com/
36 stars 3 forks source link

一个IIFE函数解释webpack打包核心原理 #19

Open zp1112 opened 6 years ago

zp1112 commented 6 years ago

bundle.js

使用webpack,最直观的可以看到打包后的结果是webpack将所有文件整合生成了一个bundle.js文件,然后往html文件里面一塞,就完事了。这么神奇,那么探索的入口就在于这个bundle.js(也没有别的入口了。。。)

由于浏览器没法直接执行CommonJS规范的模块,而webpack通过对js代码的解析和抽象,将入口文件转换成立即执行函数,将依赖模块包裹成函数,实现了模块化编程。

我们查看bundle的源码可以发现它是一个立即执行函数,这个函数的参数是modules,即webpack经过某种神力(其实是js代码解析工具,将js代码抽象成语法树(AST),然后深度遍历出一个模块依赖关系对象)整理出来的一个模块数组。数组的每个元素,都是一个函数,函数参数是module, exports, webpack_require。这个函数包裹的函数体就是每个模块的代码,其中参数module和exports是webpack自己实现的一套模块机制,类似于commonjs原理,这里使用webpack的模块机制将市面上各种AMD,CMD,commonjs等模块加载机制统一成webpack的这套机制。

webpack_require是webpack核心的模块加载函数,它通过模块id(遍历生成的),找到对应模块,执行并将模块的导出对象,变成module模块的exports对象的属性。其中这个module就是新创建的模块,如果该模块已创建过了,则使用缓存installedModule。

经过以上简单分析,可以得到如下简单的代码:

(function(modules){
  function webpack_require(moduleId) {
    // 缓存模块
    const installedModule = {};
    if (installedModule[moduleId]) {
      return installedModule[moduleId].exports;
    }
   // 新建模块
    const module = {
      id: moduleId,
      exports: {} 
    }
    // 调用模块函数,执行里面的源码,将到处对象挂载到新建模块的exports上面,内部依赖递归调用 
    // webpack_require函数
    modules[moduleId].call(module.exports, module, module.exports, webpack_require);
    // 加入缓存
    installedModule[moduleId] = module;
    // 返回导出对象
    return module.exports;
  }
  return webpack_require(0); // 执行入口文件,modules数组的第一个元素
})([function(module, exports, webpack_require) {
  // 0 app.js入口文件
  console.log(333)
  const moduleA = webpack_require(1); // 执行依赖文件,modules数组的第二个元素
  const b = moduleA.a * 2;
  console.log(b);
}, function(module, exports, webpack_require) {
  // 1 a.js
  let a = 1;
  function aa() {
    console.log(111)
  };
  module.exports = {
    a,
    aa
  }
}])

boom!!!,执行一下这段代码,可以看到正常打出333 2,执行了入口文件并执行了依赖文件。

其中 a.js和app.js都被一个函数包裹起来了,这个函数实现了将模块源码里面的commonjs语法或者其他模块加载语法导出的属性(commonjs原生支持,其他语法用相应的loader解析),转换成参数module和exports上面的属性导出。

function(module, exports, webpack_require) {
   // 模块代码
}

chunk代码分割

那么代码分割,按需引入的模块是如何加载的呢,自然不能使用直接call执行函数了,webpack中定义了专门的方法,webpack_require.e函数将代码通过script标签的形式动态加载,加载完成后,调用回调函数webpackJsonp执行模块代码。模拟了类似jsonp原理。

理解到这先。。。