手写 Webpack 过程

  • 1、参数初始化
  • 2、创建 Compiler,实例化 compiler,调用 run 方法
  • 3、调用 run 方法中的 buildModule 模块 和 emitFile 模块
  • 4、buileModule 模块主要是根基路径进行 AST 语法分析,从入口开始递归遍历依赖树,找到所有文件和相对路径,组成 key: value 的数组,

parse 解析阶段 通过 require 语法获取当前收集的依赖和源码返回, 将 require 改写成webpack_require 抹平浏览器平台差异

  • 5、emitFile 阶段, 将上文中组好的 key: value 通过模版的形式动态渲染,写到 bundle.js 文件中

bundle.js 什么意思

bundle.js 是一个自执行函数, webpack5 进行了简化, 主要是一个对象,key 为文件的相对路径,value 是一个函数,里面是用 eval 包裹的文件的执行代码,通过 eval 来执行,里面会通过 module.export 导出结果, 将 require 修改成了自己的webpack_require 方法, 在执行的时候,会从入口开始依次执行,传入三个参数,分别为 module、webpack_exports ,当执行到webackrequire_ 递归执行, 最后将最终的入口文件的 module.export 的结果展示

webpack_require 是 webpack 自定义的一个函数 入参是模块的 id,也就是文件的相对路径值, 根据路径执行方法时,返回的是 module.exports ,

最终结果是返回的是 index.js 的 module.exports 的执行结果

手写 loader 逻辑

loader 的特点

  • 第一个 loader 要返回 js 脚本
  • 每个 loader 只做一件内容 为了使 loader 在更多场景链式调用
  • 每个 loader 都是一个模块
  • 每个 loader 都是无状态的,确保 loader 在不同模块之间转换不保存状态

增加 loader 逻辑

  1. // useLoader
  2. useLoader(content, modulePath) {
  3. let rules = this.rules || [];
  4. for (let i = 0; i < rules.length; i++) {
  5. let rule = rules[i];
  6. let { test, use } = rule;
  7. let len = use.length - 1;
  8. if (test.test(modulePath)) {
  9. function nextLoader() {
  10. let loader = require(use[len--]);
  11. content = loader(content);
  12. if (len >= 0) {
  13. nextLoader();
  14. }
  15. }
  16. nextLoader();
  17. }
  18. }
  19. return content;
  20. }

手写 loader

实现 babel-loader

cnpm install @babel/core @babel/preset-env loader-utils

  1. let babel = require("@babel/core");
  2. let loaderUtils = require("loader-utils");
  3. function loader(source) {
  4. let options = loaderUtils.getOptions(this);
  5. console.log(options, "options");
  6. console.log(this, "this");
  7. let cb = this.async();
  8. babel.transform(
  9. source,
  10. {
  11. ...options,
  12. sourceMap: true,
  13. filename: this.resourePath,
  14. },
  15. function (err, result) {
  16. cb(err, result.code);
  17. console.log(result, "result....");
  18. }
  19. );
  20. return source;
  21. }
  22. module.exports = loader;

手写 plugin 逻辑

实现发布订阅

npm i tapable

  1. // 增加生命周期钩子
  2. this.hooks = {
  3. entryOption: new SyncHook(),
  4. compile: new SyncHook(),
  5. afterCompiler: new SyncHook(),
  6. afterPlugins: new SyncHook(),
  7. run: new SyncHook(),
  8. emit: new SyncHook(),
  9. done: new SyncHook(),
  10. };
  1. // useLoader
  2. useLoader(content, modulePath) {
  3. let rules = this.rules || [];
  4. for (let i = 0; i < rules.length; i++) {
  5. let rule = rules[i];
  6. let { test, use } = rule;
  7. let len = use.length - 1;
  8. if (test.test(modulePath)) {
  9. function nextLoader() {
  10. let loader = require(use[len--]);
  11. content = loader(content);
  12. if (len >= 0) {
  13. nextLoader();
  14. }
  15. }
  16. nextLoader();
  17. }
  18. }
  19. return content;
  20. }