vite-plugin-sri4 3.0.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.
Files changed (4) hide show
  1. package/README.md +12 -11
  2. package/dist/index.cjs +442 -177
  3. package/dist/index.js +442 -177
  4. package/package.json +3 -5
package/dist/index.cjs CHANGED
@@ -2,33 +2,13 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var crypto = require('crypto');
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
- // Constants definition
10
- const VITE_INTERNAL_ANALYSIS_PLUGIN = 'vite:build-import-analysis';
11
- const DEFAULT_TIMEOUT = 5000;
12
- const DEFAULT_HASH_ALGORITHM = 'sha384';
13
- const DEFAULT_PLUGIN_NAME = 'vite-plugin-sri4';
14
-
15
- // Optimized regex patterns for better readability and efficiency
16
- const HTML_PATTERNS = {
17
- script: {
18
- regex: /<script\b[^>]*?\bsrc\s*=\s*["']([^"']+)["'][^>]*><\/script>/g,
19
- endOffset: 10
20
- },
21
- stylesheet: {
22
- regex: /<link\b[^>]*?\brel\s*=\s*["']stylesheet["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
23
- endOffset: 1
24
- },
25
- modulepreload: {
26
- regex: /<link\b[^>]*?\brel\s*=\s*["']modulepreload["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
27
- endOffset: 1
28
- }
29
- };
30
-
31
- // Extended caching mechanism with expiration time
9
+ /**
10
+ * Extended caching mechanism with expiration time
11
+ */
32
12
  class ResourceCache {
33
13
  constructor(ttl = 3600000) { // Default cache for 1 hour
34
14
  this.cache = new Map();
@@ -64,11 +44,35 @@ class ResourceCache {
64
44
  }
65
45
  }
66
46
 
67
- const urlSupportCache = new ResourceCache();
68
- const resourceCache = new ResourceCache();
47
+ /**
48
+ * Cache manager for plugin instances
49
+ */
50
+ class CacheManager {
51
+ constructor() {
52
+ this.urlSupportCache = new ResourceCache();
53
+ this.resourceCache = new ResourceCache();
54
+ }
55
+
56
+ getUrlSupportCache() {
57
+ return this.urlSupportCache
58
+ }
59
+
60
+ getResourceCache() {
61
+ return this.resourceCache
62
+ }
63
+
64
+ clearAll() {
65
+ this.urlSupportCache.clear();
66
+ this.resourceCache.clear();
67
+ }
68
+ }
69
+
70
+ const DEFAULT_TIMEOUT = 5000;
69
71
 
70
- // Check if URL is from a bypass domain
71
- function isUrlFromBypassDomain(url, bypassDomains = []) {
72
+ /**
73
+ * Check if URL is from a bypass domain
74
+ */
75
+ function isUrlFromBypassDomain(url, bypassDomains = [], logger = null) {
72
76
  if (!url || typeof url !== 'string' || !url.startsWith('http')) return false
73
77
 
74
78
  try {
@@ -77,13 +81,17 @@ function isUrlFromBypassDomain(url, bypassDomains = []) {
77
81
  urlObj.hostname === domain || urlObj.hostname.endsWith(`.${domain}`)
78
82
  )
79
83
  } catch (error) {
80
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Invalid URL: ${url}`, error);
84
+ if (logger) {
85
+ logger.warn(`Invalid URL: ${url}`, error);
86
+ }
81
87
  return false
82
88
  }
83
89
  }
84
90
 
85
- // Resource check with retry mechanism
86
- async function checkResourceSupport(url, retries = 2) {
91
+ /**
92
+ * Resource check with retry mechanism
93
+ */
94
+ async function checkResourceSupport(url, urlSupportCache, logger = null, retries = 2) {
87
95
  if (urlSupportCache.has(url)) {
88
96
  return urlSupportCache.get(url)
89
97
  }
@@ -108,7 +116,9 @@ async function checkResourceSupport(url, retries = 2) {
108
116
  } catch (error) {
109
117
  lastError = error;
110
118
  if (error.name === 'AbortError') {
111
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Resource check timed out: ${url}`);
119
+ if (logger) {
120
+ logger.warn(`Resource check timed out: ${url}`);
121
+ }
112
122
  break // Don't retry timeouts
113
123
  }
114
124
 
@@ -119,13 +129,17 @@ async function checkResourceSupport(url, retries = 2) {
119
129
  }
120
130
  }
121
131
 
122
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Failed to check resource support: ${url}`, lastError);
132
+ if (logger) {
133
+ logger.warn(`Failed to check resource support: ${url}`, lastError);
134
+ }
123
135
  urlSupportCache.set(url, false);
124
136
  return false
125
137
  }
126
138
 
127
- // Optimized resource fetching function with retry mechanism and caching
128
- async function fetchResource(url, retries = 1) {
139
+ /**
140
+ * Optimized resource fetching function with retry mechanism and caching
141
+ */
142
+ async function fetchResource(url, resourceCache, logger = null, retries = 1) {
129
143
  // Check cache
130
144
  if (resourceCache.has(url)) {
131
145
  return resourceCache.get(url)
@@ -150,7 +164,9 @@ async function fetchResource(url, retries = 1) {
150
164
  } catch (error) {
151
165
  lastError = error;
152
166
  if (error.name === 'AbortError') {
153
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Resource fetch timed out: ${url}`);
167
+ if (logger) {
168
+ logger.warn(`Resource fetch timed out: ${url}`);
169
+ }
154
170
  break // Don't retry timeouts
155
171
  }
156
172
 
@@ -160,144 +176,397 @@ async function fetchResource(url, retries = 1) {
160
176
  }
161
177
  }
162
178
 
163
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Failed to fetch external resource: ${url}`, lastError);
179
+ if (logger) {
180
+ logger.warn(`Failed to fetch external resource: ${url}`, lastError);
181
+ }
164
182
  return null
165
183
  }
166
184
 
167
- function createTransformer(options, config) {
168
- const {
169
- ignoreMissingAsset,
170
- bypassDomains,
171
- hashAlgorithm = DEFAULT_HASH_ALGORITHM
172
- } = options;
173
-
174
- // Improved method for getting bundle keys
175
- const getBundleKey = (htmlPath, url) => {
176
- // Handle absolute path URLs
177
- if (url.startsWith('/')) {
178
- // Remove leading slash to match keys in bundle
179
- return url.substring(1)
180
- }
181
-
182
- // Handle relative paths (when config.base is relative)
183
- if (config.base === './' || config.base === '') {
184
- return path.posix.resolve(path.posix.dirname(htmlPath), url)
185
- }
185
+ /**
186
+ * Improved method for getting bundle keys
187
+ */
188
+ function getBundleKey(htmlPath, url, config) {
189
+ // Handle absolute path URLs
190
+ if (url.startsWith('/')) {
191
+ // Remove leading slash to match keys in bundle
192
+ return url.substring(1)
193
+ }
186
194
 
187
- // Handle other cases, remove base prefix from URL
188
- return url.startsWith(config.base)
189
- ? url.substring(config.base.length)
190
- : url
191
- };
195
+ // Handle relative paths (when config.base is relative)
196
+ if (config.base === './' || config.base === '') {
197
+ return path.posix.resolve(path.posix.dirname(htmlPath), url)
198
+ }
192
199
 
193
- const calculateIntegrity = async (bundle, htmlPath, url) => {
194
- // Skip specified domains
195
- if (isUrlFromBypassDomain(url, bypassDomains)) {
196
- return null
197
- }
200
+ // Handle other cases, remove base prefix from URL
201
+ return url.startsWith(config.base)
202
+ ? url.substring(config.base.length)
203
+ : url
204
+ }
198
205
 
199
- let source;
200
- if (url.startsWith('http')) {
201
- const isSupported = await checkResourceSupport(url);
202
- if (!isSupported) return null
203
- source = await fetchResource(url);
204
- if (!source) return null
205
- } else {
206
- const bundleKey = getBundleKey(htmlPath, url);
207
- const bundleItem = bundle[bundleKey];
206
+ /**
207
+ * Calculate SRI integrity hash for a given resource
208
+ */
209
+ async function calculateIntegrity(
210
+ bundle,
211
+ htmlPath,
212
+ url,
213
+ options,
214
+ config,
215
+ cacheManager,
216
+ logger = null
217
+ ) {
218
+ const {
219
+ ignoreMissingAsset,
220
+ bypassDomains,
221
+ hashAlgorithm
222
+ } = options;
208
223
 
209
- if (!bundleItem) {
210
- // Try to find a matching item with more flexible matching
211
- const possibleMatch = Object.keys(bundle).find(key =>
212
- key.endsWith(bundleKey) || bundleKey.endsWith(key)
213
- );
224
+ // Skip specified domains
225
+ if (isUrlFromBypassDomain(url, bypassDomains, logger)) {
226
+ return null
227
+ }
214
228
 
215
- if (possibleMatch) {
216
- source = bundle[possibleMatch].type === 'chunk'
217
- ? bundle[possibleMatch].code
218
- : bundle[possibleMatch].source;
219
- } else if (ignoreMissingAsset) {
220
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Asset not found in bundle: ${url} (path: ${htmlPath}, key: ${bundleKey})`);
221
- return null
222
- } else {
223
- throw new Error(`Asset ${url} not found in bundle (path: ${htmlPath}, key: ${bundleKey})`)
229
+ let source;
230
+ if (url.startsWith('http')) {
231
+ const isSupported = await checkResourceSupport(url, cacheManager.getUrlSupportCache(), logger);
232
+ if (!isSupported) return null
233
+ source = await fetchResource(url, cacheManager.getResourceCache(), logger);
234
+ if (!source) return null
235
+ } else {
236
+ const bundleKey = getBundleKey(htmlPath, url, config);
237
+ const bundleItem = bundle[bundleKey];
238
+
239
+ if (!bundleItem) {
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.
244
+ const possibleMatch = Object.keys(bundle).find(key =>
245
+ key.endsWith(bundleKey) || bundleKey.endsWith(key)
246
+ );
247
+
248
+ if (possibleMatch) {
249
+ if (logger) {
250
+ logger.debug(`Bundle key fallback: ${bundleKey} -> ${possibleMatch}`);
251
+ }
252
+ source = bundle[possibleMatch].type === 'chunk'
253
+ ? bundle[possibleMatch].code
254
+ : bundle[possibleMatch].source;
255
+ } else if (ignoreMissingAsset) {
256
+ if (logger) {
257
+ logger.warn(`Asset not found in bundle: ${url} (path: ${htmlPath}, key: ${bundleKey})`);
224
258
  }
259
+ return null
225
260
  } else {
226
- source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
261
+ throw new Error(`Asset ${url} not found in bundle (path: ${htmlPath}, key: ${bundleKey})`)
227
262
  }
263
+ } else {
264
+ source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
228
265
  }
266
+ }
229
267
 
230
- // Ensure source is a Uint8Array or string
231
- if (!source) return null
268
+ // Ensure source is a Uint8Array or string
269
+ if (!source) return null
270
+
271
+ if (typeof source === 'string') {
272
+ return `${hashAlgorithm}-${node_crypto.createHash(hashAlgorithm).update(source).digest('base64')}`
273
+ }
274
+
275
+ return `${hashAlgorithm}-${node_crypto.createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
276
+ }
277
+
278
+ // Optimized regex patterns for better readability and efficiency
279
+ const HTML_PATTERNS = {
280
+ script: {
281
+ regex: /<script\b[^>]*?\bsrc\s*=\s*["']([^"']+)["'][^>]*><\/script>/g,
282
+ endOffset: 10
283
+ },
284
+ stylesheet: {
285
+ regex: /<link\b[^>]*?\brel\s*=\s*["']stylesheet["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
286
+ endOffset: 1
287
+ },
288
+ modulepreload: {
289
+ regex: /<link\b[^>]*?\brel\s*=\s*["']modulepreload["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
290
+ endOffset: 1
291
+ }
292
+ };
293
+
294
+ /**
295
+ * Validate HTML input
296
+ */
297
+ function validateHtmlInput(html, htmlPath, logger) {
298
+ if (!html || typeof html !== 'string') {
299
+ logger.warn(`Invalid HTML content for ${htmlPath}`);
300
+ return false
301
+ }
302
+ return true
303
+ }
232
304
 
233
- if (typeof source === 'string') {
234
- return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(source).digest('base64')}`
305
+ /**
306
+ * Process a single match to create an integrity change object
307
+ */
308
+ async function processMatch(
309
+ match,
310
+ endOffset,
311
+ bundle,
312
+ htmlPath,
313
+ options,
314
+ config,
315
+ cacheManager,
316
+ logger
317
+ ) {
318
+ const [, url] = match;
319
+ if (!url) return null
320
+
321
+ const end = match.index + match[0].length;
322
+ const integrity = await calculateIntegrity(
323
+ bundle,
324
+ htmlPath,
325
+ url,
326
+ options,
327
+ config,
328
+ cacheManager,
329
+ logger
330
+ );
331
+
332
+ if (integrity) {
333
+ return {
334
+ integrity,
335
+ position: end - endOffset,
336
+ tagStart: match.index,
337
+ url // For logging
235
338
  }
339
+ }
340
+ return null
341
+ }
342
+
343
+ /**
344
+ * Process matches for a specific HTML pattern
345
+ */
346
+ async function processPatternMatches(
347
+ html,
348
+ pattern,
349
+ bundle,
350
+ htmlPath,
351
+ options,
352
+ config,
353
+ cacheManager,
354
+ logger
355
+ ) {
356
+ const { regex, endOffset } = pattern;
357
+ const matches = [...html.matchAll(regex)];
358
+
359
+ // Process each match in parallel
360
+ const matchResults = await Promise.all(
361
+ matches.map(match =>
362
+ processMatch(match, endOffset, bundle, htmlPath, options, config, cacheManager, logger)
363
+ )
364
+ );
365
+
366
+ // Filter out null results
367
+ return matchResults.filter(Boolean)
368
+ }
369
+
370
+ /**
371
+ * Collect all integrity changes from HTML patterns
372
+ */
373
+ async function collectIntegrityChanges(
374
+ html,
375
+ bundle,
376
+ htmlPath,
377
+ options,
378
+ config,
379
+ cacheManager,
380
+ logger
381
+ ) {
382
+ const changes = [];
383
+
384
+ // Collect changes from all patterns in parallel
385
+ await Promise.all(
386
+ Object.values(HTML_PATTERNS).map(async pattern => {
387
+ const patternChanges = await processPatternMatches(
388
+ html,
389
+ pattern,
390
+ bundle,
391
+ htmlPath,
392
+ options,
393
+ config,
394
+ cacheManager,
395
+ logger
396
+ );
397
+ changes.push(...patternChanges);
398
+ })
399
+ );
400
+
401
+ return changes
402
+ }
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
+ }
236
413
 
237
- return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
238
- };
414
+ /**
415
+ * Check if crossorigin attribute already exists in the same tag
416
+ */
417
+ function hasExistingCrossorigin(html, tagStart, position) {
418
+ return CROSSORIGIN_ATTR_RE.test(html.slice(tagStart, position))
419
+ }
239
420
 
240
- const transformHTML = async (bundle, htmlPath, html) => {
241
- if (!html || typeof html !== 'string') {
242
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Invalid HTML content for ${htmlPath}`);
243
- return html
421
+ /**
422
+ * Apply integrity changes to HTML content
423
+ */
424
+ function applyIntegrityChanges(html, changes, logger) {
425
+ // Sort by position in descending order to insert from back to front
426
+ changes.sort((a, b) => b.position - a.position);
427
+
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)) {
431
+ continue
244
432
  }
245
433
 
246
- const changes = [];
434
+ let insertText = ` integrity="${integrity}"`;
435
+ if (!hasExistingCrossorigin(html, tagStart, position)) {
436
+ insertText += ' crossorigin="anonymous"';
437
+ }
438
+ html = html.slice(0, position) + insertText + html.slice(position);
439
+ logger.debug(`Added integrity for: ${url}`);
440
+ }
247
441
 
248
- // Collect changes from all patterns in parallel
249
- await Promise.all(
250
- Object.values(HTML_PATTERNS).map(async ({ regex, endOffset }) => {
251
- const matches = [...html.matchAll(regex)];
442
+ return html
443
+ }
252
444
 
253
- // Process each match in parallel
254
- const matchResults = await Promise.all(
255
- matches.map(async match => {
256
- const [, url] = match;
257
- if (!url) return null
445
+ /**
446
+ * Transform HTML by adding SRI integrity attributes
447
+ */
448
+ async function transformHTML(
449
+ bundle,
450
+ htmlPath,
451
+ html,
452
+ options,
453
+ config,
454
+ cacheManager,
455
+ logger
456
+ ) {
457
+ if (!validateHtmlInput(html, htmlPath, logger)) {
458
+ return html
459
+ }
258
460
 
259
- const end = match.index + match[0].length;
260
- const integrity = await calculateIntegrity(bundle, htmlPath, url);
461
+ const changes = await collectIntegrityChanges(
462
+ html,
463
+ bundle,
464
+ htmlPath,
465
+ options,
466
+ config,
467
+ cacheManager,
468
+ logger
469
+ );
470
+
471
+ return applyIntegrityChanges(html, changes, logger)
472
+ }
261
473
 
262
- if (integrity) {
263
- return {
264
- integrity,
265
- position: end - endOffset,
266
- url // For logging
267
- }
268
- }
269
- return null
270
- })
271
- );
474
+ /**
475
+ * Create HTML transformer with given options and config
476
+ */
477
+ function createTransformer(options, config, cacheManager, logger) {
478
+ return {
479
+ transformHTML: (bundle, htmlPath, html) =>
480
+ transformHTML(bundle, htmlPath, html, options, config, cacheManager, logger),
481
+ calculateIntegrity: (bundle, htmlPath, url) =>
482
+ calculateIntegrity(bundle, htmlPath, url, options, config, cacheManager, logger)
483
+ }
484
+ }
272
485
 
273
- // Filter out null results
274
- matchResults.filter(Boolean).forEach(result => changes.push(result));
275
- })
276
- );
486
+ const DEFAULT_PLUGIN_NAME$1 = 'vite-plugin-sri4';
487
+
488
+ /**
489
+ * Logger class to handle different log levels without hijacking global console
490
+ */
491
+ class Logger {
492
+ constructor(logLevel = 'warn', pluginName = DEFAULT_PLUGIN_NAME$1) {
493
+ this.logLevel = logLevel;
494
+ this.pluginName = pluginName;
495
+ this.levels = {
496
+ silent: 0,
497
+ error: 1,
498
+ warn: 2,
499
+ info: 3,
500
+ debug: 4
501
+ };
502
+ this.currentLevel = this.levels[logLevel] || this.levels.warn;
503
+ }
277
504
 
278
- // Sort by position in descending order to insert from back to front (won't affect insertion points ahead)
279
- changes.sort((a, b) => b.position - a.position);
505
+ /**
506
+ * Format message with plugin name prefix
507
+ */
508
+ formatMessage(message, ...args) {
509
+ const prefix = `[${this.pluginName}]`;
510
+ if (typeof message === 'string') {
511
+ return [prefix + ' ' + message, ...args]
512
+ }
513
+ return [prefix, message, ...args]
514
+ }
280
515
 
281
- // Check if identical integrity attributes already exist to avoid duplicates
282
- for (const { integrity, position, url } of changes) {
283
- const insertText = ` integrity="${integrity}"`;
516
+ /**
517
+ * Log error messages
518
+ */
519
+ error(message, ...args) {
520
+ if (this.currentLevel >= this.levels.error) {
521
+ console.error(...this.formatMessage(message, ...args));
522
+ }
523
+ }
284
524
 
285
- // Check if integrity attribute already exists
286
- const segment = html.slice(Math.max(0, position - 100), position + 100);
287
- if (segment.includes(`integrity="${integrity}"`)) {
288
- continue // Skip elements that already have the same integrity
289
- }
525
+ /**
526
+ * Log warning messages
527
+ */
528
+ warn(message, ...args) {
529
+ if (this.currentLevel >= this.levels.warn) {
530
+ console.warn(...this.formatMessage(message, ...args));
531
+ }
532
+ }
290
533
 
291
- html = html.slice(0, position) + insertText + html.slice(position);
292
- console.debug(`[${DEFAULT_PLUGIN_NAME}] Added integrity for: ${url}`);
534
+ /**
535
+ * Log info messages
536
+ */
537
+ info(message, ...args) {
538
+ if (this.currentLevel >= this.levels.info) {
539
+ console.info(...this.formatMessage(message, ...args));
293
540
  }
541
+ }
294
542
 
295
- return html
296
- };
543
+ /**
544
+ * Log debug messages
545
+ */
546
+ debug(message, ...args) {
547
+ if (this.currentLevel >= this.levels.debug) {
548
+ console.debug(...this.formatMessage(message, ...args));
549
+ }
550
+ }
297
551
 
298
- return { transformHTML, calculateIntegrity }
552
+ /**
553
+ * Create a child logger with the same configuration
554
+ */
555
+ child(name) {
556
+ return new Logger(this.logLevel, `${this.pluginName}:${name}`)
557
+ }
299
558
  }
300
559
 
560
+ // Constants definition
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
+ ];
567
+ const DEFAULT_HASH_ALGORITHM = 'sha384';
568
+ const DEFAULT_PLUGIN_NAME = 'vite-plugin-sri4';
569
+
301
570
  function sri(options = {}) {
302
571
  const {
303
572
  ignoreMissingAsset = false,
@@ -306,16 +575,9 @@ function sri(options = {}) {
306
575
  logLevel = 'warn'
307
576
  } = options;
308
577
 
309
- // Adjust log level
310
- const originalConsoleWarn = console.warn;
311
- const originalConsoleDebug = console.debug;
312
-
313
- if (logLevel === 'error') {
314
- console.warn = () => {};
315
- console.debug = () => {};
316
- } else if (logLevel === 'warn') {
317
- console.debug = () => {};
318
- }
578
+ // Create cache manager and logger instances for this plugin instance
579
+ const cacheManager = new CacheManager();
580
+ const logger = new Logger(logLevel, DEFAULT_PLUGIN_NAME);
319
581
 
320
582
  return {
321
583
  name: DEFAULT_PLUGIN_NAME,
@@ -324,13 +586,8 @@ function sri(options = {}) {
324
586
 
325
587
  // Cleanup work
326
588
  buildEnd() {
327
- // Restore console functions
328
- console.warn = originalConsoleWarn;
329
- console.debug = originalConsoleDebug;
330
-
331
589
  // Clear caches
332
- urlSupportCache.clear();
333
- resourceCache.clear();
590
+ cacheManager.clearAll();
334
591
  },
335
592
 
336
593
  configResolved(config) {
@@ -338,7 +595,7 @@ function sri(options = {}) {
338
595
  ignoreMissingAsset,
339
596
  bypassDomains,
340
597
  hashAlgorithm
341
- }, config);
598
+ }, config, cacheManager, logger);
342
599
 
343
600
  const generateBundle = async function(_, bundle) {
344
601
  const htmlFiles = Object.entries(bundle).filter(
@@ -348,7 +605,7 @@ function sri(options = {}) {
348
605
  );
349
606
 
350
607
  if (htmlFiles.length === 0) {
351
- console.debug(`[${DEFAULT_PLUGIN_NAME}] No HTML files found in bundle`);
608
+ logger.debug('No HTML files found in bundle');
352
609
  return
353
610
  }
354
611
 
@@ -360,33 +617,41 @@ function sri(options = {}) {
360
617
  chunk.source = await transformer.transformHTML(bundle, name, originalContent);
361
618
 
362
619
  if (originalContent !== chunk.source) {
363
- console.debug(`[${DEFAULT_PLUGIN_NAME}] SRI attributes added to ${name}`);
620
+ logger.debug(`SRI attributes added to ${name}`);
364
621
  }
365
622
  } catch (error) {
366
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Error processing ${name}:`, error);
623
+ logger.warn(`Error processing ${name}:`, error);
367
624
  // Keep original content on error
368
625
  }
369
626
  })
370
627
  );
371
628
  };
372
629
 
373
- const plugin = config.plugins.find(p => p.name === VITE_INTERNAL_ANALYSIS_PLUGIN);
374
- if (!plugin) {
375
- throw new Error(`[${DEFAULT_PLUGIN_NAME}] requires Vite 2.0.0 or higher`)
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
+ )
376
639
  }
377
640
 
378
- if (typeof plugin.generateBundle === 'object' && plugin.generateBundle.handler) {
379
- const originalHandler = plugin.generateBundle.handler;
380
- plugin.generateBundle.handler = async function(...args) {
381
- await originalHandler.apply(this, args);
382
- await generateBundle.apply(this, args);
383
- };
384
- } else if (typeof plugin.generateBundle === 'function') {
385
- const originalHandler = plugin.generateBundle;
386
- plugin.generateBundle = async function(...args) {
387
- await originalHandler.apply(this, args);
388
- await generateBundle.apply(this, args);
389
- };
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
+ }
390
655
  }
391
656
  }
392
657
  }