From 6d86bc6af6e5deed702bec71d550c8a0bb997c00 Mon Sep 17 00:00:00 2001 From: neverland Date: Sun, 23 Nov 2025 19:57:04 +0800 Subject: [PATCH] feat: add integrity field to manifest when sri is enabled --- .../output/manifest-integrity/index.test.ts | 32 +++++++++++++++++++ .../manifest-integrity/rsbuild.config.ts | 13 ++++++++ .../output/manifest-integrity/src/index.ts | 3 ++ packages/core/package.json | 2 +- packages/core/src/plugins/manifest.ts | 6 ++++ packages/core/src/types/config.ts | 6 ++++ pnpm-lock.yaml | 11 +++---- website/docs/en/config/output/manifest.mdx | 6 ++++ website/docs/zh/config/output/manifest.mdx | 6 ++++ 9 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 e2e/cases/output/manifest-integrity/index.test.ts create mode 100644 e2e/cases/output/manifest-integrity/rsbuild.config.ts create mode 100644 e2e/cases/output/manifest-integrity/src/index.ts diff --git a/e2e/cases/output/manifest-integrity/index.test.ts b/e2e/cases/output/manifest-integrity/index.test.ts new file mode 100644 index 0000000000..aff5122074 --- /dev/null +++ b/e2e/cases/output/manifest-integrity/index.test.ts @@ -0,0 +1,32 @@ +import { expect, getFileContent, rspackTest } from '@e2e/helper'; +import type { ManifestData } from '@rsbuild/core'; + +rspackTest( + 'should generate manifest file with integrity in build', + async ({ build }) => { + const rsbuild = await build(); + const files = rsbuild.getDistFiles(); + const manifestContent = getFileContent(files, 'manifest.json'); + const manifest = JSON.parse(manifestContent) as ManifestData; + manifest.allFiles.forEach((item) => { + if (item.endsWith('.js')) { + expect(manifest.integrity[item]).toBeTruthy(); + } + }); + }, +); + +rspackTest( + 'should generate manifest file with integrity in dev', + async ({ dev }) => { + const rsbuild = await dev(); + const files = rsbuild.getDistFiles(); + const manifestContent = getFileContent(files, 'manifest.json'); + const manifest = JSON.parse(manifestContent) as ManifestData; + manifest.allFiles.forEach((item) => { + if (item.endsWith('.js')) { + expect(manifest.integrity[item]).toBeTruthy(); + } + }); + }, +); diff --git a/e2e/cases/output/manifest-integrity/rsbuild.config.ts b/e2e/cases/output/manifest-integrity/rsbuild.config.ts new file mode 100644 index 0000000000..5722e108d0 --- /dev/null +++ b/e2e/cases/output/manifest-integrity/rsbuild.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from '@rsbuild/core'; + +export default defineConfig({ + output: { + manifest: true, + filenameHash: false, + }, + security: { + sri: { + enable: true, + }, + }, +}); diff --git a/e2e/cases/output/manifest-integrity/src/index.ts b/e2e/cases/output/manifest-integrity/src/index.ts new file mode 100644 index 0000000000..a74715686c --- /dev/null +++ b/e2e/cases/output/manifest-integrity/src/index.ts @@ -0,0 +1,3 @@ +import React from 'react'; + +console.log('hello!', React); diff --git a/packages/core/package.json b/packages/core/package.json index f3b9673c96..4e4941bb32 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -93,7 +93,7 @@ "reduce-configs": "^1.1.1", "rslog": "^1.3.0", "rspack-chain": "^1.4.1", - "rspack-manifest-plugin": "5.1.0", + "rspack-manifest-plugin": "5.2.0", "sirv": "^3.0.2", "stacktrace-parser": "^0.1.11", "style-loader": "3.3.4", diff --git a/packages/core/src/plugins/manifest.ts b/packages/core/src/plugins/manifest.ts index f3a3020bb8..82cc5182ba 100644 --- a/packages/core/src/plugins/manifest.ts +++ b/packages/core/src/plugins/manifest.ts @@ -34,8 +34,13 @@ const generateManifest = const chunkEntries = new Map(); const licenseMap = new Map(); const publicPath = getPublicPathFromCompiler(compilation); + const integrity: Record = {}; const allFiles = files.map((file) => { + if (file.integrity) { + integrity[file.path] = file.integrity; + } + if (file.chunk) { const entryNames = recursiveChunkEntryNames(file.chunk); @@ -144,6 +149,7 @@ const generateManifest = const manifestData: ManifestData = { allFiles, entries: manifestEntries, + integrity, }; if (manifestOptions.generate) { diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index c20116a10c..142f865df3 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -1179,6 +1179,12 @@ export type ManifestData = { entries: { [entryName: string]: ManifestByEntry; }; + /** + * Subresource Integrity (SRI) hashes for emitted assets. + * The key is the asset file path, and the value is its integrity hash. + * This field is available only when the `security.sri` option is enabled. + */ + integrity: Record; }; export type ManifestObjectConfig = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c00ece7dd..5f5f06473d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -728,8 +728,8 @@ importers: specifier: ^1.4.1 version: 1.4.1 rspack-manifest-plugin: - specifier: 5.1.0 - version: 5.1.0(@rspack/core@1.6.4(@swc/helpers@0.5.17)) + specifier: 5.2.0 + version: 5.2.0(@rspack/core@1.6.4(@swc/helpers@0.5.17)) sirv: specifier: ^3.0.2 version: 3.0.2 @@ -5807,9 +5807,8 @@ packages: rspack-chain@1.4.1: resolution: {integrity: sha512-cKkRjUXl2fhQzYwQD7aux+Og1WtjuWFG5XjtOZldjYiRanRoz9OrmbRf4s2kCOgxNplPWP946mqG5hAIzY/V0w==} - rspack-manifest-plugin@5.1.0: - resolution: {integrity: sha512-XAt1VOfRt6gcNlekB7rpiYK0MuC8VuWRrM2F33Eu83B/fmEVJUNVMDxZJhqJ0NMDJNxq80404j+NAfzlYQCjrw==} - engines: {node: '>=14'} + rspack-manifest-plugin@5.2.0: + resolution: {integrity: sha512-W4fIoDE7cGiN7iBh2Zs71W/P+iRSvUi2jIcyzcvDvjgoPbqYw15AL0bZMgQ88OzEocYwtFcsn/iQFFv8h+XRAg==} peerDependencies: '@rspack/core': 0.x || 1.x peerDependenciesMeta: @@ -11857,7 +11856,7 @@ snapshots: deepmerge: 4.3.1 javascript-stringify: 2.1.0 - rspack-manifest-plugin@5.1.0(@rspack/core@1.6.4(@swc/helpers@0.5.17)): + rspack-manifest-plugin@5.2.0(@rspack/core@1.6.4(@swc/helpers@0.5.17)): dependencies: '@rspack/lite-tapable': 1.1.0 optionalDependencies: diff --git a/website/docs/en/config/output/manifest.mdx b/website/docs/en/config/output/manifest.mdx index 8539edb882..3d0529518d 100644 --- a/website/docs/en/config/output/manifest.mdx +++ b/website/docs/en/config/output/manifest.mdx @@ -91,6 +91,12 @@ type ManifestList = { */ assets?: FilePath[]; }; + /** + * Subresource Integrity (SRI) hashes for emitted assets. + * The key is the asset file path, and the value is its integrity hash. + * This field is available only when the `security.sri` option is enabled. + */ + integrity: Record; }; }; ``` diff --git a/website/docs/zh/config/output/manifest.mdx b/website/docs/zh/config/output/manifest.mdx index fabc0bd8fa..1e0eea6ad3 100644 --- a/website/docs/zh/config/output/manifest.mdx +++ b/website/docs/zh/config/output/manifest.mdx @@ -91,6 +91,12 @@ type ManifestList = { */ assets?: FilePath[]; }; + /** + * 所有产物文件的子资源完整性(SRI)哈希值 + * key 为文件路径,value 为对应的完整性哈希 + * 仅在启用 `security.sri` 选项时才会生成该字段 + */ + integrity: Record; }; }; ```