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) } //...
applyWebpackOptionsBaseDefaults
和applyWebpackOptionsDefaults
都是给没设置的根本配置加上默认值,先执行后面的是因为须要抛出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开始前面的构建环节。