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深入浅出理解前端相关技术栈
查看13道真题和解析
