webpack 源码分析(一)
在面试中,我们总会被问到webpack相关的问题,如webpack的loader,plugins是什么,有什么区别,又比如webpack的代码分割是怎么实现的,等一系列问题,很多人不能从底层彻底了解具体的实现,出于此目的,我想写一篇专栏,专门分析webpack源码的教程,其实已经有很多这种类型的文章了,但是我还想写一个专栏,我觉得市面上讲的还是有些粗糙,我要讲的更细更多一些。
废话不多说,接下来直接分析webpack源码。
平常我们一般都会使用webpack-cli来调用webpack,但其实webpack-cli,底层帮助我们做的操作就是调用了webpack的函数
webpack函数 有两个参数,第一个参数就是我们经常配置的参数 options,第二个参数其实也很简单 callback ,返回执行完的回调函数。
const webpack = (options,callback)=>{ let compiler; let watch = false; let watchOptions; const create = () => { if(Array.isArray(options)) { compiler = createMultiCompiler(options); watch = options.some(options => options.watch); watchOptions = options.map(options => options.watchOptions || {}); } else { compiler = createCompiler(webpackOptions); watch = webpackOptions.watch; watchOptions = webpackOptions.watchOptions || {}; } } if(callback) { const { compiler, watch, watchOptions } = create(); if (watch) { compiler.watch(watchOptions, callback); } else { compiler.run((err, stats) => { compiler.close(err2 => { callback(err || err2, stats); }); }); } return compiler; } else { const { compiler, watch } = create(); return compiler; } }
可以看到webpack 函数,针对options,数组和单配置传参做了处理,处理也非常的简单,核心就是createCompiler,然后执行.run方法或者watch方法。
看到这里其实就可以了解到webpack的核心代码是在createCompiler 或者说是createMultiCompiler,我们接下来看createCompiler,为什么不看后者呢,因为其实前后者基本上都一样,只不过后者是数组,多个,相当于调用多次前者罢了
const createCompiler = rawOptions => { const options = getNormalizedWebpackOptions(rawOptions); applyWebpackOptionsBaseDefaults(options); const compiler = new Compiler(options.context, options); new NodeEnvironmentPlugin({ infrastructureLogging: options.infrastructureLogging }).apply(compiler); 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); compiler.hooks.environment.call(); compiler.hooks.afterEnvironment.call(); new WebpackOptionsApply().process(options, compiler); compiler.hooks.initialize.call(); return compiler; };
我们先来看第一行代码const options = getNormalizedWebpackOptions(rawOptions);
我们知道传参就是webpack.config,js配置文件,这个函数我觉得没必要往上列上去,这个函数的核心就是为了解决配置传参可以传递多种,如 webpack 配置的entry,我们知道entry:'./src/index.ts',我们还可以entry:{main:{import:'./src/index.ts'}},其实这两种方式是一致的,所以getNormalizedWebpackOptions
函数就是支持多种参数的传递。
我们再来看applyWebpackOptionsBaseDefaults(options);
const applyWebpackOptionsBaseDefaults = options => { F(options, "context", () => process.cwd()); applyInfrastructureLoggingDefaults(options.infrastructureLogging); }; const F = (obj, prop, factory) => { if (obj[prop] === undefined) { obj[prop] = factory(); } }; const applyInfrastructureLoggingDefaults = infrastructureLogging => { F(infrastructureLogging, "stream", () => process.stderr); const tty = /** @type {any} */ (infrastructureLogging.stream).isTTY && process.env.TERM !== "dumb"; D(infrastructureLogging, "level", "info"); D(infrastructureLogging, "debug", false); D(infrastructureLogging, "colors", tty); D(infrastructureLogging, "appendOnly", !tty); }; const D = (obj, prop, value) => { if (obj[prop] === undefined) { obj[prop] = value; } };
我们可以看到首先,给option.context 赋值,如果你不给传context,那我就默认选择process.pwd()当前调用路径为上下文路径。
处理options.infrastructureLogging
,我们一般都不会传递这个属性,getNormalizedWebpackOptions
会把它处理为空对象{},之后这个函数会把它处理成{ stream:process.stderr , level:info,debug:false,colors:false or true , appendOnly:true or false},true false 取决于process.stderr.isTTY,看输出是不是终端,如果是终端为true,否则false
接来下执行核心代码我们在下一节再说,webpack源码分析(二)
回顾一下本篇文章内容,
- createComiler
- getNormalizedWebpackOptions 处理options对象
- applyWebpackOptionsBaseDefaults 处理context和infrastructureLogging 给context和日志赋值
1. 包含常规前端面试题解析,和源码分析 2. 从0-1深入浅出理解前端相关技术栈