vite-plugin-sri4 1.8.2 → 1.8.5

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 +88 -143
  2. package/dist/index.js +88 -143
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -9,8 +9,7 @@ const DEFAULT_OPTIONS = {
9
9
  algorithm: 'sha384',
10
10
  bypassDomains: [],
11
11
  crossorigin: 'anonymous',
12
- debug: false,
13
- inlineScripts: false
12
+ debug: false
14
13
  };
15
14
 
16
15
  function log(message, options) {
@@ -21,13 +20,15 @@ function log(message, options) {
21
20
 
22
21
  function computeSri(content, algorithm = 'sha384') {
23
22
  try {
24
- if (typeof content === 'string') {
25
- content = Buffer.from(content, 'utf-8');
23
+ const hash = node_crypto.createHash(algorithm);
24
+ if (Buffer.isBuffer(content) || content instanceof Uint8Array) {
25
+ hash.update(content);
26
+ } else if (typeof content === 'string') {
27
+ hash.update(Buffer.from(content, 'utf-8'));
28
+ } else {
29
+ throw new Error('Invalid content type');
26
30
  }
27
- const hash = node_crypto.createHash(algorithm)
28
- .update(content)
29
- .digest('base64');
30
- return `${algorithm}-${hash}`;
31
+ return `${algorithm}-${hash.digest('base64')}`;
31
32
  } catch (error) {
32
33
  console.error(`${LOG_PREFIX} Failed to compute SRI hash: ${error}`);
33
34
  return null;
@@ -53,21 +54,14 @@ async function externalResourceIsCorsEnabled(url, options) {
53
54
  function isBypassDomain(url, bypassDomains = []) {
54
55
  if (!bypassDomains.length) return false;
55
56
  try {
56
- let parsedUrl;
57
- if (url.startsWith('//')) {
58
- parsedUrl = new URL(`http:${url}`);
59
- } else if (url.startsWith('http://') || url.startsWith('https://')) {
60
- parsedUrl = new URL(url);
61
- } else {
62
- parsedUrl = new URL(url, 'http://dummy');
63
- }
64
-
57
+ const parsedUrl = url.startsWith('//')
58
+ ? new URL(`http:${url}`)
59
+ : new URL(url, 'http://dummy');
65
60
  return bypassDomains.some(
66
61
  (domain) => parsedUrl.hostname === domain ||
67
62
  parsedUrl.hostname.endsWith(`.${domain}`)
68
63
  );
69
64
  } catch (e) {
70
- console.error(`${LOG_PREFIX} Failed to parse URL: ${url}`, e);
71
65
  return false;
72
66
  }
73
67
  }
@@ -76,38 +70,33 @@ function hasCrossOriginAttr(tag) {
76
70
  return /(?:^|\s)crossorigin(?:=["']?[^"'\s>]*["']?)?(?:\s|>|$)/i.test(tag);
77
71
  }
78
72
 
79
- function getAllPossiblePaths(url) {
73
+ function getBundleKey(url, base = '') {
74
+ // Remove base prefix if exists
75
+ let cleanUrl = url.startsWith(base) ? url.slice(base.length) : url;
76
+ // Remove leading slash
77
+ cleanUrl = cleanUrl.startsWith('/') ? cleanUrl.slice(1) : cleanUrl;
78
+
79
+ // Try different path combinations
80
80
  const paths = [
81
- url,
82
- url.startsWith('/') ? url.slice(1) : url,
83
- url.startsWith('/static/') ? url.slice(8) : url,
84
- !url.startsWith('/static/') ? `static/${url}` : url,
85
- !url.startsWith('/static/') ? `/static/${url}` : url,
86
- url.replace(/^\/static\//, ''),
87
- url.replace(/^static\//, '')
81
+ cleanUrl,
82
+ `static/${cleanUrl}`,
83
+ cleanUrl.replace(/^static\//, '')
88
84
  ];
89
85
 
90
- // Handle Vite's hashed filenames (e.g., index-DPifqqS2.js -> index.js)
91
- const withoutHash = url.replace(/-[a-zA-Z0-9]{8}\./, '.');
92
- if (withoutHash !== url) {
93
- paths.push(...getAllPossiblePaths(withoutHash));
94
- }
95
-
96
- // Handle variations with and without /static/ prefix for hashed files
97
- const hashedMatch = url.match(/^(?:\/static\/)?(.*?)-[a-zA-Z0-9]{8}(\..*?)$/);
98
- if (hashedMatch) {
99
- const [, base, ext] = hashedMatch;
100
- paths.push(
101
- `${base}${ext}`,
102
- `/static/${base}${ext}`,
103
- `static/${base}${ext}`
104
- );
86
+ // Remove hash part if exists and try again
87
+ const withoutHash = cleanUrl.replace(/-[a-zA-Z0-9]+\.([^.]+)$/, '.$1');
88
+ if (withoutHash !== cleanUrl) {
89
+ paths.push(...[
90
+ withoutHash,
91
+ `static/${withoutHash}`,
92
+ withoutHash.replace(/^static\//, '')
93
+ ]);
105
94
  }
106
95
 
107
96
  return [...new Set(paths)];
108
97
  }
109
98
 
110
- async function processTag(tag, url, options, sriMap) {
99
+ async function processTag(tag, url, options, bundle, base = '') {
111
100
  if (tag.includes('integrity=')) {
112
101
  log(`Skip tag with existing integrity attribute: ${tag}`, options);
113
102
  return tag;
@@ -148,54 +137,49 @@ async function processTag(tag, url, options, sriMap) {
148
137
  }
149
138
 
150
139
  // Handle local resources
151
- const possiblePaths = getAllPossiblePaths(url);
152
- let integrity = null;
153
-
154
- log(`Checking possible paths for ${url}:`, options);
155
- log(`Possible paths:`, options);
156
- possiblePaths.forEach(path => log(` - ${path}`, options));
157
-
158
- for (const path of possiblePaths) {
159
- log(`- Checking path: ${path}`, options);
160
- if (sriMap.has(path)) {
161
- integrity = sriMap.get(path);
162
- log(`Found integrity for path ${path}: ${integrity}`, options);
140
+ const possibleKeys = getBundleKey(url, base);
141
+ let bundleItem = null;
142
+ let source;
143
+
144
+ log(`Looking for bundle keys:`, options);
145
+ possibleKeys.forEach(key => log(`- ${key}`, options));
146
+
147
+ for (const key of possibleKeys) {
148
+ if (bundle[key]) {
149
+ bundleItem = bundle[key];
150
+ log(`Found bundle item for key: ${key}`, options);
163
151
  break;
164
152
  }
165
153
  }
166
154
 
167
- if (integrity) {
168
- log(`Using precomputed SRI for ${url}: ${integrity}`, options);
169
- const hasCrossOrigin = hasCrossOriginAttr(tag);
170
- const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
171
- const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
172
- log(`New tag: ${newTag}`, options);
173
- return newTag;
174
- }
175
-
176
- log(`No SRI hash found for ${url}`, options);
177
- log(`Available paths in sriMap: ${Array.from(sriMap.keys()).join(', ')}`, options);
178
- return tag;
179
- }
180
-
181
- async function processInlineScript(tag, options) {
182
- if (tag.includes('integrity=')) {
183
- log(`Skip inline script with existing integrity attribute: ${tag}`, options);
155
+ if (!bundleItem) {
156
+ log(`Available bundle keys:`, options);
157
+ Object.keys(bundle).forEach(key => log(`- ${key}`, options));
184
158
  return tag;
185
159
  }
186
160
 
187
- const content = tag.match(/<script[^>]*>([\s\S]*?)<\/script>/)?.[1]?.trim();
188
- if (!content) {
189
- log(`Skip empty inline script: ${tag}`, options);
190
- return tag;
191
- }
161
+ log(`Bundle item type: ${bundleItem.type}`, options);
192
162
 
193
- const hash = computeSri(content, options.algorithm);
194
- if (hash) {
195
- log(`Computing SRI for inline script: ${hash}`, options);
196
- const newTag = tag.replace('>', ` integrity="${hash}">`);
197
- log(`New inline script tag: ${newTag}`, options);
198
- return newTag;
163
+ try {
164
+ if (bundleItem.type === 'chunk') {
165
+ source = bundleItem.code;
166
+ log(`Processing chunk content of length: ${source.length}`, options);
167
+ } else {
168
+ source = bundleItem.source;
169
+ log(`Processing asset content of length: ${source.length}`, options);
170
+ }
171
+
172
+ const integrity = computeSri(source, options.algorithm);
173
+ if (integrity) {
174
+ log(`Computing SRI for local resource ${url}: ${integrity}`, options);
175
+ const hasCrossOrigin = hasCrossOriginAttr(tag);
176
+ const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
177
+ const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
178
+ log(`New tag: ${newTag}`, options);
179
+ return newTag;
180
+ }
181
+ } catch (error) {
182
+ log(`Error processing bundle item: ${error}`, options);
199
183
  }
200
184
 
201
185
  return tag;
@@ -203,8 +187,8 @@ async function processInlineScript(tag, options) {
203
187
 
204
188
  function sri(userOptions = {}) {
205
189
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
206
- const sriMap = new Map();
207
190
  let isBuild = false;
191
+ let base = '';
208
192
 
209
193
  return {
210
194
  name: 'vite-plugin-sri4',
@@ -214,45 +198,12 @@ function sri(userOptions = {}) {
214
198
  configResolved(config) {
215
199
  options.domain = config.server?.host || '';
216
200
  isBuild = config.command === 'build';
201
+ base = config.base || '';
217
202
  log('Plugin configured in ' + (isBuild ? 'build' : 'dev') + ' mode', options);
203
+ log(`Base URL: ${base}`, options);
218
204
  },
219
205
 
220
- async renderChunk(code, chunk) {
221
- if (!isBuild) return null;
222
-
223
- const hash = computeSri(code, options.algorithm);
224
- if (hash) {
225
- for (const path of getAllPossiblePaths(chunk.fileName)) {
226
- sriMap.set(path, hash);
227
- log(`Stored SRI for path ${path}: ${hash}`, options);
228
- }
229
- }
230
- return null;
231
- },
232
-
233
- async generateBundle(_, bundle) {
234
- if (!isBuild) return;
235
-
236
- for (const fileName in bundle) {
237
- const chunk = bundle[fileName];
238
- if (chunk.type === 'asset' && !sriMap.has(fileName)) {
239
- const hash = computeSri(chunk.source, options.algorithm);
240
- if (hash) {
241
- for (const path of getAllPossiblePaths(fileName)) {
242
- sriMap.set(path, hash);
243
- log(`Computing SRI for asset ${path}: ${hash}`, options);
244
- }
245
- }
246
- }
247
- }
248
-
249
- log('Final sriMap contents:', options);
250
- for (const [key, value] of sriMap.entries()) {
251
- log(`${key} => ${value}`, options);
252
- }
253
- },
254
-
255
- async transformIndexHtml(html) {
206
+ async transformIndexHtml(html, ctx) {
256
207
  if (!isBuild || !html) {
257
208
  log('Skipping HTML transform in dev mode or empty HTML', options);
258
209
  return html;
@@ -260,38 +211,32 @@ function sri(userOptions = {}) {
260
211
 
261
212
  try {
262
213
  log('Starting HTML transformation', options);
263
- log(`SRI Map size: ${sriMap.size}`, options);
264
-
265
- // Process script tags
266
- const scriptTags = html.match(/<script[^>]+src=["']([^"']+)["'][^>]*>/g) || [];
267
- log(`Found ${scriptTags.length} script tags`, options);
268
- for (const tag of scriptTags) {
269
- const url = tag.match(/src=["']([^"']+)["']/)[1];
214
+ const bundle = ctx.bundle || {};
215
+ log(`Bundle size: ${Object.keys(bundle).length}`, options);
216
+
217
+ // Process script tags with and without quotes
218
+ const scriptTagRegex = /<script[^>]+src=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
219
+ let match;
220
+ while ((match = scriptTagRegex.exec(html)) !== null) {
221
+ const [tag, quotedUrl, unquotedUrl] = match;
222
+ const url = quotedUrl || unquotedUrl;
270
223
  log(`Processing script: ${url}`, options);
271
- const newTag = await processTag(tag, url, options, sriMap);
224
+ const newTag = await processTag(tag, url, options, bundle, base);
272
225
  html = html.replace(tag, newTag);
273
226
  }
274
227
 
275
- // Process inline scripts if enabled
276
- if (options.inlineScripts) {
277
- const inlineScripts = html.match(/<script[^>]*>([^<]+)<\/script>/g) || [];
278
- log(`Found ${inlineScripts.length} inline script tags`, options);
279
- for (const tag of inlineScripts) {
280
- const newTag = await processInlineScript(tag, options);
228
+ // Process link tags with and without quotes
229
+ const linkTagRegex = /<link[^>]+href=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
230
+ while ((match = linkTagRegex.exec(html)) !== null) {
231
+ const [tag, quotedUrl, unquotedUrl] = match;
232
+ const url = quotedUrl || unquotedUrl;
233
+ if (tag.includes('stylesheet') || tag.includes('modulepreload')) {
234
+ log(`Processing link: ${url}`, options);
235
+ const newTag = await processTag(tag, url, options, bundle, base);
281
236
  html = html.replace(tag, newTag);
282
237
  }
283
238
  }
284
239
 
285
- // Process link tags
286
- const linkTags = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*>/g) || [];
287
- log(`Found ${linkTags.length} link tags`, options);
288
- for (const tag of linkTags) {
289
- const url = tag.match(/href=["']([^"']+)["']/)[1];
290
- log(`Processing link: ${url}`, options);
291
- const newTag = await processTag(tag, url, options, sriMap);
292
- html = html.replace(tag, newTag);
293
- }
294
-
295
240
  return html;
296
241
  } catch (error) {
297
242
  console.error(`${LOG_PREFIX} Failed to transform HTML: ${error}`);
package/dist/index.js CHANGED
@@ -7,8 +7,7 @@ const DEFAULT_OPTIONS = {
7
7
  algorithm: 'sha384',
8
8
  bypassDomains: [],
9
9
  crossorigin: 'anonymous',
10
- debug: false,
11
- inlineScripts: false
10
+ debug: false
12
11
  };
13
12
 
14
13
  function log(message, options) {
@@ -19,13 +18,15 @@ function log(message, options) {
19
18
 
20
19
  function computeSri(content, algorithm = 'sha384') {
21
20
  try {
22
- if (typeof content === 'string') {
23
- content = Buffer.from(content, 'utf-8');
21
+ const hash = createHash(algorithm);
22
+ if (Buffer.isBuffer(content) || content instanceof Uint8Array) {
23
+ hash.update(content);
24
+ } else if (typeof content === 'string') {
25
+ hash.update(Buffer.from(content, 'utf-8'));
26
+ } else {
27
+ throw new Error('Invalid content type');
24
28
  }
25
- const hash = createHash(algorithm)
26
- .update(content)
27
- .digest('base64');
28
- return `${algorithm}-${hash}`;
29
+ return `${algorithm}-${hash.digest('base64')}`;
29
30
  } catch (error) {
30
31
  console.error(`${LOG_PREFIX} Failed to compute SRI hash: ${error}`);
31
32
  return null;
@@ -51,21 +52,14 @@ async function externalResourceIsCorsEnabled(url, options) {
51
52
  function isBypassDomain(url, bypassDomains = []) {
52
53
  if (!bypassDomains.length) return false;
53
54
  try {
54
- let parsedUrl;
55
- if (url.startsWith('//')) {
56
- parsedUrl = new URL(`http:${url}`);
57
- } else if (url.startsWith('http://') || url.startsWith('https://')) {
58
- parsedUrl = new URL(url);
59
- } else {
60
- parsedUrl = new URL(url, 'http://dummy');
61
- }
62
-
55
+ const parsedUrl = url.startsWith('//')
56
+ ? new URL(`http:${url}`)
57
+ : new URL(url, 'http://dummy');
63
58
  return bypassDomains.some(
64
59
  (domain) => parsedUrl.hostname === domain ||
65
60
  parsedUrl.hostname.endsWith(`.${domain}`)
66
61
  );
67
62
  } catch (e) {
68
- console.error(`${LOG_PREFIX} Failed to parse URL: ${url}`, e);
69
63
  return false;
70
64
  }
71
65
  }
@@ -74,38 +68,33 @@ function hasCrossOriginAttr(tag) {
74
68
  return /(?:^|\s)crossorigin(?:=["']?[^"'\s>]*["']?)?(?:\s|>|$)/i.test(tag);
75
69
  }
76
70
 
77
- function getAllPossiblePaths(url) {
71
+ function getBundleKey(url, base = '') {
72
+ // Remove base prefix if exists
73
+ let cleanUrl = url.startsWith(base) ? url.slice(base.length) : url;
74
+ // Remove leading slash
75
+ cleanUrl = cleanUrl.startsWith('/') ? cleanUrl.slice(1) : cleanUrl;
76
+
77
+ // Try different path combinations
78
78
  const paths = [
79
- url,
80
- url.startsWith('/') ? url.slice(1) : url,
81
- url.startsWith('/static/') ? url.slice(8) : url,
82
- !url.startsWith('/static/') ? `static/${url}` : url,
83
- !url.startsWith('/static/') ? `/static/${url}` : url,
84
- url.replace(/^\/static\//, ''),
85
- url.replace(/^static\//, '')
79
+ cleanUrl,
80
+ `static/${cleanUrl}`,
81
+ cleanUrl.replace(/^static\//, '')
86
82
  ];
87
83
 
88
- // Handle Vite's hashed filenames (e.g., index-DPifqqS2.js -> index.js)
89
- const withoutHash = url.replace(/-[a-zA-Z0-9]{8}\./, '.');
90
- if (withoutHash !== url) {
91
- paths.push(...getAllPossiblePaths(withoutHash));
92
- }
93
-
94
- // Handle variations with and without /static/ prefix for hashed files
95
- const hashedMatch = url.match(/^(?:\/static\/)?(.*?)-[a-zA-Z0-9]{8}(\..*?)$/);
96
- if (hashedMatch) {
97
- const [, base, ext] = hashedMatch;
98
- paths.push(
99
- `${base}${ext}`,
100
- `/static/${base}${ext}`,
101
- `static/${base}${ext}`
102
- );
84
+ // Remove hash part if exists and try again
85
+ const withoutHash = cleanUrl.replace(/-[a-zA-Z0-9]+\.([^.]+)$/, '.$1');
86
+ if (withoutHash !== cleanUrl) {
87
+ paths.push(...[
88
+ withoutHash,
89
+ `static/${withoutHash}`,
90
+ withoutHash.replace(/^static\//, '')
91
+ ]);
103
92
  }
104
93
 
105
94
  return [...new Set(paths)];
106
95
  }
107
96
 
108
- async function processTag(tag, url, options, sriMap) {
97
+ async function processTag(tag, url, options, bundle, base = '') {
109
98
  if (tag.includes('integrity=')) {
110
99
  log(`Skip tag with existing integrity attribute: ${tag}`, options);
111
100
  return tag;
@@ -146,54 +135,49 @@ async function processTag(tag, url, options, sriMap) {
146
135
  }
147
136
 
148
137
  // Handle local resources
149
- const possiblePaths = getAllPossiblePaths(url);
150
- let integrity = null;
151
-
152
- log(`Checking possible paths for ${url}:`, options);
153
- log(`Possible paths:`, options);
154
- possiblePaths.forEach(path => log(` - ${path}`, options));
155
-
156
- for (const path of possiblePaths) {
157
- log(`- Checking path: ${path}`, options);
158
- if (sriMap.has(path)) {
159
- integrity = sriMap.get(path);
160
- log(`Found integrity for path ${path}: ${integrity}`, options);
138
+ const possibleKeys = getBundleKey(url, base);
139
+ let bundleItem = null;
140
+ let source;
141
+
142
+ log(`Looking for bundle keys:`, options);
143
+ possibleKeys.forEach(key => log(`- ${key}`, options));
144
+
145
+ for (const key of possibleKeys) {
146
+ if (bundle[key]) {
147
+ bundleItem = bundle[key];
148
+ log(`Found bundle item for key: ${key}`, options);
161
149
  break;
162
150
  }
163
151
  }
164
152
 
165
- if (integrity) {
166
- log(`Using precomputed SRI for ${url}: ${integrity}`, options);
167
- const hasCrossOrigin = hasCrossOriginAttr(tag);
168
- const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
169
- const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
170
- log(`New tag: ${newTag}`, options);
171
- return newTag;
172
- }
173
-
174
- log(`No SRI hash found for ${url}`, options);
175
- log(`Available paths in sriMap: ${Array.from(sriMap.keys()).join(', ')}`, options);
176
- return tag;
177
- }
178
-
179
- async function processInlineScript(tag, options) {
180
- if (tag.includes('integrity=')) {
181
- log(`Skip inline script with existing integrity attribute: ${tag}`, options);
153
+ if (!bundleItem) {
154
+ log(`Available bundle keys:`, options);
155
+ Object.keys(bundle).forEach(key => log(`- ${key}`, options));
182
156
  return tag;
183
157
  }
184
158
 
185
- const content = tag.match(/<script[^>]*>([\s\S]*?)<\/script>/)?.[1]?.trim();
186
- if (!content) {
187
- log(`Skip empty inline script: ${tag}`, options);
188
- return tag;
189
- }
159
+ log(`Bundle item type: ${bundleItem.type}`, options);
190
160
 
191
- const hash = computeSri(content, options.algorithm);
192
- if (hash) {
193
- log(`Computing SRI for inline script: ${hash}`, options);
194
- const newTag = tag.replace('>', ` integrity="${hash}">`);
195
- log(`New inline script tag: ${newTag}`, options);
196
- return newTag;
161
+ try {
162
+ if (bundleItem.type === 'chunk') {
163
+ source = bundleItem.code;
164
+ log(`Processing chunk content of length: ${source.length}`, options);
165
+ } else {
166
+ source = bundleItem.source;
167
+ log(`Processing asset content of length: ${source.length}`, options);
168
+ }
169
+
170
+ const integrity = computeSri(source, options.algorithm);
171
+ if (integrity) {
172
+ log(`Computing SRI for local resource ${url}: ${integrity}`, options);
173
+ const hasCrossOrigin = hasCrossOriginAttr(tag);
174
+ const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
175
+ const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
176
+ log(`New tag: ${newTag}`, options);
177
+ return newTag;
178
+ }
179
+ } catch (error) {
180
+ log(`Error processing bundle item: ${error}`, options);
197
181
  }
198
182
 
199
183
  return tag;
@@ -201,8 +185,8 @@ async function processInlineScript(tag, options) {
201
185
 
202
186
  function sri(userOptions = {}) {
203
187
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
204
- const sriMap = new Map();
205
188
  let isBuild = false;
189
+ let base = '';
206
190
 
207
191
  return {
208
192
  name: 'vite-plugin-sri4',
@@ -212,45 +196,12 @@ function sri(userOptions = {}) {
212
196
  configResolved(config) {
213
197
  options.domain = config.server?.host || '';
214
198
  isBuild = config.command === 'build';
199
+ base = config.base || '';
215
200
  log('Plugin configured in ' + (isBuild ? 'build' : 'dev') + ' mode', options);
201
+ log(`Base URL: ${base}`, options);
216
202
  },
217
203
 
218
- async renderChunk(code, chunk) {
219
- if (!isBuild) return null;
220
-
221
- const hash = computeSri(code, options.algorithm);
222
- if (hash) {
223
- for (const path of getAllPossiblePaths(chunk.fileName)) {
224
- sriMap.set(path, hash);
225
- log(`Stored SRI for path ${path}: ${hash}`, options);
226
- }
227
- }
228
- return null;
229
- },
230
-
231
- async generateBundle(_, bundle) {
232
- if (!isBuild) return;
233
-
234
- for (const fileName in bundle) {
235
- const chunk = bundle[fileName];
236
- if (chunk.type === 'asset' && !sriMap.has(fileName)) {
237
- const hash = computeSri(chunk.source, options.algorithm);
238
- if (hash) {
239
- for (const path of getAllPossiblePaths(fileName)) {
240
- sriMap.set(path, hash);
241
- log(`Computing SRI for asset ${path}: ${hash}`, options);
242
- }
243
- }
244
- }
245
- }
246
-
247
- log('Final sriMap contents:', options);
248
- for (const [key, value] of sriMap.entries()) {
249
- log(`${key} => ${value}`, options);
250
- }
251
- },
252
-
253
- async transformIndexHtml(html) {
204
+ async transformIndexHtml(html, ctx) {
254
205
  if (!isBuild || !html) {
255
206
  log('Skipping HTML transform in dev mode or empty HTML', options);
256
207
  return html;
@@ -258,38 +209,32 @@ function sri(userOptions = {}) {
258
209
 
259
210
  try {
260
211
  log('Starting HTML transformation', options);
261
- log(`SRI Map size: ${sriMap.size}`, options);
262
-
263
- // Process script tags
264
- const scriptTags = html.match(/<script[^>]+src=["']([^"']+)["'][^>]*>/g) || [];
265
- log(`Found ${scriptTags.length} script tags`, options);
266
- for (const tag of scriptTags) {
267
- const url = tag.match(/src=["']([^"']+)["']/)[1];
212
+ const bundle = ctx.bundle || {};
213
+ log(`Bundle size: ${Object.keys(bundle).length}`, options);
214
+
215
+ // Process script tags with and without quotes
216
+ const scriptTagRegex = /<script[^>]+src=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
217
+ let match;
218
+ while ((match = scriptTagRegex.exec(html)) !== null) {
219
+ const [tag, quotedUrl, unquotedUrl] = match;
220
+ const url = quotedUrl || unquotedUrl;
268
221
  log(`Processing script: ${url}`, options);
269
- const newTag = await processTag(tag, url, options, sriMap);
222
+ const newTag = await processTag(tag, url, options, bundle, base);
270
223
  html = html.replace(tag, newTag);
271
224
  }
272
225
 
273
- // Process inline scripts if enabled
274
- if (options.inlineScripts) {
275
- const inlineScripts = html.match(/<script[^>]*>([^<]+)<\/script>/g) || [];
276
- log(`Found ${inlineScripts.length} inline script tags`, options);
277
- for (const tag of inlineScripts) {
278
- const newTag = await processInlineScript(tag, options);
226
+ // Process link tags with and without quotes
227
+ const linkTagRegex = /<link[^>]+href=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
228
+ while ((match = linkTagRegex.exec(html)) !== null) {
229
+ const [tag, quotedUrl, unquotedUrl] = match;
230
+ const url = quotedUrl || unquotedUrl;
231
+ if (tag.includes('stylesheet') || tag.includes('modulepreload')) {
232
+ log(`Processing link: ${url}`, options);
233
+ const newTag = await processTag(tag, url, options, bundle, base);
279
234
  html = html.replace(tag, newTag);
280
235
  }
281
236
  }
282
237
 
283
- // Process link tags
284
- const linkTags = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*>/g) || [];
285
- log(`Found ${linkTags.length} link tags`, options);
286
- for (const tag of linkTags) {
287
- const url = tag.match(/href=["']([^"']+)["']/)[1];
288
- log(`Processing link: ${url}`, options);
289
- const newTag = await processTag(tag, url, options, sriMap);
290
- html = html.replace(tag, newTag);
291
- }
292
-
293
238
  return html;
294
239
  } catch (error) {
295
240
  console.error(`${LOG_PREFIX} Failed to transform HTML: ${error}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-sri4",
3
- "version": "1.8.2",
3
+ "version": "1.8.5",
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",