vite-plugin-sri4 3.0.0 → 3.1.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 (3) hide show
  1. package/dist/index.cjs +393 -160
  2. package/dist/index.js +393 -160
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -6,29 +6,9 @@ var crypto = require('crypto');
6
6
  var path = require('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
+ }
69
55
 
70
- // Check if URL is from a bypass domain
71
- function isUrlFromBypassDomain(url, bypassDomains = []) {
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;
71
+
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,373 @@ 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
+ // Try to find a matching item with more flexible matching
241
+ const possibleMatch = Object.keys(bundle).find(key =>
242
+ key.endsWith(bundleKey) || bundleKey.endsWith(key)
243
+ );
244
+
245
+ if (possibleMatch) {
246
+ source = bundle[possibleMatch].type === 'chunk'
247
+ ? bundle[possibleMatch].code
248
+ : bundle[possibleMatch].source;
249
+ } else if (ignoreMissingAsset) {
250
+ if (logger) {
251
+ logger.warn(`Asset not found in bundle: ${url} (path: ${htmlPath}, key: ${bundleKey})`);
224
252
  }
253
+ return null
225
254
  } else {
226
- source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
255
+ throw new Error(`Asset ${url} not found in bundle (path: ${htmlPath}, key: ${bundleKey})`)
227
256
  }
257
+ } else {
258
+ source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
228
259
  }
260
+ }
229
261
 
230
- // Ensure source is a Uint8Array or string
231
- if (!source) return null
262
+ // Ensure source is a Uint8Array or string
263
+ if (!source) return null
264
+
265
+ if (typeof source === 'string') {
266
+ return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(source).digest('base64')}`
267
+ }
268
+
269
+ return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
270
+ }
271
+
272
+ // Optimized regex patterns for better readability and efficiency
273
+ const HTML_PATTERNS = {
274
+ script: {
275
+ regex: /<script\b[^>]*?\bsrc\s*=\s*["']([^"']+)["'][^>]*><\/script>/g,
276
+ endOffset: 10
277
+ },
278
+ stylesheet: {
279
+ regex: /<link\b[^>]*?\brel\s*=\s*["']stylesheet["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
280
+ endOffset: 1
281
+ },
282
+ modulepreload: {
283
+ regex: /<link\b[^>]*?\brel\s*=\s*["']modulepreload["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
284
+ endOffset: 1
285
+ }
286
+ };
232
287
 
233
- if (typeof source === 'string') {
234
- return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(source).digest('base64')}`
288
+ /**
289
+ * Validate HTML input
290
+ */
291
+ function validateHtmlInput(html, htmlPath, logger) {
292
+ if (!html || typeof html !== 'string') {
293
+ logger.warn(`Invalid HTML content for ${htmlPath}`);
294
+ return false
295
+ }
296
+ return true
297
+ }
298
+
299
+ /**
300
+ * Process a single match to create an integrity change object
301
+ */
302
+ async function processMatch(
303
+ match,
304
+ endOffset,
305
+ bundle,
306
+ htmlPath,
307
+ options,
308
+ config,
309
+ cacheManager,
310
+ logger
311
+ ) {
312
+ const [, url] = match;
313
+ if (!url) return null
314
+
315
+ const end = match.index + match[0].length;
316
+ const integrity = await calculateIntegrity(
317
+ bundle,
318
+ htmlPath,
319
+ url,
320
+ options,
321
+ config,
322
+ cacheManager,
323
+ logger
324
+ );
325
+
326
+ if (integrity) {
327
+ return {
328
+ integrity,
329
+ position: end - endOffset,
330
+ url // For logging
235
331
  }
332
+ }
333
+ return null
334
+ }
335
+
336
+ /**
337
+ * Process matches for a specific HTML pattern
338
+ */
339
+ async function processPatternMatches(
340
+ html,
341
+ pattern,
342
+ bundle,
343
+ htmlPath,
344
+ options,
345
+ config,
346
+ cacheManager,
347
+ logger
348
+ ) {
349
+ const { regex, endOffset } = pattern;
350
+ const matches = [...html.matchAll(regex)];
351
+
352
+ // Process each match in parallel
353
+ const matchResults = await Promise.all(
354
+ matches.map(match =>
355
+ processMatch(match, endOffset, bundle, htmlPath, options, config, cacheManager, logger)
356
+ )
357
+ );
236
358
 
237
- return `${hashAlgorithm}-${crypto.createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
238
- };
359
+ // Filter out null results
360
+ return matchResults.filter(Boolean)
361
+ }
239
362
 
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
363
+ /**
364
+ * Collect all integrity changes from HTML patterns
365
+ */
366
+ async function collectIntegrityChanges(
367
+ html,
368
+ bundle,
369
+ htmlPath,
370
+ options,
371
+ config,
372
+ cacheManager,
373
+ logger
374
+ ) {
375
+ const changes = [];
376
+
377
+ // Collect changes from all patterns in parallel
378
+ await Promise.all(
379
+ Object.values(HTML_PATTERNS).map(async pattern => {
380
+ const patternChanges = await processPatternMatches(
381
+ html,
382
+ pattern,
383
+ bundle,
384
+ htmlPath,
385
+ options,
386
+ config,
387
+ cacheManager,
388
+ logger
389
+ );
390
+ changes.push(...patternChanges);
391
+ })
392
+ );
393
+
394
+ return changes
395
+ }
396
+
397
+ /**
398
+ * Check if integrity attribute already exists in HTML segment
399
+ */
400
+ function hasExistingIntegrity(html, position, integrity) {
401
+ const segment = html.slice(Math.max(0, position - 100), position + 100);
402
+ return segment.includes(`integrity="${integrity}"`)
403
+ }
404
+
405
+ /**
406
+ * Apply integrity changes to HTML content
407
+ */
408
+ function applyIntegrityChanges(html, changes, logger) {
409
+ // Sort by position in descending order to insert from back to front
410
+ changes.sort((a, b) => b.position - a.position);
411
+
412
+ for (const { integrity, position, url } of changes) {
413
+ // Skip if integrity attribute already exists
414
+ if (hasExistingIntegrity(html, position, integrity)) {
415
+ continue
244
416
  }
245
417
 
246
- const changes = [];
418
+ const insertText = ` integrity="${integrity}"`;
419
+ html = html.slice(0, position) + insertText + html.slice(position);
420
+ logger.debug(`Added integrity for: ${url}`);
421
+ }
247
422
 
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)];
423
+ return html
424
+ }
252
425
 
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
426
+ /**
427
+ * Transform HTML by adding SRI integrity attributes
428
+ */
429
+ async function transformHTML(
430
+ bundle,
431
+ htmlPath,
432
+ html,
433
+ options,
434
+ config,
435
+ cacheManager,
436
+ logger
437
+ ) {
438
+ if (!validateHtmlInput(html, htmlPath, logger)) {
439
+ return html
440
+ }
258
441
 
259
- const end = match.index + match[0].length;
260
- const integrity = await calculateIntegrity(bundle, htmlPath, url);
442
+ const changes = await collectIntegrityChanges(
443
+ html,
444
+ bundle,
445
+ htmlPath,
446
+ options,
447
+ config,
448
+ cacheManager,
449
+ logger
450
+ );
451
+
452
+ return applyIntegrityChanges(html, changes, logger)
453
+ }
261
454
 
262
- if (integrity) {
263
- return {
264
- integrity,
265
- position: end - endOffset,
266
- url // For logging
267
- }
268
- }
269
- return null
270
- })
271
- );
455
+ /**
456
+ * Create HTML transformer with given options and config
457
+ */
458
+ function createTransformer(options, config, cacheManager, logger) {
459
+ return {
460
+ transformHTML: (bundle, htmlPath, html) =>
461
+ transformHTML(bundle, htmlPath, html, options, config, cacheManager, logger),
462
+ calculateIntegrity: (bundle, htmlPath, url) =>
463
+ calculateIntegrity(bundle, htmlPath, url, options, config, cacheManager, logger)
464
+ }
465
+ }
272
466
 
273
- // Filter out null results
274
- matchResults.filter(Boolean).forEach(result => changes.push(result));
275
- })
276
- );
467
+ const DEFAULT_PLUGIN_NAME$1 = 'vite-plugin-sri4';
468
+
469
+ /**
470
+ * Logger class to handle different log levels without hijacking global console
471
+ */
472
+ class Logger {
473
+ constructor(logLevel = 'warn', pluginName = DEFAULT_PLUGIN_NAME$1) {
474
+ this.logLevel = logLevel;
475
+ this.pluginName = pluginName;
476
+ this.levels = {
477
+ silent: 0,
478
+ error: 1,
479
+ warn: 2,
480
+ info: 3,
481
+ debug: 4
482
+ };
483
+ this.currentLevel = this.levels[logLevel] || this.levels.warn;
484
+ }
277
485
 
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);
486
+ /**
487
+ * Format message with plugin name prefix
488
+ */
489
+ formatMessage(message, ...args) {
490
+ const prefix = `[${this.pluginName}]`;
491
+ if (typeof message === 'string') {
492
+ return [prefix + ' ' + message, ...args]
493
+ }
494
+ return [prefix, message, ...args]
495
+ }
280
496
 
281
- // Check if identical integrity attributes already exist to avoid duplicates
282
- for (const { integrity, position, url } of changes) {
283
- const insertText = ` integrity="${integrity}"`;
497
+ /**
498
+ * Log error messages
499
+ */
500
+ error(message, ...args) {
501
+ if (this.currentLevel >= this.levels.error) {
502
+ console.error(...this.formatMessage(message, ...args));
503
+ }
504
+ }
284
505
 
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
- }
506
+ /**
507
+ * Log warning messages
508
+ */
509
+ warn(message, ...args) {
510
+ if (this.currentLevel >= this.levels.warn) {
511
+ console.warn(...this.formatMessage(message, ...args));
512
+ }
513
+ }
290
514
 
291
- html = html.slice(0, position) + insertText + html.slice(position);
292
- console.debug(`[${DEFAULT_PLUGIN_NAME}] Added integrity for: ${url}`);
515
+ /**
516
+ * Log info messages
517
+ */
518
+ info(message, ...args) {
519
+ if (this.currentLevel >= this.levels.info) {
520
+ console.info(...this.formatMessage(message, ...args));
293
521
  }
522
+ }
294
523
 
295
- return html
296
- };
524
+ /**
525
+ * Log debug messages
526
+ */
527
+ debug(message, ...args) {
528
+ if (this.currentLevel >= this.levels.debug) {
529
+ console.debug(...this.formatMessage(message, ...args));
530
+ }
531
+ }
297
532
 
298
- return { transformHTML, calculateIntegrity }
533
+ /**
534
+ * Create a child logger with the same configuration
535
+ */
536
+ child(name) {
537
+ return new Logger(this.logLevel, `${this.pluginName}:${name}`)
538
+ }
299
539
  }
300
540
 
541
+ // Constants definition
542
+ const VITE_INTERNAL_ANALYSIS_PLUGIN = 'vite:build-import-analysis';
543
+ const DEFAULT_HASH_ALGORITHM = 'sha384';
544
+ const DEFAULT_PLUGIN_NAME = 'vite-plugin-sri4';
545
+
301
546
  function sri(options = {}) {
302
547
  const {
303
548
  ignoreMissingAsset = false,
@@ -306,16 +551,9 @@ function sri(options = {}) {
306
551
  logLevel = 'warn'
307
552
  } = options;
308
553
 
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
- }
554
+ // Create cache manager and logger instances for this plugin instance
555
+ const cacheManager = new CacheManager();
556
+ const logger = new Logger(logLevel, DEFAULT_PLUGIN_NAME);
319
557
 
320
558
  return {
321
559
  name: DEFAULT_PLUGIN_NAME,
@@ -324,13 +562,8 @@ function sri(options = {}) {
324
562
 
325
563
  // Cleanup work
326
564
  buildEnd() {
327
- // Restore console functions
328
- console.warn = originalConsoleWarn;
329
- console.debug = originalConsoleDebug;
330
-
331
565
  // Clear caches
332
- urlSupportCache.clear();
333
- resourceCache.clear();
566
+ cacheManager.clearAll();
334
567
  },
335
568
 
336
569
  configResolved(config) {
@@ -338,7 +571,7 @@ function sri(options = {}) {
338
571
  ignoreMissingAsset,
339
572
  bypassDomains,
340
573
  hashAlgorithm
341
- }, config);
574
+ }, config, cacheManager, logger);
342
575
 
343
576
  const generateBundle = async function(_, bundle) {
344
577
  const htmlFiles = Object.entries(bundle).filter(
@@ -348,7 +581,7 @@ function sri(options = {}) {
348
581
  );
349
582
 
350
583
  if (htmlFiles.length === 0) {
351
- console.debug(`[${DEFAULT_PLUGIN_NAME}] No HTML files found in bundle`);
584
+ logger.debug('No HTML files found in bundle');
352
585
  return
353
586
  }
354
587
 
@@ -360,10 +593,10 @@ function sri(options = {}) {
360
593
  chunk.source = await transformer.transformHTML(bundle, name, originalContent);
361
594
 
362
595
  if (originalContent !== chunk.source) {
363
- console.debug(`[${DEFAULT_PLUGIN_NAME}] SRI attributes added to ${name}`);
596
+ logger.debug(`SRI attributes added to ${name}`);
364
597
  }
365
598
  } catch (error) {
366
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Error processing ${name}:`, error);
599
+ logger.warn(`Error processing ${name}:`, error);
367
600
  // Keep original content on error
368
601
  }
369
602
  })
package/dist/index.js CHANGED
@@ -2,29 +2,9 @@ import { createHash } from 'crypto';
2
2
  import path from '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
+ }
65
51
 
66
- // Check if URL is from a bypass domain
67
- function isUrlFromBypassDomain(url, bypassDomains = []) {
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;
67
+
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,373 @@ 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
+ // Try to find a matching item with more flexible matching
237
+ const possibleMatch = Object.keys(bundle).find(key =>
238
+ key.endsWith(bundleKey) || bundleKey.endsWith(key)
239
+ );
240
+
241
+ if (possibleMatch) {
242
+ source = bundle[possibleMatch].type === 'chunk'
243
+ ? bundle[possibleMatch].code
244
+ : bundle[possibleMatch].source;
245
+ } else if (ignoreMissingAsset) {
246
+ if (logger) {
247
+ logger.warn(`Asset not found in bundle: ${url} (path: ${htmlPath}, key: ${bundleKey})`);
220
248
  }
249
+ return null
221
250
  } else {
222
- source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
251
+ throw new Error(`Asset ${url} not found in bundle (path: ${htmlPath}, key: ${bundleKey})`)
223
252
  }
253
+ } else {
254
+ source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
224
255
  }
256
+ }
225
257
 
226
- // Ensure source is a Uint8Array or string
227
- if (!source) return null
258
+ // Ensure source is a Uint8Array or string
259
+ if (!source) return null
260
+
261
+ if (typeof source === 'string') {
262
+ return `${hashAlgorithm}-${createHash(hashAlgorithm).update(source).digest('base64')}`
263
+ }
264
+
265
+ return `${hashAlgorithm}-${createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
266
+ }
267
+
268
+ // Optimized regex patterns for better readability and efficiency
269
+ const HTML_PATTERNS = {
270
+ script: {
271
+ regex: /<script\b[^>]*?\bsrc\s*=\s*["']([^"']+)["'][^>]*><\/script>/g,
272
+ endOffset: 10
273
+ },
274
+ stylesheet: {
275
+ regex: /<link\b[^>]*?\brel\s*=\s*["']stylesheet["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
276
+ endOffset: 1
277
+ },
278
+ modulepreload: {
279
+ regex: /<link\b[^>]*?\brel\s*=\s*["']modulepreload["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>/g,
280
+ endOffset: 1
281
+ }
282
+ };
228
283
 
229
- if (typeof source === 'string') {
230
- return `${hashAlgorithm}-${createHash(hashAlgorithm).update(source).digest('base64')}`
284
+ /**
285
+ * Validate HTML input
286
+ */
287
+ function validateHtmlInput(html, htmlPath, logger) {
288
+ if (!html || typeof html !== 'string') {
289
+ logger.warn(`Invalid HTML content for ${htmlPath}`);
290
+ return false
291
+ }
292
+ return true
293
+ }
294
+
295
+ /**
296
+ * Process a single match to create an integrity change object
297
+ */
298
+ async function processMatch(
299
+ match,
300
+ endOffset,
301
+ bundle,
302
+ htmlPath,
303
+ options,
304
+ config,
305
+ cacheManager,
306
+ logger
307
+ ) {
308
+ const [, url] = match;
309
+ if (!url) return null
310
+
311
+ const end = match.index + match[0].length;
312
+ const integrity = await calculateIntegrity(
313
+ bundle,
314
+ htmlPath,
315
+ url,
316
+ options,
317
+ config,
318
+ cacheManager,
319
+ logger
320
+ );
321
+
322
+ if (integrity) {
323
+ return {
324
+ integrity,
325
+ position: end - endOffset,
326
+ url // For logging
231
327
  }
328
+ }
329
+ return null
330
+ }
331
+
332
+ /**
333
+ * Process matches for a specific HTML pattern
334
+ */
335
+ async function processPatternMatches(
336
+ html,
337
+ pattern,
338
+ bundle,
339
+ htmlPath,
340
+ options,
341
+ config,
342
+ cacheManager,
343
+ logger
344
+ ) {
345
+ const { regex, endOffset } = pattern;
346
+ const matches = [...html.matchAll(regex)];
347
+
348
+ // Process each match in parallel
349
+ const matchResults = await Promise.all(
350
+ matches.map(match =>
351
+ processMatch(match, endOffset, bundle, htmlPath, options, config, cacheManager, logger)
352
+ )
353
+ );
232
354
 
233
- return `${hashAlgorithm}-${createHash(hashAlgorithm).update(Buffer.from(source)).digest('base64')}`
234
- };
355
+ // Filter out null results
356
+ return matchResults.filter(Boolean)
357
+ }
235
358
 
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
359
+ /**
360
+ * Collect all integrity changes from HTML patterns
361
+ */
362
+ async function collectIntegrityChanges(
363
+ html,
364
+ bundle,
365
+ htmlPath,
366
+ options,
367
+ config,
368
+ cacheManager,
369
+ logger
370
+ ) {
371
+ const changes = [];
372
+
373
+ // Collect changes from all patterns in parallel
374
+ await Promise.all(
375
+ Object.values(HTML_PATTERNS).map(async pattern => {
376
+ const patternChanges = await processPatternMatches(
377
+ html,
378
+ pattern,
379
+ bundle,
380
+ htmlPath,
381
+ options,
382
+ config,
383
+ cacheManager,
384
+ logger
385
+ );
386
+ changes.push(...patternChanges);
387
+ })
388
+ );
389
+
390
+ return changes
391
+ }
392
+
393
+ /**
394
+ * Check if integrity attribute already exists in HTML segment
395
+ */
396
+ function hasExistingIntegrity(html, position, integrity) {
397
+ const segment = html.slice(Math.max(0, position - 100), position + 100);
398
+ return segment.includes(`integrity="${integrity}"`)
399
+ }
400
+
401
+ /**
402
+ * Apply integrity changes to HTML content
403
+ */
404
+ function applyIntegrityChanges(html, changes, logger) {
405
+ // Sort by position in descending order to insert from back to front
406
+ changes.sort((a, b) => b.position - a.position);
407
+
408
+ for (const { integrity, position, url } of changes) {
409
+ // Skip if integrity attribute already exists
410
+ if (hasExistingIntegrity(html, position, integrity)) {
411
+ continue
240
412
  }
241
413
 
242
- const changes = [];
414
+ const insertText = ` integrity="${integrity}"`;
415
+ html = html.slice(0, position) + insertText + html.slice(position);
416
+ logger.debug(`Added integrity for: ${url}`);
417
+ }
243
418
 
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)];
419
+ return html
420
+ }
248
421
 
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
422
+ /**
423
+ * Transform HTML by adding SRI integrity attributes
424
+ */
425
+ async function transformHTML(
426
+ bundle,
427
+ htmlPath,
428
+ html,
429
+ options,
430
+ config,
431
+ cacheManager,
432
+ logger
433
+ ) {
434
+ if (!validateHtmlInput(html, htmlPath, logger)) {
435
+ return html
436
+ }
254
437
 
255
- const end = match.index + match[0].length;
256
- const integrity = await calculateIntegrity(bundle, htmlPath, url);
438
+ const changes = await collectIntegrityChanges(
439
+ html,
440
+ bundle,
441
+ htmlPath,
442
+ options,
443
+ config,
444
+ cacheManager,
445
+ logger
446
+ );
447
+
448
+ return applyIntegrityChanges(html, changes, logger)
449
+ }
257
450
 
258
- if (integrity) {
259
- return {
260
- integrity,
261
- position: end - endOffset,
262
- url // For logging
263
- }
264
- }
265
- return null
266
- })
267
- );
451
+ /**
452
+ * Create HTML transformer with given options and config
453
+ */
454
+ function createTransformer(options, config, cacheManager, logger) {
455
+ return {
456
+ transformHTML: (bundle, htmlPath, html) =>
457
+ transformHTML(bundle, htmlPath, html, options, config, cacheManager, logger),
458
+ calculateIntegrity: (bundle, htmlPath, url) =>
459
+ calculateIntegrity(bundle, htmlPath, url, options, config, cacheManager, logger)
460
+ }
461
+ }
268
462
 
269
- // Filter out null results
270
- matchResults.filter(Boolean).forEach(result => changes.push(result));
271
- })
272
- );
463
+ const DEFAULT_PLUGIN_NAME$1 = 'vite-plugin-sri4';
464
+
465
+ /**
466
+ * Logger class to handle different log levels without hijacking global console
467
+ */
468
+ class Logger {
469
+ constructor(logLevel = 'warn', pluginName = DEFAULT_PLUGIN_NAME$1) {
470
+ this.logLevel = logLevel;
471
+ this.pluginName = pluginName;
472
+ this.levels = {
473
+ silent: 0,
474
+ error: 1,
475
+ warn: 2,
476
+ info: 3,
477
+ debug: 4
478
+ };
479
+ this.currentLevel = this.levels[logLevel] || this.levels.warn;
480
+ }
273
481
 
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);
482
+ /**
483
+ * Format message with plugin name prefix
484
+ */
485
+ formatMessage(message, ...args) {
486
+ const prefix = `[${this.pluginName}]`;
487
+ if (typeof message === 'string') {
488
+ return [prefix + ' ' + message, ...args]
489
+ }
490
+ return [prefix, message, ...args]
491
+ }
276
492
 
277
- // Check if identical integrity attributes already exist to avoid duplicates
278
- for (const { integrity, position, url } of changes) {
279
- const insertText = ` integrity="${integrity}"`;
493
+ /**
494
+ * Log error messages
495
+ */
496
+ error(message, ...args) {
497
+ if (this.currentLevel >= this.levels.error) {
498
+ console.error(...this.formatMessage(message, ...args));
499
+ }
500
+ }
280
501
 
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
- }
502
+ /**
503
+ * Log warning messages
504
+ */
505
+ warn(message, ...args) {
506
+ if (this.currentLevel >= this.levels.warn) {
507
+ console.warn(...this.formatMessage(message, ...args));
508
+ }
509
+ }
286
510
 
287
- html = html.slice(0, position) + insertText + html.slice(position);
288
- console.debug(`[${DEFAULT_PLUGIN_NAME}] Added integrity for: ${url}`);
511
+ /**
512
+ * Log info messages
513
+ */
514
+ info(message, ...args) {
515
+ if (this.currentLevel >= this.levels.info) {
516
+ console.info(...this.formatMessage(message, ...args));
289
517
  }
518
+ }
290
519
 
291
- return html
292
- };
520
+ /**
521
+ * Log debug messages
522
+ */
523
+ debug(message, ...args) {
524
+ if (this.currentLevel >= this.levels.debug) {
525
+ console.debug(...this.formatMessage(message, ...args));
526
+ }
527
+ }
293
528
 
294
- return { transformHTML, calculateIntegrity }
529
+ /**
530
+ * Create a child logger with the same configuration
531
+ */
532
+ child(name) {
533
+ return new Logger(this.logLevel, `${this.pluginName}:${name}`)
534
+ }
295
535
  }
296
536
 
537
+ // Constants definition
538
+ const VITE_INTERNAL_ANALYSIS_PLUGIN = 'vite:build-import-analysis';
539
+ const DEFAULT_HASH_ALGORITHM = 'sha384';
540
+ const DEFAULT_PLUGIN_NAME = 'vite-plugin-sri4';
541
+
297
542
  function sri(options = {}) {
298
543
  const {
299
544
  ignoreMissingAsset = false,
@@ -302,16 +547,9 @@ function sri(options = {}) {
302
547
  logLevel = 'warn'
303
548
  } = options;
304
549
 
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
- }
550
+ // Create cache manager and logger instances for this plugin instance
551
+ const cacheManager = new CacheManager();
552
+ const logger = new Logger(logLevel, DEFAULT_PLUGIN_NAME);
315
553
 
316
554
  return {
317
555
  name: DEFAULT_PLUGIN_NAME,
@@ -320,13 +558,8 @@ function sri(options = {}) {
320
558
 
321
559
  // Cleanup work
322
560
  buildEnd() {
323
- // Restore console functions
324
- console.warn = originalConsoleWarn;
325
- console.debug = originalConsoleDebug;
326
-
327
561
  // Clear caches
328
- urlSupportCache.clear();
329
- resourceCache.clear();
562
+ cacheManager.clearAll();
330
563
  },
331
564
 
332
565
  configResolved(config) {
@@ -334,7 +567,7 @@ function sri(options = {}) {
334
567
  ignoreMissingAsset,
335
568
  bypassDomains,
336
569
  hashAlgorithm
337
- }, config);
570
+ }, config, cacheManager, logger);
338
571
 
339
572
  const generateBundle = async function(_, bundle) {
340
573
  const htmlFiles = Object.entries(bundle).filter(
@@ -344,7 +577,7 @@ function sri(options = {}) {
344
577
  );
345
578
 
346
579
  if (htmlFiles.length === 0) {
347
- console.debug(`[${DEFAULT_PLUGIN_NAME}] No HTML files found in bundle`);
580
+ logger.debug('No HTML files found in bundle');
348
581
  return
349
582
  }
350
583
 
@@ -356,10 +589,10 @@ function sri(options = {}) {
356
589
  chunk.source = await transformer.transformHTML(bundle, name, originalContent);
357
590
 
358
591
  if (originalContent !== chunk.source) {
359
- console.debug(`[${DEFAULT_PLUGIN_NAME}] SRI attributes added to ${name}`);
592
+ logger.debug(`SRI attributes added to ${name}`);
360
593
  }
361
594
  } catch (error) {
362
- console.warn(`[${DEFAULT_PLUGIN_NAME}] Error processing ${name}:`, error);
595
+ logger.warn(`Error processing ${name}:`, error);
363
596
  // Keep original content on error
364
597
  }
365
598
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-sri4",
3
- "version": "3.0.0",
3
+ "version": "3.1.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",