webpack源码之运转流程

弁言

经由历程前面几张的铺垫,下面最先剖析webpack源码中心流程,大体上能够分为初始化,编译,输出三个阶段,下面最先剖析

初始化

这个阶段团体流程做了什么? 启动构建,读取与兼并设置参数,加载 Plugin,实例化 Compiler。

细致剖析

//经由历程yargs取得shell中的参数
yargs.parse(process.argv.slice(2), (err, argv, output) => {
    //把webpack.config.js中的参数和shell参数整合到options对象上
    let options;
        options = require("./convert-argv")(argv);

    function processOptions(options) {

        const firstOptions = [].concat(options)[0];
        const webpack = require("webpack");

        let compiler;
            //经由历程webpack要领建立compile对象,Compiler 担任文件监听和启动编译。
            //Compiler 实例中包含了完全的 Webpack 设置,全局只要一个 Compiler 实例。
            compiler = webpack(options);


        if (firstOptions.watch || options.watch) {

            compiler.watch(watchOptions, compilerCallback);
            //启动一次新的编译。
        } else compiler.run(compilerCallback);
    }

    processOptions(options);
});

申明 从源码中摘取了初始化的的第一步,做了简化,当运转webpack敕令的的时刻,运转的是webpack-cli下webpack.js,其内容是一个自实行函数,上面是实行的第一步,举行参数的剖析兼并处置惩罚,并建立compiler实例,然后启动编译运转run要领,个中关键步骤 compiler = webpack(options); 细致睁开以下所示

const webpack = (options, callback) => {
    //参数合法性校验
    const webpackOptionsValidationErrors = validateSchema(
        webpackOptionsSchema,
        options
    );

    let compiler;
    if (Array.isArray(options)) {
        compiler = new MultiCompiler(options.map(options => webpack(options)));
    } else if (typeof options === "object") {
        options = new WebpackOptionsDefaulter().process(options);
        //建立compiler对象
        compiler = new Compiler(options.context);
        compiler.options = options;
        new NodeEnvironmentPlugin().apply(compiler);
        //注册设置文件中的插件,顺次挪用插件的 apply 要领,让插件能够监听后续的一切事宜节点。同时给插件传入 compiler 实例的援用,以轻易插件经由历程 compiler 挪用 Webpack 供应的 API。
        if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                plugin.apply(compiler);
            }
        }
        //最先运用 Node.js 作风的文件体系到 compiler 对象,以轻易后续的文件寻觅和读取。
        compiler.hooks.environment.call();
        compiler.hooks.afterEnvironment.call();
        //注册内部插件
        compiler.options = new WebpackOptionsApply().process(options, compiler);
    }

    return compiler;
};

申明 注册插件历程不在睁开,webpack内置插件真的许多啊

编译

这个阶段团体流程做了什么? 从 Entry 发出,针对每一个 Module 串行挪用对应的 Loader 去翻译文件内容,再找到该 Module 依靠的 Module,递归地举行编译处置惩罚。

细致剖析

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);
                });
            });
        });

申明 从实行run要领最先,最先实行编译流程,run要领触发了before-run、run两个事宜,然后经由历程readRecords读取文件,经由历程compile举行打包,该要领中实例化了一个Compilation类

compile(callback) {
        const params = this.newCompilationParams();
        this.hooks.beforeCompile.callAsync(params, err => {
            if (err) return callback(err);

            this.hooks.compile.call(params);
// 每编译一次都邑建立一个compilation对象(比方watch 文件时,一修改就会实行),然则compile只会建立一次
            const compilation = this.newCompilation(params);
// make事宜触发了  事宜会触发SingleEntryPlugin监听函数,挪用compilation.addEntry要领
            this.hooks.make.callAsync(compilation, err => {
                if (err) return callback(err);
                
            });
        });
    }

申明 打包时触发before-compile、compile、make等事宜,同时建立非常主要的compilation对象,内部有声清楚明了许多钩子,初始化模板等等

this.hooks = {
    buildModule: new SyncHook(["module"]),
    seal: new SyncHook([]),
    optimize: new SyncHook([]),
};
//拼接终究天生代码的主模板会用到
this.mainTemplate = new MainTemplate(this.outputOptions);
//拼接终究天生代码的chunk模板会用到
this.chunkTemplate = new ChunkTemplate(this.outputOptions); 
 //拼接终究天生代码的热更新模板会用到
this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate()
//监听comple的make hooks事宜,经由历程内部的 SingleEntryPlugin 从进口文件最先实行编译
        compiler.hooks.make.tapAsync(
            "SingleEntryPlugin",
            (compilation, callback) => {
                const { entry, name, context } = this;

                const dep = SingleEntryPlugin.createDependency(entry, name);
                compilation.addEntry(context, dep, name, callback);
            }
        );

申明 监听compile的make hooks事宜,经由历程内部的 SingleEntryPlugin 从进口文件最先实行编译,挪用compilation.addEntry要领,依据模块的范例猎取对应的模块工场并建立模块,最先构建模块


doBuild(options, compilation, resolver, fs, callback) {
    const loaderContext = this.createLoaderContext(
        resolver,
        options,
        compilation,
        fs
    );
    //挪用loader处置惩罚模块
    runLoaders(
        {
            resource: this.resource,
            loaders: this.loaders,
            context: loaderContext,
            readResource: fs.readFile.bind(fs)
        },
        (err, result) => {
           
            
            const resourceBuffer = result.resourceBuffer;
            const source = result.result[0];
            const sourceMap = result.result.length >= 1 ? result.result[1] : null;
            const extraInfo = result.result.length >= 2 ? result.result[2] : null;
            

            this._source = this.createSource(
                this.binary ? asBuffer(source) : asString(source),
                resourceBuffer,
                sourceMap
            );
            //loader处置惩罚完以后 获得_source  然后ast接着处置惩罚
            this._ast =
                typeof extraInfo === "object" &&
                extraInfo !== null &&
                extraInfo.webpackAST !== undefined
                    ? extraInfo.webpackAST
                    : null;
            return callback();
        }
    );
}

申明 SingleEntryPlugin这个内存插件主要作用是从entry读取文件,依据文件范例和设置的 Loader 实行runLoaders,然后将loader处置惩罚后的文件经由历程acorn笼统成笼统语法树AST,遍历AST,构建该模块的一切依靠。

输出

这个阶段团体流程做了什么? 把编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件体系。

细致剖析

 //一切依靠build完成,最先对chunk举行优化(抽取大众模块、加hash等)
compilation.seal(err => {
    if (err) return callback(err);

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

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

申明 compilation.seal重假如对chunk举行优化,天生编译后的源码,比较主要,细致睁开以下所示

//代码天生前面优化
this.hooks.optimize.call();
this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
 
    this.hooks.beforeHash.call();
    this.createHash();
    this.hooks.afterHash.call();

    if (shouldRecord) this.hooks.recordHash.call(this.records);

    this.hooks.beforeModuleAssets.call();
    this.createModuleAssets();
    if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
        this.hooks.beforeChunkAssets.call();
        //天生终究打包输出的chunk资本,依据template文件,细致步骤以下所示
        this.createChunkAssets();
    }
    
});
--------------------------------------
//掏出末了文件须要的模板
const template = chunk.hasRuntime()
                    ? this.mainTemplate
                    : this.chunkTemplate;
//经由历程模板终究天生webpack_require花样的内容,他这个是内部封装的拼接衬着逻辑,也没用什么ejs,handlebar等这些模板东西
source = fileManifest.render();
//天生的资本保存在compilation.assets,轻易下一步emitAssets步骤中,把文件输出到硬盘
this.assets[file] = source;
    //把处置惩罚好的assets输出到output的path中
    emitAssets(compilation, callback) {
        let outputPath;
    
        const emitFiles = err => {
            if (err) return callback(err);
    
            asyncLib.forEach(
                compilation.assets,
                (source, file, callback) => {
                    const writeOut = err => {
                        //输出打包后的文件到设置中指定的目录下
                        this.outputFileSystem.writeFile(targetPath, content, callback);
                    };
    
                    writeOut();
                }
            );
        };
    
        this.hooks.emit.callAsync(compilation, err => {
            if (err) return callback(err);
            outputPath = compilation.getPath(this.outputPath);
            this.outputFileSystem.mkdirp(outputPath, emitFiles);
        });
    }

总结

假如零丁看这篇文章的话,明白起来会比较难题,引荐一下与之相干的系列铺垫文章,上面是我对webpack源码运转流程的总结, 全部流程已跑通了,不过另有蛮多点值得深切发掘的。明朗在家宅了3天,过得好快,来日诰日公司构造去奥森公园寻宝行为,期待ing 。

引荐
webpack源码之tapable
webpack源码之plugin机制
webpack源码之ast简介
webpack源码之loader机制

参考源码
webpack: “4.4.1”
webpack-cli: “2.0.13”

    原文作者:jsdt
    原文地址: https://segmentfault.com/a/1190000014221014
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞