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..58cb86e5 100644 --- a/src/loader.js +++ b/src/loader.js @@ -1,3 +1,9 @@ +/** + * 思路:通过创建子编译器来实现将 CSS 文件从 JavaScript 中提取出来 + * + * 插件可以通过监听 Webpack 的事件钩子,在适当的时机创建子编译器,并为其指定相应的入口文件和配置。 + * 通过这种方式,插件可以自定义代码的拆分和生成,实现更高级的代码分离策略和优化 + */ const path = require("path"); const { @@ -69,6 +75,7 @@ function hotLoader(content, context) { * @param {string} request */ function pitch(request) { + // 如果使用了 webpack 5 的 experiments.css 特性,并且当前模块类型为 css,则发出警告 if ( this._compiler && this._compiler.options && @@ -94,6 +101,7 @@ function pitch(request) { MiniCssExtractPlugin.pluginSymbol ]; + // 如果没有添加 mini-css-extract-plugin 插件,则抛出错误 (使用speed-measure-webpack-plugin会报错) if (!optionsFromPlugin) { callback( new Error( @@ -122,6 +130,7 @@ function pitch(request) { typeof options.esModule !== "undefined" ? options.esModule : true; /** + * 将依赖添加到 this._module的依赖中 * @param {Dependency[] | [null, object][]} dependencies */ const addDependencies = (dependencies) => { @@ -136,6 +145,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 +156,7 @@ function pitch(request) { identifierCountMap.get( /** @type {Dependency} */ (dependency).identifier ) || 0; + // 获取CssDependency类 const CssDependency = MiniCssExtractPlugin.getCssDependency(webpack); /** @type {NormalModule} */ @@ -199,6 +210,7 @@ function pitch(request) { /** @type {Dependency[] | [null, object][]} */ let dependencies; + // 将exports转为dependencies依赖 if (!Array.isArray(exports)) { dependencies = [[null, exports]]; } else { @@ -242,6 +254,7 @@ function pitch(request) { return; } + // 根据导出类型,生成最终导出的代码 const result = locals ? namedExport ? Object.keys(locals) @@ -261,15 +274,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 +301,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 +333,8 @@ function pitch(request) { publicPathForExtract = publicPath; } + // 使用loaderContext中的importModule, 参数一为userRequest + // 处理 CSS this.importModule( `${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, { @@ -333,15 +352,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 +373,7 @@ function pitch(request) { publicPath, }; + // 创建子compile来处理css const childCompiler = /** @type {Compilation} */ (this._compilation).createChildCompiler( @@ -384,6 +407,7 @@ function pitch(request) { new EnableLibraryPlugin("commonjs2").apply(childCompiler); + // 设置入口 EntryOptionPlugin.applyEntryOption(childCompiler, this.context, { child: { library: { @@ -393,7 +417,7 @@ function pitch(request) { }, }); const { LimitChunkCountPlugin } = webpack.optimize; - + // 限制只输出一个 chunk new LimitChunkCountPlugin({ maxChunks: 1 }).apply(childCompiler); const { NormalModule } = webpack; @@ -410,8 +434,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 +465,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 +482,7 @@ function pitch(request) { } ); + // 运行子compiler childCompiler.runAsChild((error, entries, compilation) => { if (error) { callback(error); @@ -494,6 +524,7 @@ function pitch(request) { let originalExports; try { + // 在loader上下文,执行刚刚导出的 source代码,导出webpack的立即执行函数 originalExports = evalModuleCode(this, source, request); } catch (e) { callback(/** @type {Error} */ (e)); @@ -501,6 +532,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