Webpack 代码基于
webpack5.88.2
。
代码解读:
-
在
webpack/lib/webpack.js
文件中:可以看到导出的 webpack 其实就是一个函数。主要就是根据传入的配置选项生成 compiler 编译器,如果有传入 callback 的话,会根据配置选项调用 compiler 编译器的watch()
或者run()
方法,否则的话返回 compiler 编译器。
// webpack/lib/webpack.js // ... const webpack = (options, callback) => { const create = () => { if (!asArray(options).every(webpackOptionsSchemaCheck)) { getValidateSchema()(webpackOptionsSchema, options); util.deprecate( () => {}, "webpack bug: Pre-compiled schema reports error while real schema is happy. This has performance drawbacks.", "DEP_WEBPACK_PRE_COMPILED_SCHEMA_INVALID" )(); } // 定义 compiler 编译器 let compiler; let watch = false; let watchOptions; // 如果传入的 options 配置是一个数组的话,会创建多个 compiler 编译器 if (Array.isArray(options)) { compiler = createMultiCompiler( options, options, ); watch = options.some(options => options.watch); watchOptions = options.map(options => options.watchOptions || {}); } else { // 否则的话,会创建一个 compiler 编译器 const webpackOptions = options; compiler = createCompiler(webpackOptions); watch = webpackOptions.watch; watchOptions = webpackOptions.watchOptions || {}; } // 返回 compiler 编译器及 watch 的数据 return { compiler, watch, watchOptions }; }; // 如果有传 callback if (callback) { try { const { compiler, watch, watchOptions } = create(); // 如果 watch 为 true,执行编译器的 watch() 方法开始编译并监听 if (watch) { compiler.watch(watchOptions, callback); } else { // 否则的话,执行编译的 run() 方法开始编译 compiler.run((err, stats) => { compiler.close(err2 => { callback(err || err2, stats); }); }); } return compiler; } catch (err) { process.nextTick(() => callback(err)); return null; } } else { // 如果没有传 callback,返回 compiler 编译器 const { compiler, watch } = create(); if (watch) { util.deprecate( () => {}, "A 'callback' argument needs to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.", "DEP_WEBPACK_WATCH_WITHOUT_CALLBACK" )(); } return compiler; } } module.exports = webpack;
-
在
webpack/lib/webpack.js
文件中:通过createCompiler()
函数生成了 compiler 编译器。const createCompiler = rawOptions => { const options = getNormalizedWebpackOptions(rawOptions); applyWebpackOptionsBaseDefaults(options); // 1. 通过 new Compiler 类创建出 compiler 对象 const compiler = new Compiler( options.context, options ); new NodeEnvironmentPlugin({ infrastructureLogging: options.infrastructureLogging }).apply(compiler); // 2. 注册所有的插件(也就是配置选项中传入的 plugins) if (Array.isArray(options.plugins)) { for (const plugin of options.plugins) { // 如果 plugin 是一个函数,那么通过 call 调用 plugin 并且传入 compiler if (typeof plugin === "function") { plugin.call(compiler, compiler); } else if (plugin) { // 否则的话,调用其 apply 方法并且传入 compiler 。其实 Webpack 中大部分 plugin 都不是函数,而是一个类,其中都是实现一个叫做 apply 的方法 plugin.apply(compiler); } } } applyWebpackOptionsDefaults(options); // 3. 调用钩子 environment 和 afterEnvironment 的 call() compiler.hooks.environment.call(); compiler.hooks.afterEnvironment.call(); // 4. process() 方法用于处理配置选项中除了 plugins 外的其他属性 new WebpackOptionsApply().process(options, compiler); compiler.hooks.initialize.call(); return compiler; };
-
在
webpack/lib/WebpackOptionsApply.js
文件中:process()
方法用于处理配置选项中除了 plugins 外的其他属性,会将传入的属性转成 Webpack 的 Plugin 注入到 Webpack 的生命周期中。
process(options, compiler) { compiler.outputPath = options.output.path; compiler.recordsInputPath = options.recordsInputPath || null; compiler.recordsOutputPath = options.recordsOutputPath || null; compiler.name = options.name; // 根据各种配置情况,导入各种插件,并且通过 new Plugin.apply() 调用 if (options.externals) { const ExternalsPlugin = require("./ExternalsPlugin"); new ExternalsPlugin(options.externalsType, options.externals).apply( compiler ); } if (options.externalsPresets.node) { const NodeTargetPlugin = require("./node/NodeTargetPlugin"); new NodeTargetPlugin().apply(compiler); } if (options.externalsPresets.electronMain) { const ElectronTargetPlugin = require("./electron/ElectronTargetPlugin"); new ElectronTargetPlugin("main").apply(compiler); } // ... }
-
在
webpack/lib/Compiler.js
文件中:可以看到 Compiler 类的constructor()
方法中,有一大堆的 hook。
任何 Plugin 都会调用其apply()
方法,在apply()
方法中就会通过 compiler 编译器的 hooks 属性找到其中的某个 hook 并注册事件,在 Webpack 编译的生命周期内一旦调用了 hook 就会执行该 hook 上注册的事件的回调函数。因此 plugin 会贯穿在 Webpack 的整个生命周期内。
-
在
webpack/lib/Compiler.js
文件中:Webpack 打包编译最终都是拿到 compiler 编译器执行的run()
方法。Webpack 源代码中 Compiler 对象和 Compilation 对象的区别:
- Compiler 对象:在 Webpack 构建之处就会创建的一个对象,在 Webpack 的整个生命周期都会存在(before - run - beforeCompiler - compile - make - finishMake - aftercompiler - done)。只要是 Webpack 的编译,都会创建一个 Compiler 对象。
- Compilation 对象:准备编译模块时才会创建 Compilation 对象,主要是在 compile - make 阶段存在使用的对象。
当修改了源代码重新编译模块时,Compiler 对象可以继续使用,但是会创建一个新的 Compilation 对象;当修改了 Webpack 配置重新执行 webpack 命令进行打包时,就会创建一个新的 Compiler 对象。
compile(callback) { // 初始化 Compilation 的参数 const params = this.newCompilationParams(); // hooks 的流程:beforeCompile -> compile -> make -> finishMake -> afterCompile this.hooks.beforeCompile.callAsync(params, err => { if (err) return callback(err); this.hooks.compile.call(params); // 创建 Compilation 对象。此处可以看到,Compilation 对象是在 compile hook 之后,make hook 之前创建的 const compilation = this.newCompilation(params); const logger = compilation.getLogger("webpack.Compiler"); logger.time("make hook"); // make 是最终的编译过程,但是它只是一个 hook 的调用,具体做的事要去找注册在这个 hook 上的回调的函数 // 以 Entry 配置选项为例,当调用 this.hooks.make.callAsync() 时,compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {compilation.addEntry(context, dep,options, err => {callback(err);});}); 中的回调函数就会被执行 this.hooks.make.callAsync(compilation, err => { logger.timeEnd("make hook"); if (err) return callback(err); logger.time("finish make hook"); this.hooks.finishMake.callAsync(compilation, err => { logger.timeEnd("finish make hook"); if (err) return callback(err); process.nextTick(() => { logger.time("finish compilation"); compilation.finish(err => { logger.timeEnd("finish compilation"); if (err) return callback(err); logger.time("seal compilation"); compilation.seal(err => { logger.timeEnd("seal compilation"); if (err) return callback(err); logger.time("afterCompile hook"); this.hooks.afterCompile.callAsync(compilation, err => { logger.timeEnd("afterCompile hook"); if (err) return callback(err); return callback(null, compilation); }); }); }); }); }); }); }); }
-
在
webpack/lib/NormalModule.js
文件中:_doBuild()
函数中的runLoaders()
处理 Loader。
-
在
/webpack/lib/Compilation.js
文件中:seal()
函数输出编译打包后的文件。
图示说明:![在这里插入图片描述](https://img-blog.csdnimg.cn/f44fccb4983c416aa7e53d5abaa204bc.png)