webpack工作流程


webpack工作流程

webpack构建的核心是完成内容的转换和资源的合并。

分初始化阶段、构建阶段、生成阶段来讲解webpack工作流程。

初始化阶段

  1. 初始化参数

将命令行参数和用户配置文件进行合并,使用schema-utils库的getValidateSchema方法进行参数校验

  1. 创建编译对象、实例化配置信息

webpack支持多个配置对象,比如一个library有多个构建目标,就需要传入多个配置对象,每个配置对象都要执行。
compiler对象包含了webpack环境所有的配置信息,比如options、loaders、plugins

createCompiler

源码123行

if (Array.isArray(options)) {
    compiler = createMultiCompiler{options}
    ...
}else {
    compiler = createCompiler(options)
    ...
}

创建compiler

const createCompiler = rawOptions => {
    const options = getNormalizedWebpackOptions(rawOptions);
    applyWebpackOptionsBaseDefaults(options);
    const compiler = new Compiler(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") {
                            plugin.call(compiler, compiler);
                    } else {
                            plugin.apply(compiler);
                    }
            }
    }
    applyWebpackOptionsDefaults(options);
    compiler.hooks.environment.call();
    compiler.hooks.afterEnvironment.call();
    new WebpackOptionsApply().process(options, compiler);
    compiler.hooks.initialize.call();
    return compiler;
};

接下来针对createCompiler创建流程细化

NodeEnvironmentPlugin

实例化NodeEnvironmentPlugin

//65行
new NodeEnvironmentPlugin({
	infrastructureLogging: options.infrastructureLogging
}).apply(compiler);

NodeEnvironmentPlugin可以对文件输入、输出、缓存、监听。源码

附含compiler hook

class NodeEnvironmentPlugin {
    constructor(options) {
        this.options = options;
    }
    
    apply(compiler) {
        ...
        //文件输入
        compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
        //文件输出
        compiler.outputFilesSystem = fs; //const fs = require("graceful-fs");
        //文件缓存
        compiler.intermdiateFileSystem = fs;
        //文件监听
        compiler.watchFileSystem = new NodeMatchFileSystem{ compiler.inputFileSystem };
        //项目配置插件提供了`inputFileSystem`对象就使用项目提供的
        compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
            if (compiler.inputFileSystem === inputFileSystem) {
                compiler.fsStartTime = Date.now();
                inputFileSystem.purge();
            }
        }
    }
}

解构options.plugins(注册plugins)

判断plugins属性是否为数组,是数组然后解构为一个一个的plugin,直达

遍历arguments对象,arguments成员->插件实例调用自身的apply方法执行注册流程。

if (Array.isArray(options.plugins)) {
    for (const plugin of options.plugins) {
        if (typeof plugin === "function") {
            plugin.call(compiler, compiler);
        } else {
            plugin.apply(compiler);
        }
    }
}
applyWebpackOptionsDefaults(options);

compiler.options的重新赋值

compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
new WebpackOptionsApply().process(options, compiler);

WebpackOptionsApply类里面提供一个process方法。process(options, compiler) { }。而它主要做了以下几件事情

  1. 将传入的options上的属性赋值给compiler对于对象
  2. 根据options配置是否需要注册一些内部自带的插件和resolverFactory.hooks
  3. 解析entrynew EntryOptionPlugin().apply(compiler);compiler.hooks.entryOption.call(options.context, options.entry);
  4. 返回options

resolverFactory.hooks主要包含normalcontextloader

初始化

compiler.hooks.initialize.call();

构建阶段

构建阶段主要有以下几个流程:

  1. 开始编译
  2. 确认编译入口(读取entires配置,递归遍历所有入口文件)
  3. 编译模块(从entry文件开始,调用loader对模块进行转译,通过acorn转换为AST)
  4. 完成模块编译

compiler.run

Compiler.run()

const run = () => {
    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);

                this.compile(onCompiled);
            });
        });
    });
};

compiler.compile

Compiler.compile(callback)

在compile方法依次执行了以下几个hook

  1. beforeCompile
  2. compile
  3. make
  4. finishMake
  5. afterCompile

在complie阶段通过const compilation = this.newCompilation(params);实例化了Compilation,对于compilation对象表现了当前的模块资源、编译生成的资源、变化的文件以及被跟踪依赖的状态信息,代表一次资源的构建。

生成阶段

输出资源、写入文件系统

关于Loader、Plugin

Loader 是对一个个的文件进行处理,它是一个转换器,将A文件进行编译成B文件。

Plugin 是贯穿在整个构建生命周期,可以对不同阶段的构建产物进行处理。

loader

webpack的loader本质是一个ESM模块,它导出一个函数,这个函数就是对打包资源进行转换然后输出结果。

function loader(source) => {
    //source 资源文件内容
    //TODO 处理过程
    return '加工后的输出'
}

loader.pitch = function () {
    //pitch loader
    //TODO
}

module.exports = loader;

plugin

plugin是导出一个class,其中类包含了一个固定的方法名为apply,apply方法的第一个参数为compiler,我们可以通过拿到compiler对象的hooks进行添加事件.

Tapable: 一个工具库,其中包含了很多hook。

class EchoPlugin {
    constructor(options){
        //options
    }
    
    apply(compiler) {
        //tap: 同步/异步;topAsync:异步
        //可以在compiler任意生命周期内进行操作,举例emit的钩子
        compiler.hooks.emit.tap('EchoPlugin', compilation => {
            //TODO
        }
    }
}

总体流程图

image.png
推荐链接剑指前端Offer


文章作者: echo_sixi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 echo_sixi !
  目录