diff --git a/examples/mini-css-extract-plugin/index.js b/examples/mini-css-extract-plugin/index.js index b48cc8a..eb3ba14 100644 --- a/examples/mini-css-extract-plugin/index.js +++ b/examples/mini-css-extract-plugin/index.js @@ -1,3 +1,3 @@ -import("./style.module.css").then((module) => { +import(/* webpackChunkName: "style" */ "./style.module.css").then((module) => { console.log(module["default"] ? "ok" : "error"); }); diff --git a/examples/mini-css-extract-plugin/webpack.config.js b/examples/mini-css-extract-plugin/webpack.config.js index 3c23763..dea6852 100644 --- a/examples/mini-css-extract-plugin/webpack.config.js +++ b/examples/mini-css-extract-plugin/webpack.config.js @@ -2,8 +2,12 @@ const { SubresourceIntegrityPlugin } = require("webpack-subresource-integrity"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const { RunInPuppeteerPlugin } = require("wsi-test-helper"); +const expect = require("expect"); +const fs = require("fs"); +const path = require("path"); module.exports = { + mode: "none", entry: { index: "./index.js", }, @@ -12,7 +16,7 @@ module.exports = { // Options similar to the same options in webpackOptions.output // both options are optional filename: "[name].css", - chunkFilename: "[id].css", + chunkFilename: "[name].css", }), new SubresourceIntegrityPlugin({ hashFuncNames: ["sha256", "sha384"], @@ -20,6 +24,25 @@ module.exports = { }), new HtmlWebpackPlugin(), new RunInPuppeteerPlugin(), + { + apply: (compiler) => { + compiler.hooks.done.tap("wsi-test", (stats) => { + expect(stats.compilation.warnings).toEqual([]); + expect(stats.compilation.errors).toEqual([]); + const cssIntegrity = stats + .toJson() + .assets.find((asset) => asset.name === "style.css").integrity; + const source = fs.readFileSync( + path.join(__dirname, "./dist/runtime.js"), + "utf-8" + ); + const sriManifest = JSON.parse( + source.match(/__webpack_require__.sriHashes = ({.+});/)?.[1] || "{}" + ); + expect(sriManifest["style_css/mini-extract"]).toEqual(cssIntegrity); + }); + }, + }, ], output: { crossOriginLoading: "anonymous", @@ -44,4 +67,10 @@ module.exports = { }, ], }, + optimization: { + chunkIds: "named", + runtimeChunk: { + name: "runtime", + }, + }, }; diff --git a/webpack-subresource-integrity/README.md b/webpack-subresource-integrity/README.md index b35f31b..aa05067 100644 --- a/webpack-subresource-integrity/README.md +++ b/webpack-subresource-integrity/README.md @@ -59,6 +59,20 @@ default), the `integrity` attribute will be set automatically. The `output.crossOriginLoading` webpack option. There is nothing else to be done. +#### With MiniCssExtractPlugin + +Currently, developers can only add integrity to link elements via the insert method. + +```js +new MiniCssExtractPlugin({ + insert: (link) => { + link.integrity = + __webpack_require__.sriHashes[chunkId + "_css/mini-extract"]; + document.head.appendChild(link); + }, +}) +``` + #### With HtmlWebpackPlugin({ inject: false }) When you use html-webpack-plugin with `inject: false`, you are @@ -221,7 +235,7 @@ With Webpack and long-term caching this means using `[contenthash]` (with `[contenthash]` with `realContentHash` disabled, or using a different type of hash placeholder (such as `[chunkhash]`) provides weaker guarantees, which is why this plugin will output a warning in these cases. See [issue -#162](https://github.com/waysact/webpack-subresource-integrity/issues/162) +# 162](https://github.com/waysact/webpack-subresource-integrity/issues/162) for more information. ### Proxies @@ -251,7 +265,7 @@ tags, but preloading with SRI doesn't work as expected in current Chrome versions. The resource will be loaded twice, defeating the purpose of preloading. This problem doesn't appear to exist in Firefox or Safari. See [issue -#111](https://github.com/waysact/webpack-subresource-integrity/issues/111) +# 111](https://github.com/waysact/webpack-subresource-integrity/issues/111) for more information. ### Browser support @@ -275,7 +289,7 @@ using a tool such as [`http-server`](https://github.com/indexzero/http-server). ### Safari 13 (and earlier versions) and Assets that Require Cookies As detailed in [Webpack Issue -#6972](https://github.com/webpack/webpack/issues/6972), the `crossOrigin` +# 6972](https://github.com/webpack/webpack/issues/6972), the `crossOrigin` attribute can break loading of assets in Safari versions prior to 14 in certain edge cases due to a browser bug. Since SRI requires the `crossOrigin` attribute to be set, you may run into this case even when source URL is same-origin with diff --git a/webpack-subresource-integrity/src/index.ts b/webpack-subresource-integrity/src/index.ts index 3fda2fd..3a486ef 100644 --- a/webpack-subresource-integrity/src/index.ts +++ b/webpack-subresource-integrity/src/index.ts @@ -169,13 +169,13 @@ export class SubresourceIntegrityPlugin { ? plugin.getChildChunksToAddToChunkManifest(chunk) : findChunks(chunk); const includedChunks = chunk.getChunkMaps(false).hash; - if (Object.keys(includedChunks).length > 0) { return compilation.compiler.webpack.Template.asString([ source, `${sriHashVariableReference} = ` + JSON.stringify( generateSriHashPlaceholders( + compilation, Array.from(allChunks).filter( (depChunk) => depChunk.id !== null && @@ -201,6 +201,7 @@ export class SubresourceIntegrityPlugin { chunk, new AddLazySriRuntimeModule( generateSriHashPlaceholders( + compilation, childChunks, this.options.hashFuncNames ), diff --git a/webpack-subresource-integrity/src/plugin.ts b/webpack-subresource-integrity/src/plugin.ts index e617de0..326ed0f 100644 --- a/webpack-subresource-integrity/src/plugin.ts +++ b/webpack-subresource-integrity/src/plugin.ts @@ -32,6 +32,7 @@ import { tryGetSource, replaceInSource, usesAnyHash, + normalizeChunkId, } from "./util"; import { getChunkToManifestMap } from "./manifest"; import { AssetIntegrity } from "./integrity"; @@ -176,7 +177,10 @@ export class Plugin { if (childChunk.id !== null) { this.hashByPlaceholder.set( - makePlaceholder(this.options.hashFuncNames, childChunk.id), + makePlaceholder( + this.options.hashFuncNames, + normalizeChunkId(sourcePath, childChunk, this.compilation) + ), integrity ); } diff --git a/webpack-subresource-integrity/src/util.ts b/webpack-subresource-integrity/src/util.ts index d07f2d8..c1e6b4d 100644 --- a/webpack-subresource-integrity/src/util.ts +++ b/webpack-subresource-integrity/src/util.ts @@ -14,6 +14,8 @@ export type ChunkGroup = ReturnType; export const sriHashVariableReference = "__webpack_require__.sriHashes"; +export const miniCssExtractType = "css/mini-extract"; + export function assert(value: unknown, message: string): asserts value { if (!value) { throw new Error(message); @@ -113,11 +115,21 @@ export function notNil( } export function generateSriHashPlaceholders( + compilation: Compilation, chunks: Iterable, hashFuncNames: [string, ...string[]] ): Record { return Array.from(chunks).reduce((sriHashes, depChunk: Chunk) => { if (depChunk.id) { + const hasMiniCssExtractFile = compilation.chunkGraph + .getChunkModules(depChunk) + .find((module) => module.type === miniCssExtractType); + if (hasMiniCssExtractFile) { + sriHashes[`${depChunk.id}_${miniCssExtractType}`] = makePlaceholder( + hashFuncNames, + `${depChunk.id}_${miniCssExtractType}` + ); + } sriHashes[depChunk.id] = makePlaceholder(hashFuncNames, depChunk.id); } return sriHashes; @@ -291,3 +303,19 @@ export function hasOwnProperty( ): obj is X & Record { return Object.prototype.hasOwnProperty.call(obj, prop); } + +export const normalizeChunkId = ( + sourcePath: string, + chunk: Chunk, + compilation: Compilation +) => { + if ( + sourcePath.endsWith(".css") && + compilation.chunkGraph + .getChunkModules(chunk) + .find((module) => module.type === miniCssExtractType) + ) { + return `${chunk.id}_${miniCssExtractType}`; + } + return chunk.id as string | number; +}; diff --git a/yarn.lock b/yarn.lock index 74aab7f..51d257f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10073,4 +10073,4 @@ __metadata: z-schema: bin/z-schema checksum: 8a1d66817ae4384dc3f63311f0cccaadd95cc9640eaade5fd3fbf91aa80d6bb82fb95d9b9171fa82ac371a0155b32b7f5f77bbe84dabaca611b66f74c628f0b8 languageName: node - linkType: hard + linkType: hard \ No newline at end of file