vite-plugin-sri4 1.8.0 → 1.8.2

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 CHANGED
@@ -9,7 +9,8 @@ const DEFAULT_OPTIONS = {
9
9
  algorithm: 'sha384',
10
10
  bypassDomains: [],
11
11
  crossorigin: 'anonymous',
12
- debug: false
12
+ debug: false,
13
+ inlineScripts: false
13
14
  };
14
15
 
15
16
  function log(message, options) {
@@ -20,6 +21,9 @@ function log(message, options) {
20
21
 
21
22
  function computeSri(content, algorithm = 'sha384') {
22
23
  try {
24
+ if (typeof content === 'string') {
25
+ content = Buffer.from(content, 'utf-8');
26
+ }
23
27
  const hash = node_crypto.createHash(algorithm)
24
28
  .update(content)
25
29
  .digest('base64');
@@ -49,14 +53,21 @@ async function externalResourceIsCorsEnabled(url, options) {
49
53
  function isBypassDomain(url, bypassDomains = []) {
50
54
  if (!bypassDomains.length) return false;
51
55
  try {
52
- const parsedUrl = url.startsWith('//')
53
- ? new URL(`http:${url}`)
54
- : new URL(url, 'http://dummy');
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
+
55
65
  return bypassDomains.some(
56
66
  (domain) => parsedUrl.hostname === domain ||
57
67
  parsedUrl.hostname.endsWith(`.${domain}`)
58
68
  );
59
69
  } catch (e) {
70
+ console.error(`${LOG_PREFIX} Failed to parse URL: ${url}`, e);
60
71
  return false;
61
72
  }
62
73
  }
@@ -76,11 +87,23 @@ function getAllPossiblePaths(url) {
76
87
  url.replace(/^static\//, '')
77
88
  ];
78
89
 
79
- const withoutHash = url.replace(/-[a-zA-Z0-9]+\./, '.');
90
+ // Handle Vite's hashed filenames (e.g., index-DPifqqS2.js -> index.js)
91
+ const withoutHash = url.replace(/-[a-zA-Z0-9]{8}\./, '.');
80
92
  if (withoutHash !== url) {
81
93
  paths.push(...getAllPossiblePaths(withoutHash));
82
94
  }
83
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
+ );
105
+ }
106
+
84
107
  return [...new Set(paths)];
85
108
  }
86
109
 
@@ -129,6 +152,9 @@ async function processTag(tag, url, options, sriMap) {
129
152
  let integrity = null;
130
153
 
131
154
  log(`Checking possible paths for ${url}:`, options);
155
+ log(`Possible paths:`, options);
156
+ possiblePaths.forEach(path => log(` - ${path}`, options));
157
+
132
158
  for (const path of possiblePaths) {
133
159
  log(`- Checking path: ${path}`, options);
134
160
  if (sriMap.has(path)) {
@@ -152,6 +178,29 @@ async function processTag(tag, url, options, sriMap) {
152
178
  return tag;
153
179
  }
154
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;
199
+ }
200
+
201
+ return tag;
202
+ }
203
+
155
204
  function sri(userOptions = {}) {
156
205
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
157
206
  const sriMap = new Map();
@@ -223,6 +272,16 @@ function sri(userOptions = {}) {
223
272
  html = html.replace(tag, newTag);
224
273
  }
225
274
 
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);
281
+ html = html.replace(tag, newTag);
282
+ }
283
+ }
284
+
226
285
  // Process link tags
227
286
  const linkTags = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*>/g) || [];
228
287
  log(`Found ${linkTags.length} link tags`, options);
package/dist/index.js CHANGED
@@ -7,7 +7,8 @@ const DEFAULT_OPTIONS = {
7
7
  algorithm: 'sha384',
8
8
  bypassDomains: [],
9
9
  crossorigin: 'anonymous',
10
- debug: false
10
+ debug: false,
11
+ inlineScripts: false
11
12
  };
12
13
 
13
14
  function log(message, options) {
@@ -18,6 +19,9 @@ function log(message, options) {
18
19
 
19
20
  function computeSri(content, algorithm = 'sha384') {
20
21
  try {
22
+ if (typeof content === 'string') {
23
+ content = Buffer.from(content, 'utf-8');
24
+ }
21
25
  const hash = createHash(algorithm)
22
26
  .update(content)
23
27
  .digest('base64');
@@ -47,14 +51,21 @@ async function externalResourceIsCorsEnabled(url, options) {
47
51
  function isBypassDomain(url, bypassDomains = []) {
48
52
  if (!bypassDomains.length) return false;
49
53
  try {
50
- const parsedUrl = url.startsWith('//')
51
- ? new URL(`http:${url}`)
52
- : new URL(url, 'http://dummy');
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
+
53
63
  return bypassDomains.some(
54
64
  (domain) => parsedUrl.hostname === domain ||
55
65
  parsedUrl.hostname.endsWith(`.${domain}`)
56
66
  );
57
67
  } catch (e) {
68
+ console.error(`${LOG_PREFIX} Failed to parse URL: ${url}`, e);
58
69
  return false;
59
70
  }
60
71
  }
@@ -74,11 +85,23 @@ function getAllPossiblePaths(url) {
74
85
  url.replace(/^static\//, '')
75
86
  ];
76
87
 
77
- const withoutHash = url.replace(/-[a-zA-Z0-9]+\./, '.');
88
+ // Handle Vite's hashed filenames (e.g., index-DPifqqS2.js -> index.js)
89
+ const withoutHash = url.replace(/-[a-zA-Z0-9]{8}\./, '.');
78
90
  if (withoutHash !== url) {
79
91
  paths.push(...getAllPossiblePaths(withoutHash));
80
92
  }
81
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
+ );
103
+ }
104
+
82
105
  return [...new Set(paths)];
83
106
  }
84
107
 
@@ -127,6 +150,9 @@ async function processTag(tag, url, options, sriMap) {
127
150
  let integrity = null;
128
151
 
129
152
  log(`Checking possible paths for ${url}:`, options);
153
+ log(`Possible paths:`, options);
154
+ possiblePaths.forEach(path => log(` - ${path}`, options));
155
+
130
156
  for (const path of possiblePaths) {
131
157
  log(`- Checking path: ${path}`, options);
132
158
  if (sriMap.has(path)) {
@@ -150,6 +176,29 @@ async function processTag(tag, url, options, sriMap) {
150
176
  return tag;
151
177
  }
152
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;
197
+ }
198
+
199
+ return tag;
200
+ }
201
+
153
202
  function sri(userOptions = {}) {
154
203
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
155
204
  const sriMap = new Map();
@@ -221,6 +270,16 @@ function sri(userOptions = {}) {
221
270
  html = html.replace(tag, newTag);
222
271
  }
223
272
 
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);
279
+ html = html.replace(tag, newTag);
280
+ }
281
+ }
282
+
224
283
  // Process link tags
225
284
  const linkTags = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*>/g) || [];
226
285
  log(`Found ${linkTags.length} link tags`, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-sri4",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
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",