webpack 编译器

webpack 紧接入口文件创建编译器时,会创建大量的勾子,并对勾子对象的变量进行初始化。其中还有获取缓存的API,获取基础设施日志记录器,清理上一次编译的残留数据,清理上一个 NormalModuleFactory,释放其缓存,以监听模式运行编译,运行编译,运行子编译器,清除输入文件系统,编译生成的资源,记录编译过程,读取记录数据,创建子编译,判断是子编译,创建一个新的 Compilation 实例,生成常规模块的构建配置,解析和创建上下文模块,编译,关闭,方法组成。

js 复制代码
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const parseJson = require("json-parse-even-better-errors");
const asyncLib = require("neo-async");
const {
	SyncHook,
	SyncBailHook,
	AsyncParallelHook,
	AsyncSeriesHook
} = require("tapable");
const { SizeOnlySource } = require("webpack-sources");
const webpack = require(".");
const Cache = require("./Cache");
const CacheFacade = require("./CacheFacade");
const ChunkGraph = require("./ChunkGraph");
const Compilation = require("./Compilation");
const ConcurrentCompilationError = require("./ConcurrentCompilationError");
const ContextModuleFactory = require("./ContextModuleFactory");
const ModuleGraph = require("./ModuleGraph");
const NormalModuleFactory = require("./NormalModuleFactory");
const RequestShortener = require("./RequestShortener");
const ResolverFactory = require("./ResolverFactory");
const Stats = require("./Stats");
const Watching = require("./Watching");
const WebpackError = require("./WebpackError");
const { Logger } = require("./logging/Logger");
const { join, dirname, mkdirp } = require("./util/fs");
const { makePathsRelative } = require("./util/identifier");
const { isSourceEqual } = require("./util/source");

/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../declarations/WebpackOptions").EntryNormalized} Entry */
/** @typedef {import("../declarations/WebpackOptions").OutputNormalized} OutputOptions */
/** @typedef {import("../declarations/WebpackOptions").WatchOptions} WatchOptions */
/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
/** @typedef {import("../declarations/WebpackOptions").WebpackPluginInstance} WebpackPluginInstance */
/** @typedef {import("./Chunk")} Chunk */
/** @typedef {import("./Compilation").References} References */
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./FileSystemInfo").FileSystemInfoEntry} FileSystemInfoEntry */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./Module").BuildInfo} BuildInfo */
/** @typedef {import("./config/target").PlatformTargetProperties} PlatformTargetProperties */
/** @typedef {import("./logging/createConsoleLogger").LoggingFunction} LoggingFunction */
/** @typedef {import("./util/fs").IStats} IStats */
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
/** @typedef {import("./util/fs").IntermediateFileSystem} IntermediateFileSystem */
/** @typedef {import("./util/fs").OutputFileSystem} OutputFileSystem */
/** @typedef {import("./util/fs").WatchFileSystem} WatchFileSystem */

/**
 * @template {any[]} T
 * @template V
 * @typedef {import("./util/WeakTupleMap")<T, V>} WeakTupleMap
 */

/**
 * @typedef {object} CompilationParams
 * @property {NormalModuleFactory} normalModuleFactory
 * @property {ContextModuleFactory} contextModuleFactory
 */

/**
 * @template T
 * @callback RunCallback
 * @param {Error | null} err
 * @param {T=} result
 */

/**
 * @template T
 * @callback Callback
 * @param {(Error | null)=} err
 * @param {T=} result
 */

/**
 * @callback RunAsChildCallback
 * @param {Error | null} err
 * @param {Chunk[]=} entries
 * @param {Compilation=} compilation
 */

/**
 * @typedef {object} AssetEmittedInfo
 * @property {Buffer} content
 * @property {Source} source
 * @property {Compilation} compilation
 * @property {string} outputPath
 * @property {string} targetPath
 */

/** @typedef {{ sizeOnlySource: SizeOnlySource | undefined, writtenTo: Map<string, number> }} CacheEntry */
/** @typedef {{ path: string, source: Source, size: number | undefined, waiting: ({ cacheEntry: any, file: string }[] | undefined) }} SimilarEntry */

/** @typedef {{ buildInfo: BuildInfo, references: References | undefined, memCache: WeakTupleMap<any, any> }} ModuleMemCachesItem */

/**
 * @param {string[]} array an array
 * @returns {boolean} true, if the array is sorted
 */
const isSorted = array => {
	for (let i = 1; i < array.length; i++) {
		if (array[i - 1] > array[i]) return false;
	}
	return true;
};

/**
 * @param {{[key: string]: any}} obj an object
 * @param {string[]} keys the keys of the object
 * @returns {{[key: string]: any}} the object with properties sorted by property name
 */
const sortObject = (obj, keys) => {
	/** @type {{[key: string]: any}} */
	const o = {};
	for (const k of keys.sort()) {
		o[k] = obj[k];
	}
	return o;
};

/**
 * @param {string} filename filename
 * @param {string | string[] | undefined} hashes list of hashes
 * @returns {boolean} true, if the filename contains any hash
 */
const includesHash = (filename, hashes) => {
	if (!hashes) return false;
	if (Array.isArray(hashes)) {
		return hashes.some(hash => filename.includes(hash));
	}
	return filename.includes(hashes);
};

class Compiler {
	/**
	 * @param {string} context the compilation path
	 * @param {WebpackOptions} options options
	*/
    /**
     * Webpack 编译器(Compiler)构造函数
     * @param {string} context - Webpack 项目的根目录
     * @param {WebpackOptions} [options={}] - Webpack 配置选项
     */
    constructor(context, options = /** @type {WebpackOptions} */ ({})) {
            // 定义 Webpack 编译过程中使用的各个生命周期钩子
            this.hooks = Object.freeze({
                    /** Webpack 初始化时触发 */
                    initialize: new SyncHook([]),

                    /** 在 `emit` 钩子执行前触发,决定是否需要生成输出文件 */
                    shouldEmit: new SyncBailHook(["compilation"]),
                    /** Webpack 构建完成后触发(异步) */
                    done: new AsyncSeriesHook(["stats"]),
                    /** `done` 钩子后触发(同步) */
                    afterDone: new SyncHook(["stats"]),
                    /** 触发额外的构建过程(如 watch 模式下的额外编译) */
                    additionalPass: new AsyncSeriesHook([]),
                    /** 在 Webpack 开始执行编译之前触发 */
                    beforeRun: new AsyncSeriesHook(["compiler"]),
                    /** Webpack 开始执行编译时触发 */
                    run: new AsyncSeriesHook(["compiler"]),
                    /** 在资源生成阶段触发(如写入 `dist/` 目录前) */
                    emit: new AsyncSeriesHook(["compilation"]),
                    /** 当某个资源被写入磁盘后触发 */
                    assetEmitted: new AsyncSeriesHook(["file", "info"]),
                    /** 资源写入后触发 */
                    afterEmit: new AsyncSeriesHook(["compilation"]),

                    /** 创建新 `Compilation` 实例时触发 */
                    thisCompilation: new SyncHook(["compilation", "params"]),
                    /** 生成 `Compilation` 过程时触发 */
                    compilation: new SyncHook(["compilation", "params"]),
                    /** `NormalModuleFactory` 创建时触发 */
                    normalModuleFactory: new SyncHook(["normalModuleFactory"]),
                    /** `ContextModuleFactory` 创建时触发 */
                    contextModuleFactory: new SyncHook(["contextModuleFactory"]),

                    /** 编译开始前触发 */
                    beforeCompile: new AsyncSeriesHook(["params"]),
                    /** 编译开始时触发 */
                    compile: new SyncHook(["params"]),
                    /** `make` 阶段:构建模块的过程中触发(并行执行) */
                    make: new AsyncParallelHook(["compilation"]),
                    /** `make` 阶段完成后触发 */
                    finishMake: new AsyncSeriesHook(["compilation"]),
                    /** 编译完成后触发 */
                    afterCompile: new AsyncSeriesHook(["compilation"]),

                    /** 读取记录文件时触发 */
                    readRecords: new AsyncSeriesHook([]),
                    /** 写入记录文件时触发 */
                    emitRecords: new AsyncSeriesHook([]),

                    /** 监听模式(watch)启动前触发 */
                    watchRun: new AsyncSeriesHook(["compiler"]),
                    /** Webpack 编译失败时触发 */
                    failed: new SyncHook(["error"]),
                    /** 监听模式中文件发生变更时触发 */
                    invalid: new SyncHook(["filename", "changeTime"]),
                    /** 监听模式(watch)关闭时触发 */
                    watchClose: new SyncHook([]),
                    /** Webpack 关闭前触发 */
                    shutdown: new AsyncSeriesHook([]),

                    /** Webpack 基础设施日志(如插件日志记录) */
                    infrastructureLog: new SyncBailHook(["origin", "type", "args"]),

                    // TODO 这些钩子的位置不合理,Webpack 5 需要调整
                    /** Webpack 环境初始化时触发 */
                    environment: new SyncHook([]),
                    /** Webpack 环境初始化完成后触发 */
                    afterEnvironment: new SyncHook([]),
                    /** 插件加载完成后触发 */
                    afterPlugins: new SyncHook(["compiler"]),
                    /** 解析器(Resolvers)加载完成后触发 */
                    afterResolvers: new SyncHook(["compiler"]),
                    /** `entry` 入口选项解析时触发 */
                    entryOption: new SyncBailHook(["context", "entry"])
            });

            // Webpack 核心对象
            this.webpack = webpack;

            /** @type {string | undefined} 编译器名称 */
            this.name = undefined;
            /** @type {Compilation | undefined} 父级 Compilation */
            this.parentCompilation = undefined;
            /** @type {Compiler} 根 Compiler 实例 */
            this.root = this;
            /** @type {string} 输出路径 */
            this.outputPath = "";
            /** @type {Watching | undefined} Webpack 监听模式(watching)实例 */
            this.watching = undefined;

            /** @type {OutputFileSystem | null} 输出文件系统 */
            this.outputFileSystem = null;
            /** @type {IntermediateFileSystem | null} 中间文件系统 */
            this.intermediateFileSystem = null;
            /** @type {InputFileSystem | null} 输入文件系统 */
            this.inputFileSystem = null;
            /** @type {WatchFileSystem | null} 监听文件系统 */
            this.watchFileSystem = null;

            /** @type {string|null} 记录输入文件路径 */
            this.recordsInputPath = null;
            /** @type {string|null} 记录输出文件路径 */
            this.recordsOutputPath = null;
            /** @type {Record<string, TODO>} 记录信息 */
            this.records = {};
            /** @type {Set<string | RegExp>} 受 Webpack 监控的路径 */
            this.managedPaths = new Set();
            /** @type {Set<string | RegExp>} 不受 Webpack 监控的路径 */
            this.unmanagedPaths = new Set();
            /** @type {Set<string | RegExp>} 认为是不可变的路径 */
            this.immutablePaths = new Set();

            /** @type {ReadonlySet<string> | undefined} 发生修改的文件 */
            this.modifiedFiles = undefined;
            /** @type {ReadonlySet<string> | undefined} 被删除的文件 */
            this.removedFiles = undefined;
            /** @type {ReadonlyMap<string, FileSystemInfoEntry | "ignore" | null> | undefined} 文件时间戳 */
            this.fileTimestamps = undefined;
            /** @type {ReadonlyMap<string, FileSystemInfoEntry | "ignore" | null> | undefined} 目录时间戳 */
            this.contextTimestamps = undefined;
            /** @type {number | undefined} 文件系统扫描起始时间 */
            this.fsStartTime = undefined;

            /** @type {ResolverFactory} 解析器工厂 */
            this.resolverFactory = new ResolverFactory();

            /** @type {LoggingFunction | undefined} 基础设施日志 */
            this.infrastructureLogger = undefined;

            /** @type {Readonly<PlatformTargetProperties>} 目标平台属性 */
            this.platform = {
                    web: null,
                    browser: null,
                    webworker: null,
                    node: null,
                    nwjs: null,
                    electron: null
            };

            // Webpack 选项配置
            this.options = options;

            // Webpack 项目根目录
            this.context = context;

            // 请求路径缩短工具
            this.requestShortener = new RequestShortener(context, this.root);

            // 缓存管理器
            this.cache = new Cache();

            /** @type {Map<Module, ModuleMemCachesItem> | undefined} 模块缓存 */
            this.moduleMemCaches = undefined;

            /** @type {string} 编译器路径 */
            this.compilerPath = "";

            /** @type {boolean} Webpack 是否在运行中 */
            this.running = false;

            /** @type {boolean} Webpack 是否处于空闲状态 */
            this.idle = false;

            /** @type {boolean} 是否处于监听模式 */
            this.watchMode = false;

            // Webpack 兼容性选项
            this._backCompat = this.options.experiments.backCompat !== false;

            // 记录上一次编译
            this._lastCompilation = undefined;
            this._lastNormalModuleFactory = undefined;

            // 私有缓存
            this._assetEmittingSourceCache = new WeakMap();
            this._assetEmittingWrittenFiles = new Map();
            this._assetEmittingPreviousFiles = new Set();
    }
    /**
     * 获取缓存的封装实例
     * @param {string} name 缓存名称
     * @returns {CacheFacade} 返回 CacheFacade 实例
     */
    getCache(name) {
            // 新建缓存对象返回并传入参数
            return new CacheFacade(
                    this.cache,
                    `${this.compilerPath}${name}`,
                    this.options.output.hashFunction
            );
    }

    /**
     * 获取基础设施日志记录器
     * @param {string | (function(): string)} name 日志名称或用于生成日志名称的函数
     * @returns {Logger} 返回具有指定名称的日志记录器
     * @throws {TypeError} 如果没有提供名称或函数未返回有效名称,则抛出错误
     */
    getInfrastructureLogger(name) {
            if (!name) {
                    throw new TypeError(
                            "Compiler.getInfrastructureLogger(name) called without a name"
                    );
            }
            // 返回新建的日志对象
            return new Logger(
                    // 在日志对象中的第一个参数中传入函数
                    (type, args) => {
                            if (typeof name === "function") {
                                    name = name();
                                    if (!name) {
                                            throw new TypeError(
                                                    "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
                                            );
                                    }
                            }
                            // 触发 `infrastructureLog` 钩子,如果返回 `undefined`,则使用 `infrastructureLogger`
                            if (
                                    this.hooks.infrastructureLog.call(name, type, args) === undefined &&
                                    this.infrastructureLogger !== undefined
                            ) {
                                    this.infrastructureLogger(name, type, args);
                            }
                    },
                    // 传入函数并将childName 做为参数传入函数内部
                    childName => {
                            // 递归生成子日志记录器,确保子名称有效
                            if (typeof name === "function") {
                                    if (typeof childName === "function") {
                                            return this.getInfrastructureLogger(() => {
                                                    if (typeof name === "function") {
                                                            name = name();
                                                            if (!name) {
                                                                    throw new TypeError(
                                                                            "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
                                                                    );
                                                            }
                                                    }
                                                    if (typeof childName === "function") {
                                                            childName = childName();
                                                            if (!childName) {
                                                                    throw new TypeError(
                                                                            "Logger.getChildLogger(name) called with a function not returning a name"
                                                                    );
                                                            }
                                                    }
                                                    return `${name}/${childName}`;
                                            });
                                    }
                                    // 创建一个子日志记录器(子 logger),用于继承当前 logger 的名称,并在其基础上添加一个子名称 (childName)。
                                    return this.getInfrastructureLogger(() => {
                                            if (typeof name === "function") {
                                                    name = name();
                                                    if (!name) {
                                                            throw new TypeError(
                                                                    "Compiler.getInfrastructureLogger(name) called with a function not returning a name"
                                                            );
                                                    }
                                            }
                                            return `${name}/${childName}`;
                                    });
                            }
                            if (typeof childName === "function") {
                                    // 创建一个子日志记录器(子 logger),用于继承当前 logger 的名称,并在其基础上添加一个子名称 (childName)。
                                    return this.getInfrastructureLogger(() => {
                                            if (typeof childName === "function") {
                                                    childName = childName();
                                                    if (!childName) {
                                                            throw new TypeError(
                                                                    "Logger.getChildLogger(name) called with a function not returning a name"
                                                            );
                                                    }
                                            }
                                            return `${name}/${childName}`;
                                    });
                            }
                            return this.getInfrastructureLogger(`${name}/${childName}`);
                    }
            );
    }

    // TODO webpack 6: 以更好的方式解决这个问题
    // 例如,将特定于 `Compilation` 的信息从 `Modules` 移动到 `ModuleGraph`
    /**
     * 清理上一次编译的残留数据
     * 释放 `ChunkGraph` 和 `ModuleGraph` 相关的数据,以避免缓存不必要的数据
     * 释放 `module` 和 `chunk` 的相关信息
     */
    _cleanupLastCompilation() {
            if (this._lastCompilation !== undefined) {
                    for (const childCompilation of this._lastCompilation.children) {
                            for (const module of childCompilation.modules) {
                                    ChunkGraph.clearChunkGraphForModule(module);
                                    ModuleGraph.clearModuleGraphForModule(module);
                                    module.cleanupForCache();
                            }
                            for (const chunk of childCompilation.chunks) {
                                    ChunkGraph.clearChunkGraphForChunk(chunk);
                            }
                    }

                    for (const module of this._lastCompilation.modules) {
                            ChunkGraph.clearChunkGraphForModule(module);
                            ModuleGraph.clearModuleGraphForModule(module);
                            module.cleanupForCache();
                    }
                    for (const chunk of this._lastCompilation.chunks) {
                            ChunkGraph.clearChunkGraphForChunk(chunk);
                    }
                    this._lastCompilation = undefined;
            }
    }

    // TODO webpack 6: 以更好的方式解决这个问题
    /**
     * 清理上一个 `NormalModuleFactory`,释放其缓存
     */
    _cleanupLastNormalModuleFactory() {
            if (this._lastNormalModuleFactory !== undefined) {
                    this._lastNormalModuleFactory.cleanupForCache();
                    this._lastNormalModuleFactory = undefined;
            }
    }

    /**
     * 以监听模式运行编译
     * @param {WatchOptions} watchOptions 监听选项
     * @param {RunCallback<Stats>} handler 监听模式完成时的回调函数
     * @returns {Watching} 返回 `Watching` 实例
     */
    watch(watchOptions, handler) {
            if (this.running) {
                    return handler(new ConcurrentCompilationError());
            }

            this.running = true;
            this.watchMode = true;
            this.watching = new Watching(this, watchOptions, handler);
            return this.watching;
    }

    /**
     * 运行编译
     * @param {RunCallback<Stats>} callback 运行完成时的回调
     */
    run(callback) {
            if (this.running) {
                    return callback(new ConcurrentCompilationError());
            }
            /** @type {Logger | undefined} */
            let logger;

            /**
             * 处理最终回调
             * @param {Error | null} err 错误信息
             * @param {Stats=} stats 编译统计信息
             */
            const finalCallback = (err, stats) => {
                    // 如果有 logger,记录时间标签 "beginIdle"
                    if (logger) logger.time("beginIdle");

                    // 标记编译处于空闲状态
                    this.idle = true;
                    this.cache.beginIdle();  // 通知缓存开始空闲状态

                    // 如果有 logger,记录 "beginIdle" 结束时间
                    if (logger) logger.timeEnd("beginIdle");

                    // 标记编译状态为不在运行
                    this.running = false;

                    // 如果有错误,触发 failed 钩子
                    if (err) {
                            this.hooks.failed.call(err);
                    }

                    // 如果存在最终回调,调用它,并传递错误和编译统计信息
                    if (callback !== undefined) callback(err, stats);

                    // 触发 afterDone 钩子,表示编译过程结束
                    this.hooks.afterDone.call(/** @type {Stats} */(stats));
            };

            // 获取当前时间,表示编译开始时间
            const startTime = Date.now();

            // 标记编译正在进行中
            this.running = true;

            /**
             * 处理编译完成事件
             * @param {Error | null} err 错误信息
             * @param {Compilation=} _compilation 编译实例
             */
            const onCompiled = (err, _compilation) => {
                    // 如果发生错误,调用最终回调
                    if (err) return finalCallback(err);

                    // 获取编译实例
                    const compilation = /** @type {Compilation} */ (_compilation);

                    // 如果钩子 shouldEmit 返回 false,跳过发出资源
                    if (this.hooks.shouldEmit.call(compilation) === false) {
                            compilation.startTime = startTime;
                            compilation.endTime = Date.now();

                            // 创建编译统计信息
                            const stats = new Stats(compilation);

                            // 异步调用 done 钩子
                            this.hooks.done.callAsync(stats, err => {
                                    // 如果发生错误,调用最终回调
                                    if (err) return finalCallback(err);

                                    // 编译完成,调用最终回调
                                    return finalCallback(null, stats);
                            });
                            return;
                    }

                    // 异步执行接下来的操作
                    process.nextTick(() => {
                            // 获取 logger 实例
                            logger = compilation.getLogger("webpack.Compiler");

                            // 记录时间标签 "emitAssets"
                            logger.time("emitAssets");

                            // 发出资源
                            this.emitAssets(compilation, err => {
                                    // 记录 "emitAssets" 操作结束时间
                                    logger.timeEnd("emitAssets");

                                    // 如果发生错误,调用最终回调
                                    if (err) return finalCallback(err);

                                    // 设置编译的开始和结束时间
                                    compilation.startTime = startTime;
                                    compilation.endTime = Date.now();

                                    // 记录时间标签 "done hook"
                                    logger.time("done hook");

                                    // 创建编译统计信息
                                    const stats = new Stats(compilation);

                                    // 异步调用 done 钩子
                                    this.hooks.done.callAsync(stats, err => {
                                            // 记录 "done hook" 操作结束时间
                                            logger.timeEnd("done hook");

                                            // 如果发生错误,调用最终回调
                                            if (err) return finalCallback(err);

                                            // 存储构建依赖关系
                                            this.cache.storeBuildDependencies(
                                                    compilation.buildDependencies,
                                                    err => {
                                                            // 如果发生错误,调用最终回调
                                                            if (err) return finalCallback(err);

                                                            // 编译完成,调用最终回调
                                                            return finalCallback(null, stats);
                                                    }
                                            );
                                    });
                            });
                    });
            };

            // 定义执行编译的逻辑
            const run = () => {
                    // 执行 beforeRun 钩子
                    this.hooks.beforeRun.callAsync(this, err => {
                            // 如果发生错误,调用最终回调
                            if (err) return finalCallback(err);

                            // 执行 run 钩子
                            this.hooks.run.callAsync(this, err => {
                                    // 如果发生错误,调用最终回调
                                    if (err) return finalCallback(err);

                                    // 读取记录并开始编译
                                    this.readRecords(err => {
                                            // 如果发生错误,调用最终回调
                                            if (err) return finalCallback(err);

                                            // 开始编译
                                            this.compile(onCompiled);
                                    });
                            });
                    });
            };

            // 如果处于空闲状态,结束空闲状态并开始编译
            if (this.idle) {
                    this.cache.endIdle(err => {
                            // 如果发生错误,调用最终回调
                            if (err) return finalCallback(err);

                            // 标记不再空闲
                            this.idle = false;

                            // 执行编译
                            run();
                    });
            } else {
                    // 如果不在空闲状态,直接开始编译
                    run();
            }

    }
    
    /**
     * 将一个子编译器(child compiler)作为当前编译器的子进程运行
     * @param {RunAsChildCallback} callback 完成时回调
     * @returns {void}
     */
    runAsChild(callback) {
            const startTime = Date.now();

            /**
             * @param {Error | null} err 错误
             * @param {Chunk[]=} entries 入口
             * @param {Compilation=} compilation 编译
             */
            const finalCallback = (err, entries, compilation) => {
                    try {
                            callback(err, entries, compilation);
                    } catch (runAsChildErr) {
                            const err = new WebpackError(
                                    `compiler.runAsChild 回调错误: ${runAsChildErr}`
                            );
                            err.details = /** @type {Error} */ (runAsChildErr).stack;
                            /** @type {Compilation} */
                            (this.parentCompilation).errors.push(err);
                    }
            };

            this.compile((err, _compilation) => {
                    if (err) return finalCallback(err);

                    const compilation = /** @type {Compilation} */ (_compilation);
                    const parentCompilation = /** @type {Compilation} */ (
                            this.parentCompilation
                    );

                    parentCompilation.children.push(compilation);

                    for (const { name, source, info } of compilation.getAssets()) {
                            parentCompilation.emitAsset(name, source, info);
                    }

                    /** @type {Chunk[]} */
                    const entries = [];

                    for (const ep of compilation.entrypoints.values()) {
                            entries.push(...ep.chunks);
                    }

                    compilation.startTime = startTime;
                    compilation.endTime = Date.now();

                    return finalCallback(null, entries, compilation);
            });
    }

    /**
     * 清除输入文件系统
     * @returns {void}
     */
    purgeInputFileSystem() {
            if (this.inputFileSystem && this.inputFileSystem.purge) {
                    this.inputFileSystem.purge();
            }
    }

    /**
     * 编译生成的资源
     * @param {Compilation} compilation 编译对象
     * @param {Callback<void>} callback 完成时回调
     * @returns {void}
     */
    emitAssets(compilation, callback) {
            /** @type {string} */
            let outputPath;

            /**
             * @param {Error=} err 错误
             * @returns {void}
             */
            const emitFiles = err => {
                    if (err) return callback(err);

                    const assets = compilation.getAssets();
                    compilation.assets = { ...compilation.assets };
                    /** @type {Map<string, SimilarEntry>} */
                    const caseInsensitiveMap = new Map();
                    /** @type {Set<string>} */
                    const allTargetPaths = new Set();
                    asyncLib.forEachLimit(
                            assets,
                            15,
                            ({ name: file, source, info }, callback) => {
                                    let targetFile = file;
                                    let immutable = info.immutable;
                                    const queryStringIdx = targetFile.indexOf("?");
                                    if (queryStringIdx >= 0) {
                                            targetFile = targetFile.slice(0, queryStringIdx);
                                            // 可能会去除查询字符串中的哈希
                                            // 所以重新检查文件是否不可变
                                            immutable =
                                                    immutable &&
                                                    (includesHash(targetFile, info.contenthash) ||
                                                            includesHash(targetFile, info.chunkhash) ||
                                                            includesHash(targetFile, info.modulehash) ||
                                                            includesHash(targetFile, info.fullhash));
                                    }

                                    /**
                                     * @param {Error=} err 错误
                                     * @returns {void}
                                     */
                                    const writeOut = err => {
                                            if (err) return callback(err);
                                            const targetPath = join(
                                                    /** @type {OutputFileSystem} */
                                                    (this.outputFileSystem),
                                                    outputPath,
                                                    targetFile
                                            );
                                            allTargetPaths.add(targetPath);

                                            // 检查目标文件是否已经由此编译器写入
                                            const targetFileGeneration =
                                                    this._assetEmittingWrittenFiles.get(targetPath);

                                            // 如果没有现有缓存条目,则创建缓存
                                            let cacheEntry = this._assetEmittingSourceCache.get(source);
                                            if (cacheEntry === undefined) {
                                                    cacheEntry = {
                                                            sizeOnlySource: undefined,
                                                            writtenTo: new Map()
                                                    };
                                                    this._assetEmittingSourceCache.set(source, cacheEntry);
                                            }

                                            /** @type {SimilarEntry | undefined} */
                                            let similarEntry;

                                            const checkSimilarFile = () => {
                                                    const caseInsensitiveTargetPath = targetPath.toLowerCase();
                                                    similarEntry = caseInsensitiveMap.get(caseInsensitiveTargetPath);
                                                    if (similarEntry !== undefined) {
                                                            const { path: other, source: otherSource } = similarEntry;
                                                            if (isSourceEqual(otherSource, source)) {
                                                                    // 如果已经写入的文件相同,检查大小
                                                                    if (similarEntry.size !== undefined) {
                                                                            updateWithReplacementSource(similarEntry.size);
                                                                    } else {
                                                                            if (!similarEntry.waiting) similarEntry.waiting = [];
                                                                            similarEntry.waiting.push({ file, cacheEntry });
                                                                    }
                                                                    alreadyWritten();
                                                            } else {
                                                                    const err =
                                                                            new WebpackError(`防止写入与已写入文件仅在大小写或查询字符串上不同的文件。
    这会导致在不区分大小写的文件系统上出现竞争条件和文件损坏。
    ${targetPath}
    ${other}`);
                                                                    err.file = file;
                                                                    callback(err);
                                                            }
                                                            return true;
                                                    }
                                                    caseInsensitiveMap.set(
                                                            caseInsensitiveTargetPath,
                                                            (similarEntry = /** @type {SimilarEntry} */ ({
                                                                    path: targetPath,
                                                                    source,
                                                                    size: undefined,
                                                                    waiting: undefined
                                                            }))
                                                    );
                                                    return false;
                                            };

                                            /**
                                             * 获取源内容的二进制(Buffer)数据
                                             * @returns {Buffer} 源内容
                                             */
                                            const getContent = () => {
                                                    if (typeof source.buffer === "function") {
                                                            return source.buffer();
                                                    }
                                                    const bufferOrString = source.source();
                                                    if (Buffer.isBuffer(bufferOrString)) {
                                                            return bufferOrString;
                                                    }
                                                    return Buffer.from(bufferOrString, "utf8");
                                            };

                                            const alreadyWritten = () => {
                                                    // 将信息缓存到已写入该位置的Source
                                                    if (targetFileGeneration === undefined) {
                                                            const newGeneration = 1;
                                                            this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
                                                            /** @type {CacheEntry} */
                                                            (cacheEntry).writtenTo.set(targetPath, newGeneration);
                                                    } else {
                                                            /** @type {CacheEntry} */
                                                            (cacheEntry).writtenTo.set(targetPath, targetFileGeneration);
                                                    }
                                                    callback();
                                            };

                                            /**
                                             * 将文件写入输出文件系统
                                             * @param {Buffer} content 内容
                                             * @returns {void}
                                             */
                                            const doWrite = content => {
                                                    /** @type {OutputFileSystem} */
                                                    (this.outputFileSystem).writeFile(targetPath, content, err => {
                                                            if (err) return callback(err);

                                                            // 标记资产已被发射
                                                            compilation.emittedAssets.add(file);

                                                            // 将源文件写入位置的缓存信息标记为已写入
                                                            const newGeneration =
                                                                    targetFileGeneration === undefined
                                                                            ? 1
                                                                            : targetFileGeneration + 1;
                                                            /** @type {CacheEntry} */
                                                            (cacheEntry).writtenTo.set(targetPath, newGeneration);
                                                            this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
                                                            this.hooks.assetEmitted.callAsync(
                                                                    file,
                                                                    {
                                                                            content,
                                                                            source,
                                                                            outputPath,
                                                                            compilation,
                                                                            targetPath
                                                                    },
                                                                    callback
                                                            );
                                                    });
                                            };

                                            /**
                                             * @param {number} size 文件大小
                                             */
                                            const updateWithReplacementSource = size => {
                                                    updateFileWithReplacementSource(
                                                            file,
                                                            /** @type {CacheEntry} */ (cacheEntry),
                                                            size
                                                    );
                                                    /** @type {SimilarEntry} */
                                                    (similarEntry).size = size;
                                                    if (
                                                            /** @type {SimilarEntry} */ (similarEntry).waiting !== undefined
                                                    ) {
                                                            for (const { file, cacheEntry } of /** @type {SimilarEntry} */ (
                                                                    similarEntry
                                                            ).waiting) {
                                                                    updateFileWithReplacementSource(file, cacheEntry, size);
                                                            }
                                                    }
                                            };

                                            /**
                                             * @param {string} file 文件
                                             * @param {CacheEntry} cacheEntry 缓存条目
                                             * @param {number} size 大小
                                             */
                                            const updateFileWithReplacementSource = (
                                                    file,
                                                    cacheEntry,
                                                    size
                                            ) => {
                                                    // 创建一个只允许请求大小的替换资源
                                                    // 这样可以清理掉源分配的所有内存
                                                    if (!cacheEntry.sizeOnlySource) {
                                                            cacheEntry.sizeOnlySource = new SizeOnlySource(size);
                                                    }
                                                    compilation.updateAsset(file, cacheEntry.sizeOnlySource, {
                                                            size
                                                    });
                                            };

                                            /**
                                             * @param {IStats} stats 文件状态
                                             * @returns {void}
                                             */
                                            const processExistingFile = stats => {
                                                    // 如果是不可变文件,跳过发射
                                                    if (immutable) {
                                                            updateWithReplacementSource(/** @type {number} */ (stats.size));
                                                            return alreadyWritten();
                                                    }

                                                    const content = getContent();

                                                    updateWithReplacementSource(content.length);

                                                    // 如果文件已存在且内容匹配,跳过写入
                                                    // 如果文件大小一致,先做快速比较
                                                    if (content.length === stats.size) {
                                                            compilation.comparedForEmitAssets.add(file);
                                                            return /** @type {OutputFileSystem} */ (
                                                                    this.outputFileSystem
                                                            ).readFile(targetPath, (err, existingContent) => {
                                                                    if (
                                                                            err ||
                                                                            !content.equals(/** @type {Buffer} */ (existingContent))
                                                                    ) {
                                                                            return doWrite(content);
                                                                    }
                                                                    return alreadyWritten();
                                                            });
                                                    }

                                                    return doWrite(content);
                                            };

                                            const processMissingFile = () => {
                                                    const content = getContent();

                                                    updateWithReplacementSource(content.length);

                                                    return doWrite(content);
                                            };

                                            // 如果目标文件已经写入
                                            if (targetFileGeneration !== undefined) {
                                                    // 检查Source是否已写入此目标文件
                                                    const writtenGeneration = /** @type {CacheEntry} */ (
                                                            cacheEntry
                                                    ).writtenTo.get(targetPath);
                                                    if (writtenGeneration === targetFileGeneration) {
                                                            // 如果是,跳过写入该文件
                                                            // 假设编译期间文件没有被修改
                                                            // 其他情况下可能会删除它们
                                                            if (this._assetEmittingPreviousFiles.has(targetPath)) {
                                                                    const sizeOnlySource = /** @type {SizeOnlySource} */ (
                                                                            /** @type {CacheEntry} */ (cacheEntry).sizeOnlySource
                                                                    );

                                                                    // 假设上次编译的资产保持不变
                                                                    compilation.updateAsset(file, sizeOnlySource, {
                                                                            size: sizeOnlySource.size()
                                                                    });

                                                                    return callback();
                                                            }
                                                            // 设置不可变文件会跳过比较
                                                            immutable = true;
                                                    } else if (!immutable) {
                                                            if (checkSimilarFile()) return;
                                                            // 假设内容已经改变
                                                            return processMissingFile();
                                                    }
                                            }

                                            if (checkSimilarFile()) return;
                                            if (this.options.output.compareBeforeEmit) {
                                                    /** @type {OutputFileSystem} */
                                                    (this.outputFileSystem).stat(targetPath, (err, stats) => {
                                                            const exists = !err && /** @type {IStats} */ (stats).isFile();

                                                            if (exists) {
                                                                    processExistingFile(/** @type {IStats} */ (stats));
                                                            } else {
                                                                    processMissingFile();
                                                            }
                                                    });
                                            } else {
                                                    processMissingFile();
                                            }
                                    };

                                    if (/\/|\\/.test(targetFile)) {
                                            const fs = /** @type {OutputFileSystem} */ (this.outputFileSystem);
                                            const dir = dirname(fs, join(fs, outputPath, targetFile));
                                            mkdirp(fs, dir, writeOut);
                                    } else {
                                            writeOut();
                                    }
                            },
                            err => {
                                    // 清空map释放内存
                                    caseInsensitiveMap.clear();
                                    if (err) {
                                            this._assetEmittingPreviousFiles.clear();
                                            return callback(err);
                                    }

                                    this._assetEmittingPreviousFiles = allTargetPaths;

                                    this.hooks.afterEmit.callAsync(compilation, err => {
                                            if (err) return callback(err);

                                            return callback();
                                    });
                            }
                    );
            };

            this.hooks.emit.callAsync(compilation, err => {
                    if (err) return callback(err);
                    outputPath = compilation.getPath(this.outputPath, {});
                    mkdirp(
                            /** @type {OutputFileSystem} */(this.outputFileSystem),
                            outputPath,
                            emitFiles
                    );
            });
    }

    /**
     * 记录编译过程
     * @param {Callback<void>} callback 完成时回调
     * @returns {void}
     */
    emitRecords(callback) {
            if (this.hooks.emitRecords.isUsed()) {
                    if (this.recordsOutputPath) {
                            asyncLib.parallel(
                                    [
                                            cb => this.hooks.emitRecords.callAsync(cb),
                                            this._emitRecords.bind(this)
                                    ],
                                    err => callback(err)
                            );
                    } else {
                            this.hooks.emitRecords.callAsync(callback);
                    }
            } else if (this.recordsOutputPath) {
                    this._emitRecords(callback);
            } else {
                    callback();
            }
    }

    /**
     *_emitRecords 函数的主要作用是将 Webpack 编译过程中的记录(通常是关于编译的元数据,如模块哈希值、模块状态等)写入到指定的文件路径。这个过程是 emitRecords 调用的一部分,实际上执行保存记录到文件的操作。
     * @param {Callback<void>} callback 完成时回调
     * @returns {void}
     */
    _emitRecords(callback) {
            const writeFile = () => {
                    /** @type {OutputFileSystem} */
                    (this.outputFileSystem).writeFile(
                            /** @type {string} */(this.recordsOutputPath),
                            JSON.stringify(
                                    this.records,
                                    (n, value) => {
                                            if (
                                                    typeof value === "object" &&
                                                    value !== null &&
                                                    !Array.isArray(value)
                                            ) {
                                                    const keys = Object.keys(value);
                                                    if (!isSorted(keys)) {
                                                            return sortObject(value, keys);
                                                    }
                                            }
                                            return value;
                                    },
                                    2
                            ),
                            callback
                    );
            };

            const recordsOutputPathDirectory = dirname(
                    /** @type {OutputFileSystem} */(this.outputFileSystem),
                    /** @type {string} */(this.recordsOutputPath)
            );
            if (!recordsOutputPathDirectory) {
                    return writeFile();
            }
            mkdirp(
                    /** @type {OutputFileSystem} */(this.outputFileSystem),
                    recordsOutputPathDirectory,
                    err => {
                            if (err) return callback(err);
                            writeFile();
                    }
            );
    }

    /**
     * 读取记录数据
     * @param {Callback<void>} callback 表示调用完成时的回调函数
     * @returns {void}
     */
    readRecords(callback) {
            if (this.hooks.readRecords.isUsed()) {
                    if (this.recordsInputPath) {
                            asyncLib.parallel(
                                    [
                                            cb => this.hooks.readRecords.callAsync(cb),
                                            this._readRecords.bind(this)
                                    ],
                                    err => callback(err)
                            );
                    } else {
                            this.records = {};
                            this.hooks.readRecords.callAsync(callback);
                    }
            } else if (this.recordsInputPath) {
                    this._readRecords(callback);
            } else {
                    this.records = {};
                    callback();
            }
    }

    /**
     * 从指定的输入路径 (recordsInputPath) 中读取记录数据
     * @param {Callback<void>} callback 表示调用完成时的回调函数
     * @returns {void}
     */
    _readRecords(callback) {
            if (!this.recordsInputPath) {
                    this.records = {};
                    return callback();
            }
            /** @type {InputFileSystem} */
            (this.inputFileSystem).stat(this.recordsInputPath, err => {
                    // 文件不存在,忽略处理
                    if (err) return callback();

                    /** @type {InputFileSystem} */
                    (this.inputFileSystem).readFile(
                            /** @type {string} */(this.recordsInputPath),
                            (err, content) => {
                                    if (err) return callback(err);

                                    try {
                                            this.records = parseJson(
                                                    /** @type {Buffer} */(content).toString("utf-8")
                                            );
                                    } catch (parseErr) {
                                            return callback(
                                                    new Error(
                                                            `无法解析记录: ${/** @type {Error} */ (parseErr).message}`
                                                    )
                                            );
                                    }

                                    return callback();
                            }
                    );
            });
    }

    /**
     * 创建子编译
     * @param {Compilation} compilation 编译对象
     * @param {string} compilerName 编译器的名称
     * @param {number} compilerIndex 编译器的索引
     * @param {Partial<OutputOptions>=} outputOptions 输出选项
     * @param {WebpackPluginInstance[]=} plugins 要应用的插件
     * @returns {Compiler} 子编译器
     */
    createChildCompiler(
            compilation,
            compilerName,
            compilerIndex,
            outputOptions,
            plugins
    ) {
            const childCompiler = new Compiler(this.context, {
                    ...this.options,
                    output: {
                            ...this.options.output,
                            ...outputOptions
                    }
            });
            childCompiler.name = compilerName;
            childCompiler.outputPath = this.outputPath;
            childCompiler.inputFileSystem = this.inputFileSystem;
            childCompiler.outputFileSystem = null;
            childCompiler.resolverFactory = this.resolverFactory;
            childCompiler.modifiedFiles = this.modifiedFiles;
            childCompiler.removedFiles = this.removedFiles;
            childCompiler.fileTimestamps = this.fileTimestamps;
            childCompiler.contextTimestamps = this.contextTimestamps;
            childCompiler.fsStartTime = this.fsStartTime;
            childCompiler.cache = this.cache;
            childCompiler.compilerPath = `${this.compilerPath}${compilerName}|${compilerIndex}|`;
            childCompiler._backCompat = this._backCompat;

            const relativeCompilerName = makePathsRelative(
                    this.context,
                    compilerName,
                    this.root
            );
            if (!this.records[relativeCompilerName]) {
                    this.records[relativeCompilerName] = [];
            }
            if (this.records[relativeCompilerName][compilerIndex]) {
                    childCompiler.records = this.records[relativeCompilerName][compilerIndex];
            } else {
                    this.records[relativeCompilerName].push((childCompiler.records = {}));
            }

            childCompiler.parentCompilation = compilation;
            childCompiler.root = this.root;
            if (Array.isArray(plugins)) {
                    for (const plugin of plugins) {
                            if (plugin) {
                                    plugin.apply(childCompiler);
                            }
                    }
            }
            for (const name in this.hooks) {
                    if (
                            ![
                                    "make",
                                    "compile",
                                    "emit",
                                    "afterEmit",
                                    "invalid",
                                    "done",
                                    "thisCompilation"
                            ].includes(name) &&
                            childCompiler.hooks[/** @type {keyof Compiler["hooks"]} */ (name)]
                    ) {
                            childCompiler.hooks[/** @type {keyof Compiler["hooks"]} */
                            (name)
                            ].taps =
                                    this.hooks[/** @type {keyof Compiler["hooks"]} */
                                    (name)
                                    ].taps.slice();
                    }
            }

            compilation.hooks.childCompiler.call(
                    childCompiler,
                    compilerName,
                    compilerIndex
            );

            return childCompiler;
    }
    // 判断是子编译
    isChild() {
            return Boolean(this.parentCompilation);
    }

    /**
     * 创建一个新的 Compilation 实例
     * @param {CompilationParams} params 编译参数
     * @returns {Compilation} 编译对象
     */
    createCompilation(params) {
            this._cleanupLastCompilation();
            return (this._lastCompilation = new Compilation(this, params));
    }

    /**
     * 创建一个新的 Compilation 实例
     * @param {CompilationParams} params 编译参数
     * @returns {Compilation} 创建的编译对象
     */
    newCompilation(params) {
            const compilation = this.createCompilation(params);
            compilation.name = this.name;
            compilation.records = this.records;
            this.hooks.thisCompilation.call(compilation, params);
            this.hooks.compilation.call(compilation, params);
            return compilation;
    }
    // 生成常规模块的构建配置
    createNormalModuleFactory() {
            this._cleanupLastNormalModuleFactory();
            const normalModuleFactory = new NormalModuleFactory({
                    context: this.options.context,
                    fs: /** @type {InputFileSystem} */ (this.inputFileSystem),
                    resolverFactory: this.resolverFactory,
                    options: this.options.module,
                    associatedObjectForCache: this.root,
                    layers: this.options.experiments.layers
            });
            this._lastNormalModuleFactory = normalModuleFactory;
            this.hooks.normalModuleFactory.call(normalModuleFactory);
            return normalModuleFactory;
    }
    // 解析和创建上下文模块
    createContextModuleFactory() {
            const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);
            this.hooks.contextModuleFactory.call(contextModuleFactory);
            return contextModuleFactory;
    }

    newCompilationParams() {
            const params = {
                    normalModuleFactory: this.createNormalModuleFactory(),
                    contextModuleFactory: this.createContextModuleFactory()
            };
            return params;
    }

    /**
     * @param {RunCallback<Compilation>} callback 编译完成后的回调函数
     * @returns {void}
     */
    compile(callback) {
            const params = this.newCompilationParams();
            this.hooks.beforeCompile.callAsync(params, err => {
                    if (err) return callback(err);

                    this.hooks.compile.call(params);

                    const compilation = this.newCompilation(params);

                    const logger = compilation.getLogger("webpack.Compiler");

                    logger.time("make hook");
                    this.hooks.make.callAsync(compilation, err => {
                            logger.timeEnd("make hook");
                            if (err) return callback(err);

                            logger.time("finish make hook");
                            this.hooks.finishMake.callAsync(compilation, err => {
                                    logger.timeEnd("finish make hook");
                                    if (err) return callback(err);

                                    process.nextTick(() => {
                                            logger.time("finish compilation");
                                            compilation.finish(err => {
                                                    logger.timeEnd("finish compilation");
                                                    if (err) return callback(err);

                                                    logger.time("seal compilation");
                                                    compilation.seal(err => {
                                                            logger.timeEnd("seal compilation");
                                                            if (err) return callback(err);

                                                            logger.time("afterCompile hook");
                                                            this.hooks.afterCompile.callAsync(compilation, err => {
                                                                    logger.timeEnd("afterCompile hook");
                                                                    if (err) return callback(err);

                                                                    return callback(null, compilation);
                                                            });
                                                    });
                                            });
                                    });
                            });
                    });
            });
    }

    /**
     * @param {RunCallback<void>} callback 编译器关闭后的回调函数
     * @returns {void}
     */
    close(callback) {
            if (this.watching) {
                    // 如果有正在进行的 watching,先关闭它
                    this.watching.close(err => {
                            this.close(callback);
                    });
                    return;
            }
            this.hooks.shutdown.callAsync(err => {
                    if (err) return callback(err);
                    // 清理对最后编译的引用以避免内存泄漏
                    // 我们不能直接调用 this._cleanupLastCompilation,因为该编译的 Stats 可能仍在使用中。
                    // 我们尝试通过清理缓存的引用来解决这个问题。
                    this._lastCompilation = undefined;
                    this._lastNormalModuleFactory = undefined;
                    this.cache.shutdown(callback);
            });
    }
}

module.exports = Compiler;
相关推荐
姑苏洛言2 小时前
从0到1搭建315答题抽奖小程序:技术踩坑与运营真相
前端
午后书香3 小时前
一天三场面试,口干舌燥要晕倒(一)
前端·javascript·面试
鱼樱前端3 小时前
vue3+three.js入门小demo
前端·vue.js
whyfail3 小时前
React + TypeScript 实战指南:用类型守护你的组件
前端·react.js·typescript
阿金要当大魔王~~3 小时前
post get 给后端传参数
前端·javascript·html
慌张的葡萄3 小时前
24岁裸辞,今年26岁了😭做乞丐做保安做六边形战士
前端·面试
weixin_535854223 小时前
快手,蓝禾,优博讯,三七互娱,顺丰,oppo,游卡,汤臣倍健,康冠科技,作业帮,高途教育25届春招内推
java·前端·python·算法·硬件工程
IT、木易3 小时前
大白话css第九章主要聚焦于前沿技术整合、生态贡献与技术传承
前端·css
徐同保4 小时前
vue3后端管理项目,左侧菜单可以拖拽调整宽度
前端·javascript·vue.js