图片.png
图片.png

webpack工作流

模拟插件

  1. //类
  2. module.exports = class DonePlugin {
  3. apply(compiler) {
  4. //钩子 像发布订阅模式
  5. //hooks是个对象 done是个钩子
  6. compiler.hooks.done.tap("DonePlugin", () => {
  7. console.log("DonePlugin");
  8. });
  9. }
  10. };
//类
module.exports = class RunPlugin {
  apply(compiler) {
    //钩子 像发布订阅模式
    //监听run事件
    compiler.hooks.run.tap("RunPlugin", () => {
      console.log("RunPlugin");
    });
  }
};

模拟工作流

image.png
文件系统fs
webpack-dev-server用的是memory-fs 内存

/**
 * webpack 工作流程
 */

let { SyncHook } = require("tapable");

let fs = require("fs");
let path = require("path");
class Compiler {
  constructor(options) {
    this.options = options;
    this.hooks = {
      run: new SyncHook(),
      done: new SyncHook(),
    };
  }
  run() {
    //模拟工作流
    //模块=>代码块=>文件
    let modules = [];
    let chunks = [];
    let files = [];
    //广播事件
    this.hooks.run.call(); //触发run钩子执行
    //根据配置中的entry找到入口文件
    let entry = path.join(this.options.context, this.options.entry);
    //从入口文件出发 调用所有配置的loader对模块进行编译 再找出该模块依赖的模块
    //再递归本步骤直到所有入口依赖文件都经过本步骤处理
    //1.读取模块内容 可能是less不一定是js
    let entryContent = fs.readFileSync(entry, "utf8");
    let entrySource = babelLoader(entryContent);
    //模块module chunk代码块 bundle 文件的关系
    let entryModule = { id: entry, source: entrySource };
    modules.push(entryModule);
    //把入口模块的代码转成抽象语法树AST 分析里面的import和require依赖
    let title = path.join(this.options.context, "./src/title.js");
    let titleContent = fs.readFileSync(title, "utf8");
    let titleSource = babelLoader(titleContent);
    //模块module chunk代码块 bundle 文件的关系
    let titleModule = { id: title, source: titleSource };
    modules.push(titleModule);
    //根据入口和模块之间的依赖关系 组装成一个个含多个模块的chunk

    let chunk = { name: "main", modules };
    chunks.push(chunk);
    //再把chunk转换成一个单独的文件加入到输出列表
    let file = {
      file: this.options.output.filename,
      source: "source转义后的代码",
    };
    files.push(file);
    //在确定输出内容后, 根据配置的确定输出的路径和文件名 把文件内容写入到文件系统
    let outputPath = path.join(
      this.options.output.path,
      this.options.output.filename
    );
    fs.writeFileSync(outputPath, file.source, "utf8");
    //广播事件
    //在以上过程中 webpack会在特定的时间点广播出特定的事件
    //插件在听到目标时间后会执行特定逻辑
    //并且插件可以调用webpack提供的api改变webpack的运行结果
    this.hooks.done.call();
  }
  done() {
    this.hooks.done.call(); //触发run钩子执行
  }
}

/**
 * 1.初始化参数 从配置文件和shell语句中读取并合并参数 得出最终的结果
 */

let options = require("./webpack.config");
//开始编译 用上一步得到的参数初始化Compiler对象
let compiler = new Compiler(options);
//加载所有配置的插件 执行对象的run方法开始执行编译
if (options.plugins && Array.isArray(options.plugins)) {
  for (const plugin of options.plugins) {
    plugin.apply(compiler);
  }
}
//确定入口 根据配置中的entry找到入口
compiler.run();

//es6编译成es5
function babelLoader(source) {
  return "let a = 'ES5'";
}

发布订阅模式

image.png

/**
 * 发布订阅
 * button.addEventListener("click",()=>console.log("click"))
 * button.trigger("click")
 */

let { SyncHook } = require("tapable");
let hook = new SyncHook();
//注册监听
hook.tap("some name", () => {
  console.log("some name");
});
//触发监听函数执行
hook.call();

tapable分类

image.png
image.png