vite-plugin-sri4 1.8.6 → 1.8.7
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/dist/index.cjs +136 -130
- package/dist/index.js +136 -130
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -47,7 +47,7 @@ async function externalResourceIsCorsEnabled(url, options) {
|
|
|
47
47
|
}
|
|
48
48
|
return false;
|
|
49
49
|
} catch (error) {
|
|
50
|
-
|
|
50
|
+
console.error(`${LOG_PREFIX} Failed to fetch CORS headers from ${url}`, error);
|
|
51
51
|
return false;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -107,108 +107,12 @@ function getBundleKey(url, base = '') {
|
|
|
107
107
|
return [...new Set(paths)];
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
async function processTag(tag, url, options, bundle, base = '') {
|
|
111
|
-
if (tag.includes('integrity=')) {
|
|
112
|
-
log(`Skip tag with existing integrity attribute: ${tag}`, options);
|
|
113
|
-
return tag;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
log(`Processing tag: ${tag}`, options);
|
|
117
|
-
log(`URL: ${url}`, options);
|
|
118
|
-
|
|
119
|
-
// Handle external resources
|
|
120
|
-
if (/^(https?:)?\/\//i.test(url)) {
|
|
121
|
-
if (isBypassDomain(url, options.bypassDomains)) {
|
|
122
|
-
log(`Skip SRI for bypass domain: ${url}`, options);
|
|
123
|
-
return tag;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const corsOk = await externalResourceIsCorsEnabled(url, options);
|
|
127
|
-
if (!corsOk) {
|
|
128
|
-
log(`External resource ${url} does not support CORS`, options);
|
|
129
|
-
return tag;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
try {
|
|
133
|
-
const response = await fetch(url);
|
|
134
|
-
const content = await response.arrayBuffer();
|
|
135
|
-
const hash = computeSri(Buffer.from(content), options.algorithm);
|
|
136
|
-
if (hash) {
|
|
137
|
-
log(`Computing SRI for external resource ${url}: ${hash}`, options);
|
|
138
|
-
const hasCrossOrigin = hasCrossOriginAttr(tag);
|
|
139
|
-
const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
|
|
140
|
-
const newTag = tag.replace(/>$/, ` integrity="${hash}"${crossOriginAttr}>`);
|
|
141
|
-
log(`New tag: ${newTag}`, options);
|
|
142
|
-
return newTag;
|
|
143
|
-
}
|
|
144
|
-
} catch (error) {
|
|
145
|
-
log(`Failed to process external resource ${url}: ${error}`, options);
|
|
146
|
-
}
|
|
147
|
-
return tag;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Handle local resources
|
|
151
|
-
const possibleKeys = getBundleKey(url, base);
|
|
152
|
-
let bundleItem = null;
|
|
153
|
-
let source;
|
|
154
|
-
|
|
155
|
-
log(`Looking for bundle keys:`, options);
|
|
156
|
-
possibleKeys.forEach(key => log(`- ${key}`, options));
|
|
157
|
-
|
|
158
|
-
for (const key of possibleKeys) {
|
|
159
|
-
if (bundle[key]) {
|
|
160
|
-
bundleItem = bundle[key];
|
|
161
|
-
log(`Found bundle item for key: ${key}`, options);
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!bundleItem) {
|
|
167
|
-
log(`Bundle item not found for ${url}`, options);
|
|
168
|
-
if (!options.ignoreMissingAsset) {
|
|
169
|
-
console.warn(`${LOG_PREFIX} Asset not found in bundle: ${url}`);
|
|
170
|
-
log(`Available bundle keys:`, options);
|
|
171
|
-
Object.keys(bundle).forEach(key => log(`- ${key}`, options));
|
|
172
|
-
} else {
|
|
173
|
-
log(`Ignoring missing asset due to ignoreMissingAsset option`, options);
|
|
174
|
-
}
|
|
175
|
-
return tag;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
log(`Bundle item type: ${bundleItem.type}`, options);
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
if (bundleItem.type === 'chunk') {
|
|
182
|
-
source = bundleItem.code;
|
|
183
|
-
log(`Processing chunk content of length: ${source.length}`, options);
|
|
184
|
-
} else {
|
|
185
|
-
source = bundleItem.source;
|
|
186
|
-
log(`Processing asset content of length: ${source.length}`, options);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const integrity = computeSri(source, options.algorithm);
|
|
190
|
-
if (integrity) {
|
|
191
|
-
log(`Computing SRI for local resource ${url}: ${integrity}`, options);
|
|
192
|
-
const hasCrossOrigin = hasCrossOriginAttr(tag);
|
|
193
|
-
const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
|
|
194
|
-
const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
|
|
195
|
-
log(`New tag: ${newTag}`, options);
|
|
196
|
-
return newTag;
|
|
197
|
-
}
|
|
198
|
-
} catch (error) {
|
|
199
|
-
log(`Error processing bundle item: ${error}`, options);
|
|
200
|
-
if (!options.ignoreMissingAsset) {
|
|
201
|
-
console.error(`${LOG_PREFIX} Failed to process asset: ${url}`, error);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return tag;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
110
|
function sri(userOptions = {}) {
|
|
209
111
|
const options = { ...DEFAULT_OPTIONS, ...userOptions };
|
|
210
112
|
let isBuild = false;
|
|
211
113
|
let base = '';
|
|
114
|
+
const htmlFiles = new Map(); // Store HTML file info for processing
|
|
115
|
+
const sriCache = new Map(); // Cache SRI hashes
|
|
212
116
|
|
|
213
117
|
return {
|
|
214
118
|
name: 'vite-plugin-sri4',
|
|
@@ -220,49 +124,151 @@ function sri(userOptions = {}) {
|
|
|
220
124
|
isBuild = config.command === 'build';
|
|
221
125
|
base = config.base || '';
|
|
222
126
|
log('Plugin configured in ' + (isBuild ? 'build' : 'dev') + ' mode', options);
|
|
223
|
-
log(`Base URL: ${base}`, options);
|
|
224
|
-
log(`ignoreMissingAsset: ${options.ignoreMissingAsset}`, options);
|
|
225
127
|
},
|
|
226
128
|
|
|
227
129
|
async transformIndexHtml(html, ctx) {
|
|
228
130
|
if (!isBuild || !html) {
|
|
229
|
-
log('Skipping HTML transform in dev mode or empty HTML', options);
|
|
230
131
|
return html;
|
|
231
132
|
}
|
|
232
133
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
134
|
+
// Store HTML file info for later processing
|
|
135
|
+
const resourceTags = [];
|
|
136
|
+
|
|
137
|
+
// Find script tags
|
|
138
|
+
const scriptTagRegex = /<script[^>]+src=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
|
|
139
|
+
let match;
|
|
140
|
+
while ((match = scriptTagRegex.exec(html)) !== null) {
|
|
141
|
+
const [tag, quotedUrl, unquotedUrl] = match;
|
|
142
|
+
resourceTags.push({
|
|
143
|
+
tag,
|
|
144
|
+
url: quotedUrl || unquotedUrl,
|
|
145
|
+
type: 'script'
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Find link tags
|
|
150
|
+
const linkTagRegex = /<link[^>]+href=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
|
|
151
|
+
while ((match = linkTagRegex.exec(html)) !== null) {
|
|
152
|
+
const [tag, quotedUrl, unquotedUrl] = match;
|
|
153
|
+
if (tag.includes('stylesheet') || tag.includes('modulepreload')) {
|
|
154
|
+
resourceTags.push({
|
|
155
|
+
tag,
|
|
156
|
+
url: quotedUrl || unquotedUrl,
|
|
157
|
+
type: 'link'
|
|
158
|
+
});
|
|
247
159
|
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Store HTML file info
|
|
163
|
+
htmlFiles.set(ctx.filename, {
|
|
164
|
+
content: html,
|
|
165
|
+
resources: resourceTags
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return html;
|
|
169
|
+
},
|
|
248
170
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
171
|
+
async writeBundle(options, bundle) {
|
|
172
|
+
for (const [filename, htmlInfo] of htmlFiles) {
|
|
173
|
+
let content = htmlInfo.content;
|
|
174
|
+
|
|
175
|
+
// Process all resources in parallel
|
|
176
|
+
const updates = await Promise.all(
|
|
177
|
+
htmlInfo.resources.map(async ({ tag, url, type }) => {
|
|
178
|
+
if (tag.includes('integrity=')) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Handle external resources
|
|
183
|
+
if (/^(https?:)?\/\//i.test(url)) {
|
|
184
|
+
if (isBypassDomain(url, options.bypassDomains)) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check cache first
|
|
189
|
+
if (sriCache.has(url)) {
|
|
190
|
+
return {
|
|
191
|
+
tag,
|
|
192
|
+
newTag: sriCache.get(url)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const corsOk = await externalResourceIsCorsEnabled(url, options);
|
|
197
|
+
if (!corsOk) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const response = await fetch(url);
|
|
203
|
+
const content = await response.arrayBuffer();
|
|
204
|
+
const hash = computeSri(Buffer.from(content), options.algorithm);
|
|
205
|
+
if (hash) {
|
|
206
|
+
const hasCrossOrigin = hasCrossOriginAttr(tag);
|
|
207
|
+
const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
|
|
208
|
+
const newTag = tag.replace(/>$/, ` integrity="${hash}"${crossOriginAttr}>`);
|
|
209
|
+
sriCache.set(url, newTag);
|
|
210
|
+
return { tag, newTag };
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
log(`Failed to process external resource ${url}: ${error}`, options);
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Handle local resources
|
|
219
|
+
const possibleKeys = getBundleKey(url, base);
|
|
220
|
+
let bundleItem = null;
|
|
221
|
+
|
|
222
|
+
for (const key of possibleKeys) {
|
|
223
|
+
if (bundle[key]) {
|
|
224
|
+
bundleItem = bundle[key];
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!bundleItem) {
|
|
230
|
+
if (!options.ignoreMissingAsset) {
|
|
231
|
+
console.warn(`${LOG_PREFIX} Asset not found in bundle: ${url}`);
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
|
|
238
|
+
const integrity = computeSri(source, options.algorithm);
|
|
239
|
+
|
|
240
|
+
if (integrity) {
|
|
241
|
+
const hasCrossOrigin = hasCrossOriginAttr(tag);
|
|
242
|
+
const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
|
|
243
|
+
const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
|
|
244
|
+
return { tag, newTag };
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (!options.ignoreMissingAsset) {
|
|
248
|
+
console.error(`${LOG_PREFIX} Failed to process asset: ${url}`, error);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return null;
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Apply all updates to the HTML content
|
|
257
|
+
updates.forEach(update => {
|
|
258
|
+
if (update) {
|
|
259
|
+
content = content.replace(update.tag, update.newTag);
|
|
258
260
|
}
|
|
259
|
-
}
|
|
261
|
+
});
|
|
260
262
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
// Write the modified content back to the bundle
|
|
264
|
+
if (bundle[filename]) {
|
|
265
|
+
bundle[filename].source = content;
|
|
266
|
+
}
|
|
265
267
|
}
|
|
268
|
+
|
|
269
|
+
// Clear the caches
|
|
270
|
+
htmlFiles.clear();
|
|
271
|
+
sriCache.clear();
|
|
266
272
|
}
|
|
267
273
|
};
|
|
268
274
|
}
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ async function externalResourceIsCorsEnabled(url, options) {
|
|
|
45
45
|
}
|
|
46
46
|
return false;
|
|
47
47
|
} catch (error) {
|
|
48
|
-
|
|
48
|
+
console.error(`${LOG_PREFIX} Failed to fetch CORS headers from ${url}`, error);
|
|
49
49
|
return false;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -105,108 +105,12 @@ function getBundleKey(url, base = '') {
|
|
|
105
105
|
return [...new Set(paths)];
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
async function processTag(tag, url, options, bundle, base = '') {
|
|
109
|
-
if (tag.includes('integrity=')) {
|
|
110
|
-
log(`Skip tag with existing integrity attribute: ${tag}`, options);
|
|
111
|
-
return tag;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
log(`Processing tag: ${tag}`, options);
|
|
115
|
-
log(`URL: ${url}`, options);
|
|
116
|
-
|
|
117
|
-
// Handle external resources
|
|
118
|
-
if (/^(https?:)?\/\//i.test(url)) {
|
|
119
|
-
if (isBypassDomain(url, options.bypassDomains)) {
|
|
120
|
-
log(`Skip SRI for bypass domain: ${url}`, options);
|
|
121
|
-
return tag;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const corsOk = await externalResourceIsCorsEnabled(url, options);
|
|
125
|
-
if (!corsOk) {
|
|
126
|
-
log(`External resource ${url} does not support CORS`, options);
|
|
127
|
-
return tag;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const response = await fetch(url);
|
|
132
|
-
const content = await response.arrayBuffer();
|
|
133
|
-
const hash = computeSri(Buffer.from(content), options.algorithm);
|
|
134
|
-
if (hash) {
|
|
135
|
-
log(`Computing SRI for external resource ${url}: ${hash}`, options);
|
|
136
|
-
const hasCrossOrigin = hasCrossOriginAttr(tag);
|
|
137
|
-
const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
|
|
138
|
-
const newTag = tag.replace(/>$/, ` integrity="${hash}"${crossOriginAttr}>`);
|
|
139
|
-
log(`New tag: ${newTag}`, options);
|
|
140
|
-
return newTag;
|
|
141
|
-
}
|
|
142
|
-
} catch (error) {
|
|
143
|
-
log(`Failed to process external resource ${url}: ${error}`, options);
|
|
144
|
-
}
|
|
145
|
-
return tag;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Handle local resources
|
|
149
|
-
const possibleKeys = getBundleKey(url, base);
|
|
150
|
-
let bundleItem = null;
|
|
151
|
-
let source;
|
|
152
|
-
|
|
153
|
-
log(`Looking for bundle keys:`, options);
|
|
154
|
-
possibleKeys.forEach(key => log(`- ${key}`, options));
|
|
155
|
-
|
|
156
|
-
for (const key of possibleKeys) {
|
|
157
|
-
if (bundle[key]) {
|
|
158
|
-
bundleItem = bundle[key];
|
|
159
|
-
log(`Found bundle item for key: ${key}`, options);
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (!bundleItem) {
|
|
165
|
-
log(`Bundle item not found for ${url}`, options);
|
|
166
|
-
if (!options.ignoreMissingAsset) {
|
|
167
|
-
console.warn(`${LOG_PREFIX} Asset not found in bundle: ${url}`);
|
|
168
|
-
log(`Available bundle keys:`, options);
|
|
169
|
-
Object.keys(bundle).forEach(key => log(`- ${key}`, options));
|
|
170
|
-
} else {
|
|
171
|
-
log(`Ignoring missing asset due to ignoreMissingAsset option`, options);
|
|
172
|
-
}
|
|
173
|
-
return tag;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
log(`Bundle item type: ${bundleItem.type}`, options);
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
if (bundleItem.type === 'chunk') {
|
|
180
|
-
source = bundleItem.code;
|
|
181
|
-
log(`Processing chunk content of length: ${source.length}`, options);
|
|
182
|
-
} else {
|
|
183
|
-
source = bundleItem.source;
|
|
184
|
-
log(`Processing asset content of length: ${source.length}`, options);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const integrity = computeSri(source, options.algorithm);
|
|
188
|
-
if (integrity) {
|
|
189
|
-
log(`Computing SRI for local resource ${url}: ${integrity}`, options);
|
|
190
|
-
const hasCrossOrigin = hasCrossOriginAttr(tag);
|
|
191
|
-
const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
|
|
192
|
-
const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
|
|
193
|
-
log(`New tag: ${newTag}`, options);
|
|
194
|
-
return newTag;
|
|
195
|
-
}
|
|
196
|
-
} catch (error) {
|
|
197
|
-
log(`Error processing bundle item: ${error}`, options);
|
|
198
|
-
if (!options.ignoreMissingAsset) {
|
|
199
|
-
console.error(`${LOG_PREFIX} Failed to process asset: ${url}`, error);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return tag;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
108
|
function sri(userOptions = {}) {
|
|
207
109
|
const options = { ...DEFAULT_OPTIONS, ...userOptions };
|
|
208
110
|
let isBuild = false;
|
|
209
111
|
let base = '';
|
|
112
|
+
const htmlFiles = new Map(); // Store HTML file info for processing
|
|
113
|
+
const sriCache = new Map(); // Cache SRI hashes
|
|
210
114
|
|
|
211
115
|
return {
|
|
212
116
|
name: 'vite-plugin-sri4',
|
|
@@ -218,49 +122,151 @@ function sri(userOptions = {}) {
|
|
|
218
122
|
isBuild = config.command === 'build';
|
|
219
123
|
base = config.base || '';
|
|
220
124
|
log('Plugin configured in ' + (isBuild ? 'build' : 'dev') + ' mode', options);
|
|
221
|
-
log(`Base URL: ${base}`, options);
|
|
222
|
-
log(`ignoreMissingAsset: ${options.ignoreMissingAsset}`, options);
|
|
223
125
|
},
|
|
224
126
|
|
|
225
127
|
async transformIndexHtml(html, ctx) {
|
|
226
128
|
if (!isBuild || !html) {
|
|
227
|
-
log('Skipping HTML transform in dev mode or empty HTML', options);
|
|
228
129
|
return html;
|
|
229
130
|
}
|
|
230
131
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
132
|
+
// Store HTML file info for later processing
|
|
133
|
+
const resourceTags = [];
|
|
134
|
+
|
|
135
|
+
// Find script tags
|
|
136
|
+
const scriptTagRegex = /<script[^>]+src=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
|
|
137
|
+
let match;
|
|
138
|
+
while ((match = scriptTagRegex.exec(html)) !== null) {
|
|
139
|
+
const [tag, quotedUrl, unquotedUrl] = match;
|
|
140
|
+
resourceTags.push({
|
|
141
|
+
tag,
|
|
142
|
+
url: quotedUrl || unquotedUrl,
|
|
143
|
+
type: 'script'
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Find link tags
|
|
148
|
+
const linkTagRegex = /<link[^>]+href=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
|
|
149
|
+
while ((match = linkTagRegex.exec(html)) !== null) {
|
|
150
|
+
const [tag, quotedUrl, unquotedUrl] = match;
|
|
151
|
+
if (tag.includes('stylesheet') || tag.includes('modulepreload')) {
|
|
152
|
+
resourceTags.push({
|
|
153
|
+
tag,
|
|
154
|
+
url: quotedUrl || unquotedUrl,
|
|
155
|
+
type: 'link'
|
|
156
|
+
});
|
|
245
157
|
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Store HTML file info
|
|
161
|
+
htmlFiles.set(ctx.filename, {
|
|
162
|
+
content: html,
|
|
163
|
+
resources: resourceTags
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return html;
|
|
167
|
+
},
|
|
246
168
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
169
|
+
async writeBundle(options, bundle) {
|
|
170
|
+
for (const [filename, htmlInfo] of htmlFiles) {
|
|
171
|
+
let content = htmlInfo.content;
|
|
172
|
+
|
|
173
|
+
// Process all resources in parallel
|
|
174
|
+
const updates = await Promise.all(
|
|
175
|
+
htmlInfo.resources.map(async ({ tag, url, type }) => {
|
|
176
|
+
if (tag.includes('integrity=')) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle external resources
|
|
181
|
+
if (/^(https?:)?\/\//i.test(url)) {
|
|
182
|
+
if (isBypassDomain(url, options.bypassDomains)) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check cache first
|
|
187
|
+
if (sriCache.has(url)) {
|
|
188
|
+
return {
|
|
189
|
+
tag,
|
|
190
|
+
newTag: sriCache.get(url)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const corsOk = await externalResourceIsCorsEnabled(url, options);
|
|
195
|
+
if (!corsOk) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const response = await fetch(url);
|
|
201
|
+
const content = await response.arrayBuffer();
|
|
202
|
+
const hash = computeSri(Buffer.from(content), options.algorithm);
|
|
203
|
+
if (hash) {
|
|
204
|
+
const hasCrossOrigin = hasCrossOriginAttr(tag);
|
|
205
|
+
const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
|
|
206
|
+
const newTag = tag.replace(/>$/, ` integrity="${hash}"${crossOriginAttr}>`);
|
|
207
|
+
sriCache.set(url, newTag);
|
|
208
|
+
return { tag, newTag };
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
log(`Failed to process external resource ${url}: ${error}`, options);
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Handle local resources
|
|
217
|
+
const possibleKeys = getBundleKey(url, base);
|
|
218
|
+
let bundleItem = null;
|
|
219
|
+
|
|
220
|
+
for (const key of possibleKeys) {
|
|
221
|
+
if (bundle[key]) {
|
|
222
|
+
bundleItem = bundle[key];
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!bundleItem) {
|
|
228
|
+
if (!options.ignoreMissingAsset) {
|
|
229
|
+
console.warn(`${LOG_PREFIX} Asset not found in bundle: ${url}`);
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
|
|
236
|
+
const integrity = computeSri(source, options.algorithm);
|
|
237
|
+
|
|
238
|
+
if (integrity) {
|
|
239
|
+
const hasCrossOrigin = hasCrossOriginAttr(tag);
|
|
240
|
+
const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
|
|
241
|
+
const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
|
|
242
|
+
return { tag, newTag };
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (!options.ignoreMissingAsset) {
|
|
246
|
+
console.error(`${LOG_PREFIX} Failed to process asset: ${url}`, error);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// Apply all updates to the HTML content
|
|
255
|
+
updates.forEach(update => {
|
|
256
|
+
if (update) {
|
|
257
|
+
content = content.replace(update.tag, update.newTag);
|
|
256
258
|
}
|
|
257
|
-
}
|
|
259
|
+
});
|
|
258
260
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
// Write the modified content back to the bundle
|
|
262
|
+
if (bundle[filename]) {
|
|
263
|
+
bundle[filename].source = content;
|
|
264
|
+
}
|
|
263
265
|
}
|
|
266
|
+
|
|
267
|
+
// Clear the caches
|
|
268
|
+
htmlFiles.clear();
|
|
269
|
+
sriCache.clear();
|
|
264
270
|
}
|
|
265
271
|
};
|
|
266
272
|
}
|