vite-plugin-sri4 2.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.
- package/README.md +10 -0
- package/dist/index.cjs +393 -160
- package/dist/index.js +393 -160
- package/package.json +6 -6
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
84
|
+
if (logger) {
|
|
85
|
+
logger.warn(`Invalid URL: ${url}`, error);
|
|
86
|
+
}
|
|
81
87
|
return false
|
|
82
88
|
}
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
+
if (logger) {
|
|
180
|
+
logger.warn(`Failed to fetch external resource: ${url}`, lastError);
|
|
181
|
+
}
|
|
164
182
|
return null
|
|
165
183
|
}
|
|
166
184
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
);
|
|
224
|
+
// Skip specified domains
|
|
225
|
+
if (isUrlFromBypassDomain(url, bypassDomains, logger)) {
|
|
226
|
+
return null
|
|
227
|
+
}
|
|
214
228
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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
|
-
|
|
234
|
-
|
|
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
|
-
|
|
238
|
-
|
|
359
|
+
// Filter out null results
|
|
360
|
+
return matchResults.filter(Boolean)
|
|
361
|
+
}
|
|
239
362
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
Object.values(HTML_PATTERNS).map(async ({ regex, endOffset }) => {
|
|
251
|
-
const matches = [...html.matchAll(regex)];
|
|
423
|
+
return html
|
|
424
|
+
}
|
|
252
425
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
274
|
-
|
|
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
|
-
|
|
279
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
310
|
-
const
|
|
311
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
596
|
+
logger.debug(`SRI attributes added to ${name}`);
|
|
364
597
|
}
|
|
365
598
|
} catch (error) {
|
|
366
|
-
|
|
599
|
+
logger.warn(`Error processing ${name}:`, error);
|
|
367
600
|
// Keep original content on error
|
|
368
601
|
}
|
|
369
602
|
})
|