vite-plugin-sri4 1.8.1 → 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.
- package/dist/index.cjs +58 -127
- package/dist/index.js +58 -127
- 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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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,26 +65,27 @@ function hasCrossOriginAttr(tag) {
|
|
|
76
65
|
return /(?:^|\s)crossorigin(?:=["']?[^"'\s>]*["']?)?(?:\s|>|$)/i.test(tag);
|
|
77
66
|
}
|
|
78
67
|
|
|
79
|
-
function
|
|
80
|
-
|
|
68
|
+
function getBundleKey(url, base = '') {
|
|
69
|
+
// 嘗試各種可能的路徑格式
|
|
70
|
+
const possiblePaths = [
|
|
81
71
|
url,
|
|
82
|
-
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
|
-
|
|
79
|
+
// 移除 hash 後的版本
|
|
80
|
+
const withoutHash = url.replace(/-[a-zA-Z0-9]+\.([^.]+)$/, '.$1');
|
|
91
81
|
if (withoutHash !== url) {
|
|
92
|
-
|
|
82
|
+
possiblePaths.push(...getBundleKey(withoutHash, base));
|
|
93
83
|
}
|
|
94
84
|
|
|
95
|
-
return [...new Set(
|
|
85
|
+
return [...new Set(possiblePaths)];
|
|
96
86
|
}
|
|
97
87
|
|
|
98
|
-
async function processTag(tag, url, options,
|
|
88
|
+
async function processTag(tag, url, options, bundle, base = '') {
|
|
99
89
|
if (tag.includes('integrity=')) {
|
|
100
90
|
log(`Skip tag with existing integrity attribute: ${tag}`, options);
|
|
101
91
|
return tag;
|
|
@@ -136,51 +126,31 @@ async function processTag(tag, url, options, sriMap) {
|
|
|
136
126
|
}
|
|
137
127
|
|
|
138
128
|
// Handle local resources
|
|
139
|
-
const
|
|
140
|
-
let
|
|
129
|
+
const possibleKeys = getBundleKey(url, base);
|
|
130
|
+
let bundleItem = null;
|
|
141
131
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
integrity = sriMap.get(path);
|
|
147
|
-
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);
|
|
148
136
|
break;
|
|
149
137
|
}
|
|
150
138
|
}
|
|
151
139
|
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function processInlineScript(tag, options) {
|
|
167
|
-
if (tag.includes('integrity=')) {
|
|
168
|
-
log(`Skip inline script with existing integrity attribute: ${tag}`, options);
|
|
169
|
-
return tag;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const content = tag.match(/<script[^>]*>([\s\S]*?)<\/script>/)?.[1]?.trim();
|
|
173
|
-
if (!content) {
|
|
174
|
-
log(`Skip empty inline script: ${tag}`, options);
|
|
175
|
-
return tag;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const hash = computeSri(content, options.algorithm);
|
|
179
|
-
if (hash) {
|
|
180
|
-
log(`Computing SRI for inline script: ${hash}`, options);
|
|
181
|
-
const newTag = tag.replace('>', ` integrity="${hash}">`);
|
|
182
|
-
log(`New inline script tag: ${newTag}`, options);
|
|
183
|
-
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);
|
|
184
154
|
}
|
|
185
155
|
|
|
186
156
|
return tag;
|
|
@@ -188,8 +158,8 @@ async function processInlineScript(tag, options) {
|
|
|
188
158
|
|
|
189
159
|
function sri(userOptions = {}) {
|
|
190
160
|
const options = { ...DEFAULT_OPTIONS, ...userOptions };
|
|
191
|
-
const sriMap = new Map();
|
|
192
161
|
let isBuild = false;
|
|
162
|
+
let base = '';
|
|
193
163
|
|
|
194
164
|
return {
|
|
195
165
|
name: 'vite-plugin-sri4',
|
|
@@ -199,45 +169,12 @@ function sri(userOptions = {}) {
|
|
|
199
169
|
configResolved(config) {
|
|
200
170
|
options.domain = config.server?.host || '';
|
|
201
171
|
isBuild = config.command === 'build';
|
|
172
|
+
base = config.base || '';
|
|
202
173
|
log('Plugin configured in ' + (isBuild ? 'build' : 'dev') + ' mode', options);
|
|
174
|
+
log(`Base URL: ${base}`, options);
|
|
203
175
|
},
|
|
204
176
|
|
|
205
|
-
async
|
|
206
|
-
if (!isBuild) return null;
|
|
207
|
-
|
|
208
|
-
const hash = computeSri(code, options.algorithm);
|
|
209
|
-
if (hash) {
|
|
210
|
-
for (const path of getAllPossiblePaths(chunk.fileName)) {
|
|
211
|
-
sriMap.set(path, hash);
|
|
212
|
-
log(`Stored SRI for path ${path}: ${hash}`, options);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return null;
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
async generateBundle(_, bundle) {
|
|
219
|
-
if (!isBuild) return;
|
|
220
|
-
|
|
221
|
-
for (const fileName in bundle) {
|
|
222
|
-
const chunk = bundle[fileName];
|
|
223
|
-
if (chunk.type === 'asset' && !sriMap.has(fileName)) {
|
|
224
|
-
const hash = computeSri(chunk.source, options.algorithm);
|
|
225
|
-
if (hash) {
|
|
226
|
-
for (const path of getAllPossiblePaths(fileName)) {
|
|
227
|
-
sriMap.set(path, hash);
|
|
228
|
-
log(`Computing SRI for asset ${path}: ${hash}`, options);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
log('Final sriMap contents:', options);
|
|
235
|
-
for (const [key, value] of sriMap.entries()) {
|
|
236
|
-
log(`${key} => ${value}`, options);
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
async transformIndexHtml(html) {
|
|
177
|
+
async transformIndexHtml(html, ctx) {
|
|
241
178
|
if (!isBuild || !html) {
|
|
242
179
|
log('Skipping HTML transform in dev mode or empty HTML', options);
|
|
243
180
|
return html;
|
|
@@ -245,38 +182,32 @@ function sri(userOptions = {}) {
|
|
|
245
182
|
|
|
246
183
|
try {
|
|
247
184
|
log('Starting HTML transformation', options);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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;
|
|
255
194
|
log(`Processing script: ${url}`, options);
|
|
256
|
-
const newTag = await processTag(tag, url, options,
|
|
195
|
+
const newTag = await processTag(tag, url, options, bundle, base);
|
|
257
196
|
html = html.replace(tag, newTag);
|
|
258
197
|
}
|
|
259
198
|
|
|
260
|
-
// Process
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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);
|
|
266
207
|
html = html.replace(tag, newTag);
|
|
267
208
|
}
|
|
268
209
|
}
|
|
269
210
|
|
|
270
|
-
// Process link tags
|
|
271
|
-
const linkTags = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*>/g) || [];
|
|
272
|
-
log(`Found ${linkTags.length} link tags`, options);
|
|
273
|
-
for (const tag of linkTags) {
|
|
274
|
-
const url = tag.match(/href=["']([^"']+)["']/)[1];
|
|
275
|
-
log(`Processing link: ${url}`, options);
|
|
276
|
-
const newTag = await processTag(tag, url, options, sriMap);
|
|
277
|
-
html = html.replace(tag, newTag);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
211
|
return html;
|
|
281
212
|
} catch (error) {
|
|
282
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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,26 +63,27 @@ function hasCrossOriginAttr(tag) {
|
|
|
74
63
|
return /(?:^|\s)crossorigin(?:=["']?[^"'\s>]*["']?)?(?:\s|>|$)/i.test(tag);
|
|
75
64
|
}
|
|
76
65
|
|
|
77
|
-
function
|
|
78
|
-
|
|
66
|
+
function getBundleKey(url, base = '') {
|
|
67
|
+
// 嘗試各種可能的路徑格式
|
|
68
|
+
const possiblePaths = [
|
|
79
69
|
url,
|
|
80
|
-
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
|
-
|
|
77
|
+
// 移除 hash 後的版本
|
|
78
|
+
const withoutHash = url.replace(/-[a-zA-Z0-9]+\.([^.]+)$/, '.$1');
|
|
89
79
|
if (withoutHash !== url) {
|
|
90
|
-
|
|
80
|
+
possiblePaths.push(...getBundleKey(withoutHash, base));
|
|
91
81
|
}
|
|
92
82
|
|
|
93
|
-
return [...new Set(
|
|
83
|
+
return [...new Set(possiblePaths)];
|
|
94
84
|
}
|
|
95
85
|
|
|
96
|
-
async function processTag(tag, url, options,
|
|
86
|
+
async function processTag(tag, url, options, bundle, base = '') {
|
|
97
87
|
if (tag.includes('integrity=')) {
|
|
98
88
|
log(`Skip tag with existing integrity attribute: ${tag}`, options);
|
|
99
89
|
return tag;
|
|
@@ -134,51 +124,31 @@ async function processTag(tag, url, options, sriMap) {
|
|
|
134
124
|
}
|
|
135
125
|
|
|
136
126
|
// Handle local resources
|
|
137
|
-
const
|
|
138
|
-
let
|
|
127
|
+
const possibleKeys = getBundleKey(url, base);
|
|
128
|
+
let bundleItem = null;
|
|
139
129
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
integrity = sriMap.get(path);
|
|
145
|
-
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);
|
|
146
134
|
break;
|
|
147
135
|
}
|
|
148
136
|
}
|
|
149
137
|
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function processInlineScript(tag, options) {
|
|
165
|
-
if (tag.includes('integrity=')) {
|
|
166
|
-
log(`Skip inline script with existing integrity attribute: ${tag}`, options);
|
|
167
|
-
return tag;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const content = tag.match(/<script[^>]*>([\s\S]*?)<\/script>/)?.[1]?.trim();
|
|
171
|
-
if (!content) {
|
|
172
|
-
log(`Skip empty inline script: ${tag}`, options);
|
|
173
|
-
return tag;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const hash = computeSri(content, options.algorithm);
|
|
177
|
-
if (hash) {
|
|
178
|
-
log(`Computing SRI for inline script: ${hash}`, options);
|
|
179
|
-
const newTag = tag.replace('>', ` integrity="${hash}">`);
|
|
180
|
-
log(`New inline script tag: ${newTag}`, options);
|
|
181
|
-
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);
|
|
182
152
|
}
|
|
183
153
|
|
|
184
154
|
return tag;
|
|
@@ -186,8 +156,8 @@ async function processInlineScript(tag, options) {
|
|
|
186
156
|
|
|
187
157
|
function sri(userOptions = {}) {
|
|
188
158
|
const options = { ...DEFAULT_OPTIONS, ...userOptions };
|
|
189
|
-
const sriMap = new Map();
|
|
190
159
|
let isBuild = false;
|
|
160
|
+
let base = '';
|
|
191
161
|
|
|
192
162
|
return {
|
|
193
163
|
name: 'vite-plugin-sri4',
|
|
@@ -197,45 +167,12 @@ function sri(userOptions = {}) {
|
|
|
197
167
|
configResolved(config) {
|
|
198
168
|
options.domain = config.server?.host || '';
|
|
199
169
|
isBuild = config.command === 'build';
|
|
170
|
+
base = config.base || '';
|
|
200
171
|
log('Plugin configured in ' + (isBuild ? 'build' : 'dev') + ' mode', options);
|
|
172
|
+
log(`Base URL: ${base}`, options);
|
|
201
173
|
},
|
|
202
174
|
|
|
203
|
-
async
|
|
204
|
-
if (!isBuild) return null;
|
|
205
|
-
|
|
206
|
-
const hash = computeSri(code, options.algorithm);
|
|
207
|
-
if (hash) {
|
|
208
|
-
for (const path of getAllPossiblePaths(chunk.fileName)) {
|
|
209
|
-
sriMap.set(path, hash);
|
|
210
|
-
log(`Stored SRI for path ${path}: ${hash}`, options);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return null;
|
|
214
|
-
},
|
|
215
|
-
|
|
216
|
-
async generateBundle(_, bundle) {
|
|
217
|
-
if (!isBuild) return;
|
|
218
|
-
|
|
219
|
-
for (const fileName in bundle) {
|
|
220
|
-
const chunk = bundle[fileName];
|
|
221
|
-
if (chunk.type === 'asset' && !sriMap.has(fileName)) {
|
|
222
|
-
const hash = computeSri(chunk.source, options.algorithm);
|
|
223
|
-
if (hash) {
|
|
224
|
-
for (const path of getAllPossiblePaths(fileName)) {
|
|
225
|
-
sriMap.set(path, hash);
|
|
226
|
-
log(`Computing SRI for asset ${path}: ${hash}`, options);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
log('Final sriMap contents:', options);
|
|
233
|
-
for (const [key, value] of sriMap.entries()) {
|
|
234
|
-
log(`${key} => ${value}`, options);
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
async transformIndexHtml(html) {
|
|
175
|
+
async transformIndexHtml(html, ctx) {
|
|
239
176
|
if (!isBuild || !html) {
|
|
240
177
|
log('Skipping HTML transform in dev mode or empty HTML', options);
|
|
241
178
|
return html;
|
|
@@ -243,38 +180,32 @@ function sri(userOptions = {}) {
|
|
|
243
180
|
|
|
244
181
|
try {
|
|
245
182
|
log('Starting HTML transformation', options);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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;
|
|
253
192
|
log(`Processing script: ${url}`, options);
|
|
254
|
-
const newTag = await processTag(tag, url, options,
|
|
193
|
+
const newTag = await processTag(tag, url, options, bundle, base);
|
|
255
194
|
html = html.replace(tag, newTag);
|
|
256
195
|
}
|
|
257
196
|
|
|
258
|
-
// Process
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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);
|
|
264
205
|
html = html.replace(tag, newTag);
|
|
265
206
|
}
|
|
266
207
|
}
|
|
267
208
|
|
|
268
|
-
// Process link tags
|
|
269
|
-
const linkTags = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*>/g) || [];
|
|
270
|
-
log(`Found ${linkTags.length} link tags`, options);
|
|
271
|
-
for (const tag of linkTags) {
|
|
272
|
-
const url = tag.match(/href=["']([^"']+)["']/)[1];
|
|
273
|
-
log(`Processing link: ${url}`, options);
|
|
274
|
-
const newTag = await processTag(tag, url, options, sriMap);
|
|
275
|
-
html = html.replace(tag, newTag);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
209
|
return html;
|
|
279
210
|
} catch (error) {
|
|
280
211
|
console.error(`${LOG_PREFIX} Failed to transform HTML: ${error}`);
|