mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 14:16:16 -08:00
fix(performance): count each initial request URL and soften the CI wording
Deduplicate initial resources by request URL rather than by file, so a file referenced twice with different query strings is counted twice, as the browser fetches it; fragments are still ignored. Each resource now carries both its file path and its request URL. Describe the CI ratchet as the goal of the loop rather than as existing enforcement until the baseline and job PRs merge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
3cd032510e
commit
5cb69b1ec1
4 files changed
+50
-26
No files matched your search
@@ -58,8 +58,10 @@ function classify(tag, attributes) {
|
||||
|
||||
/**
|
||||
* Lists the same-origin resources `index.html` puts on the initial path, in
|
||||
* document order and without duplicates. Query strings and fragments are
|
||||
* stripped so the URL maps to a file on disk.
|
||||
* document order. Duplicates are collapsed by request URL, not by file: the
|
||||
* browser fetches `chunk.js?v=1` and `chunk.js?v=2` separately, so both count,
|
||||
* while a fragment never reaches the server and is ignored. `path` is the
|
||||
* file on disk the URL maps to.
|
||||
*/
|
||||
export function extractInitialResources(html) {
|
||||
const seen = new Set();
|
||||
@@ -70,10 +72,11 @@ export function extractInitialResources(html) {
|
||||
parseAttributes(rawAttributes)
|
||||
);
|
||||
if (!resource || EXTERNAL_URL.test(resource.url)) continue;
|
||||
const file = resource.url.replace(/[?#].*$/, '').replace(/^\.?\//, '');
|
||||
if (!file || seen.has(file)) continue;
|
||||
seen.add(file);
|
||||
resources.push({ path: file, kind: resource.kind });
|
||||
const url = resource.url.replace(/#.*$/, '').replace(/^\.?\//, '');
|
||||
const file = url.replace(/\?.*$/, '');
|
||||
if (!file || seen.has(url)) continue;
|
||||
seen.add(url);
|
||||
resources.push({ path: file, url, kind: resource.kind });
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
@@ -160,7 +163,7 @@ function formatBytes(bytes) {
|
||||
export function formatReport(measurement) {
|
||||
const rows = [...measurement.resources].sort((a, b) => b.bytes - a.bytes);
|
||||
const width = Math.max(
|
||||
...rows.map((row) => row.path.length),
|
||||
...rows.map((row) => row.url.length),
|
||||
'index.html'.length
|
||||
);
|
||||
const lines = [
|
||||
@@ -169,7 +172,7 @@ export function formatReport(measurement) {
|
||||
`${'index.html'.padEnd(width)} html ${formatBytes(measurement.indexHtml.bytes).padStart(11)}`,
|
||||
...rows.map(
|
||||
(row) =>
|
||||
`${row.path.padEnd(width)} ${row.kind.padEnd(13)} ${formatBytes(row.bytes).padStart(11)}`
|
||||
`${row.url.padEnd(width)} ${row.kind.padEnd(13)} ${formatBytes(row.bytes).padStart(11)}`
|
||||
),
|
||||
'',
|
||||
`scripts ${formatBytes(measurement.totals.script).padStart(11)}`,
|
||||
|
||||
@@ -69,14 +69,20 @@ after(async () => {
|
||||
});
|
||||
|
||||
test('extracts scripts, stylesheets and modulepreload chunks in document order', () => {
|
||||
assert.deepEqual(extractInitialResources(BUILT_INDEX_HTML), [
|
||||
{ path: 'assets/app-config.js', kind: 'script' },
|
||||
{ path: 'styles-VDU4SQ5F.css', kind: 'stylesheet' },
|
||||
{ path: 'chunk-B6uziQ1i.js', kind: 'modulepreload' },
|
||||
{ path: 'chunk-Cn2Agfvf.js', kind: 'modulepreload' },
|
||||
{ path: 'polyfills-EBB6HFCX.js', kind: 'script' },
|
||||
{ path: 'main-EI6PCDGR.js', kind: 'script' },
|
||||
]);
|
||||
assert.deepEqual(
|
||||
extractInitialResources(BUILT_INDEX_HTML).map(({ path, kind }) => ({
|
||||
path,
|
||||
kind,
|
||||
})),
|
||||
[
|
||||
{ path: 'assets/app-config.js', kind: 'script' },
|
||||
{ path: 'styles-VDU4SQ5F.css', kind: 'stylesheet' },
|
||||
{ path: 'chunk-B6uziQ1i.js', kind: 'modulepreload' },
|
||||
{ path: 'chunk-Cn2Agfvf.js', kind: 'modulepreload' },
|
||||
{ path: 'polyfills-EBB6HFCX.js', kind: 'script' },
|
||||
{ path: 'main-EI6PCDGR.js', kind: 'script' },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('ignores manifest, icon and external references', () => {
|
||||
@@ -90,24 +96,37 @@ test('ignores manifest, icon and external references', () => {
|
||||
<script>inline()</script>
|
||||
<script src="main.js" type="module"></script>`;
|
||||
assert.deepEqual(extractInitialResources(html), [
|
||||
{ path: 'main.js', kind: 'script' },
|
||||
{ path: 'main.js', url: 'main.js', kind: 'script' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('deduplicates references and normalizes relative URLs', () => {
|
||||
test('deduplicates by request URL, ignores fragments and normalizes relative URLs', () => {
|
||||
const html = `
|
||||
<LINK REL="modulepreload" HREF='./chunk-a.js'>
|
||||
<link rel="modulepreload" href="chunk-a.js">
|
||||
<link rel="modulepreload" href="chunk-a.js?v=2">
|
||||
<link rel="modulepreload" href="/chunk-b.js#hash">
|
||||
<link rel="modulepreload" href="chunk-b.js#other">
|
||||
<script src=main.js></script>
|
||||
<script src="main.js"></script>`;
|
||||
assert.deepEqual(extractInitialResources(html), [
|
||||
{ path: 'chunk-a.js', kind: 'modulepreload' },
|
||||
{ path: 'chunk-b.js', kind: 'modulepreload' },
|
||||
{ path: 'main.js', kind: 'script' },
|
||||
{ path: 'chunk-a.js', url: 'chunk-a.js', kind: 'modulepreload' },
|
||||
{ path: 'chunk-a.js', url: 'chunk-a.js?v=2', kind: 'modulepreload' },
|
||||
{ path: 'chunk-b.js', url: 'chunk-b.js', kind: 'modulepreload' },
|
||||
{ path: 'main.js', url: 'main.js', kind: 'script' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('counts a file once per distinct request URL', async () => {
|
||||
const distDir = await writeDist('cache-busted', {
|
||||
indexHtml: `<link rel="modulepreload" href="chunk-a.js"><link rel="modulepreload" href="chunk-a.js?v=2">`,
|
||||
files: { 'chunk-a.js': 100 },
|
||||
});
|
||||
const measurement = await measureInitialBytes({ distDir });
|
||||
assert.equal(measurement.resources.length, 2);
|
||||
assert.equal(measurement.totals.modulepreload, 200);
|
||||
});
|
||||
|
||||
test('sums index.html and every referenced file into the counter', async () => {
|
||||
const distDir = await writeDist('built');
|
||||
const measurement = await measureInitialBytes({ distDir });
|
||||
|
||||
Reference in new issue
Block a user