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深入浅出理解前端相关技术栈