From e295cc54ee5ef9b302fe9eca346f78f2371e4073 Mon Sep 17 00:00:00 2001 From: msidolphin Date: Sat, 7 Oct 2023 17:07:41 +0800 Subject: [PATCH] feat: add beforeTagInsert hook --- .cspell.json | 3 +- README.md | 21 ++++++ package-lock.json | 3 +- package.json | 3 +- src/hooks.js | 35 +++++++++ src/index.js | 19 ++++- ...b728e64.css => 0.04f5273a6b9819ed9e63.css} | 0 ...9bb728e64.css => 04f5273a6b9819ed9e63.css} | 0 .../expected/webpack-5-importModule/main.js | 3 +- ...27988e6.css => 0.04f5273a6b9819ed9e63.css} | 0 ...6627988e6.css => 04f5273a6b9819ed9e63.css} | 0 .../expected/webpack-5/main.js | 3 +- test/cases/hmr/expected/main.js | 1 + test/cases/insert-function/expected/main.js | 1 + test/cases/insert-string/expected/main.js | 1 + test/cases/insert-undefined/expected/main.js | 1 + test/hooks.test.js | 74 +++++++++++++++++++ types/hooks.d.ts | 17 +++++ types/index.d.ts | 9 ++- 19 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 src/hooks.js rename test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/{0.7f0e5fa686a9bb728e64.css => 0.04f5273a6b9819ed9e63.css} (100%) rename test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/{7f0e5fa686a9bb728e64.css => 04f5273a6b9819ed9e63.css} (100%) rename test/cases/chunkFilename-fullhash/expected/webpack-5/{0.100253bb7576627988e6.css => 0.04f5273a6b9819ed9e63.css} (100%) rename test/cases/chunkFilename-fullhash/expected/webpack-5/{100253bb7576627988e6.css => 04f5273a6b9819ed9e63.css} (100%) create mode 100644 test/hooks.test.js create mode 100644 types/hooks.d.ts diff --git a/.cspell.json b/.cspell.json index 40c8d65e..89d901e5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -30,7 +30,8 @@ "vspace", "commitlint", "unreload", - "cnfg" + "cnfg", + "tapable" ], "ignorePaths": [ diff --git a/README.md b/README.md index 31d5aea0..7d499a09 100644 --- a/README.md +++ b/README.md @@ -1194,6 +1194,27 @@ If you'd like to extract the media queries from the extracted CSS (so mobile use - [Media Query Plugin](https://github.com/SassNinja/media-query-plugin) - [Media Query Splitting Plugin](https://github.com/mike-diamond/media-query-splitting-plugin) +## Hooks + +The mini-css-extract-plugin provides hooks to extend it to your needs. + +### beforeTagInsert + +`SyncWaterfallHook` + +Called before inject the insert code for link tag. Should return a string + +```javascript +MiniCssExtractPlugin.getCompilationHooks(compilation).beforeTagInsert.tap( + "changeHref", + (source, varNames) => + Template.asString([ + source, + `${varNames.tag}.setAttribute("href", "https://github.com/webpack-contrib/mini-css-extract-plugin");`, + ]) +); +``` + ## Contributing Please take a moment to read our contributing guidelines if you haven't yet done so. diff --git a/package-lock.json b/package-lock.json index 328cd2f2..37cb0023 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11761,8 +11761,7 @@ "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "terminal-link": { "version": "2.1.1", diff --git a/package.json b/package.json index 0f5c14b4..a9e822f0 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "webpack": "^5.0.0" }, "dependencies": { - "schema-utils": "^4.0.0" + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "devDependencies": { "@babel/cli": "^7.21.0", diff --git a/src/hooks.js b/src/hooks.js new file mode 100644 index 00000000..5b03cd72 --- /dev/null +++ b/src/hooks.js @@ -0,0 +1,35 @@ +const { SyncWaterfallHook } = require("tapable"); + +/** @typedef {import("webpack").Compilation} Compilation */ +/** + * @typedef {Object} VarNames + * @property {string} tag + * @property {string} chunkId + * @property {string} href + * @property {string} resolve + * @property {string} reject + */ + +/** + * @typedef {Object} MiniCssExtractPluginCompilationHooks + * @property {import("tapable").SyncWaterfallHook<[string, VarNames], string>} beforeTagInsert + */ + +/** @type {WeakMap} */ +const compilationHooksMap = new WeakMap(); + +/** + * + * @param {Compilation} compilation the compilation + * @returns {MiniCssExtractPluginCompilationHooks} the compilation hooks + */ +exports.getCompilationHooks = function getCompilationHooks(compilation) { + let hooks = compilationHooksMap.get(compilation); + if (!hooks) { + hooks = { + beforeTagInsert: new SyncWaterfallHook(["source", "varNames"], "string"), + }; + compilationHooksMap.set(compilation, hooks); + } + return hooks; +}; diff --git a/src/index.js b/src/index.js index e47612f1..6d344585 100644 --- a/src/index.js +++ b/src/index.js @@ -15,6 +15,7 @@ const { getUndoPath, BASE_URI, } = require("./utils"); +const { getCompilationHooks } = require("./hooks"); /** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ /** @typedef {import("webpack").Compiler} Compiler */ @@ -513,6 +514,14 @@ class MiniCssExtractPlugin { return CssDependency; } + /** + * Returns all hooks for the given compilation + * @param {Compilation} compilation + */ + static getCompilationHooks(compilation) { + return getCompilationHooks(compilation); + } + /** * @param {PluginOptions} [options] */ @@ -843,7 +852,6 @@ class MiniCssExtractPlugin { if (!withLoading && !withHmr) { return ""; } - return Template.asString([ 'if (typeof document === "undefined") return;', `var createStylesheet = ${runtimeTemplate.basicFunction( @@ -902,6 +910,15 @@ class MiniCssExtractPlugin { "}", ]) : "", + MiniCssExtractPlugin.getCompilationHooks( + compilation + ).beforeTagInsert.call("", { + tag: "linkTag", + chunkId: "chunkId", + href: "fullhref", + resolve: "resolve", + reject: "reject", + }) || "", typeof this.runtimeOptions.insert !== "undefined" ? typeof this.runtimeOptions.insert === "function" ? `(${this.runtimeOptions.insert.toString()})(linkTag)` diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/0.7f0e5fa686a9bb728e64.css b/test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/0.04f5273a6b9819ed9e63.css similarity index 100% rename from test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/0.7f0e5fa686a9bb728e64.css rename to test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/0.04f5273a6b9819ed9e63.css diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/7f0e5fa686a9bb728e64.css b/test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/04f5273a6b9819ed9e63.css similarity index 100% rename from test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/7f0e5fa686a9bb728e64.css rename to test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/04f5273a6b9819ed9e63.css diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/main.js b/test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/main.js index 42f7a5b2..84f5a0c2 100644 --- a/test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/main.js +++ b/test/cases/chunkFilename-fullhash/expected/webpack-5-importModule/main.js @@ -73,7 +73,7 @@ __webpack_require__.r(__webpack_exports__); /******/ /******/ /* webpack/runtime/getFullHash */ /******/ (() => { -/******/ __webpack_require__.h = () => ("7f0e5fa686a9bb728e64") +/******/ __webpack_require__.h = () => ("04f5273a6b9819ed9e63") /******/ })(); /******/ /******/ /* webpack/runtime/global */ @@ -201,6 +201,7 @@ __webpack_require__.r(__webpack_exports__); /******/ linkTag.onerror = linkTag.onload = onLinkComplete; /******/ linkTag.href = fullhref; /******/ +/******/ /******/ if (oldTag) { /******/ oldTag.parentNode.insertBefore(linkTag, oldTag.nextSibling); /******/ } else { diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5/0.100253bb7576627988e6.css b/test/cases/chunkFilename-fullhash/expected/webpack-5/0.04f5273a6b9819ed9e63.css similarity index 100% rename from test/cases/chunkFilename-fullhash/expected/webpack-5/0.100253bb7576627988e6.css rename to test/cases/chunkFilename-fullhash/expected/webpack-5/0.04f5273a6b9819ed9e63.css diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5/100253bb7576627988e6.css b/test/cases/chunkFilename-fullhash/expected/webpack-5/04f5273a6b9819ed9e63.css similarity index 100% rename from test/cases/chunkFilename-fullhash/expected/webpack-5/100253bb7576627988e6.css rename to test/cases/chunkFilename-fullhash/expected/webpack-5/04f5273a6b9819ed9e63.css diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5/main.js b/test/cases/chunkFilename-fullhash/expected/webpack-5/main.js index 62e0fb02..84f5a0c2 100644 --- a/test/cases/chunkFilename-fullhash/expected/webpack-5/main.js +++ b/test/cases/chunkFilename-fullhash/expected/webpack-5/main.js @@ -73,7 +73,7 @@ __webpack_require__.r(__webpack_exports__); /******/ /******/ /* webpack/runtime/getFullHash */ /******/ (() => { -/******/ __webpack_require__.h = () => ("100253bb7576627988e6") +/******/ __webpack_require__.h = () => ("04f5273a6b9819ed9e63") /******/ })(); /******/ /******/ /* webpack/runtime/global */ @@ -201,6 +201,7 @@ __webpack_require__.r(__webpack_exports__); /******/ linkTag.onerror = linkTag.onload = onLinkComplete; /******/ linkTag.href = fullhref; /******/ +/******/ /******/ if (oldTag) { /******/ oldTag.parentNode.insertBefore(linkTag, oldTag.nextSibling); /******/ } else { diff --git a/test/cases/hmr/expected/main.js b/test/cases/hmr/expected/main.js index bd36274c..93cf1da2 100644 --- a/test/cases/hmr/expected/main.js +++ b/test/cases/hmr/expected/main.js @@ -964,6 +964,7 @@ __webpack_require__.r(__webpack_exports__); /******/ linkTag.onerror = linkTag.onload = onLinkComplete; /******/ linkTag.href = fullhref; /******/ +/******/ /******/ if (oldTag) { /******/ oldTag.parentNode.insertBefore(linkTag, oldTag.nextSibling); /******/ } else { diff --git a/test/cases/insert-function/expected/main.js b/test/cases/insert-function/expected/main.js index 20571243..b8110b02 100644 --- a/test/cases/insert-function/expected/main.js +++ b/test/cases/insert-function/expected/main.js @@ -185,6 +185,7 @@ /******/ linkTag.onerror = linkTag.onload = onLinkComplete; /******/ linkTag.href = fullhref; /******/ +/******/ /******/ (function (linkTag) { /******/ const reference = document.querySelector(".hot-reload"); /******/ if (reference) { diff --git a/test/cases/insert-string/expected/main.js b/test/cases/insert-string/expected/main.js index 9f386939..5c96e825 100644 --- a/test/cases/insert-string/expected/main.js +++ b/test/cases/insert-string/expected/main.js @@ -185,6 +185,7 @@ /******/ linkTag.onerror = linkTag.onload = onLinkComplete; /******/ linkTag.href = fullhref; /******/ +/******/ /******/ var target = document.querySelector("script[src='1.js']"); /******/ target.parentNode.insertBefore(linkTag, target.nextSibling); /******/ return linkTag; diff --git a/test/cases/insert-undefined/expected/main.js b/test/cases/insert-undefined/expected/main.js index 8a27fc4c..d3c59c2f 100644 --- a/test/cases/insert-undefined/expected/main.js +++ b/test/cases/insert-undefined/expected/main.js @@ -185,6 +185,7 @@ /******/ linkTag.onerror = linkTag.onload = onLinkComplete; /******/ linkTag.href = fullhref; /******/ +/******/ /******/ if (oldTag) { /******/ oldTag.parentNode.insertBefore(linkTag, oldTag.nextSibling); /******/ } else { diff --git a/test/hooks.test.js b/test/hooks.test.js new file mode 100644 index 00000000..373c21cf --- /dev/null +++ b/test/hooks.test.js @@ -0,0 +1,74 @@ +/* eslint-env browser */ +import path from "path"; + +import { Template } from "webpack"; + +import MiniCssExtractPlugin from "../src"; + +import { runInJsDom, compile, getCompiler } from "./helpers/index"; + +describe("hooks", () => { + it(`beforeTagInsert`, async () => { + const webpackCompiler = getCompiler( + "insert.js", + {}, + { + mode: "none", + output: { + publicPath: "", + path: path.resolve(__dirname, "../outputs"), + filename: "[name].bundle.js", + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: "[name].css", + }), + { + /** + * + * @param {import('webpack').Compiler} compiler + */ + apply: (compiler) => { + compiler.hooks.compilation.tap("sri", (compilation) => { + MiniCssExtractPlugin.getCompilationHooks( + compilation + ).beforeTagInsert.tap("sri", (source, varNames) => + Template.asString([ + source, + `${varNames.tag}.setAttribute("integrity", "sriHashes[${varNames.chunkId}]");`, + ]) + ); + }); + }, + }, + { + /** + * + * @param {import('webpack').Compiler} compiler + */ + apply: (compiler) => { + compiler.hooks.compilation.tap("href", (compilation) => { + MiniCssExtractPlugin.getCompilationHooks( + compilation + ).beforeTagInsert.tap("changeHref", (source, varNames) => + Template.asString([ + source, + `${varNames.tag}.setAttribute("href", "https://github.com/webpack-contrib/mini-css-extract-plugin");`, + ]) + ); + }); + }, + }, + ], + } + ); + const stats = await compile(webpackCompiler); + runInJsDom("main.bundle.js", webpackCompiler, stats, (dom) => { + const [tag] = dom.window.document.head.getElementsByTagName("link"); + expect(tag.getAttribute("integrity")).toBe("sriHashes[chunkId]"); + expect(tag.getAttribute("href")).toBe( + "https://github.com/webpack-contrib/mini-css-extract-plugin" + ); + }); + }); +}); diff --git a/types/hooks.d.ts b/types/hooks.d.ts new file mode 100644 index 00000000..e88af2fa --- /dev/null +++ b/types/hooks.d.ts @@ -0,0 +1,17 @@ +export function getCompilationHooks( + compilation: Compilation +): MiniCssExtractPluginCompilationHooks; +export type Compilation = import("webpack").Compilation; +export type VarNames = { + tag: string; + chunkId: string; + href: string; + resolve: string; + reject: string; +}; +export type MiniCssExtractPluginCompilationHooks = { + beforeTagInsert: import("tapable").SyncWaterfallHook< + [string, VarNames], + string + >; +}; diff --git a/types/index.d.ts b/types/index.d.ts index 9c4f81f8..5aa18062 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -12,6 +12,13 @@ declare class MiniCssExtractPlugin { static getCssDependency( webpack: Compiler["webpack"] ): CssDependencyConstructor; + /** + * Returns all hooks for the given compilation + * @param {Compilation} compilation + */ + static getCompilationHooks( + compilation: Compilation + ): import("./hooks").MiniCssExtractPluginCompilationHooks; /** * @param {PluginOptions} [options] */ @@ -103,6 +110,7 @@ type CssDependencyConstructor = new ( context: string | null, identifierIndex: number ) => CssDependency; +type Compilation = import("webpack").Compilation; type PluginOptions = { filename?: Required["output"]["filename"]; chunkFilename?: Required["output"]["chunkFilename"]; @@ -166,7 +174,6 @@ declare const pluginName: "mini-css-extract-plugin"; declare const pluginSymbol: unique symbol; declare var loader: string; type Schema = import("schema-utils/declarations/validate").Schema; -type Compilation = import("webpack").Compilation; type ChunkGraph = import("webpack").ChunkGraph; type Chunk = import("webpack").Chunk; type ChunkGroup = Parameters[0];