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