webpack 之 webpack 打包原理的 Tapable、编译流程、模块构建、chunk 生成和 webpack 的 Loader 和插件的处理

一、webpack 打包原理的 Tapable、编译流程、模块构建、chunk 生成

  1. Webpack 的本质,Webpack 可以将其理解是一种基于事件流的编程范例,一系列的插件运行。
  2. Tapable 是一个类似于 Node.jsEventEmitter 的库,主要是控制钩子函数的发布于订阅,控制着 webpack 的插件系统。Tapable 库暴露了很多 Hook 钩子类,为插件提供挂载的钩子。Tapable hooks 类型,如下所示:
  • Hook,所有钩子的后缀,
  • Waterfall,同步方法,但是它会传值给下一个函数
  • Bail,熔断,当函数有任何返回值,就会在当前执行函数停止
  • Loop,监听函数返回 true 表示继续循环,返回 undefine 表示结束循环
  • Sync,同步方法
  • AsyncSeries,异步串行钩子
  • AsyncParallel,异步并行执行钩子
  1. Tapable 的使用,如下所示:
  • 通过 -new Hook 新建钩子,Tapable 暴露出来的都是类方法,new 一个类方法获得我们需要的钩子,class 接受数组参数 options,非必传。类方法会根据传参,接受同样数量的参数,如 const hook1 = new SyncHook(['arg1', 'arg2', 'arg3']);
  • 钩子的绑定与执行,Tapable 提供了同步和异步绑定钩子的方法,并且他们都有绑定事件和执行事件对应的方法
  • hook 基本用法示例,如下所示:
const hook1 = new SyncHook(['arg1', 'arg2', 'arg3']);
hook1.tap('hook1', (arg1, arg2, arg3) => console.log((arg1, arg2, arg3)) 
hook1.call(1,2,3)
  1. webpack 的编译都按照下面的钩子调用顺序执行,如下所示:
  • entry-option,初始化 option
  • run,开始编译
  • make,从 entry 开始递归的分析依赖,对每个依赖模块进行 build
  • before-resolve,对模块位置进行解析
  • build-module,开始构建某个模块
  • normal-module-loader,将 loader 加载完成的 module 进行编译,生成 AST
  • program,遍历 AST,当遇到 require 等一些调用表达式时,收集依赖
  • seal,所有依赖 build 完成,开始优化
  • emit,输出到 dist 目录
  1. WebpackOptionsApply,将所有的配置 options 参数转换成 webpack 内部插件,使用默认插件列表。Compiler hooks,如下所示:
  • 流程相关,(before-)run、(before-/after-)compile、make、(after-)emit、done
  • 监听相关,watch-run、watch-close
  1. Compilation,Compiler 调用 Compilation 生命周期方法,addEntry-> addModuleChain、finish(上报模块错误)、sealModuleFactory 包括 NormalModuleFactoryContextModuleFactoryModule 包括 NormalModule、ContextModule、ExternalModule、DelegatedModuleMultiModuleNormalModuleBuild 时,如下所示:
  • 使用 loader-runner 运行 loaders
  • 通过 Parser 解析,内部是 acron
  • ParserPlugins 添加依赖
  1. Compilation hooks,如下所示:
  • 模块相关,build-module、failed-module、succeed-module
  • 资源生成相关,module-asset、chunk-asset
  • 优化和 seal 相关,after-optimize-modules、after--optimize-chunks、after-optimize-tree
  1. Chunk 生成算法,如下所示:
  • webpack 先将 entry 中对应的 module 都生成一个新的 chunk
  • 遍历 module 的依赖列表,将依赖的 module 也加入到 chunk
  • 如果一个依赖 module 是动态引入的模块,那么就会根据这个 module 创建一个新的 chunk,继续遍历依赖
  • 重复上面的过程,直至得到所有的 chunks

二、webpack 的 Loader 和插件的处理

  1. 模块化,增强代码可读性和可维护性,如下所示:
  • 传统的网页开发转变成 Web Apps 开发
  • 代码复杂度在逐步提高
  • 分离的 JS 文件/模块,便于后续代码的维护性
  • 部署时希望把代码优化成几个 HTTP 请求
  • 常见的几种模块化方式,ES module、CJS、AMD
  1. AST,抽象语法树,或者语法树,是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码,树上的每个节点都表示源代码中的一种结构。
  2. loader 只是一个导出为函数的 JavaScript 模块,一个最简单的 loader 代码结构,如下所示:
module.exports = function(source) {
  return source;
};
  1. 多个 Loader 时的执行顺序,多个 Loader 串行执行,顺序从后到前。对于函数组合的两种情况,Unix 中的 piplineComposewebpack 采取的是 Compose,如 compose = (f, g) => (...args) => f(g(...args));
  2. loader-runner,允许你在不安装 webpack 的情况下允许 loaders,作用如下所示:
  • 作为 webpack 的依赖,webpack 中使用它执行 loader
  • 进行 loader 的开发和调试
  1. loader-runner 的使用,resource 是资源的绝对路径(可以增加查询字符串),loadersloader 的绝对路径(可以增加查询字符串),context 是基础上下文之外的额外 loader 上下文,readResource 是读取资源的函数,代码如下:
import { runLoaders } from 'loader-runner';

runLoaders({
  resource: '/abs/path/to/file.txt?query',
  loaders: ['/abs/path/to/loader.js?query'],
  context: [minimize: true],
  readResource: fs.readFile.bind(fs)
}, function (err, result) {
  // err: Error?
  // result.result: Buffer | String
})
  1. loader 的参数获取,通过 loader-utilsgetOptions 方法获取,代码如下:
const loaderUtils = require('loader-utils');
module.exports = function(content) {
  const { name } = loaderUtils.getOptions(this);
};
  1. loader 异常处理,loader 内直接通过 throw 抛出,通过 this.callback 传递错误,代码如下:
this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap ?: SourceMap,
  meta ?: any
);
  1. loader 的异步处理,通过 this.async 来返回一个异步函数,第一个参数是 Error,第二个参数是处理的结果,代码如下:
module.exports = function(input) {
  const callback = this.async();
  // No callback -> return synchronous results
  // if (callback) { ... }
  callback(null, input + input);
};
  1. loader 中使用缓存,webpack 中默认开启 loader 缓存,可以使用 this.cacheable(false) 关掉缓存。缓存条件是 loader 的结果在相同的输入下有确定的输出,有依赖的 loader 无法使用缓存。loader 中可以通过 this.emitFile 进行文件写入。
  2. 插件的运行环境,插件没有像 loader 那样的独立运行环境,只能在 webpack 里面运行。插件的基本结构是插件名称、插件上的 apply 方法、插件的 hooks 和插件处理逻辑。对于使用时,plugins: [ new MyPlugin()],代码如下:
class MyPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('My Plugin', (
      stats
    ) => {
      console.log('hello');
    });
  }
}
module.exports = MyPlugin;
  1. 插件中如何获取传递的参数,通过插件的构造函数进行获取,代码如下:
module.exports = class MyPlugin {
  constructor(options) {
    this.options = options;
  }
  apply() {
    console.log('apply', this.options);
  }
};
  1. 插件的错误处理,如下所示:
  • 参数校验阶段可以直接 throw 的方式抛出,代码如下:
throw new Error('Error Message')
  • 通过 compilation 对象的 warningserrors 接收,代码如下:
compilation.warnings.push('warning');
compilation.errors.push('error');
  1. 通过 Compilation 进行文件写入,Compilation 上的 assets 可以用于文件写入,可以将 zip 资源包设置到 compilation.assets 对象上,文件写入需要使用 webpack-sources

你可能感兴趣的:(Webpack,Vite,Gulp,webpack,Tapable,编译流程,模块构建,chunk,生成,Loader,和插件的处理)