vite-plugin-sri4 3.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/dist/index.cjs +61 -29
- package/dist/index.js +59 -27
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ A Vite plugin to generate Subresource Integrity (SRI) hashes for your assets dur
|
|
|
27
27
|
- **Bypass Domains:** Option to specify domains to bypass SRI injection.
|
|
28
28
|
- **Missing Asset Handling:** Configurable warning suppression for missing assets.
|
|
29
29
|
- **Robust Content Support:** Handles various content types including strings, Buffer, and Uint8Array.
|
|
30
|
-
- **Vite Compatibility:** Compatible with Vite
|
|
30
|
+
- **Vite Compatibility:** Compatible with Vite 7.0 and Vite 8.0 (including the Rolldown-based native build path).
|
|
31
31
|
|
|
32
32
|
## Installation
|
|
33
33
|
|
|
@@ -48,13 +48,13 @@ export default defineConfig({
|
|
|
48
48
|
plugins: [
|
|
49
49
|
sri({
|
|
50
50
|
// Optional. The security hash algorithm. Defaults to "sha384".
|
|
51
|
-
|
|
51
|
+
hashAlgorithm: 'sha384',
|
|
52
52
|
// Optional. Domains to bypass SRI injection.
|
|
53
53
|
bypassDomains: ['example.com'],
|
|
54
54
|
// Optional. Suppress warnings for missing assets.
|
|
55
55
|
ignoreMissingAsset: false,
|
|
56
|
-
// Optional.
|
|
57
|
-
|
|
56
|
+
// Optional. Log verbosity: 'silent' | 'error' | 'warn' | 'info' | 'debug'. Defaults to 'warn'.
|
|
57
|
+
logLevel: 'warn'
|
|
58
58
|
})
|
|
59
59
|
]
|
|
60
60
|
});
|
|
@@ -76,14 +76,14 @@ Output:
|
|
|
76
76
|
|
|
77
77
|
## Plugin Options
|
|
78
78
|
|
|
79
|
-
* `
|
|
80
|
-
The hash algorithm used for computing SRI. Default is sha384
|
|
79
|
+
* `hashAlgorithm` (string):
|
|
80
|
+
The hash algorithm used for computing SRI. Default is `sha384`. You may change it to other supported algorithms like `sha256` or `sha512`.
|
|
81
81
|
* `bypassDomains` (Array<string>):
|
|
82
82
|
Array of domain names where SRI injection should be skipped. This allows external resources from specified domains to be excluded from SRI checks (for example, when they may not support CORS).
|
|
83
83
|
* `ignoreMissingAsset` (boolean):
|
|
84
|
-
When true, suppresses warnings for assets that are not found in the bundle. Default is false
|
|
85
|
-
* `
|
|
86
|
-
|
|
84
|
+
When true, suppresses warnings for assets that are not found in the bundle. Default is `false`.
|
|
85
|
+
* `logLevel` (string):
|
|
86
|
+
Log verbosity. One of `silent`, `error`, `warn`, `info`, `debug`. Default is `warn`. Use `debug` to see per-resource decisions during the build.
|
|
87
87
|
|
|
88
88
|
## Example Project
|
|
89
89
|
|
|
@@ -150,11 +150,11 @@ The example project shows:
|
|
|
150
150
|
|
|
151
151
|
### Debug Mode
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
Set `logLevel: 'debug'` to see detailed logs:
|
|
154
154
|
|
|
155
155
|
```javascript
|
|
156
156
|
sri({
|
|
157
|
-
|
|
157
|
+
logLevel: 'debug'
|
|
158
158
|
})
|
|
159
159
|
```
|
|
160
160
|
|
|
@@ -163,6 +163,7 @@ This will show:
|
|
|
163
163
|
- SRI hash computation
|
|
164
164
|
- CORS checks
|
|
165
165
|
- Missing asset warnings
|
|
166
|
+
- Bundle-key fallback matches (when a URL is resolved via suffix match)
|
|
166
167
|
|
|
167
168
|
## Contributing
|
|
168
169
|
|
package/dist/index.cjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
6
|
-
var path = require('path');
|
|
5
|
+
var node_crypto = require('node:crypto');
|
|
6
|
+
var path = require('node:path');
|
|
7
7
|
var fetch = require('cross-fetch');
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -237,12 +237,18 @@ async function calculateIntegrity(
|
|
|
237
237
|
const bundleItem = bundle[bundleKey];
|
|
238
238
|
|
|
239
239
|
if (!bundleItem) {
|
|
240
|
-
//
|
|
240
|
+
// Fall back to suffix match in either direction to absorb hashed
|
|
241
|
+
// filenames AND base-prefix mismatches (e.g. URL "/base/main.js" with
|
|
242
|
+
// bare bundle key "main.js"). A mismatch here just produces a wrong
|
|
243
|
+
// integrity hash, which the browser rejects — failure-closed.
|
|
241
244
|
const possibleMatch = Object.keys(bundle).find(key =>
|
|
242
245
|
key.endsWith(bundleKey) || bundleKey.endsWith(key)
|
|
243
246
|
);
|
|
244
247
|
|
|
245
248
|
if (possibleMatch) {
|
|
249
|
+
if (logger) {
|
|
250
|
+
logger.debug(`Bundle key fallback: ${bundleKey} -> ${possibleMatch}`);
|
|
251
|
+
}
|
|
246
252
|
source = bundle[possibleMatch].type === 'chunk'
|
|
247
253
|
? bundle[possibleMatch].code
|
|
248
254
|
: bundle[possibleMatch].source;
|
|
@@ -263,10 +269,10 @@ async function calculateIntegrity(
|
|
|
263
269
|
if (!source) return null
|
|
264
270
|
|
|
265
271
|
if (typeof source === 'string') {
|
|
266
|
-
return `${hashAlgorithm}-${
|
|
272
|
+
return `${hashAlgorithm}-${node_crypto.createHash(hashAlgorithm).update(source).digest('base64')}`
|
|
267
273
|
}
|
|
268
274
|
|
|
269
|
-
return `${hashAlgorithm}-${
|
|
275
|
+
return `${hashAlgorithm}-${node_crypto.createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
|
|
270
276
|
}
|
|
271
277
|
|
|
272
278
|
// Optimized regex patterns for better readability and efficiency
|
|
@@ -327,6 +333,7 @@ async function processMatch(
|
|
|
327
333
|
return {
|
|
328
334
|
integrity,
|
|
329
335
|
position: end - endOffset,
|
|
336
|
+
tagStart: match.index,
|
|
330
337
|
url // For logging
|
|
331
338
|
}
|
|
332
339
|
}
|
|
@@ -394,12 +401,21 @@ async function collectIntegrityChanges(
|
|
|
394
401
|
return changes
|
|
395
402
|
}
|
|
396
403
|
|
|
404
|
+
const CROSSORIGIN_ATTR_RE = /\bcrossorigin\s*=/i;
|
|
405
|
+
const INTEGRITY_ATTR_RE = /\bintegrity\s*=/i;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Check if integrity attribute already exists in the same tag
|
|
409
|
+
*/
|
|
410
|
+
function hasExistingIntegrity(html, tagStart, position) {
|
|
411
|
+
return INTEGRITY_ATTR_RE.test(html.slice(tagStart, position))
|
|
412
|
+
}
|
|
413
|
+
|
|
397
414
|
/**
|
|
398
|
-
* Check if
|
|
415
|
+
* Check if crossorigin attribute already exists in the same tag
|
|
399
416
|
*/
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
return segment.includes(`integrity="${integrity}"`)
|
|
417
|
+
function hasExistingCrossorigin(html, tagStart, position) {
|
|
418
|
+
return CROSSORIGIN_ATTR_RE.test(html.slice(tagStart, position))
|
|
403
419
|
}
|
|
404
420
|
|
|
405
421
|
/**
|
|
@@ -409,13 +425,16 @@ function applyIntegrityChanges(html, changes, logger) {
|
|
|
409
425
|
// Sort by position in descending order to insert from back to front
|
|
410
426
|
changes.sort((a, b) => b.position - a.position);
|
|
411
427
|
|
|
412
|
-
for (const { integrity, position, url } of changes) {
|
|
413
|
-
// Skip if integrity attribute already exists
|
|
414
|
-
if (hasExistingIntegrity(html,
|
|
428
|
+
for (const { integrity, position, tagStart, url } of changes) {
|
|
429
|
+
// Skip if integrity attribute already exists on this tag
|
|
430
|
+
if (hasExistingIntegrity(html, tagStart, position)) {
|
|
415
431
|
continue
|
|
416
432
|
}
|
|
417
433
|
|
|
418
|
-
|
|
434
|
+
let insertText = ` integrity="${integrity}"`;
|
|
435
|
+
if (!hasExistingCrossorigin(html, tagStart, position)) {
|
|
436
|
+
insertText += ' crossorigin="anonymous"';
|
|
437
|
+
}
|
|
419
438
|
html = html.slice(0, position) + insertText + html.slice(position);
|
|
420
439
|
logger.debug(`Added integrity for: ${url}`);
|
|
421
440
|
}
|
|
@@ -539,7 +558,12 @@ class Logger {
|
|
|
539
558
|
}
|
|
540
559
|
|
|
541
560
|
// Constants definition
|
|
542
|
-
|
|
561
|
+
// Vite 6/7 uses `vite:build-import-analysis`; Vite 8 Rolldown native path
|
|
562
|
+
// adds `native:import-analysis-build`. We patch whichever (or both) is present.
|
|
563
|
+
const VITE_INTERNAL_ANALYSIS_PLUGINS = [
|
|
564
|
+
'vite:build-import-analysis',
|
|
565
|
+
'native:import-analysis-build'
|
|
566
|
+
];
|
|
543
567
|
const DEFAULT_HASH_ALGORITHM = 'sha384';
|
|
544
568
|
const DEFAULT_PLUGIN_NAME = 'vite-plugin-sri4';
|
|
545
569
|
|
|
@@ -603,23 +627,31 @@ function sri(options = {}) {
|
|
|
603
627
|
);
|
|
604
628
|
};
|
|
605
629
|
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
630
|
+
const targets = config.plugins.filter(
|
|
631
|
+
p => p && VITE_INTERNAL_ANALYSIS_PLUGINS.includes(p.name)
|
|
632
|
+
);
|
|
633
|
+
if (targets.length === 0) {
|
|
634
|
+
throw new Error(
|
|
635
|
+
`[${DEFAULT_PLUGIN_NAME}] could not find a Vite import-analysis plugin to hook into ` +
|
|
636
|
+
`(looked for: ${VITE_INTERNAL_ANALYSIS_PLUGINS.join(', ')}). ` +
|
|
637
|
+
`Requires Vite 6.0.0 or higher.`
|
|
638
|
+
)
|
|
609
639
|
}
|
|
610
640
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
641
|
+
for (const plugin of targets) {
|
|
642
|
+
if (typeof plugin.generateBundle === 'object' && plugin.generateBundle.handler) {
|
|
643
|
+
const originalHandler = plugin.generateBundle.handler;
|
|
644
|
+
plugin.generateBundle.handler = async function(...args) {
|
|
645
|
+
await originalHandler.apply(this, args);
|
|
646
|
+
await generateBundle.apply(this, args);
|
|
647
|
+
};
|
|
648
|
+
} else if (typeof plugin.generateBundle === 'function') {
|
|
649
|
+
const originalHandler = plugin.generateBundle;
|
|
650
|
+
plugin.generateBundle = async function(...args) {
|
|
651
|
+
await originalHandler.apply(this, args);
|
|
652
|
+
await generateBundle.apply(this, args);
|
|
653
|
+
};
|
|
654
|
+
}
|
|
623
655
|
}
|
|
624
656
|
}
|
|
625
657
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import fetch from 'cross-fetch';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -233,12 +233,18 @@ async function calculateIntegrity(
|
|
|
233
233
|
const bundleItem = bundle[bundleKey];
|
|
234
234
|
|
|
235
235
|
if (!bundleItem) {
|
|
236
|
-
//
|
|
236
|
+
// Fall back to suffix match in either direction to absorb hashed
|
|
237
|
+
// filenames AND base-prefix mismatches (e.g. URL "/base/main.js" with
|
|
238
|
+
// bare bundle key "main.js"). A mismatch here just produces a wrong
|
|
239
|
+
// integrity hash, which the browser rejects — failure-closed.
|
|
237
240
|
const possibleMatch = Object.keys(bundle).find(key =>
|
|
238
241
|
key.endsWith(bundleKey) || bundleKey.endsWith(key)
|
|
239
242
|
);
|
|
240
243
|
|
|
241
244
|
if (possibleMatch) {
|
|
245
|
+
if (logger) {
|
|
246
|
+
logger.debug(`Bundle key fallback: ${bundleKey} -> ${possibleMatch}`);
|
|
247
|
+
}
|
|
242
248
|
source = bundle[possibleMatch].type === 'chunk'
|
|
243
249
|
? bundle[possibleMatch].code
|
|
244
250
|
: bundle[possibleMatch].source;
|
|
@@ -323,6 +329,7 @@ async function processMatch(
|
|
|
323
329
|
return {
|
|
324
330
|
integrity,
|
|
325
331
|
position: end - endOffset,
|
|
332
|
+
tagStart: match.index,
|
|
326
333
|
url // For logging
|
|
327
334
|
}
|
|
328
335
|
}
|
|
@@ -390,12 +397,21 @@ async function collectIntegrityChanges(
|
|
|
390
397
|
return changes
|
|
391
398
|
}
|
|
392
399
|
|
|
400
|
+
const CROSSORIGIN_ATTR_RE = /\bcrossorigin\s*=/i;
|
|
401
|
+
const INTEGRITY_ATTR_RE = /\bintegrity\s*=/i;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Check if integrity attribute already exists in the same tag
|
|
405
|
+
*/
|
|
406
|
+
function hasExistingIntegrity(html, tagStart, position) {
|
|
407
|
+
return INTEGRITY_ATTR_RE.test(html.slice(tagStart, position))
|
|
408
|
+
}
|
|
409
|
+
|
|
393
410
|
/**
|
|
394
|
-
* Check if
|
|
411
|
+
* Check if crossorigin attribute already exists in the same tag
|
|
395
412
|
*/
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
return segment.includes(`integrity="${integrity}"`)
|
|
413
|
+
function hasExistingCrossorigin(html, tagStart, position) {
|
|
414
|
+
return CROSSORIGIN_ATTR_RE.test(html.slice(tagStart, position))
|
|
399
415
|
}
|
|
400
416
|
|
|
401
417
|
/**
|
|
@@ -405,13 +421,16 @@ function applyIntegrityChanges(html, changes, logger) {
|
|
|
405
421
|
// Sort by position in descending order to insert from back to front
|
|
406
422
|
changes.sort((a, b) => b.position - a.position);
|
|
407
423
|
|
|
408
|
-
for (const { integrity, position, url } of changes) {
|
|
409
|
-
// Skip if integrity attribute already exists
|
|
410
|
-
if (hasExistingIntegrity(html,
|
|
424
|
+
for (const { integrity, position, tagStart, url } of changes) {
|
|
425
|
+
// Skip if integrity attribute already exists on this tag
|
|
426
|
+
if (hasExistingIntegrity(html, tagStart, position)) {
|
|
411
427
|
continue
|
|
412
428
|
}
|
|
413
429
|
|
|
414
|
-
|
|
430
|
+
let insertText = ` integrity="${integrity}"`;
|
|
431
|
+
if (!hasExistingCrossorigin(html, tagStart, position)) {
|
|
432
|
+
insertText += ' crossorigin="anonymous"';
|
|
433
|
+
}
|
|
415
434
|
html = html.slice(0, position) + insertText + html.slice(position);
|
|
416
435
|
logger.debug(`Added integrity for: ${url}`);
|
|
417
436
|
}
|
|
@@ -535,7 +554,12 @@ class Logger {
|
|
|
535
554
|
}
|
|
536
555
|
|
|
537
556
|
// Constants definition
|
|
538
|
-
|
|
557
|
+
// Vite 6/7 uses `vite:build-import-analysis`; Vite 8 Rolldown native path
|
|
558
|
+
// adds `native:import-analysis-build`. We patch whichever (or both) is present.
|
|
559
|
+
const VITE_INTERNAL_ANALYSIS_PLUGINS = [
|
|
560
|
+
'vite:build-import-analysis',
|
|
561
|
+
'native:import-analysis-build'
|
|
562
|
+
];
|
|
539
563
|
const DEFAULT_HASH_ALGORITHM = 'sha384';
|
|
540
564
|
const DEFAULT_PLUGIN_NAME = 'vite-plugin-sri4';
|
|
541
565
|
|
|
@@ -599,23 +623,31 @@ function sri(options = {}) {
|
|
|
599
623
|
);
|
|
600
624
|
};
|
|
601
625
|
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
626
|
+
const targets = config.plugins.filter(
|
|
627
|
+
p => p && VITE_INTERNAL_ANALYSIS_PLUGINS.includes(p.name)
|
|
628
|
+
);
|
|
629
|
+
if (targets.length === 0) {
|
|
630
|
+
throw new Error(
|
|
631
|
+
`[${DEFAULT_PLUGIN_NAME}] could not find a Vite import-analysis plugin to hook into ` +
|
|
632
|
+
`(looked for: ${VITE_INTERNAL_ANALYSIS_PLUGINS.join(', ')}). ` +
|
|
633
|
+
`Requires Vite 6.0.0 or higher.`
|
|
634
|
+
)
|
|
605
635
|
}
|
|
606
636
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
637
|
+
for (const plugin of targets) {
|
|
638
|
+
if (typeof plugin.generateBundle === 'object' && plugin.generateBundle.handler) {
|
|
639
|
+
const originalHandler = plugin.generateBundle.handler;
|
|
640
|
+
plugin.generateBundle.handler = async function(...args) {
|
|
641
|
+
await originalHandler.apply(this, args);
|
|
642
|
+
await generateBundle.apply(this, args);
|
|
643
|
+
};
|
|
644
|
+
} else if (typeof plugin.generateBundle === 'function') {
|
|
645
|
+
const originalHandler = plugin.generateBundle;
|
|
646
|
+
plugin.generateBundle = async function(...args) {
|
|
647
|
+
await originalHandler.apply(this, args);
|
|
648
|
+
await generateBundle.apply(this, args);
|
|
649
|
+
};
|
|
650
|
+
}
|
|
619
651
|
}
|
|
620
652
|
}
|
|
621
653
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-sri4",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "A Vite plugin to generate Subresource Integrity (SRI) hashes for output files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -23,20 +23,18 @@
|
|
|
23
23
|
"prepublishOnly": "npm run build"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"cheerio": "^1.0.0",
|
|
27
26
|
"cross-fetch": "^4.1.0"
|
|
28
27
|
},
|
|
29
28
|
"peerDependencies": {
|
|
30
|
-
"vite": "^
|
|
29
|
+
"vite": "^7.0.0 || ^8.0.0"
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
33
32
|
"@rollup/plugin-commonjs": "^25.0.7",
|
|
34
33
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
35
34
|
"@vitest/coverage-v8": "^3.2.4",
|
|
36
|
-
"cross-fetch": "^4.0.0",
|
|
37
35
|
"memfs": "^4.6.0",
|
|
38
36
|
"rollup": "^4.9.6",
|
|
39
|
-
"vite": "^
|
|
37
|
+
"vite": "^8.0.0",
|
|
40
38
|
"vitest": "^3.2.4"
|
|
41
39
|
},
|
|
42
40
|
"keywords": [
|