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.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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
80
|
+
if (logger) {
|
|
81
|
+
logger.warn(`Invalid URL: ${url}`, error);
|
|
82
|
+
}
|
|
77
83
|
return false
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
if (logger) {
|
|
176
|
+
logger.warn(`Failed to fetch external resource: ${url}`, lastError);
|
|
177
|
+
}
|
|
160
178
|
return null
|
|
161
179
|
}
|
|
162
180
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
);
|
|
220
|
+
// Skip specified domains
|
|
221
|
+
if (isUrlFromBypassDomain(url, bypassDomains, logger)) {
|
|
222
|
+
return null
|
|
223
|
+
}
|
|
210
224
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
234
|
-
|
|
355
|
+
// Filter out null results
|
|
356
|
+
return matchResults.filter(Boolean)
|
|
357
|
+
}
|
|
235
358
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
Object.values(HTML_PATTERNS).map(async ({ regex, endOffset }) => {
|
|
247
|
-
const matches = [...html.matchAll(regex)];
|
|
419
|
+
return html
|
|
420
|
+
}
|
|
248
421
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
256
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
306
|
-
const
|
|
307
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
592
|
+
logger.debug(`SRI attributes added to ${name}`);
|
|
360
593
|
}
|
|
361
594
|
} catch (error) {
|
|
362
|
-
|
|
595
|
+
logger.warn(`Error processing ${name}:`, error);
|
|
363
596
|
// Keep original content on error
|
|
364
597
|
}
|
|
365
598
|
})
|