From ac015132511c5004edb2d31dcf7511fa53e9ad56 Mon Sep 17 00:00:00 2001 From: sunfutao <“sunfutao@didiglobal.com”> Date: Fri, 21 Jul 2023 19:15:07 +0800 Subject: [PATCH 1/3] =?UTF-8?q?docs:=20=E5=A2=9E=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- note/data_source.js | 23 +++++++++++++++++++++++ src/index.js | 10 ++++++++++ src/loader.js | 29 +++++++++++++++++++++++++++-- src/utils.js | 1 + 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 note/data_source.js diff --git a/note/data_source.js b/note/data_source.js new file mode 100644 index 00000000..7abfc241 --- /dev/null +++ b/note/data_source.js @@ -0,0 +1,23 @@ +// mini-css-extract-plugin 处理assets(compilation.hooks.processAssets)生成的source +// type: string + +'/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode:…esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ +/************************************************************************/ +/******/ +/******/ +// startup/******/ +// Load entry module and return exports/******/ +// This entry module can't be inlined because the eval devtool is used. +/******/ var __webpack_exports__ = __webpack_require__("./node_modules/css-loader/dist/cjs.js!./test/style.css"); +/******/ module.exports = __webpack_exports__; +/******/ /******/ })();' + diff --git a/src/index.js b/src/index.js index b40d0e10..a09fe6b0 100644 --- a/src/index.js +++ b/src/index.js @@ -391,6 +391,9 @@ class MiniCssExtractPlugin { /** * Prevent creation of multiple CssDependency classes to allow other integrations to get the current CssDependency. */ + // 判断有没有将CssDependency这个class存入缓存,这里的webpack来自于compiler.webpack,它是个方法 + // 当前插件apply时compiler.webpack会被speed-measure-webpack-plugin 设为proxy + // 但是第二次normal-loader-hook触发时,传入的不是proxy,引发bug if (cssDependencyCache.has(webpack)) { return /** @type {CssDependencyConstructor} */ ( cssDependencyCache.get(webpack) @@ -636,6 +639,7 @@ class MiniCssExtractPlugin { const { loader: normalModuleHook } = NormalModule.getCompilationHooks(compilation); + // 添加插件标记,loader转化时会检测 normalModuleHook.tap( pluginName, /** @@ -652,6 +656,7 @@ class MiniCssExtractPlugin { ); }); + // 初始化 compilation 时调用,在触发 compilation 事件之前调用。这个钩子 不会 被复制到子编译器 compiler.hooks.thisCompilation.tap(pluginName, (compilation) => { class CssModuleFactory { /** @@ -668,6 +673,7 @@ class MiniCssExtractPlugin { } } + // webpack中添加CssDependency类型,用于生成CssModule compilation.dependencyFactories.set( CssDependency, new CssModuleFactory() @@ -678,11 +684,13 @@ class MiniCssExtractPlugin { apply() {} } + // 设置CssDependency模版 compilation.dependencyTemplates.set( CssDependency, new CssDependencyTemplate() ); + // 这个hook会根据chunk生成对应的manifest,其中manifest.render()可以根据template生成对应的source compilation.hooks.renderManifest.tap( pluginName, /** @@ -745,6 +753,7 @@ class MiniCssExtractPlugin { } ); + // 生成css chunk的contentHash compilation.hooks.contentHash.tap(pluginName, (chunk) => { const { outputOptions, chunkGraph } = compilation; const modules = this.sortModules( @@ -1305,6 +1314,7 @@ class MiniCssExtractPlugin { } /** + * 根据模版生成assets * @private * @param {Compiler} compiler * @param {Compilation} compilation diff --git a/src/loader.js b/src/loader.js index 36b54386..733a6880 100644 --- a/src/loader.js +++ b/src/loader.js @@ -69,6 +69,7 @@ function hotLoader(content, context) { * @param {string} request */ function pitch(request) { + // 如果使用了 webpack 5 的 experiments.css 特性,并且当前模块类型为 css,则发出警告 if ( this._compiler && this._compiler.options && @@ -94,6 +95,7 @@ function pitch(request) { MiniCssExtractPlugin.pluginSymbol ]; + // 如果没有添加 mini-css-extract-plugin 插件,则抛出错误 (使用speed-measure-webpack-plugin会报错) if (!optionsFromPlugin) { callback( new Error( @@ -122,6 +124,7 @@ function pitch(request) { typeof options.esModule !== "undefined" ? options.esModule : true; /** + * 将依赖添加到 this._module的依赖中 * @param {Dependency[] | [null, object][]} dependencies */ const addDependencies = (dependencies) => { @@ -136,6 +139,7 @@ function pitch(request) { const identifierCountMap = new Map(); let lastDep; + // 遍历模块依赖,将依赖添加到当前 this._module 的依赖中 for (const dependency of dependencies) { if (!(/** @type {Dependency} */ (dependency).identifier) || !emit) { // eslint-disable-next-line no-continue @@ -146,6 +150,7 @@ function pitch(request) { identifierCountMap.get( /** @type {Dependency} */ (dependency).identifier ) || 0; + // 获取CssDependency类 const CssDependency = MiniCssExtractPlugin.getCssDependency(webpack); /** @type {NormalModule} */ @@ -199,6 +204,7 @@ function pitch(request) { /** @type {Dependency[] | [null, object][]} */ let dependencies; + // 将exports转为dependencies依赖 if (!Array.isArray(exports)) { dependencies = [[null, exports]]; } else { @@ -242,6 +248,7 @@ function pitch(request) { return; } + // 根据导出类型,生成最终导出的代码 const result = locals ? namedExport ? Object.keys(locals) @@ -261,15 +268,18 @@ function pitch(request) { let resultSource = `// extracted by ${MiniCssExtractPlugin.pluginName}`; + // 只有在支持热更新并且 emit 为 true 时,才会添加热更新代码 // only attempt hotreloading if the css is actually used for something other than hash values resultSource += this.hot && emit ? hotLoader(result, { loaderContext: this, options, locals }) : result; + // 将处理后的结果传递给下一个 Loader callback(null, resultSource); }; + // 设置publicPath let { publicPath } = /** @type {Compilation} */ (this._compilation).outputOptions; @@ -285,6 +295,7 @@ function pitch(request) { publicPath = AUTO_PUBLIC_PATH; } + // 使用实验特性 experimentalUseImportModule 或者支持 this.importModule 函数时,通过this.importModule处理 CSS if ( (typeof optionsFromPlugin.experimentalUseImportModule === "undefined" && typeof this.importModule === "function") || @@ -316,6 +327,7 @@ function pitch(request) { publicPathForExtract = publicPath; } + // 处理 CSS this.importModule( `${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, { @@ -333,15 +345,18 @@ function pitch(request) { return; } - + // 导出处理结果 handleExports(exports); } ); return; } + // 不使用实验特性时,使用子compile来处理css + // 删除当前mini-css-extract-plugin loader const loaders = this.loaders.slice(this.loaderIndex + 1); + // 将当前 CSS 文件添加为依赖 this.addDependency(this.resourcePath); const childFilename = "*"; @@ -351,6 +366,7 @@ function pitch(request) { publicPath, }; + // 创建子compile来处理css const childCompiler = /** @type {Compilation} */ (this._compilation).createChildCompiler( @@ -384,6 +400,7 @@ function pitch(request) { new EnableLibraryPlugin("commonjs2").apply(childCompiler); + // 设置入口 EntryOptionPlugin.applyEntryOption(childCompiler, this.context, { child: { library: { @@ -393,7 +410,7 @@ function pitch(request) { }, }); const { LimitChunkCountPlugin } = webpack.optimize; - + // 限制只输出一个 chunk new LimitChunkCountPlugin({ maxChunks: 1 }).apply(childCompiler); const { NormalModule } = webpack; @@ -410,8 +427,11 @@ function pitch(request) { normalModuleHook.tap( `${MiniCssExtractPlugin.pluginName} loader`, (loaderContext, module) => { + // 下次loader转化时,如果遇到当前css模块 if (module.request === request) { // eslint-disable-next-line no-param-reassign + + // 更新为删除mini-css-extract-plugin loader 后的loader列表,避免当前Loader再次处理 module.loaders = loaders.map((loader) => { return { type: null, @@ -438,11 +458,13 @@ function pitch(request) { compilation.hooks.processAssets.tap( MiniCssExtractPlugin.pluginName, () => { + // 保存source source = compilation.assets[childFilename] && compilation.assets[childFilename].source(); // Remove all chunk assets + // 为了避免将样式内联到 JavaScript 文件中,清除assets compilation.chunks.forEach((chunk) => { chunk.files.forEach((file) => { compilation.deleteAsset(file); @@ -453,6 +475,7 @@ function pitch(request) { } ); + // 运行子compiler childCompiler.runAsChild((error, entries, compilation) => { if (error) { callback(error); @@ -494,6 +517,7 @@ function pitch(request) { let originalExports; try { + // 在loader上下文,执行刚刚导出的 source代码,导出webpack的立即执行函数 originalExports = evalModuleCode(this, source, request); } catch (e) { callback(/** @type {Error} */ (e)); @@ -501,6 +525,7 @@ function pitch(request) { return; } + // 将导出的立即执行函数,利用module.addDependicies设为当前模块的依赖 handleExports(originalExports, compilation, assets, assetsInfo); }); } diff --git a/src/utils.js b/src/utils.js index 68ab5f08..fda95464 100644 --- a/src/utils.js +++ b/src/utils.js @@ -35,6 +35,7 @@ function findModuleById(compilation, id) { } /** + * 新建node.js模块,loaderContext中执行 字符串代码 * @param {LoaderContext} loaderContext * @param {string | Buffer} code * @param {string} filename From 5440696bda1f849b57ca0f160a39872f5405ba3c Mon Sep 17 00:00:00 2001 From: sunfutao <“sunfutao@didiglobal.com”> Date: Wed, 26 Jul 2023 18:51:14 +0800 Subject: [PATCH 2/3] docs: add annotation --- src/loader.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/loader.js b/src/loader.js index 733a6880..a21b2bbc 100644 --- a/src/loader.js +++ b/src/loader.js @@ -327,6 +327,7 @@ function pitch(request) { publicPathForExtract = publicPath; } + // 使用loaderContext中的importModule, 参数一为userRequest // 处理 CSS this.importModule( `${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, From 061e9234f73e35b0d87da8a91db8661fee99a015 Mon Sep 17 00:00:00 2001 From: sunfutao <“sunfutao@didiglobal.com”> Date: Mon, 31 Jul 2023 19:28:50 +0800 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20=E6=8F=90=E5=8F=96css=E6=80=9D?= =?UTF-8?q?=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/loader.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/loader.js b/src/loader.js index a21b2bbc..58cb86e5 100644 --- a/src/loader.js +++ b/src/loader.js @@ -1,3 +1,9 @@ +/** + * 思路:通过创建子编译器来实现将 CSS 文件从 JavaScript 中提取出来 + * + * 插件可以通过监听 Webpack 的事件钩子,在适当的时机创建子编译器,并为其指定相应的入口文件和配置。 + * 通过这种方式,插件可以自定义代码的拆分和生成,实现更高级的代码分离策略和优化 + */ const path = require("path"); const {