# 18. webpack相关问题

# 1. 介绍下 webpack 热更新原理

  1. 当修改了一个或多个文件;
  2. 文件系统接收更改并通知webpack;
  3. webpack重新编译构建一个或多个模块,并通知HMR服务器进行更新;
  4. HMR Server 使用webSocket通知HMR runtime 需要更新,HMR运行时通过HTTP请求更新jsonp;
  5. HMR运行时替换更新中的模块,如果确定这些模块无法更新,则触发整个页面刷新。

# 2. babel 怎么把字符串解析成 AST,是怎么进行词法/语法分析的?

Babel 是将 ES6、ES7等代码转译为 ES5 代码且能安全稳定运行的工具。
1.先对输入代码进行分词,根据最小有效语法单元,对字符串进行切割。 -> 2.AST,然后进行语法分析,会涉及到读取、暂存、回溯、暂存点销毁等操作。 -> 3.然后转换生成新的 AST。-> 4.最后根据新生成的 AST 输出目标代码。

解析

  • Babylon是一个解析器,它可以将javascript字符串,转化为更加友好的表现形式,称之为抽象语法树;
  • 在解析过程中有两个阶段:词法分析和语法分析,
  • 词法分析阶段:字符串形式的代码转换为令牌(tokens)流,令牌类似于AST中的节点;
  • 语法分析阶段:把一个令牌流转化为AST的形式,同时这个阶段会把令牌中的信息转化为AST的表述结构

转换

  • babel-traverse 模块允许你浏览、分析和修改抽象语法树(AST Abstract Syntax Tree)
  • Babel接收解析得到的AST并通过babel-traverse对其进行深度优先遍历,在此过程中对节点进行添加、更新及移除操作。

生成

  • babel-generator 模块用来将转换后的抽象语法树(AST Abstract Syntax Tree)转化为Javascript 字符串
  • 将经过转换的AST通过babel-generator再转换为js代码,过程及时深度遍历整个AST,然后构建转换后的代码字符串。

# 3. 介绍下 npm 模块安装机制,为什么输入 npm install 就可以自动安装对应的模块?

  1. 发出npm install命令;
  2. npm 向 registry 查询模块压缩包的网址;
  3. 下载压缩包,存放在~/.npm目录;
  4. 解压压缩包到当前项目的node_modules目录;

# 4. webpack插件开发?

webpack 插件由以下组成:

  • 一个 JavaScript 命名函数或 JavaScript 类。
  • 在插件函数的 prototype 上定义一个 apply 方法。
  • 指定一个绑定到 webpack 自身的事件钩子。
  • 处理 webpack 内部实例的特定数据。
  • 功能完成后调用 webpack 提供的回调。

Compiler 它扩展自 Tapable 类,设置了一系列事件钩子和各种配置参数,并定义了 webpack 诸如启动编译、观测文件变动、将编译结果文件写入本地等一系列核心方法。

常见事件钩子介绍:

  • beforeRun:在编译器开始读取 records 之前执行;
  • run:在读取 records 之前;
  • thisCompilation:在 compilation 初始化时执行;
  • compilation:在 compilation 创建之后执行;
  • make:在 complication 完成之前执行;
  • afterCompilation:在 compilation 完成后执行;
  • emit:在生成文件到 output 目录之前执行;
  • afterEmit:在生成文件到 output 目录之后执行;
  • done:在 compilation 完成之后执行;

实现方法:

class MyWebpackPlugin {
  constructor(doneCallback, emitCallback) {
    this.emitCallback = emitCallback
    this.doneCallback = doneCallback
  }
  apply(compiler) {
    compiler.hooks.emit.tap('MyWebpackPlugin', () => {
      // 在 emit 事件中回调 emitCallback
      this.emitCallback();
    });
    compiler.hooks.done.tap('MyWebpackPlugin', (err) => {
      // 在 done 事件中回调 doneCallback
      this.doneCallback();
    });
    compiler.hooks.compilation.tap('MyWebpackPlugin', () => {
      // compilation('编译器'对'编译ing'这个事件的监听)
      console.log("The compiler is starting a new compilation...")
    });
    compiler.hooks.compile.tap('MyWebpackPlugin', () => {
      // compile('编译器'对'开始编译'这个事件的监听)
      console.log("The compiler is starting to compile...")
    });
  }
}
// 导出插件
module.exports = MyWebpackPlugin;


// 使用插件
module.exports = {
  plugins: [
    new MyWebpackPlugin(
      () => {
        // Webpack 模块完成转换成功
        console.log('emit 事件发生啦,所有模块的转换和代码块对应的文件已经生成好~')
      },
      () => {
        // Webpack 构建成功,并且文件输出了后会执行到这里,在这里可以做发布文件操作
        console.log('done 事件发生啦,成功构建完成~')
      }
    )
  ]
}