Webpack初始化

const webpack = require("webpack");const config = require("./webpack.config");const compiler = webpack(config);compiler.run();

尽管大部分状况都在用cli或者dev-server跑webpack,它们能提供很多命令,接管参数,配置不同的npm script去跑不同的config等。但它们最终会跑以上代码的时候,开始进行打包的工作。当然,监听文件改变是用compiler.watch

webpack(config)

首先执行const compiler = webpack(config)

webpack.js

const webpack =  (    (options, callback) => {          //...          const webpackOptions = (options);            //构建compiler          compiler = createCompiler(webpackOptions);          //...              return { compiler };    });const createCompiler = rawOptions => {    //将没解决过的options进行解决    const options = getNormalizedWebpackOptions(rawOptions);    //设置default值    applyWebpackOptionsBaseDefaults(options);    const compiler = new Compiler(options.context, options);        //NodeEnvironmentPlugin会引入独立库(enhanced-resolve, NodeWatchFileSystem)来加强Node模块    new NodeEnvironmentPlugin({        infrastructureLogging: options.infrastructureLogging    }).apply(compiler);    //注册内部plugin    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);    //...    new WebpackOptionsApply().process(options, compiler);    return compiler;};

首先webpack会拿到options,并且调用createCompiler(options)生成compiler实例并返回。

getNormalizedWebpackOptions会先解决options,传进来的options并不是拿来就用,有许多配置须要解决。

//getNormalizedWebpackOptions.jsconst getNormalizedWebpackOptions = config => {    return {        cache: optionalNestedConfig(config.cache, cache => {            if (cache === false) return false;            if (cache === true) {                return {                    type: "memory",                    maxGenerations: undefined                };            }            switch (cache.type) {                case "filesystem":                    return {                        //....                    };                case undefined:                case "memory":                    return {                        type: "memory",                        maxGenerations: cache.maxGenerations                    };                default:                    throw new Error(`Not implemented cache.type ${cache.type}`);            }        }),        devServer: optionalNestedConfig(config.devServer, devServer => ({            ...devServer        })),        entry:            config.entry === undefined                ? { main: {} }                : typeof config.entry === "function"                ? (                        fn => () =>                            Promise.resolve().then(fn).then(getNormalizedEntryStatic)                  )(config.entry)                : getNormalizedEntryStatic(config.entry)        }        //...

applyWebpackOptionsBaseDefaultsapplyWebpackOptionsDefaults都是给没设置的根本配置加上默认值,先执行后面的是因为须要抛出options给上面的NodeEnvironmentPlugin应用

//如果没有该属性就设置工厂函数的返回值const F = (obj, prop, factory) => {    if (obj[prop] === undefined) {        obj[prop] = factory();    }};//如果没有该属性就进行设置const D = (obj, prop, value) => {    if (obj[prop] === undefined) {        obj[prop] = value;    }};const applyWebpackOptionsBaseDefaults = options => {    //...    F(infrastructureLogging, "stream", () => process.stderr);    D(infrastructureLogging, "level", "info");    D(infrastructureLogging, "debug", false);    D(infrastructureLogging, "colors", tty);    D(infrastructureLogging, "appendOnly", !tty);};const applyWebpackOptionsDefaults = options => {    F(options, "context", () => process.cwd());    F(options, "target", () => {        return getDefaultTarget(options.context);    });    //...    F(options, "devtool", () => (development ? "eval" : false));    D(options, "watch", false);    //...}

解决完options之后就会实例化生成Compiler对象,这时候就能够往Compiler注入插件。它们会执行所有options.plugins里的apply办法,写过插件的人都晓得,编写插件须要裸露apply函数,并且失去Compiler对象往compiler.hooks里注入钩子, 如果不分明hook的用法,倡议读我写的这篇文章。

最初调用new WebpackOptionsApply().process(options, compiler)办法,为该有的配置去注册相应的插件。初始化Compiler的工作就实现了

//WebpackOptionsApply.js//....if (options.externals) {    const ExternalsPlugin = require("./ExternalsPlugin");    new ExternalsPlugin(options.externalsType, options.externals).apply(        compiler    );}if (options.optimization.usedExports) {    const FlagDependencyUsagePlugin = require("./FlagDependencyUsagePlugin");    new FlagDependencyUsagePlugin(        options.optimization.usedExports === "global"    ).apply(compiler);}//....

compiler.run()

run(callback) {    //...    const run = () => {            //...            this.compile(onCompiled);        });    };        run()}//....compile(callback) {    //获取生成Compilation须要的参数    const params = this.newCompilationParams();    this.hooks.beforeCompile.callAsync(params, err => {        if (err) return callback(err);                this.hooks.compile.call(params);                //生成compilation        const compilation = this.newCompilation(params);        const logger = compilation.getLogger("webpack.Compiler");        logger.time("make hook");        this.hooks.make.callAsync(compilation, err => {            //...        });    });}

run办法里会调用一些钩子与记录信息,在这里并不重要,次要在于this.compile(onCompiled),onCompiled是最终seal阶段之后的会执行的回调。

生成Compilation

compile函数首先会生成params给实例化Compilation作为参数

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

normalModuleFactory会生成normalModule,webpack里的模块就是normalModule对象。contextModuleFactory会生成contextModule,它是为了解决(require.context援用进来的模块。

createCompilation(params) {    this._cleanupLastCompilation();    //依据参数实例化Compilation    return (this._lastCompilation = new Compilation(this, params));}newCompilation(params) {    //实例化Compilation    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;}

newCompilation会调用createCompilation实例化Compilation对象,并且调用钩子。

因为这时候compiler对象曾经有了compilation和normalModule,所以能够传递给插件应用它们 , 或给它们的钩子注入函数实现相干性能。

在thisCompilation钩子里的插件有九个,compilation钩子甚至有四十几个,它们都是些外部插件。

thisCompilation.taps

Compilation.taps

ruleSetCompiler

在实例化normalModuleFactory的时候还会对rule进行解决,能够为之后解决模块的时候判断应用什么loader

//normalModuleFactory.jsconst ruleSetCompiler = new RuleSetCompiler([    new BasicMatcherRulePlugin("test", "resource"),    new BasicMatcherRulePlugin("scheme"),    new BasicMatcherRulePlugin("mimetype"),    new BasicMatcherRulePlugin("dependency"),    new BasicMatcherRulePlugin("include", "resource"),    new BasicMatcherRulePlugin("exclude", "resource", true),    //...]);class normalModuleFactory {    construator() {        //...        this.ruleSet = ruleSetCompiler.compile([            {                rules: options.defaultRules            },            {                rules: options.rules            }        ]);        //...    }}

实例化ruleSetCompiler的时候会把本人作为参数给插件用。而后调用compile,将options.rules和options.defaultRules传入进去。defaultRules是在applyWebpackOptionsDefaults的时候生成的默认rules。

//RuleSetCompiler.jsclass RuleSetCompiler {    constructor(plugins) {        this.hooks = Object.freeze({            //...        });        if (plugins) {            for (const plugin of plugins) {                plugin.apply(this);            }        }    }    compile(ruleSet) {        const refs = new Map();        //编译rules        const rules = this.compileRules("ruleSet", ruleSet, refs);        //用于依据rule抛出对应的loader        const execRule = (data, rule, effects) => {            //..        };        return {            references: refs,            exec: data => {                /** @type {Effect[]} */                const effects = [];                for (const rule of rules) {                    execRule(data, rule, effects);                }                return effects;            }        };    }    compileRules(path, rules, refs) {        return rules.map((rule, i) =>            //递归options.rules和options.defaultRules            this.compileRule(`${path}[${i}]`, rule, refs)        );    }    compileRule(path, rule, refs) {        //...    }    

RuleSetCompiler.compile会调用compileRules("ruleSet", ruleSet, refs)拼凑path并递归进行解决。

第一次调用compileRules传进来的path为ruleSet,ruleSet是下面蕴含options.rules和options.defaultRules的数组 。

    compileRule = (path, rule, refs)  => {        const unhandledProperties = new Set(            Object.keys(rule).filter(key => rule[key] !== undefined)        );        /** @type {CompiledRule} */        const compiledRule = {            conditions: [],            effects: [],            rules: undefined,            oneOf: undefined        };        //判断是否含有rules的某些参数以退出到compiledRule里        this.hooks.rule.call(path, rule, unhandledProperties, compiledRule, refs);        //判断key是否蕴含rules        if (unhandledProperties.has("rules")) {            unhandledProperties.delete("rules");            const rules = rule.rules;            if (!Array.isArray(rules))                throw this.error(path, rules, "Rule.rules must be an array of rules");            compiledRule.rules = this.compileRules(`${path}.rules`, rules, refs);        }                //判断key是否蕴含oneOf        if (unhandledProperties.has("oneOf")) {            unhandledProperties.delete("oneOf");            const oneOf = rule.oneOf;            if (!Array.isArray(oneOf))                throw this.error(path, oneOf, "Rule.oneOf must be an array of rules");            compiledRule.oneOf = this.compileRules(`${path}.oneOf`, oneOf, refs);        }        if (unhandledProperties.size > 0) {            throw this.error(                path,                rule,                `Properties ${Array.from(unhandledProperties).join(", ")} are unknown`            );        }        return compiledRule;    }

compileRule会递归解决所有含有rules和oneOf的嵌套对象,比方传进来的path为rulSet[0],所以会取第一个对象为options.defaultRules。而后unhandledProperties会取出数组每个Object keys,options.defaultRules对象的key为'rules',所以满足unhandledProperties.has("rules")。会调用compiledRule.rules = this.compileRules(`${path}.rules`, rules, refs)递归defaultRules数组

第二次递归path为rulSet[0].rules[0],而后会调用this.hooks.rule.call解决defaultRules里的每个规定。钩子会调用之前注册的BasicMatcherRulePlugin对rules的属性生成不同的conditions

class BasicMatcherRulePlugin {    constructor(ruleProperty, dataProperty, invert) {        this.ruleProperty = ruleProperty;        this.dataProperty = dataProperty || ruleProperty;        this.invert = invert || false;    }    apply(ruleSetCompiler) {        ruleSetCompiler.hooks.rule.tap(            "BasicMatcherRulePlugin",            (path, rule, unhandledProperties, result) => {                if (unhandledProperties.has(this.ruleProperty)) {                    unhandledProperties.delete(this.ruleProperty);                    const value = rule[this.ruleProperty];                    //生成Condition                    const condition = ruleSetCompiler.compileCondition(                        `${path}.${this.ruleProperty}`,                        value                    );                    const fn = condition.fn;                    //增加到compileRule里                    result.conditions.push({                        property: this.dataProperty,                        matchWhenEmpty: this.invert                            ? !condition.matchWhenEmpty                            : condition.matchWhenEmpty,                        fn: this.invert ? v => !fn(v) : fn                    });                }            }        );    }}

比方rule为{ test: /\.js/ , use: babel-loader },插件new BasicMatcherRulePlugin("test", "resource")会解决所有蕴含test属性的rules,会生成如下:

[    {        conditions: [            { property: "resource", matchWhenEmpty: false, fn:v => typeof v === "string" && condition.test(v) },            { property: "resource", matchWhenEmpty: true, fn:v => !fn(v) }        ],        effects: [{ type: "use", value: { loader: "babel-loader" } }]    }];

condition就是/\.js/,对于之后调用exec解析js模块就会抛出babel-loader。解决完所有的rules后,RuleSetCompiler.compile会返回如下对象

{    references: refs,    //exec会对模块名执行合乎的condition并抛出effects数组,effects蕴含对应的loader信息    exec: data => {        /** @type {Effect[]} */        const effects = [];        for (const rule of rules) {            execRule(data, rule, effects);        }        return effects;    }};

之后只有执行RuleSetCompiler.exec()就能返回绝对应的loader,应用办法如下

this.ruleSet.exec({    resource: resourceDataForRules.path,        //资源的绝对路径    realResource: resourceData.path,    resourceQuery: resourceDataForRules.query,        //资源携带的query string    resourceFragment: resourceDataForRules.fragment,        scheme,        //URL计划 ,列如,data,file    assertions,    mimetype: matchResourceData        ? ""        : resourceData.data.mimetype || "",   // mimetype    dependency: dependencyType,            // 依赖类型    descriptionData: matchResourceData        //    形容文件数据,比方package.json        ? undefined        : resourceData.data.descriptionFileData,    issuer: contextInfo.issuer,                        //发动申请的模块    compiler: contextInfo.compiler,                //以后webpack的compiler    issuerLayer: contextInfo.issuerLayer || ""});

到这里,生成compilation的工作就做完了,持续Compiler的钩子流程,之后就是调用this.hooks.make.callAsync办法了,开始从入口构建模块。之后会有很多async hook的代码,因为是异步的起因所以会有callback hell问题,浏览起来特地恶心,而且因为async hook里能够是setTimeout,源码实现也并没有返回promise,所以也不能应用async await解决回调问题

总结

以上就是一些初始化的代码,解决options,rules,注册插件,实例化normalModule,compilation对象,调用钩子传递对象给插件应用等。所有的工作做完了,会调用make hook开始前面的构建环节。