Webpack源码深入-compiler

发布于:2024-06-21 ⋅ 阅读:(109) ⋅ 点赞:(0)

compiler

上述中执行cli.run(arg)就是执行webpack-cli.js中的run方法,在执行的过程中,会加载webpack/lib/webpack.js中,得到this.webpack模块,然后运行this.runWebpack()方法,创建compiler对象

创建compiler

创建compiler的工作是由webpack/lib/webpack.js模块完成的。
基本代码结构为

const webpack = (options, callback) => {
  const create = () => {};

  if (callback) {
    // 传递 callback 的情况
    try {
      const { compiler, watch, watchOptions } = create();
      
      if (watch) {
        compiler.watch(watchOptions, callback);
      } else {
        compiler.run((err, stats) => {});
      }
      
      return compiler;
    } catch (err) {}
  } else {
    // 未传递 callback 的情况
    const { compiler, watch } = create();
    return compiler;
  }
}
  • options: 用户传入创建编译器实例的配置项
  • callback:处理webpack编译工作的回调函数
    代码流程为: 声明具体的compiler中的create方法,该方法创建了compiler逻辑,然后判断传入callback决定是否需要启动编译(compiler.run)。传入callback,调用私有的create方法创建compiler对象,接下来判断是否为watch模式,如果是则调用compiler.watch,否则调用comiler.run启动编译。如果没有传入callback,则说明不需要启动编译,在创建compiler对象后返回。

create方法

const create = () => {
    if (!asArray(options).every(webpackOptionsSchemaCheck)) {
        getValidateSchema()(webpackOptionsSchema, options);
        util.deprecate(
            () => {},
            "webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.",
            "DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID"
        )();
    }
    /** @type {MultiCompiler|Compiler} */
    let compiler;
    /** @type {boolean | undefined} */
    let watch = false;
    /** @type {WatchOptions|WatchOptions[]} */
    let watchOptions;
    if (Array.isArray(options)) {
        /** @type {MultiCompiler} */
        compiler = createMultiCompiler(
            options,
            /** @type {MultiCompilerOptions} */ (options)
        );
        watch = options.some(options => options.watch);
        watchOptions = options.map(options => options.watchOptions || {});
    } else {
        const webpackOptions = /** @type {WebpackOptions} */ (options);
        /** @type {Compiler} */
        compiler = createCompiler(webpackOptions);
        watch = webpackOptions.watch;
        watchOptions = webpackOptions.watchOptions || {};
    }
    return { compiler, watch, watchOptions };
};

create方法做了如下的工作。

  1. 校验用户传递的options是否符合webpack内部的约定好的schema。
  2. 判断options的类型,根据不同的类型,执行不同的操作
    1. array。创建多项目编译器。判断这些项目是否需要存在watch模式,然后获得各个配置项中的watchOption配置
    2. 非数组。调用createCompiler创建编译器的实例。然后获取watch模式以及watchOptions的配置项目。
  3. 返回compiler, watch, watchOptions

createCompiler方法

createCompiler函数创建了一个编译器对象,并根据传入的rawOptions配置对compiler对象进行初始化。

const createCompiler = (rawOptions, compilerIndex) => {
	const options = getNormalizedWebpackOptions(rawOptions);
	applyWebpackOptionsBaseDefaults(options);
	const compiler = new Compiler(
		/** @type {string} */ (options.context),
		options
	);
	new NodeEnvironmentPlugin({
		infrastructureLogging: options.infrastructureLogging
	}).apply(compiler);
	if (Array.isArray(options.plugins)) {
		for (const plugin of options.plugins) {
			if (typeof plugin === "function") {
				/** @type {WebpackPluginFunction} */
				(plugin).call(compiler, compiler);
			} else if (plugin) {
				plugin.apply(compiler);
			}
		}
   	}
}
  1. getNormalizedWebpackOptions格式化rawOptions,经过处理webpack的标准配置对象,抹平各种语法的配置对象。
  2. 根据上文得到的配置对象,调用applyWebpackOptionsBaseDefaults进行配置。
  3. 创建compiler对象实例。传入上文中得到的配置对象。
  4. 初始化NodeEnvironmentPlugin插件,这个插件是Compiler工作zhon 一个模块,负责文件io,监听文件内容的改变。由四个基本文件系统组成。nputFileSystem、outputFileSystem 处理文件i/o,watchFileSystem 监听文件改动,intermediateFileSystem则处理所有不被看做是输入或输出文件系统操作,比如写记录,缓存或者分析输出等。
    5.初始化插件调用,webpack的插件以及自定义插件上定义的apply都是在这个时候调用的,调用这些钩子会传入compiler对象,通过这个对象可以注册全局声明周期钩子
new NodeEnvironmentPlugin({
    infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
  1. new WebpackOptionsApply().process(options, compiler);wanchengcompiler特性的应用工作。
  2. compiler.hooks.initialize.call();触发 compiler.hooks.environment、compiler.hooks.afterEnvironment、compiler.hooks.initialize 钩子。
  3. 返回compiler对象

WebpackOptionsApply

位置: webpack/lib/webpackOptionsApply.js

JavascriptModulesPlugin

javascriptMoudlesPlugin的作用是注册各种模块的JavaScript的解析器以及JavaScript产物生成器。分别注册在normalModuleFactory.hooks.createParser和normalModuleFactory.hooks.createGenertor钩子上。

JsonModulesPlugin

JsonModulesPlugin工作和前面的JavaScriptModulesPlugin类似,JsonModulesPlugin注册是JSON模块的解析器和内容生成器

EntryOptionPlugin

EntryOptionPlugin是处理的entry注册的插件,这个是会在webpack.config.js中作为入口,根据不同类型的入口声明选择DynamicEntryPlugin或者Entryplugin进行入口的注册,入口也是一个编译的起始模块,webpack会从指定的入口开始构建模块并产出依赖图谱。最终生成bundle

WebAssemblyModulesPlugin

WebAssswmblyModulesPlugin是注册webAsswmbly模块的解析器和内容生成器。

ModuleConcateNationPlugin

通过这个插件,将在打包的时候将所有的模块的预编译到一个闭包中,以提升代码在浏览器中的执行速度。=> “作用域提升”

SplitChunkPlugin

拆分chunk的时候,按照一定的规则将一个较大的chunk拆分称为多个较小的chunk。

if(options.cache&&typeof options.cache === “object”)

options.cache: 设置该选项,webpack构建将会在初次构建的时候写入缓存并在之后构建中优先使用缓存中的内容,目的是webpack答复提升构建速度,缓存中包括模块,request解析结构,loader等构建缓存等多项中间产物的缓存。

AddBuildDependenciesPlugin

这个插件是将options.cache.buildDependencies主动声明的构建依赖项目假如到webpack收集的构建依赖范围中。默认收集loader等node_modules下的内容作为构建依赖。

IdleFileCachePlugin

处理webpack缓存的写入和恢复的插件,只负责调度。

PackFileCacheStrategy

配合上面的IdleFileCachePlugin插件使用的,这里面定义缓存的具体读写校验逻辑。

ResolverCachePlugin

webpack的构建的耗时很多一部分都花费在了resolve中,这是webpack优化编译的时候的手段。

  1. 统计缓存利用率
  2. 拦截resolve过程,优先使用缓存,如果缓存有效则直接返回缓存结果,否则进行常规的resolve过程。
compiler.resolverFactory.hooks.resolveOptions.for()

为webpack内建的三种模块注册路径解析器的配置项

  1. normal: NormalModule的resolver创建的是所需的配置项
  2. loader: loader模块resolver创建的时候需要的配置项
  3. context: ContextModule模块的resolver创建时所需要的配置项

compiler对象

位置: compiler

核心参数:

  1. context: 构建的上下文目录,即运行构建命令的工作目录
  2. options: options选项对象

方法

  1. run: 常规启动webpack编译,一次编译,输出bundle后即退出进程
  2. watch: watch模式启动编译,一次编译后不退出进程,见识到文件变化后重启webpack编程流程
  3. getCache: 该方法是创建持久化的缓存门面对象的方法
class Compiler {
    constructor(context, opotions = {}) {
    
    }
    getCache () {}
}

构造参数

	constructor(context, options =  ({})) {
		this.hooks = Object.freeze({
			initialize: new SyncHook([]),
			shouldEmit: new SyncBailHok(["compilation"]),
			done: new AsyncSeriesHook(["stats"]),
			afterDone: new SyncHook(["stats"]),
			additionalPass: new AsyncSeriesHook([]),
			beforeRun: new AsyncSeriesHook(["compiler"]),
			run: new AsyncSeriesHook(["compiler"]),
			emit: new AsyncSeriesHook(["compilation"]),
			assetEmitted: new AsyncSeriesHook(["file", "info"]),
			afterEmit: new AsyncSeriesHook(["compilation"]),
			thisCompilation: new SyncHook(["compilation", "params"]),
			compilation: new SyncHook(["compilation", "params"]),
			normalModuleFactory: new SyncHook(["normalModuleFactory"]),
			contextModuleFactory: new SyncHook(["contextModuleFactory"]),
			beforeCompile: new AsyncSeriesHook(["params"]),
			compile: new SyncHook(["params"]),
			make: new AsyncParallelHook(["compilation"]),
			finishMake: new AsyncSeriesHook(["compilation"]),
			afterCompile: new AsyncSeriesHook(["compilation"]),
			readRecords: new AsyncSeriesHook([]),
			emitRecords: new AsyncSeriesHook([]),
			watchRun: new AsyncSeriesHook(["compiler"]),
			failed: new SyncHook(["error"]),
			invalid: new SyncHook(["filename", "changeTime"]),
			watchClose: new SyncHook([]),
			shutdown: new AsyncSeriesHook([]),
			infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
			environment: new SyncHook([]),
			afterEnvironment: new SyncHook([]),
			afterPlugins: new SyncHook(["compiler"]),
			afterResolvers: new SyncHook(["compiler"]),
			entryOption: new SyncBailHook(["context", "entry"])
		});

		this.webpack = webpack;
		this.name = undefined;
		this.parentCompilation = undefined;
		this.root = this;
		this.outputPath = "";
		this.watching = undefined;
		this.outputFileSystem = null;
		this.intermediateFileSystem = null;
		this.inputFileSystem = null;
		this.watchFileSystem = null;
		this.recordsInputPath = null;
		this.recordsOutputPath = null;
		this.records = {};
		this.managedPaths = new Set();
		this.unmanagedPaths = new Set();
		this.immutablePaths = new Set();
		this.modifiedFiles = undefined;
		this.removedFiles = undefined;
		this.fileTimestamps = undefined;
		this.contextTimestamps = undefined;
		this.fsStartTime = undefined;
		this.resolverFactory = new ResolverFactory();
		this.infrastructureLogger = undefined;
		this.platform = {
			web: null,
			browser: null,
			webworker: null,
			node: null,
			nwjs: null,
			electron: null
		};
		this.options = options;
		this.context = context;
		this.requestShortener = new RequestShortener(context, this.root);
		this.cache = new Cache();
		this.moduleMemCaches = undefined;
		this.compilerPath = "";
		this.running = false;
		this.idle = false;
		this.watchMode = false;
		this._backCompat = this.options.experiments.backCompat !== false;
		this._lastCompilation = undefined;
		this._lastNormalModuleFactory = undefined;
		this._assetEmittingSourceCache = new WeakMap();
		this._assetEmittingPreviousFiles = new Set();
	}
compiler.hooks
  1. initialize: SyncHook,同步钩子,触发的时候表示webpack的初始化已经完成
  2. done: AsyncSeriesHook,异步串行钩子,参数stats对象,该函数触发的时候表示webpack构建完成
  3. run: AsyncSeriesHook,异步串行钩子,参数compiler实例对象,该狗仔在run方法被调用的启动编译的时候触发
  4. emit:AsyncSeriesHook,异步串行钩子,参数compilation对象,表示在编译后期发射编译产物文件前触发。
  5. afterEmit: AsyncSeriesHook,异步串行钩子,参数compilation对象,表示该钩子在完成文件发射的时候触发。
  6. thisCompilation: SyncHook,同步钩子,参数 compilation, params,该钩子隶属于本次编译,在 compilation 钩子创建前触发,该钩子注册的插件不会被复制到子编译器;
  7. compilation: SyncHook 同步钩子,参数 compilation, params 对象,compilation 钩子在 compilation 对象被创建后触发,compilation 在整个编译声明周期内创建一次,有别于 thisComilation;
  8. normalModuleFactory: SyncHook,同步钩子,参数 normalModuleFactory 对象,实例化 NormalModuleFactory 后触发,normalModuleFactory 用创建常规模块;
  9. compile: SyncHook,同步钩子,params 用于创建 compilation 的 params,于编译启动时,创建 compilation 对象前触发;
  10. AsyncParallelHook,异步并行钩子,参数 compilation 对象,于创建 compilation 对象后触发,开启 entry 收集。EntryPlugin 就是定义在了 make 钩子上,模块的创建和构建过程主要集中在make阶段。
  11. finishMake: AsyncSeriesHook,异步串行钩子,参数 compilation 对象,在 make 阶段完成后触发;
  12. readRecords: AsyncSeriesHook,异步串行钩子,无参数,读取 records 文件后触发;
  13. emitRecords: AsyncSeriesHook,异步串行钩子,无参数,向文件系统写入 records 文件后触发;
  14. watchRun: AsyncSeriesHook,异步串行钩子,参数 compiler 对象,watch 模式下启动编译后触发;
  15. shutdown: AsyncSeriesHook,异步串行钩子,参数无,编译停止后触发,这个钩子主要是持久化缓存写入工作的信号;
  16. environment: SyncHook,同步钩子,参数无,初始化用户插件并完成 webpack 内部默认配置初始化完成后,应用 webpack 默认特性插件(new WebpackOptionsApply().process)之前触发;
文件系统声明

webpack构建过程中到依赖文件系统,没有直接引用fs模块,而是针对不同的工程设计了不同的文件系统。这些文件系统底层是依赖于fs模块的。
启动dev-server会默认将文件写好,相当于是接管了原有的文件系统写入
主要有四个文件系统

  1. outputFileSystem: 用于向文件系统中写入的场景
  2. intermediateFileSystem:这个属于webpack特有的文件系统,负责webpack构建过程中的中间产物。
  3. inputFileSystem:用于webpack文件的读取场景
  4. watchFileSystem:该文件系统封装了监听文件变化的能力,用于watch模式下的监听文件变化的文件系统。
records对象声明

records对象是webpack提供的一种用于记录编译过程信息的;对象,可以记录编译过程中的各种信息,比如模块的依赖关系,错误信息等,通过records对象,开发人员可以方便的分析和调试编译过程的问题,从而提高开发效率。

this.recordsInputPath = null;
/** @type {string|null} */
this.recordsOutputPath = null;
/** @type {Record<string, TODO>} */
this.records = {};
  1. recordInputPath: records的输入路径。
  2. recordOutputPath: records的输出路径
  3. records: 收集到的records内容存储。
resolverFactory实例化

resolverFactory适用于创建resolver[路径解析器]的工厂。

this.resolverFactory = new ResolverFactory() 
持久化缓存声明

webpack内部启用持久化缓存之后,整个构建流程中的缓存都由compiler.cache同一调度。

this.cache = new Cache()

后续构建过程中都会通过compiler.getCache方法获得相应的缓存对象,cache类型本身不负责缓存读写,cache主要负责调度,compiler.cache.hooks的触发,读写的部分是有strategy来负责。