diff --git a/examples/webpack-module-federaition-host/README.md b/examples/webpack-module-federaition-host/README.md new file mode 100644 index 0000000..1af3245 --- /dev/null +++ b/examples/webpack-module-federaition-host/README.md @@ -0,0 +1,3 @@ +# With Webpack module federation + +Usage of With Webpack module federation as host app and eager dependency diff --git a/examples/webpack-module-federaition-host/bootstrap.js b/examples/webpack-module-federaition-host/bootstrap.js new file mode 100644 index 0000000..1c45e61 --- /dev/null +++ b/examples/webpack-module-federaition-host/bootstrap.js @@ -0,0 +1,4 @@ +// mock dependency +require("lodash"); + +console.log("ok"); diff --git a/examples/webpack-module-federaition-host/index.js b/examples/webpack-module-federaition-host/index.js new file mode 100644 index 0000000..50599f7 --- /dev/null +++ b/examples/webpack-module-federaition-host/index.js @@ -0,0 +1 @@ +import("./bootstrap"); diff --git a/examples/webpack-module-federaition-host/package.json b/examples/webpack-module-federaition-host/package.json new file mode 100644 index 0000000..b0e73ad --- /dev/null +++ b/examples/webpack-module-federaition-host/package.json @@ -0,0 +1,17 @@ +{ + "name": "webpack-module-federaition-host", + "description": "Ensure integrity works with webpack module federation as host app", + "version": "1.0.0", + "license": "MIT", + "private": true, + "devDependencies": { + "expect": "^26.6.2", + "nyc": "*", + "webpack": "^5.44.0", + "webpack-cli": "4", + "webpack-subresource-integrity": "*" + }, + "dependencies": { + "lodash": "^4.17.21" + } +} diff --git a/examples/webpack-module-federaition-host/webpack.config.js b/examples/webpack-module-federaition-host/webpack.config.js new file mode 100644 index 0000000..9199353 --- /dev/null +++ b/examples/webpack-module-federaition-host/webpack.config.js @@ -0,0 +1,47 @@ +const { SubresourceIntegrityPlugin } = require("webpack-subresource-integrity"); +const expect = require("expect"); +const container = require("webpack").container; + +const { ModuleFederationPlugin } = container; + +module.exports = { + entry: { + index: "./index.js", + }, + output: { + crossOriginLoading: "anonymous", + }, + plugins: [ + new SubresourceIntegrityPlugin({ + hashFuncNames: ["sha256"], + enabled: true, + }), + new ModuleFederationPlugin({ + name: "host", + filename: "remoteEntry.js", + remotes: { + app1: "app1@http://localhost:3001/remoteEntry.js", + }, + exposes: { + "./remote": "./bootstrap", + }, + shared: { + lodash: { + singleton: true, + eager: true, + }, + }, + }), + { + apply: (compiler) => { + compiler.hooks.done.tap("wsi-test", (stats) => { + const assets = stats.toJson().assets; + + assets.forEach((asset) => { + expect(asset).not.toContain("*-*-*-CHUNK-SRI-HASH-"); + }); + }); + }, + }, + ], +}; diff --git a/examples/webpack-module-federaition-remote/README.md b/examples/webpack-module-federaition-remote/README.md new file mode 100644 index 0000000..e412fce --- /dev/null +++ b/examples/webpack-module-federaition-remote/README.md @@ -0,0 +1,3 @@ +# With Webpack module federation + +Usage of With Webpack module federation as remote app diff --git a/examples/webpack-module-federaition-remote/bootstrap.js b/examples/webpack-module-federaition-remote/bootstrap.js new file mode 100644 index 0000000..1c45e61 --- /dev/null +++ b/examples/webpack-module-federaition-remote/bootstrap.js @@ -0,0 +1,4 @@ +// mock dependency +require("lodash"); + +console.log("ok"); diff --git a/examples/webpack-module-federaition-remote/index.js b/examples/webpack-module-federaition-remote/index.js new file mode 100644 index 0000000..50599f7 --- /dev/null +++ b/examples/webpack-module-federaition-remote/index.js @@ -0,0 +1 @@ +import("./bootstrap"); diff --git a/examples/webpack-module-federaition-remote/package.json b/examples/webpack-module-federaition-remote/package.json new file mode 100644 index 0000000..e5607c1 --- /dev/null +++ b/examples/webpack-module-federaition-remote/package.json @@ -0,0 +1,17 @@ +{ + "name": "webpack-module-federaition-remote", + "description": "Ensure integrity works with webpack module federation as remote app", + "version": "1.0.0", + "license": "MIT", + "private": true, + "devDependencies": { + "expect": "^26.6.2", + "nyc": "*", + "webpack": "^5.44.0", + "webpack-cli": "4", + "webpack-subresource-integrity": "*" + }, + "dependencies": { + "lodash": "^4.17.21" + } +} diff --git a/examples/webpack-module-federaition-remote/webpack.config.js b/examples/webpack-module-federaition-remote/webpack.config.js new file mode 100644 index 0000000..a55ebb3 --- /dev/null +++ b/examples/webpack-module-federaition-remote/webpack.config.js @@ -0,0 +1,43 @@ +const { SubresourceIntegrityPlugin } = require("webpack-subresource-integrity"); +const expect = require("expect"); +const container = require("webpack").container; + +const { ModuleFederationPlugin } = container; + +module.exports = { + entry: { + index: "./index.js", + }, + output: { + crossOriginLoading: "anonymous", + }, + plugins: [ + new SubresourceIntegrityPlugin({ + hashFuncNames: ["sha256"], + enabled: true, + }), + new ModuleFederationPlugin({ + name: "remote", + filename: "remoteEntry.js", + exposes: { + "./remote": "./bootstrap", + }, + shared: { + lodash: { + singleton: true, + }, + }, + }), + { + apply: (compiler) => { + compiler.hooks.done.tap("wsi-test", (stats) => { + const assets = stats.toJson().assets; + + assets.forEach((asset) => { + expect(asset).not.toContain("*-*-*-CHUNK-SRI-HASH-"); + }); + }); + }, + }, + ], +}; diff --git a/webpack-subresource-integrity/src/index.ts b/webpack-subresource-integrity/src/index.ts index 3fda2fd..66313fe 100644 --- a/webpack-subresource-integrity/src/index.ts +++ b/webpack-subresource-integrity/src/index.ts @@ -167,7 +167,7 @@ export class SubresourceIntegrityPlugin { const allChunks = this.options.hashLoading === "lazy" ? plugin.getChildChunksToAddToChunkManifest(chunk) - : findChunks(chunk); + : findChunks(chunk, compilation); const includedChunks = chunk.getChunkMaps(false).hash; if (Object.keys(includedChunks).length > 0) { diff --git a/webpack-subresource-integrity/src/manifest.ts b/webpack-subresource-integrity/src/manifest.ts index 079624c..04a2fb5 100644 --- a/webpack-subresource-integrity/src/manifest.ts +++ b/webpack-subresource-integrity/src/manifest.ts @@ -17,9 +17,10 @@ import { allChunksInChunkIterable, allChunksInPrimaryChunkIterable, sriHashVariableReference, + wmfSharedChunk, } from "./util"; import { createDAGfromGraph } from "./scc"; -import { RuntimeModule, Template, Chunk } from "webpack"; +import { RuntimeModule, Template, Chunk, Compilation } from "webpack"; // This implementation assumes a directed acyclic graph (such as one produced by createDAGfromGraph), // and does not attempt to detect cycles @@ -41,7 +42,8 @@ function topologicalSort({ vertices, edges }: Graph): T[] { } function buildTopologicallySortedChunkGraph( - chunks: Iterable + chunks: Iterable, + compilation: Compilation ): [ sortedVertices: StronglyConnectedComponent[], sccGraph: Graph>, @@ -52,13 +54,18 @@ function buildTopologicallySortedChunkGraph( // Chunks should have *all* chunks, not simply entry chunks for (const vertex of chunks) { - if (addIfNotExist(vertices, vertex)) { + if ( + wmfSharedChunk(vertex, compilation) || + addIfNotExist(vertices, vertex) + ) { continue; } edges.set(vertex, new Set()); for (const childChunk of allChunksInChunkIterable(vertex)) { - edges.get(vertex)?.add(childChunk); + if (!wmfSharedChunk(childChunk, compilation)) { + edges.get(vertex)?.add(childChunk); + } } } @@ -82,9 +89,9 @@ class ChunkToManifestMapBuilder { // or its parents regardless of the tree traversal used from the roots private hashesByChunkGroupAndParents = new Map>(); - constructor(chunks: Iterable) { + constructor(compilation: Compilation) { const [sortedVertices, , chunkToSccMap] = - buildTopologicallySortedChunkGraph(chunks); + buildTopologicallySortedChunkGraph(compilation.chunks, compilation); this.sortedVertices = sortedVertices; this.chunkToSccMap = chunkToSccMap; this.manifest = this.createManifest(); @@ -186,12 +193,12 @@ class ChunkToManifestMapBuilder { } export function getChunkToManifestMap( - chunks: Iterable + compilation: Compilation ): [ sortedVertices: StronglyConnectedComponent[], chunkManifest: Map> ] { - return new ChunkToManifestMapBuilder(chunks).build(); + return new ChunkToManifestMapBuilder(compilation).build(); } export class AddLazySriRuntimeModule extends RuntimeModule { diff --git a/webpack-subresource-integrity/src/plugin.ts b/webpack-subresource-integrity/src/plugin.ts index 8d363b4..d7dac97 100644 --- a/webpack-subresource-integrity/src/plugin.ts +++ b/webpack-subresource-integrity/src/plugin.ts @@ -150,7 +150,7 @@ export class Plugin { chunk: Chunk, assets: Record ): void => { - Array.from(findChunks(chunk)) + Array.from(findChunks(chunk, this.compilation)) .reverse() .forEach((chunk) => this.processChunkAssets(chunk, assets)); }; @@ -296,7 +296,7 @@ export class Plugin { beforeRuntimeRequirements = (): void => { if (this.options.hashLoading === "lazy") { const [sortedSccChunks, chunkManifest] = getChunkToManifestMap( - this.compilation.chunks + this.compilation ); this.sortedSccChunks = sortedSccChunks; this.chunkManifest = chunkManifest; diff --git a/webpack-subresource-integrity/src/util.ts b/webpack-subresource-integrity/src/util.ts index d07f2d8..ae2913c 100644 --- a/webpack-subresource-integrity/src/util.ts +++ b/webpack-subresource-integrity/src/util.ts @@ -87,8 +87,22 @@ export function addIfNotExist(set: Set, item: T): boolean { set.add(item); return false; } +/** + * Filter out shared module that are not used in the build + */ +export function wmfSharedChunk( + chunk: Chunk, + compilation: Compilation +): boolean { + const rootModules = compilation.chunkGraph.getChunkRootModules(chunk); + const isSharedModule = rootModules.some( + (module) => module.type === "consume-shared-module" + ); -export function findChunks(chunk: Chunk): Set { + return isSharedModule && rootModules.length === 1; +} + +export function findChunks(chunk: Chunk, compilation: Compilation): Set { const allChunks = new Set(); const groupsVisited = new Set(); @@ -99,7 +113,12 @@ export function findChunks(chunk: Chunk): Set { group.childrenIterable.forEach(recurseGroup); } - if (addIfNotExist(allChunks, childChunk)) return; + if ( + wmfSharedChunk(childChunk, compilation) || + addIfNotExist(allChunks, childChunk) + ) { + return; + } Array.from(childChunk.groupsIterable).forEach(recurseGroup); })(chunk); diff --git a/yarn.lock b/yarn.lock index b489c0d..da73407 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9574,6 +9574,32 @@ __metadata: languageName: node linkType: hard +"webpack-module-federaition-host@workspace:examples/webpack-module-federaition-host": + version: 0.0.0-use.local + resolution: "webpack-module-federaition-host@workspace:examples/webpack-module-federaition-host" + dependencies: + expect: ^26.6.2 + lodash: ^4.17.21 + nyc: "*" + webpack: ^5.44.0 + webpack-cli: 4 + webpack-subresource-integrity: "*" + languageName: unknown + linkType: soft + +"webpack-module-federaition-remote@workspace:examples/webpack-module-federaition-remote": + version: 0.0.0-use.local + resolution: "webpack-module-federaition-remote@workspace:examples/webpack-module-federaition-remote" + dependencies: + expect: ^26.6.2 + lodash: ^4.17.21 + nyc: "*" + webpack: ^5.44.0 + webpack-cli: 4 + webpack-subresource-integrity: "*" + languageName: unknown + linkType: soft + "webpack-preload@workspace:examples/webpack-preload": version: 0.0.0-use.local resolution: "webpack-preload@workspace:examples/webpack-preload"