一、compiler - qianlongdoit/webpack GitHub Wiki

complier

主要规定了webpack编译任务、hooks、 compilation上的方法的执行顺序

compiler上的钩子函数的执行顺序在官网中罗列的很详细了,参考下面链接。

compiler钩子(中文)

下面是几个主要的方法:

  • run 调用compiler的时候启动
  • readRecords
  • compile
  • onCompiled 编译结束后执行内部定义的回调
  • emitAssets 在onCompiled最后一步执行
  • 将 Source对象里面的content取出来转为Buffer
    • 然后视情况是否进行 compilation.updateAsset
    • 最后生成对应的文件

run

内部定义了一个 onCompiled 的函数,传入 compile 作为参数

onCompiled 在执行到最后,触发 emmitAssets 进行输出文件

// lib/compiler.js
run(callback) {
    if (this.running) return callback(new ConcurrentCompilationError());

    const finalCallback = (err, stats) => {
        this.running = false;

        if (err) {
            this.hooks.failed.call(err);
        }

        if (callback !== undefined) return callback(err, stats);
    };

    const startTime = Date.now();

    this.running = true;
	//  定义了每次编译完成后的回调
    const onCompiled = (err, compilation) => {
        if (err) return finalCallback(err);

        if (this.hooks.shouldEmit.call(compilation) === false) {
            const stats = new Stats(compilation);
            stats.startTime = startTime;
            stats.endTime = Date.now();
            this.hooks.done.callAsync(stats, err => {
                if (err) return finalCallback(err);
                return finalCallback(null, stats);
            });
            return;
        }

        //	最后完成后输出文件
        this.emitAssets(compilation, err => {
            if (err) return finalCallback(err);

            if (compilation.hooks.needAdditionalPass.call()) {
                compilation.needAdditionalPass = true;

                const stats = new Stats(compilation);
                stats.startTime = startTime;
                stats.endTime = Date.now();
                this.hooks.done.callAsync(stats, err => {
                    if (err) return finalCallback(err);

                    this.hooks.additionalPass.callAsync(err => {
                        if (err) return finalCallback(err);
                        this.compile(onCompiled);
                    });
                });
                return;
            }

            this.emitRecords(err => {
                if (err) return finalCallback(err);

                const stats = new Stats(compilation);
                stats.startTime = startTime;
                stats.endTime = Date.now();
                this.hooks.done.callAsync(stats, err => {
                    if (err) return finalCallback(err);
                    return finalCallback(null, stats);
                });
            });
        });
    };

    this.hooks.beforeRun.callAsync(this, err => {
        if (err) return finalCallback(err);

        this.hooks.run.callAsync(this, err => {
            if (err) return finalCallback(err);

            this.readRecords(err => {
                if (err) return finalCallback(err);
				//  此时传递给 compile 作为参数
                this.compile(onCompiled);
            });
        });
    });
}

compile

定义了一系列的 compilation、 hooks 函数的执行顺序

//  lib/compiler.js
//   回调函数会在执行失败或者最终执行成功的时候调用
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);

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

            compilation.finish(err => {
                if (err) return callback(err);

                compilation.seal(err => {
                    if (err) return callback(err);

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

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

emmitAssets

输出Assets成为文件

// lib/compiler.js
emitAssets(compilation, callback) {
    let outputPath;
    const emitFiles = err => {
        if (err) return callback(err);
        
        //  这里引入了一个限制并发的异步库 asyncLib.forEachLimit
        //  这个函数的第三个参数是一个函数,函数接受的第一个参数来自第一个参数执行的返回值
        //  所以 file = assetName; source = compilation.assets[assetName];
        asyncLib.forEachLimit(
            compilation.getAssets(),
            15,
            ({ name: file, source }, callback) => {
                let targetFile = file;
                const queryStringIdx = targetFile.indexOf("?");
                if (queryStringIdx >= 0) {
                    targetFile = targetFile.substr(0, queryStringIdx);
                }

                const writeOut = err => {
                    if (err) return callback(err);
                    const targetPath = this.outputFileSystem.join(
                        outputPath,
                        targetFile
                    );
                    // TODO webpack 5 remove futureEmitAssets option and make it on by default
                    if (this.options.output.futureEmitAssets) {
                        // check if the target file has already been written by this Compiler
                        const targetFileGeneration = this._assetEmittingWrittenFiles.get(
                            targetPath
                        );

                        // create an cache entry for this Source if not already existing
                        let cacheEntry = this._assetEmittingSourceCache.get(source);
                        if (cacheEntry === undefined) {
                            cacheEntry = {
                                sizeOnlySource: undefined,
                                writtenTo: new Map()
                            };
                            this._assetEmittingSourceCache.set(source, cacheEntry);
                        }

                        // if the target file has already been written
                        if (targetFileGeneration !== undefined) {
                            // check if the Source has been written to this target file
                            const writtenGeneration = cacheEntry.writtenTo.get(targetPath);
                            if (writtenGeneration === targetFileGeneration) {
                                // if yes, we skip writing the file
                                // as it's already there
                                // (we assume one doesn't remove files while the Compiler is running)

                                compilation.updateAsset(file, cacheEntry.sizeOnlySource, {
                                    size: cacheEntry.sizeOnlySource.size()
                                });

                                return callback();
                            }
                        }

                        // TODO webpack 5: if info.immutable check if file already exists in output
                        // skip emitting if it's already there

                        // get the binary (Buffer) content from the Source
                        /** @type {Buffer} */
                        let content;
                        if (typeof source.buffer === "function") {
                            content = source.buffer();
                        } else {
                            const bufferOrString = source.source();
                            if (Buffer.isBuffer(bufferOrString)) {
                                content = bufferOrString;
                            } else {
                                content = Buffer.from(bufferOrString, "utf8");
                            }
                        }

                        // Create a replacement resource which only allows to ask for size
                        // This allows to GC all memory allocated by the Source
                        // (expect when the Source is stored in any other cache)
                        cacheEntry.sizeOnlySource = new SizeOnlySource(content.length);
                        compilation.updateAsset(file, cacheEntry.sizeOnlySource, {
                            size: content.length
                        });

                        // Write the file to output file system
                        this.outputFileSystem.writeFile(targetPath, content, err => {
                            if (err) return callback(err);

                            // information marker that the asset has been emitted
                            compilation.emittedAssets.add(file);

                            // cache the information that the Source has been written to that location
                            const newGeneration =
                                targetFileGeneration === undefined
                                    ? 1
                                    : targetFileGeneration + 1;
                            cacheEntry.writtenTo.set(targetPath, newGeneration);
                            this._assetEmittingWrittenFiles.set(targetPath, newGeneration);
                            this.hooks.assetEmitted.callAsync(file, content, callback);
                        });
                    } else {
                        if (source.existsAt === targetPath) {
                            source.emitted = false;
                            return callback();
                        }
                        let content = source.source();

                        if (!Buffer.isBuffer(content)) {
                            content = Buffer.from(content, "utf8");
                        }

                        source.existsAt = targetPath;
                        source.emitted = true;
                        this.outputFileSystem.writeFile(targetPath, content, err => {
                            if (err) return callback(err);
                            this.hooks.assetEmitted.callAsync(file, content, callback);
                        });
                    }
                };

                if (targetFile.match(/\/|\\/)) {
                    const dir = path.dirname(targetFile);
                    this.outputFileSystem.mkdirp(
                        this.outputFileSystem.join(outputPath, dir),
                        writeOut
                    );
                } else {
                    writeOut();
                }
            },
            err => {
                if (err) return callback(err);

                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);
        this.outputFileSystem.mkdirp(outputPath, emitFiles);
    });
}
⚠️ **GitHub.com Fallback** ⚠️