vite-plugin-sri4 1.8.2 → 1.8.3

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 +58 -142
  2. package/dist/index.js +58 -142
  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,9 +20,6 @@ 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');
26
- }
27
23
  const hash = node_crypto.createHash(algorithm)
28
24
  .update(content)
29
25
  .digest('base64');
@@ -53,21 +49,14 @@ async function externalResourceIsCorsEnabled(url, options) {
53
49
  function isBypassDomain(url, bypassDomains = []) {
54
50
  if (!bypassDomains.length) return false;
55
51
  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
-
52
+ const parsedUrl = url.startsWith('//')
53
+ ? new URL(`http:${url}`)
54
+ : new URL(url, 'http://dummy');
65
55
  return bypassDomains.some(
66
56
  (domain) => parsedUrl.hostname === domain ||
67
57
  parsedUrl.hostname.endsWith(`.${domain}`)
68
58
  );
69
59
  } catch (e) {
70
- console.error(`${LOG_PREFIX} Failed to parse URL: ${url}`, e);
71
60
  return false;
72
61
  }
73
62
  }
@@ -76,38 +65,27 @@ function hasCrossOriginAttr(tag) {
76
65
  return /(?:^|\s)crossorigin(?:=["']?[^"'\s>]*["']?)?(?:\s|>|$)/i.test(tag);
77
66
  }
78
67
 
79
- function getAllPossiblePaths(url) {
80
- const paths = [
68
+ function getBundleKey(url, base = '') {
69
+ // 嘗試各種可能的路徑格式
70
+ const possiblePaths = [
81
71
  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,
72
+ url.replace(/^\//, ''),
86
73
  url.replace(/^\/static\//, ''),
87
- url.replace(/^static\//, '')
74
+ url.replace(/^static\//, ''),
75
+ url.replace(base, ''),
76
+ url.replace(base, '').replace(/^\//, '')
88
77
  ];
89
78
 
90
- // Handle Vite's hashed filenames (e.g., index-DPifqqS2.js -> index.js)
91
- const withoutHash = url.replace(/-[a-zA-Z0-9]{8}\./, '.');
79
+ // 移除 hash 後的版本
80
+ const withoutHash = url.replace(/-[a-zA-Z0-9]+\.([^.]+)$/, '.$1');
92
81
  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
- );
82
+ possiblePaths.push(...getBundleKey(withoutHash, base));
105
83
  }
106
84
 
107
- return [...new Set(paths)];
85
+ return [...new Set(possiblePaths)];
108
86
  }
109
87
 
110
- async function processTag(tag, url, options, sriMap) {
88
+ async function processTag(tag, url, options, bundle, base = '') {
111
89
  if (tag.includes('integrity=')) {
112
90
  log(`Skip tag with existing integrity attribute: ${tag}`, options);
113
91
  return tag;
@@ -148,54 +126,31 @@ async function processTag(tag, url, options, sriMap) {
148
126
  }
149
127
 
150
128
  // 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));
129
+ const possibleKeys = getBundleKey(url, base);
130
+ let bundleItem = null;
157
131
 
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);
132
+ for (const key of possibleKeys) {
133
+ if (bundle[key]) {
134
+ bundleItem = bundle[key];
135
+ log(`Found bundle item for key: ${key}`, options);
163
136
  break;
164
137
  }
165
138
  }
166
139
 
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);
184
- return tag;
185
- }
186
-
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
- }
192
-
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;
140
+ if (bundleItem) {
141
+ const source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
142
+ const integrity = computeSri(source, options.algorithm);
143
+ if (integrity) {
144
+ log(`Computing SRI for local resource ${url}: ${integrity}`, options);
145
+ const hasCrossOrigin = hasCrossOriginAttr(tag);
146
+ const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
147
+ const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
148
+ log(`New tag: ${newTag}`, options);
149
+ return newTag;
150
+ }
151
+ } else {
152
+ log(`No bundle item found for ${url}`, options);
153
+ log(`Available bundle keys: ${Object.keys(bundle).join(', ')}`, options);
199
154
  }
200
155
 
201
156
  return tag;
@@ -203,8 +158,8 @@ async function processInlineScript(tag, options) {
203
158
 
204
159
  function sri(userOptions = {}) {
205
160
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
206
- const sriMap = new Map();
207
161
  let isBuild = false;
162
+ let base = '';
208
163
 
209
164
  return {
210
165
  name: 'vite-plugin-sri4',
@@ -214,45 +169,12 @@ function sri(userOptions = {}) {
214
169
  configResolved(config) {
215
170
  options.domain = config.server?.host || '';
216
171
  isBuild = config.command === 'build';
172
+ base = config.base || '';
217
173
  log('Plugin configured in ' + (isBuild ? 'build' : 'dev') + ' mode', options);
174
+ log(`Base URL: ${base}`, options);
218
175
  },
219
176
 
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) {
177
+ async transformIndexHtml(html, ctx) {
256
178
  if (!isBuild || !html) {
257
179
  log('Skipping HTML transform in dev mode or empty HTML', options);
258
180
  return html;
@@ -260,38 +182,32 @@ function sri(userOptions = {}) {
260
182
 
261
183
  try {
262
184
  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];
185
+ const bundle = ctx.bundle || {};
186
+ log(`Bundle size: ${Object.keys(bundle).length}`, options);
187
+
188
+ // Process script tags with and without quotes
189
+ const scriptTagRegex = /<script[^>]+src=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
190
+ let match;
191
+ while ((match = scriptTagRegex.exec(html)) !== null) {
192
+ const [tag, quotedUrl, unquotedUrl] = match;
193
+ const url = quotedUrl || unquotedUrl;
270
194
  log(`Processing script: ${url}`, options);
271
- const newTag = await processTag(tag, url, options, sriMap);
195
+ const newTag = await processTag(tag, url, options, bundle, base);
272
196
  html = html.replace(tag, newTag);
273
197
  }
274
198
 
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);
199
+ // Process link tags with and without quotes
200
+ const linkTagRegex = /<link[^>]+href=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
201
+ while ((match = linkTagRegex.exec(html)) !== null) {
202
+ const [tag, quotedUrl, unquotedUrl] = match;
203
+ const url = quotedUrl || unquotedUrl;
204
+ if (tag.includes('stylesheet') || tag.includes('modulepreload')) {
205
+ log(`Processing link: ${url}`, options);
206
+ const newTag = await processTag(tag, url, options, bundle, base);
281
207
  html = html.replace(tag, newTag);
282
208
  }
283
209
  }
284
210
 
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
211
  return html;
296
212
  } catch (error) {
297
213
  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,9 +18,6 @@ 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');
24
- }
25
21
  const hash = createHash(algorithm)
26
22
  .update(content)
27
23
  .digest('base64');
@@ -51,21 +47,14 @@ async function externalResourceIsCorsEnabled(url, options) {
51
47
  function isBypassDomain(url, bypassDomains = []) {
52
48
  if (!bypassDomains.length) return false;
53
49
  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
-
50
+ const parsedUrl = url.startsWith('//')
51
+ ? new URL(`http:${url}`)
52
+ : new URL(url, 'http://dummy');
63
53
  return bypassDomains.some(
64
54
  (domain) => parsedUrl.hostname === domain ||
65
55
  parsedUrl.hostname.endsWith(`.${domain}`)
66
56
  );
67
57
  } catch (e) {
68
- console.error(`${LOG_PREFIX} Failed to parse URL: ${url}`, e);
69
58
  return false;
70
59
  }
71
60
  }
@@ -74,38 +63,27 @@ function hasCrossOriginAttr(tag) {
74
63
  return /(?:^|\s)crossorigin(?:=["']?[^"'\s>]*["']?)?(?:\s|>|$)/i.test(tag);
75
64
  }
76
65
 
77
- function getAllPossiblePaths(url) {
78
- const paths = [
66
+ function getBundleKey(url, base = '') {
67
+ // 嘗試各種可能的路徑格式
68
+ const possiblePaths = [
79
69
  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,
70
+ url.replace(/^\//, ''),
84
71
  url.replace(/^\/static\//, ''),
85
- url.replace(/^static\//, '')
72
+ url.replace(/^static\//, ''),
73
+ url.replace(base, ''),
74
+ url.replace(base, '').replace(/^\//, '')
86
75
  ];
87
76
 
88
- // Handle Vite's hashed filenames (e.g., index-DPifqqS2.js -> index.js)
89
- const withoutHash = url.replace(/-[a-zA-Z0-9]{8}\./, '.');
77
+ // 移除 hash 後的版本
78
+ const withoutHash = url.replace(/-[a-zA-Z0-9]+\.([^.]+)$/, '.$1');
90
79
  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
- );
80
+ possiblePaths.push(...getBundleKey(withoutHash, base));
103
81
  }
104
82
 
105
- return [...new Set(paths)];
83
+ return [...new Set(possiblePaths)];
106
84
  }
107
85
 
108
- async function processTag(tag, url, options, sriMap) {
86
+ async function processTag(tag, url, options, bundle, base = '') {
109
87
  if (tag.includes('integrity=')) {
110
88
  log(`Skip tag with existing integrity attribute: ${tag}`, options);
111
89
  return tag;
@@ -146,54 +124,31 @@ async function processTag(tag, url, options, sriMap) {
146
124
  }
147
125
 
148
126
  // 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));
127
+ const possibleKeys = getBundleKey(url, base);
128
+ let bundleItem = null;
155
129
 
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);
130
+ for (const key of possibleKeys) {
131
+ if (bundle[key]) {
132
+ bundleItem = bundle[key];
133
+ log(`Found bundle item for key: ${key}`, options);
161
134
  break;
162
135
  }
163
136
  }
164
137
 
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);
182
- return tag;
183
- }
184
-
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
- }
190
-
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;
138
+ if (bundleItem) {
139
+ const source = bundleItem.type === 'chunk' ? bundleItem.code : bundleItem.source;
140
+ const integrity = computeSri(source, options.algorithm);
141
+ if (integrity) {
142
+ log(`Computing SRI for local resource ${url}: ${integrity}`, options);
143
+ const hasCrossOrigin = hasCrossOriginAttr(tag);
144
+ const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
145
+ const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
146
+ log(`New tag: ${newTag}`, options);
147
+ return newTag;
148
+ }
149
+ } else {
150
+ log(`No bundle item found for ${url}`, options);
151
+ log(`Available bundle keys: ${Object.keys(bundle).join(', ')}`, options);
197
152
  }
198
153
 
199
154
  return tag;
@@ -201,8 +156,8 @@ async function processInlineScript(tag, options) {
201
156
 
202
157
  function sri(userOptions = {}) {
203
158
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
204
- const sriMap = new Map();
205
159
  let isBuild = false;
160
+ let base = '';
206
161
 
207
162
  return {
208
163
  name: 'vite-plugin-sri4',
@@ -212,45 +167,12 @@ function sri(userOptions = {}) {
212
167
  configResolved(config) {
213
168
  options.domain = config.server?.host || '';
214
169
  isBuild = config.command === 'build';
170
+ base = config.base || '';
215
171
  log('Plugin configured in ' + (isBuild ? 'build' : 'dev') + ' mode', options);
172
+ log(`Base URL: ${base}`, options);
216
173
  },
217
174
 
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) {
175
+ async transformIndexHtml(html, ctx) {
254
176
  if (!isBuild || !html) {
255
177
  log('Skipping HTML transform in dev mode or empty HTML', options);
256
178
  return html;
@@ -258,38 +180,32 @@ function sri(userOptions = {}) {
258
180
 
259
181
  try {
260
182
  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];
183
+ const bundle = ctx.bundle || {};
184
+ log(`Bundle size: ${Object.keys(bundle).length}`, options);
185
+
186
+ // Process script tags with and without quotes
187
+ const scriptTagRegex = /<script[^>]+src=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
188
+ let match;
189
+ while ((match = scriptTagRegex.exec(html)) !== null) {
190
+ const [tag, quotedUrl, unquotedUrl] = match;
191
+ const url = quotedUrl || unquotedUrl;
268
192
  log(`Processing script: ${url}`, options);
269
- const newTag = await processTag(tag, url, options, sriMap);
193
+ const newTag = await processTag(tag, url, options, bundle, base);
270
194
  html = html.replace(tag, newTag);
271
195
  }
272
196
 
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);
197
+ // Process link tags with and without quotes
198
+ const linkTagRegex = /<link[^>]+href=(?:["']([^"']+)["']|([^ >]+))[^>]*>/g;
199
+ while ((match = linkTagRegex.exec(html)) !== null) {
200
+ const [tag, quotedUrl, unquotedUrl] = match;
201
+ const url = quotedUrl || unquotedUrl;
202
+ if (tag.includes('stylesheet') || tag.includes('modulepreload')) {
203
+ log(`Processing link: ${url}`, options);
204
+ const newTag = await processTag(tag, url, options, bundle, base);
279
205
  html = html.replace(tag, newTag);
280
206
  }
281
207
  }
282
208
 
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
209
  return html;
294
210
  } catch (error) {
295
211
  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.3",
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",