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.
Files changed (3) hide show
  1. package/dist/index.cjs +136 -130
  2. package/dist/index.js +136 -130
  3. 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
- log(`Failed to fetch CORS headers from ${url}: ${error}`, options);
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
- try {
234
- log('Starting HTML transformation', options);
235
- const bundle = ctx.bundle || {};
236
- log(`Bundle size: ${Object.keys(bundle).length}`, options);
237
-
238
- // Process script tags with and without quotes
239
- const scriptTagRegex = /<script[^>]+src=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
240
- let match;
241
- while ((match = scriptTagRegex.exec(html)) !== null) {
242
- const [tag, quotedUrl, unquotedUrl] = match;
243
- const url = quotedUrl || unquotedUrl;
244
- log(`Processing script: ${url}`, options);
245
- const newTag = await processTag(tag, url, options, bundle, base);
246
- html = html.replace(tag, newTag);
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
- // Process link tags with and without quotes
250
- const linkTagRegex = /<link[^>]+href=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
251
- while ((match = linkTagRegex.exec(html)) !== null) {
252
- const [tag, quotedUrl, unquotedUrl] = match;
253
- const url = quotedUrl || unquotedUrl;
254
- if (tag.includes('stylesheet') || tag.includes('modulepreload')) {
255
- log(`Processing link: ${url}`, options);
256
- const newTag = await processTag(tag, url, options, bundle, base);
257
- html = html.replace(tag, newTag);
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
- return html;
262
- } catch (error) {
263
- console.error(`${LOG_PREFIX} Failed to transform HTML: ${error}`);
264
- return html;
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
- log(`Failed to fetch CORS headers from ${url}: ${error}`, options);
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
- try {
232
- log('Starting HTML transformation', options);
233
- const bundle = ctx.bundle || {};
234
- log(`Bundle size: ${Object.keys(bundle).length}`, options);
235
-
236
- // Process script tags with and without quotes
237
- const scriptTagRegex = /<script[^>]+src=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
238
- let match;
239
- while ((match = scriptTagRegex.exec(html)) !== null) {
240
- const [tag, quotedUrl, unquotedUrl] = match;
241
- const url = quotedUrl || unquotedUrl;
242
- log(`Processing script: ${url}`, options);
243
- const newTag = await processTag(tag, url, options, bundle, base);
244
- html = html.replace(tag, newTag);
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
- // Process link tags with and without quotes
248
- const linkTagRegex = /<link[^>]+href=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
249
- while ((match = linkTagRegex.exec(html)) !== null) {
250
- const [tag, quotedUrl, unquotedUrl] = match;
251
- const url = quotedUrl || unquotedUrl;
252
- if (tag.includes('stylesheet') || tag.includes('modulepreload')) {
253
- log(`Processing link: ${url}`, options);
254
- const newTag = await processTag(tag, url, options, bundle, base);
255
- html = html.replace(tag, newTag);
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
- return html;
260
- } catch (error) {
261
- console.error(`${LOG_PREFIX} Failed to transform HTML: ${error}`);
262
- return html;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-sri4",
3
- "version": "1.8.6",
3
+ "version": "1.8.7",
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",