vite-plugin-sri4 1.7.0 → 1.8.0

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
@@ -12,23 +12,12 @@ const DEFAULT_OPTIONS = {
12
12
  debug: false
13
13
  };
14
14
 
15
- /**
16
- * Log debug messages if debug mode is enabled
17
- * @param {string} message - Message to log
18
- * @param {Object} options - Plugin options
19
- */
20
15
  function log(message, options) {
21
16
  if (options.debug) {
22
17
  console.log(`${LOG_PREFIX} ${message}`);
23
18
  }
24
19
  }
25
20
 
26
- /**
27
- * Compute SRI hash for given content
28
- * @param {string|Buffer} content - Content to hash
29
- * @param {string} algorithm - Hash algorithm to use
30
- * @returns {string|null} SRI hash string or null if failed
31
- */
32
21
  function computeSri(content, algorithm = 'sha384') {
33
22
  try {
34
23
  const hash = node_crypto.createHash(algorithm)
@@ -41,17 +30,10 @@ function computeSri(content, algorithm = 'sha384') {
41
30
  }
42
31
  }
43
32
 
44
- /**
45
- * Check if external resource has CORS enabled
46
- * @param {string} url - URL to check
47
- * @param {Object} options - Plugin options
48
- * @returns {Promise<boolean>} Whether CORS is enabled
49
- */
50
33
  async function externalResourceIsCorsEnabled(url, options) {
51
34
  try {
52
35
  const response = await fetch(url, {
53
- method: 'HEAD',
54
- timeout: 5000
36
+ method: 'HEAD'
55
37
  });
56
38
  const acao = response.headers.get('access-control-allow-origin');
57
39
  if (acao && (acao === '*' || acao.includes(options.domain || ''))) {
@@ -64,12 +46,6 @@ async function externalResourceIsCorsEnabled(url, options) {
64
46
  }
65
47
  }
66
48
 
67
- /**
68
- * Check if URL belongs to bypass domains
69
- * @param {string} url - URL to check
70
- * @param {string[]} bypassDomains - List of domains to bypass
71
- * @returns {boolean} Whether URL belongs to bypass domains
72
- */
73
49
  function isBypassDomain(url, bypassDomains = []) {
74
50
  if (!bypassDomains.length) return false;
75
51
  try {
@@ -85,29 +61,38 @@ function isBypassDomain(url, bypassDomains = []) {
85
61
  }
86
62
  }
87
63
 
88
- /**
89
- * Check if tag already has crossorigin attribute
90
- * @param {string} tag - HTML tag to check
91
- * @returns {boolean} Whether tag has crossorigin attribute
92
- */
93
64
  function hasCrossOriginAttr(tag) {
94
65
  return /(?:^|\s)crossorigin(?:=["']?[^"'\s>]*["']?)?(?:\s|>|$)/i.test(tag);
95
66
  }
96
67
 
97
- /**
98
- * Process individual HTML tags and add SRI attributes
99
- * @param {string} tag - HTML tag to process
100
- * @param {string} url - Resource URL
101
- * @param {Object} options - Plugin options
102
- * @param {Map} sriMap - Map of file names to SRI hashes
103
- * @returns {Promise<string>} Processed HTML tag
104
- */
68
+ function getAllPossiblePaths(url) {
69
+ const paths = [
70
+ url,
71
+ url.startsWith('/') ? url.slice(1) : url,
72
+ url.startsWith('/static/') ? url.slice(8) : url,
73
+ !url.startsWith('/static/') ? `static/${url}` : url,
74
+ !url.startsWith('/static/') ? `/static/${url}` : url,
75
+ url.replace(/^\/static\//, ''),
76
+ url.replace(/^static\//, '')
77
+ ];
78
+
79
+ const withoutHash = url.replace(/-[a-zA-Z0-9]+\./, '.');
80
+ if (withoutHash !== url) {
81
+ paths.push(...getAllPossiblePaths(withoutHash));
82
+ }
83
+
84
+ return [...new Set(paths)];
85
+ }
86
+
105
87
  async function processTag(tag, url, options, sriMap) {
106
88
  if (tag.includes('integrity=')) {
107
89
  log(`Skip tag with existing integrity attribute: ${tag}`, options);
108
90
  return tag;
109
91
  }
110
92
 
93
+ log(`Processing tag: ${tag}`, options);
94
+ log(`URL: ${url}`, options);
95
+
111
96
  // Handle external resources
112
97
  if (/^(https?:)?\/\//i.test(url)) {
113
98
  if (isBypassDomain(url, options.bypassDomains)) {
@@ -129,7 +114,9 @@ async function processTag(tag, url, options, sriMap) {
129
114
  log(`Computing SRI for external resource ${url}: ${hash}`, options);
130
115
  const hasCrossOrigin = hasCrossOriginAttr(tag);
131
116
  const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
132
- return tag.replace(/>$/, ` integrity="${hash}"${crossOriginAttr}>`);
117
+ const newTag = tag.replace(/>$/, ` integrity="${hash}"${crossOriginAttr}>`);
118
+ log(`New tag: ${newTag}`, options);
119
+ return newTag;
133
120
  }
134
121
  } catch (error) {
135
122
  log(`Failed to process external resource ${url}: ${error}`, options);
@@ -138,24 +125,33 @@ async function processTag(tag, url, options, sriMap) {
138
125
  }
139
126
 
140
127
  // Handle local resources
141
- const fileName = url.startsWith('/') ? url.slice(1) : url;
142
- const integrity = sriMap.get(fileName);
128
+ const possiblePaths = getAllPossiblePaths(url);
129
+ let integrity = null;
130
+
131
+ log(`Checking possible paths for ${url}:`, options);
132
+ for (const path of possiblePaths) {
133
+ log(`- Checking path: ${path}`, options);
134
+ if (sriMap.has(path)) {
135
+ integrity = sriMap.get(path);
136
+ log(`Found integrity for path ${path}: ${integrity}`, options);
137
+ break;
138
+ }
139
+ }
140
+
143
141
  if (integrity) {
144
- log(`Using precomputed SRI for ${fileName}: ${integrity}`, options);
142
+ log(`Using precomputed SRI for ${url}: ${integrity}`, options);
145
143
  const hasCrossOrigin = hasCrossOriginAttr(tag);
146
144
  const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
147
- return tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
145
+ const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
146
+ log(`New tag: ${newTag}`, options);
147
+ return newTag;
148
148
  }
149
149
 
150
- log(`No SRI hash found for ${fileName}`, options);
150
+ log(`No SRI hash found for ${url}`, options);
151
+ log(`Available paths in sriMap: ${Array.from(sriMap.keys()).join(', ')}`, options);
151
152
  return tag;
152
153
  }
153
154
 
154
- /**
155
- * Vite plugin for adding Subresource Integrity (SRI) hashes to assets
156
- * @param {Object} userOptions - Plugin options
157
- * @returns {import('vite').Plugin} Vite plugin object
158
- */
159
155
  function sri(userOptions = {}) {
160
156
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
161
157
  const sriMap = new Map();
@@ -177,8 +173,10 @@ function sri(userOptions = {}) {
177
173
 
178
174
  const hash = computeSri(code, options.algorithm);
179
175
  if (hash) {
180
- sriMap.set(chunk.fileName, hash);
181
- log(`Computing SRI for chunk ${chunk.fileName}: ${hash}`, options);
176
+ for (const path of getAllPossiblePaths(chunk.fileName)) {
177
+ sriMap.set(path, hash);
178
+ log(`Stored SRI for path ${path}: ${hash}`, options);
179
+ }
182
180
  }
183
181
  return null;
184
182
  },
@@ -191,11 +189,18 @@ function sri(userOptions = {}) {
191
189
  if (chunk.type === 'asset' && !sriMap.has(fileName)) {
192
190
  const hash = computeSri(chunk.source, options.algorithm);
193
191
  if (hash) {
194
- sriMap.set(fileName, hash);
195
- log(`Computing SRI for asset ${fileName}: ${hash}`, options);
192
+ for (const path of getAllPossiblePaths(fileName)) {
193
+ sriMap.set(path, hash);
194
+ log(`Computing SRI for asset ${path}: ${hash}`, options);
195
+ }
196
196
  }
197
197
  }
198
198
  }
199
+
200
+ log('Final sriMap contents:', options);
201
+ for (const [key, value] of sriMap.entries()) {
202
+ log(`${key} => ${value}`, options);
203
+ }
199
204
  },
200
205
 
201
206
  async transformIndexHtml(html) {
@@ -205,18 +210,25 @@ function sri(userOptions = {}) {
205
210
  }
206
211
 
207
212
  try {
213
+ log('Starting HTML transformation', options);
214
+ log(`SRI Map size: ${sriMap.size}`, options);
215
+
208
216
  // Process script tags
209
217
  const scriptTags = html.match(/<script[^>]+src=["']([^"']+)["'][^>]*>/g) || [];
218
+ log(`Found ${scriptTags.length} script tags`, options);
210
219
  for (const tag of scriptTags) {
211
220
  const url = tag.match(/src=["']([^"']+)["']/)[1];
221
+ log(`Processing script: ${url}`, options);
212
222
  const newTag = await processTag(tag, url, options, sriMap);
213
223
  html = html.replace(tag, newTag);
214
224
  }
215
225
 
216
226
  // Process link tags
217
227
  const linkTags = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*>/g) || [];
228
+ log(`Found ${linkTags.length} link tags`, options);
218
229
  for (const tag of linkTags) {
219
230
  const url = tag.match(/href=["']([^"']+)["']/)[1];
231
+ log(`Processing link: ${url}`, options);
220
232
  const newTag = await processTag(tag, url, options, sriMap);
221
233
  html = html.replace(tag, newTag);
222
234
  }
package/dist/index.js CHANGED
@@ -10,23 +10,12 @@ const DEFAULT_OPTIONS = {
10
10
  debug: false
11
11
  };
12
12
 
13
- /**
14
- * Log debug messages if debug mode is enabled
15
- * @param {string} message - Message to log
16
- * @param {Object} options - Plugin options
17
- */
18
13
  function log(message, options) {
19
14
  if (options.debug) {
20
15
  console.log(`${LOG_PREFIX} ${message}`);
21
16
  }
22
17
  }
23
18
 
24
- /**
25
- * Compute SRI hash for given content
26
- * @param {string|Buffer} content - Content to hash
27
- * @param {string} algorithm - Hash algorithm to use
28
- * @returns {string|null} SRI hash string or null if failed
29
- */
30
19
  function computeSri(content, algorithm = 'sha384') {
31
20
  try {
32
21
  const hash = createHash(algorithm)
@@ -39,17 +28,10 @@ function computeSri(content, algorithm = 'sha384') {
39
28
  }
40
29
  }
41
30
 
42
- /**
43
- * Check if external resource has CORS enabled
44
- * @param {string} url - URL to check
45
- * @param {Object} options - Plugin options
46
- * @returns {Promise<boolean>} Whether CORS is enabled
47
- */
48
31
  async function externalResourceIsCorsEnabled(url, options) {
49
32
  try {
50
33
  const response = await fetch(url, {
51
- method: 'HEAD',
52
- timeout: 5000
34
+ method: 'HEAD'
53
35
  });
54
36
  const acao = response.headers.get('access-control-allow-origin');
55
37
  if (acao && (acao === '*' || acao.includes(options.domain || ''))) {
@@ -62,12 +44,6 @@ async function externalResourceIsCorsEnabled(url, options) {
62
44
  }
63
45
  }
64
46
 
65
- /**
66
- * Check if URL belongs to bypass domains
67
- * @param {string} url - URL to check
68
- * @param {string[]} bypassDomains - List of domains to bypass
69
- * @returns {boolean} Whether URL belongs to bypass domains
70
- */
71
47
  function isBypassDomain(url, bypassDomains = []) {
72
48
  if (!bypassDomains.length) return false;
73
49
  try {
@@ -83,29 +59,38 @@ function isBypassDomain(url, bypassDomains = []) {
83
59
  }
84
60
  }
85
61
 
86
- /**
87
- * Check if tag already has crossorigin attribute
88
- * @param {string} tag - HTML tag to check
89
- * @returns {boolean} Whether tag has crossorigin attribute
90
- */
91
62
  function hasCrossOriginAttr(tag) {
92
63
  return /(?:^|\s)crossorigin(?:=["']?[^"'\s>]*["']?)?(?:\s|>|$)/i.test(tag);
93
64
  }
94
65
 
95
- /**
96
- * Process individual HTML tags and add SRI attributes
97
- * @param {string} tag - HTML tag to process
98
- * @param {string} url - Resource URL
99
- * @param {Object} options - Plugin options
100
- * @param {Map} sriMap - Map of file names to SRI hashes
101
- * @returns {Promise<string>} Processed HTML tag
102
- */
66
+ function getAllPossiblePaths(url) {
67
+ const paths = [
68
+ url,
69
+ url.startsWith('/') ? url.slice(1) : url,
70
+ url.startsWith('/static/') ? url.slice(8) : url,
71
+ !url.startsWith('/static/') ? `static/${url}` : url,
72
+ !url.startsWith('/static/') ? `/static/${url}` : url,
73
+ url.replace(/^\/static\//, ''),
74
+ url.replace(/^static\//, '')
75
+ ];
76
+
77
+ const withoutHash = url.replace(/-[a-zA-Z0-9]+\./, '.');
78
+ if (withoutHash !== url) {
79
+ paths.push(...getAllPossiblePaths(withoutHash));
80
+ }
81
+
82
+ return [...new Set(paths)];
83
+ }
84
+
103
85
  async function processTag(tag, url, options, sriMap) {
104
86
  if (tag.includes('integrity=')) {
105
87
  log(`Skip tag with existing integrity attribute: ${tag}`, options);
106
88
  return tag;
107
89
  }
108
90
 
91
+ log(`Processing tag: ${tag}`, options);
92
+ log(`URL: ${url}`, options);
93
+
109
94
  // Handle external resources
110
95
  if (/^(https?:)?\/\//i.test(url)) {
111
96
  if (isBypassDomain(url, options.bypassDomains)) {
@@ -127,7 +112,9 @@ async function processTag(tag, url, options, sriMap) {
127
112
  log(`Computing SRI for external resource ${url}: ${hash}`, options);
128
113
  const hasCrossOrigin = hasCrossOriginAttr(tag);
129
114
  const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
130
- return tag.replace(/>$/, ` integrity="${hash}"${crossOriginAttr}>`);
115
+ const newTag = tag.replace(/>$/, ` integrity="${hash}"${crossOriginAttr}>`);
116
+ log(`New tag: ${newTag}`, options);
117
+ return newTag;
131
118
  }
132
119
  } catch (error) {
133
120
  log(`Failed to process external resource ${url}: ${error}`, options);
@@ -136,24 +123,33 @@ async function processTag(tag, url, options, sriMap) {
136
123
  }
137
124
 
138
125
  // Handle local resources
139
- const fileName = url.startsWith('/') ? url.slice(1) : url;
140
- const integrity = sriMap.get(fileName);
126
+ const possiblePaths = getAllPossiblePaths(url);
127
+ let integrity = null;
128
+
129
+ log(`Checking possible paths for ${url}:`, options);
130
+ for (const path of possiblePaths) {
131
+ log(`- Checking path: ${path}`, options);
132
+ if (sriMap.has(path)) {
133
+ integrity = sriMap.get(path);
134
+ log(`Found integrity for path ${path}: ${integrity}`, options);
135
+ break;
136
+ }
137
+ }
138
+
141
139
  if (integrity) {
142
- log(`Using precomputed SRI for ${fileName}: ${integrity}`, options);
140
+ log(`Using precomputed SRI for ${url}: ${integrity}`, options);
143
141
  const hasCrossOrigin = hasCrossOriginAttr(tag);
144
142
  const crossOriginAttr = hasCrossOrigin ? '' : ` crossorigin="${options.crossorigin}"`;
145
- return tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
143
+ const newTag = tag.replace(/>$/, ` integrity="${integrity}"${crossOriginAttr}>`);
144
+ log(`New tag: ${newTag}`, options);
145
+ return newTag;
146
146
  }
147
147
 
148
- log(`No SRI hash found for ${fileName}`, options);
148
+ log(`No SRI hash found for ${url}`, options);
149
+ log(`Available paths in sriMap: ${Array.from(sriMap.keys()).join(', ')}`, options);
149
150
  return tag;
150
151
  }
151
152
 
152
- /**
153
- * Vite plugin for adding Subresource Integrity (SRI) hashes to assets
154
- * @param {Object} userOptions - Plugin options
155
- * @returns {import('vite').Plugin} Vite plugin object
156
- */
157
153
  function sri(userOptions = {}) {
158
154
  const options = { ...DEFAULT_OPTIONS, ...userOptions };
159
155
  const sriMap = new Map();
@@ -175,8 +171,10 @@ function sri(userOptions = {}) {
175
171
 
176
172
  const hash = computeSri(code, options.algorithm);
177
173
  if (hash) {
178
- sriMap.set(chunk.fileName, hash);
179
- log(`Computing SRI for chunk ${chunk.fileName}: ${hash}`, options);
174
+ for (const path of getAllPossiblePaths(chunk.fileName)) {
175
+ sriMap.set(path, hash);
176
+ log(`Stored SRI for path ${path}: ${hash}`, options);
177
+ }
180
178
  }
181
179
  return null;
182
180
  },
@@ -189,11 +187,18 @@ function sri(userOptions = {}) {
189
187
  if (chunk.type === 'asset' && !sriMap.has(fileName)) {
190
188
  const hash = computeSri(chunk.source, options.algorithm);
191
189
  if (hash) {
192
- sriMap.set(fileName, hash);
193
- log(`Computing SRI for asset ${fileName}: ${hash}`, options);
190
+ for (const path of getAllPossiblePaths(fileName)) {
191
+ sriMap.set(path, hash);
192
+ log(`Computing SRI for asset ${path}: ${hash}`, options);
193
+ }
194
194
  }
195
195
  }
196
196
  }
197
+
198
+ log('Final sriMap contents:', options);
199
+ for (const [key, value] of sriMap.entries()) {
200
+ log(`${key} => ${value}`, options);
201
+ }
197
202
  },
198
203
 
199
204
  async transformIndexHtml(html) {
@@ -203,18 +208,25 @@ function sri(userOptions = {}) {
203
208
  }
204
209
 
205
210
  try {
211
+ log('Starting HTML transformation', options);
212
+ log(`SRI Map size: ${sriMap.size}`, options);
213
+
206
214
  // Process script tags
207
215
  const scriptTags = html.match(/<script[^>]+src=["']([^"']+)["'][^>]*>/g) || [];
216
+ log(`Found ${scriptTags.length} script tags`, options);
208
217
  for (const tag of scriptTags) {
209
218
  const url = tag.match(/src=["']([^"']+)["']/)[1];
219
+ log(`Processing script: ${url}`, options);
210
220
  const newTag = await processTag(tag, url, options, sriMap);
211
221
  html = html.replace(tag, newTag);
212
222
  }
213
223
 
214
224
  // Process link tags
215
225
  const linkTags = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*>/g) || [];
226
+ log(`Found ${linkTags.length} link tags`, options);
216
227
  for (const tag of linkTags) {
217
228
  const url = tag.match(/href=["']([^"']+)["']/)[1];
229
+ log(`Processing link: ${url}`, options);
218
230
  const newTag = await processTag(tag, url, options, sriMap);
219
231
  html = html.replace(tag, newTag);
220
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-sri4",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
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",