Skip to content

webpack

webpack 介绍

  • webpack 是一个 JavaScript 应用程序的静态模块打包工具

入口(entry)

  • 入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的

输出(output)

  • output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件

loader

  • webpack 只能理解 JavaScript 和 JSON 文件
  • loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中
  • 多个 loader,回从右往左执行

插件(plugin)

  • loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量

commonjs 模块 简易实现

js
(() => {
  let modules = {
    "./src/title.js": (module) => {
      // commonjs模块
      module.exports = "title";
    },
  };
  let cache = {};
  function require(moduleId) {
    let cacheModule = cache[moduleId];
    if (cacheModule) {
      return cacheModule.exports;
    }
    let module = (cache[moduleId] = {
      exports: {},
    });
    modules[moduleId](module, module.exports, require);
    return module.exports;
  }

  (() => {
    let title = require("./src/title.js");
    console.log(title);
  })();
})();

common.js 加载 common.js 模块

js
(() => {
  let modules = {
    "./src/title.js": (module, exports) => {
      // commonjs模块
      exports.name = "title_hl";
      exports.age = "title_18";
    },
  };
  let cache = {};
  function require(moduleId) {
    let cacheModule = cache[moduleId];
    if (cacheModule) {
      return cacheModule.exports;
    }
    let module = (cache[moduleId] = {
      exports: {},
    });
    modules[moduleId](module, module.exports, require);
    return module.exports;
  }

  (() => {
    let title = require("./src/title.js");
    console.log(title);
  })();
})();

common.js 加载 es6 模块

js
(() => {
  let modules = {
    "./src/title.js": (module, exports) => {
      // es6 模块导出
      require.r(exports);
      require.d(exports, {
        default: () => DEFAULT_EXPORTS, //值是一个getter
        age: () => age,
      });
      // 默认导出
      const DEFAULT_EXPORTS = "title";
      // 命名导出
      const age = 18;
    },
  };

  let cache = {};
  function require(moduleId) {
    let cacheModule = cache[moduleId];
    if (cacheModule) {
      return cacheModule.exports;
    }
    let module = (cache[moduleId] = {
      exports: {},
    });
    modules[moduleId](module, module.exports);
    return module.exports;
  }

  // 判断对象是否具有某个属性
  require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);

  // 给exports添加属性
  require.r = (exports) => {
    Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
    Object.defineProperty(exports, "__esModule", { value: true });
  };

  // 绑定属性
  require.d = (exports, definition) => {
    for (let key in definition) {
      // 如果definition有,exports没有,则绑定
      if (require.o(definition, key) && !require.o(exports, key)) {
        // getter
        Object.defineProperty(exports, key, {
          enumerable: true,
          get: definition[key],
        });
      }
    }
  };

  (() => {
    let title = require("./src/title.js");
    console.log(title);
  })();
})();

es6 模块加载 es6 模块

js
(() => {
  let modules = {
    "./src/title.js": (module, exports) => {
      // es6模块的导出
      require.r(exports);
      require.d(exports, {
        default: () => DEFAULT_EXPORTS, //值是一个getter
        age: () => age,
      });
      // 默认导出
      const DEFAULT_EXPORTS = "title";
      // 命名导出
      let age = 18;
      // es module 获取的是最终的值
      // 导出的值的引用
      setTimeout(() => {
        age = 20;
      }, 1000);
    },
  };

  let cache = {};
  function require(moduleId) {
    let cacheModule = cache[moduleId];
    if (cacheModule) {
      return cacheModule.exports;
    }
    let module = (cache[moduleId] = {
      exports: {},
    });
    modules[moduleId](module, module.exports);
    return module.exports;
  }

  // 判断对象是否具有某个属性
  require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);

  // 给exports添加属性
  require.r = (exports) => {
    Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
    Object.defineProperty(exports, "__esModule", { value: true });
  };

  // 绑定属性
  require.d = (exports, definition) => {
    for (let key in definition) {
      // 如果definition有,exports没有,则绑定
      if (require.o(definition, key) && !require.o(exports, key)) {
        // getter
        Object.defineProperty(exports, key, {
          enumerable: true,
          get: definition[key],
        });
      }
    }
  };
  let exports = {};
  (() => {
    //只要打包前的模块是一个es module,那么就会调用require.r方法进行处理
    require.r(exports);
    let title = require("./src/title.js");
    console.log(title);
  })();
})();

es6 模块加载 common.js 模块

js
(() => {
  let modules = {
    "./src/title.js": (module, exports) => {
      // commonjs模块
      module.exports = {
        name: "title_name",
        age: "title_age",
      };
    },
  };

  let cache = {};
  function require(moduleId) {
    let cacheModule = cache[moduleId];
    if (cacheModule) {
      return cacheModule.exports;
    }
    let module = (cache[moduleId] = {
      exports: {},
    });
    modules[moduleId](module, module.exports);
    return module.exports;
  }

  // 判断对象是否具有某个属性
  require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);

  // 给exports添加属性
  require.r = (exports) => {
    Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
    Object.defineProperty(exports, "__esModule", { value: true });
  };

  // 绑定属性
  require.d = (exports, definition) => {
    for (let key in definition) {
      // 如果definition有,exports没有,则绑定
      if (require.o(definition, key) && !require.o(exports, key)) {
        // getter
        Object.defineProperty(exports, key, {
          enumerable: true,
          get: definition[key],
        });
      }
    }
  };

  require.n = (module) => {
    var getter =
      module && module.__esModule ? () => module["default"] : () => module;
    return getter;
  };

  let exports = {};
  (() => {
    //只要打包前的模块是一个es module,那么就会调用require.r方法进行处理
    require.r(exports);
    let title = require("./src/title.js");
    var title_default = require.n(title);
    console.log(title_default());
    console.log(title.age);
  })();
})();

模块异步加载

  • 1、通过 chunkId 去查找对应的代码块文件其实就是 hello.main.js
  • 2、通过动态创建script标签加载 chunk 文件(JSONP 方式)
  • 3、加载完成后,会调用 webpackJsonpCallback 函数,将 chunk 文件中的模块添加到 modules 中,并且通过 installedChunks 记录 chunk 文件加载状态
js
[动态import()] → [生成chunkId] → [查找chunk文件] → [JSONP加载]
    ↑                                      ↓
[Promise返回] ← [触发回调] ← [执行webpackJsonpCallback] ← [chunk执行]
js
let modules = {};
let cache = {};

function require(moduleId) {
  let cacheModule = cache[moduleId];
  if (cacheModule) {
    return cacheModule.exports;
  }
  let module = (cache[moduleId] = {
    exports: {},
  });
  modules[moduleId](module, module.exports);
  return module.exports;
}

// 判断对象是否具有某个属性
require.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);

// 给exports添加属性
require.r = (exports) => {
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
  Object.defineProperty(exports, "__esModule", { value: true });
};

// 绑定属性
require.d = (exports, definition) => {
  for (let key in definition) {
    // 如果definition有,exports没有,则绑定
    if (require.o(definition, key) && !require.o(exports, key)) {
      // getter
      Object.defineProperty(exports, key, {
        enumerable: true,
        get: definition[key],
      });
    }
  }
};

require.f = {};

//返回此文件对应的访问路径
require.p = "";
//返回此代码块对应的文件名
require.u = function (chunkId) {
  return chunkId + ".main.js";
};

//存放加载的代码块的状态
//key是代码块的名字
//0表示已经加载完成了
var installedChunks = {
  main: 0,
  //'hello': [resolve, reject, promise]
};

require.e = (chunkId) => {
  let promises = [];
  require.f.j(chunkId, promises);
  return Promise.all(promises);
};

/**
 * 通过JSONP异步加载一个chunkId对应的代码块文件,其实就是hello.main.js
 * 会返回一个Promise
 * @param {*} chunkId 代码块ID
 * @param {*} promises promise数组
 */
require.f.j = function (chunkId, promises) {
  //当前的代码块的数据
  let installedChunkData;
  //创建一个promise
  const promise = new Promise((resolve, reject) => {
    installedChunkData = installedChunks[chunkId] = [resolve, reject];
  });
  installedChunkData[2] = promise;
  promises.push(promise);
  const url = require.p + require.u(chunkId);
  require.l(url);
};

// 加载远程的模块
require.l = function (url) {
  let script = document.createElement("script");
  script.src = url;
  document.head.appendChild(script);
};

var chunkLoadingGlobal = (window["webpack5"] = []);
chunkLoadingGlobal.push = webpackJsonpCallback;
/**
 *
 * @param {*} chunkIds 代码块ID数组
 * @param {*} moreModules 额外的模块定义
 */
function webpackJsonpCallback([chunkIds, moreModules]) {
  const resolves = [];
  for (let i = 0; i < chunkIds.length; i++) {
    const chunkId = chunkIds[i];
    resolves.push(installedChunks[chunkId][0]);
    installedChunks[chunkId] = 0; //表示此代码块已经下载完毕
  }
  //合并模块定义到modules去
  for (const moduleId in moreModules) {
    modules[moduleId] = moreModules[moduleId];
  }
  //依次取出resolve方法并执行 => promise resolve
  while (resolves.length) {
    resolves.shift()();
  }
}

/**
 * require.e异步加载hello代码块文件 hello.main.js
 * promise成功后会把 hello.main.js里面的代码定义合并到require.m对象上,也就是modules上
 * 调用require方法加载./src/hello.js模块,获取 模块的导出对象,进行打印
 */
require
  .e("hello")
  .then(require.bind(require, "./src/hello.js"))
  .then((result) => {
    console.log(result);
  });

webpack 编译流程

  • 1、初始化参数:从配置文件和 Shell 语句中读取参数并且和 webpack参数进行合并,得出最终的配置对象
  • 2、用上一步得到的参数初始化 Compiler 对象
  • 3、加载所有的plugin配置的插件,调用插件的apply函数并且传递 Compiler 对象
  • 4、执行 Compiler 对象的 run 方法开始执行编译
  • 5、执行 Complication 对象的 build, 根据配置中的 entry 找出入口文件
  • 6、从入口文件出发,调用所有配置的Loader对模块进行编译
  • 7、再找出该模块依赖的模块,再递归直到所有入口依赖的文件都经过了本步骤的处理
  • 8、根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
  • 9、再把每个 Chunk 转换成一个单独的文件加入到输出列表
  • 10、在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

module、chunk、bundlle

  • module:各个源码文件,webpack 中一切皆模块
  • chunk:多模块合并成的
  • bundlle:最终的输出文件