Skip to content

Webpack 原理与插件开发 ​

打包原理 ​

Webpack 的运行流程是一个串行的过程,从启动到结束依次执行以下流程:

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

在以上过程中,Webpack 会在特定时间点广播出特定的事件,插件在监听到感兴趣的事件后执行特定的逻辑,并且可以调用 Webpack 提供的 API 改变运行结果。

开发一个 Loader ​

Loader 支持以数组形式配置多个,Webpack 会按顺序链式调用。前一个 Loader 返回的内容会作为下一个的入参。返回值必须是标准 JS 代码字符串。

js
// my-loader.js
module.exports = function (source) {
  const content = doSomeThing2JsString(source)

  // 如果 loader 配置了 options,this.query 指向 options
  const options = this.query

  // this.callback 参数:
  // error: Error | null
  // content: String | Buffer,编译后需要导出的内容
  // sourceMap: 生成的 source map
  // ast: 本次生成的 AST,后续 loader 可直接使用
  this.callback(null, content)
  // or return content
}
// my-loader.js
module.exports = function (source) {
  const content = doSomeThing2JsString(source)

  // 如果 loader 配置了 options,this.query 指向 options
  const options = this.query

  // this.callback 参数:
  // error: Error | null
  // content: String | Buffer,编译后需要导出的内容
  // sourceMap: 生成的 source map
  // ast: 本次生成的 AST,后续 loader 可直接使用
  this.callback(null, content)
  // or return content
}

Loader 函数中的 this 上下文由 webpack 的 loader-runner 提供。

开发一个 Plugin ​

Plugin 需要实现一个 apply 方法:

js
// file-webpack-plugin.js
class FileWebpackPlugin {
  constructor(options) {
    this.options = options
  }
  // compiler: webpack 实例
  apply(compiler) {
    // 定义插件执行时机(异步)
    compiler.hooks.emit.tapAsync('FileWebpackPlugin', (compilation, callback) => {
      const packFilesLen = Object.keys(compilation.assets).length
      let content = `本次构建结果统计:\n\r生成文件数量:${packFilesLen}个;\n\r`
      for (let filename in compilation.assets) {
        content += `文件名称:${filename},文件大小:${compilation.assets[filename].size()};\n\r`
      }
      compilation.assets['file.txt'] = {
        source: () => content,
        size: () => 1024,
      }
      callback()
    })
  }
}
module.exports = FileWebpackPlugin
// file-webpack-plugin.js
class FileWebpackPlugin {
  constructor(options) {
    this.options = options
  }
  // compiler: webpack 实例
  apply(compiler) {
    // 定义插件执行时机(异步)
    compiler.hooks.emit.tapAsync('FileWebpackPlugin', (compilation, callback) => {
      const packFilesLen = Object.keys(compilation.assets).length
      let content = `本次构建结果统计:\n\r生成文件数量:${packFilesLen}个;\n\r`
      for (let filename in compilation.assets) {
        content += `文件名称:${filename},文件大小:${compilation.assets[filename].size()};\n\r`
      }
      compilation.assets['file.txt'] = {
        source: () => content,
        size: () => 1024,
      }
      callback()
    })
  }
}
module.exports = FileWebpackPlugin
js
// webpack.config.js
plugins: [new FileWebpackPlugin({ author: 'HBF' })]
// webpack.config.js
plugins: [new FileWebpackPlugin({ author: 'HBF' })]

Compiler vs Compilation ​

对象说明
Compiler包含 Webpack 环境所有的配置信息(options、loaders、plugins),Webpack 启动时实例化,全局唯一,可理解为 Webpack 实例
Compilation包含当前模块资源、编译生成资源、变化的文件。开发模式下每次文件变化会创建新的 Compilation

Compiler 代表了整个 Webpack 从启动到关闭的生命周期,Compilation 只代表一次新的编译。

Compiler 事件钩子 ​

compiler hooks

Compilation 事件钩子 ​

compilation hooks

常见 Loader ​

Loader说明
file-loader把文件输出到文件夹,代码中通过相对 URL 引用
url-loader类似 file-loader,小文件以 base64 方式注入代码
source-map-loader加载额外的 Source Map 文件,方便断点调试
image-loader加载并压缩图片文件
babel-loader把 ES6 转换成 ES5
css-loader加载 CSS,支持模块化、压缩、文件导入
style-loader把 CSS 注入到 JavaScript 中,通过 DOM 加载
eslint-loader通过 ESLint 检查 JS 代码(已弃用,改用 eslint-webpack-plugin)

常见 Plugin ​

Plugin说明
ProvidePlugin自动加载模块,代替 require 和 import(webpack 内置)
html-webpack-plugin根据模板自动生成 HTML,自动引用 CSS 和 JS
DefinePlugin编译时配置全局变量(webpack 内置)
HotModuleReplacementPlugin热更新(webpack 内置)
mini-css-extract-plugin将 JS 中的样式单独抽离成 CSS 文件
css-minimizer-webpack-pluginCSS 压缩去重
webpack-bundle-analyzerBundle 文件分析工具(可交互 treemap)
compression-webpack-pluginGzip 压缩 JS 和 CSS
happypack多进程模型加速代码构建
clean-webpack-plugin清理上次打包未使用的文件
speed-measure-webpack-plugin查看每个 Loader 和 Plugin 执行耗时