webpack 源码分析(四)
回顾内容,我们之前讲完了了NodeEnvironmentPlugin。先回答一道题,你了解哪些webpack插件,他们是干什么的?
- 我了解NodeEnvironmentPlugin这个插件
- 它是webpack内置函数,而且是webpack第一个执行的插件,它的主要作用是用来操作webpack输入输出,以及给webpack提供日志,集成了inputFileSystem,infrastructureLogger
- 它相较于fs or graceful-fs,它增加了缓存策略,多次调用文件,会把方法合并,只调用一个文件,500ms内,如果再次调用该文件,直接走缓存。
- 监听watchFileSystem,加入了轮训的机制,原来没用fs.watch(webpack5给换回来了),跨平台支持(磨平macos和windows的区别)
接着接着分析代码
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
来了来了,我们配置的plugin来了,这里发现,插件支持两种形式,一种是对象,一种是函数,如果是类需要增加apply方法,如果是函数直接执行。
到这里,我们已经学废了,如何写一个webpack插件
class Plugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
console.log(" hello 我是wepack插件")
});
}
}
这就是最简单的一个plugin
以后面试的时候,面试官问你,loader先加载还是plugin,记得回答plugin先执行。
applyWebpackOptionsDefaults(options);
接下来走到了applyWebpackOptionsDefaults,走到这里我最开始看的也有点懵,为啥不直接和getNormalizedWebpackOptions结合呢,emmm,我现在感觉是因为代码太多了,拆开了更好理解,前面的normalize,很明显就是格式化代码,把不同传参统一成一套传参,这个apply很明显是修改webpack默认传参的。
代码如下
const applyWebpackOptionsDefaults = options => {
// 再次给context赋值,我到现在也不知道有什么意义,明明前面已经给过了
F(options, "context", () => process.cwd());
// 给webpack制定默认环境
F(options, "target", () => {
return getDefaultTarget(/** @type {string} */ (options.context));
});
const { mode, name, target } = options;
let targetProperties =
target === false
? /** @type {false} */ (false)
: typeof target === "string"
? getTargetProperties(target, /** @type {Context} */ (options.context))
: getTargetsProperties(
/** @type {string[]} */ (target),
/** @type {Context} */ (options.context)
);
const development = mode === "development";
const production = mode === "production" || !mode;
// 如果 entry:{main:{}} ----> entry:{main:{import:[./src]}}
if (typeof options.entry !== "function") {
for (const key of Object.keys(options.entry)) {
F(
options.entry[key],
"import",
() => /** @type {[string]} */ (["./src"])
);
}
}
// devtool 如果没有赋值,在这里完成赋值
F(options, "devtool", () => (development ? "eval" : false));
D(options, "watch", false);
D(options, "profile", false);
D(options, "parallelism", 100);
D(options, "recordsInputPath", false);
D(options, "recordsOutputPath", false);
// 赋值experiments
applyExperimentsDefaults(options.experiments, {
production,
development,
targetProperties
});
const futureDefaults =
/** @type {NonNullable<ExperimentsNormalized["futureDefaults"]>} */
(options.experiments.futureDefaults);
F(options, "cache", () =>
development ? { type: /** @type {"memory"} */ ("memory") } : false
);
applyCacheDefaults(options.cache, {
name: name || "default",
mode: mode || "production",
development,
cacheUnaffected: options.experiments.cacheUnaffected
});
const cache = !!options.cache;
applySnapshotDefaults(options.snapshot, {
production,
futureDefaults
});
applyModuleDefaults(options.module, {
cache,
syncWebAssembly:
/** @type {NonNullable<ExperimentsNormalized["syncWebAssembly"]>} */
(options.experiments.syncWebAssembly),
asyncWebAssembly:
/** @type {NonNullable<ExperimentsNormalized["asyncWebAssembly"]>} */
(options.experiments.asyncWebAssembly),
css:
/** @type {NonNullable<ExperimentsNormalized["css"]>} */
(options.experiments.css),
futureDefaults,
isNode: targetProperties && targetProperties.node === true
});
applyOutputDefaults(options.output, {
context: /** @type {Context} */ (options.context),
targetProperties,
isAffectedByBrowserslist:
target === undefined ||
(typeof target === "string" && target.startsWith("browserslist")) ||
(Array.isArray(target) &&
target.some(target => target.startsWith("browserslist"))),
outputModule:
/** @type {NonNullable<ExperimentsNormalized["outputModule"]>} */
(options.experiments.outputModule),
development,
entry: options.entry,
module: options.module,
futureDefaults
});
applyExternalsPresetsDefaults(options.externalsPresets, {
targetProperties,
buildHttp: !!options.experiments.buildHttp
});
applyLoaderDefaults(
/** @type {NonNullable<WebpackOptions["loader"]>} */ (options.loader),
{ targetProperties }
);
F(options, "externalsType", () => {
const validExternalTypes = require("../../schemas/WebpackOptions.json")
.definitions.ExternalsType.enum;
return options.output.library &&
validExternalTypes.includes(options.output.library.type)
? /** @type {ExternalsType} */ (options.output.library.type)
: options.output.module
? "module"
: "var";
});
applyNodeDefaults(options.node, {
futureDefaults:
/** @type {NonNullable<WebpackOptions["experiments"]["futureDefaults"]>} */
(options.experiments.futureDefaults),
targetProperties
});
F(options, "performance", () =>
production &&
targetProperties &&
(targetProperties.browser || targetProperties.browser === null)
? {}
: false
);
applyPerformanceDefaults(
/** @type {NonNullable<WebpackOptions["performance"]>} */
(options.performance),
{
production
}
);
applyOptimizationDefaults(options.optimization, {
development,
production,
css:
/** @type {NonNullable<ExperimentsNormalized["css"]>} */
(options.experiments.css),
records: !!(options.recordsInputPath || options.recordsOutputPath)
});
options.resolve = cleverMerge(
getResolveDefaults({
cache,
context: /** @type {Context} */ (options.context),
targetProperties,
mode: /** @type {Mode} */ (options.mode)
}),
options.resolve
);
options.resolveLoader = cleverMerge(
getResolveLoaderDefaults({ cache }),
options.resolveLoader
);
};
这里就是给我们没有传递的配置文件传递默认的初始值。
这章先到这里
回顾一下
- 什么是webpack插件,怎么写webpack插件
- applyWebpackOptionsDefaults是干什么的
前端技术专栏分享 文章被收录于专栏
1. 包含常规前端面试题解析,和源码分析 2. 从0-1深入浅出理解前端相关技术栈

