mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
build: guard the initial chunks by module id; main-chunk check in CI
Review of Task 0: - build/initialChunkGuard.js, a Rollup plugin in the browser build, fails the build when an initial chunk (an entry and what it imports statically) holds a module from node_modules/three, @navaramap/* or postprocessing. Checked by forcing it on satellite.js, which the main chunk does hold: the build fails naming the chunk and module. navaraAssets.test now pins both build plugins. - scripts/check-main-chunk.mjs is one script for both main-chunk checks: the composition (text markers in the initial chunks, sizes) and, when scripts/main-chunk-baseline.json exists, the phase-4 branch's gzip-9 budget, with its exports (checkMainChunk, measureMainChunk) so the two branches merge into this file. The composition test moved to src/tooling/mainChunkComposition.test.mjs to leave phase 4's mainChunk.test.mjs its name. - CI runs npm run check:main-chunk after each npm run build (unit, browser-gate and Windows jobs).
This commit is contained in:
1 parent
f7f4e9a45c
commit
76851ee949
8 files changed
+276
-53
No files matched your search
@@ -59,6 +59,9 @@ jobs:
|
|||||||
- name: Build production bundle
|
- name: Build production bundle
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Main chunk (no three or Navara in Cesium mode; size budget when recorded)
|
||||||
|
run: npm run check:main-chunk
|
||||||
|
|
||||||
street-level-browser:
|
street-level-browser:
|
||||||
# Hermetic: every Mapillary request is answered from fixtures, so the
|
# Hermetic: every Mapillary request is answered from fixtures, so the
|
||||||
# dummy token below never leaves the page.
|
# dummy token below never leaves the page.
|
||||||
@@ -89,6 +92,9 @@ jobs:
|
|||||||
MAPILLARY_CLIENT_TOKEN: 'MLY|0|qa-fixture'
|
MAPILLARY_CLIENT_TOKEN: 'MLY|0|qa-fixture'
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Main chunk (no three or Navara in Cesium mode; size budget when recorded)
|
||||||
|
run: npm run check:main-chunk
|
||||||
|
|
||||||
- name: Start the preview server
|
- name: Start the preview server
|
||||||
env:
|
env:
|
||||||
MAPILLARY_CLIENT_TOKEN: 'MLY|0|qa-fixture'
|
MAPILLARY_CLIENT_TOKEN: 'MLY|0|qa-fixture'
|
||||||
@@ -154,3 +160,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Build production bundle
|
- name: Build production bundle
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Main chunk (no three or Navara in Cesium mode; size budget when recorded)
|
||||||
|
run: npm run check:main-chunk
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Build guard: the Cesium-mode page must not load three or Navara. The
|
||||||
|
// initial chunks are every entry chunk and what it imports statically;
|
||||||
|
// navaraMode.js reaches Navara only through import(), so three and
|
||||||
|
// @navaramap/* belong to lazy chunks. This checks the bundle's module ids
|
||||||
|
// (what Rollup put in each chunk), and fails the build otherwise;
|
||||||
|
// scripts/check-main-chunk.mjs checks the built files' text as well.
|
||||||
|
|
||||||
|
/** Module ids that only the Navara engine may load. */
|
||||||
|
export const ENGINE_ONLY_MODULES =
|
||||||
|
/[\\/]node_modules[\\/](?:three|@navaramap[\\/][^\\/]+|postprocessing)[\\/]/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Engine-only modules in initial chunks of a Rollup bundle, sorted by chunk.
|
||||||
|
* @param {Record<string, { type: string, isEntry?: boolean, imports?: string[], moduleIds?: string[] }>} bundle
|
||||||
|
* @returns {{ chunk: string, module: string }[]}
|
||||||
|
*/
|
||||||
|
export function initialChunkViolations(bundle, pattern = ENGINE_ONLY_MODULES) {
|
||||||
|
const initial = new Set();
|
||||||
|
const visit = (fileName) => {
|
||||||
|
const chunk = bundle[fileName];
|
||||||
|
if (!chunk || chunk.type !== 'chunk' || initial.has(fileName)) return;
|
||||||
|
initial.add(fileName);
|
||||||
|
for (const imported of chunk.imports ?? []) visit(imported);
|
||||||
|
};
|
||||||
|
for (const [fileName, chunk] of Object.entries(bundle))
|
||||||
|
if (chunk.type === 'chunk' && chunk.isEntry) visit(fileName);
|
||||||
|
const violations = [];
|
||||||
|
for (const fileName of [...initial].sort())
|
||||||
|
for (const module of bundle[fileName].moduleIds ?? [])
|
||||||
|
if (pattern.test(module)) violations.push({ chunk: fileName, module });
|
||||||
|
return violations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The Rollup plugin: fails generateBundle when an initial chunk holds three or Navara. */
|
||||||
|
export function initialChunkGuard({ log = console.log } = {}) {
|
||||||
|
return {
|
||||||
|
name: 'gev-initial-chunk-guard',
|
||||||
|
generateBundle(_options, bundle) {
|
||||||
|
const violations = initialChunkViolations(bundle);
|
||||||
|
if (violations.length) {
|
||||||
|
this.error(
|
||||||
|
'three/Navara in the Cesium-mode initial chunks:\n' +
|
||||||
|
violations
|
||||||
|
.map(({ chunk, module }) => ` ${chunk}: ${module}`)
|
||||||
|
.join('\n'),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log('[initial-chunks] no three or Navara modules in the initial chunks');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
+3
-1
@@ -4,6 +4,7 @@ import { DEFAULT_ALLOWED_HOSTS, hostCheckPlugin } from './allowedHosts.js';
|
|||||||
import { embedFramingPlugin } from './embed-framing.js';
|
import { embedFramingPlugin } from './embed-framing.js';
|
||||||
import { panelBuildPlugin } from './panel.js';
|
import { panelBuildPlugin } from './panel.js';
|
||||||
import { navaraChunkFileNames, navaraWorkerAssets } from './navaraAssets.js';
|
import { navaraChunkFileNames, navaraWorkerAssets } from './navaraAssets.js';
|
||||||
|
import { initialChunkGuard } from './initialChunkGuard.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content-Security-Policy for every document the dev/preview server serves.
|
* Content-Security-Policy for every document the dev/preview server serves.
|
||||||
@@ -132,7 +133,8 @@ export function createBrowserViteConfig({
|
|||||||
// engine is a separate chunk loaded on ?engine=navara).
|
// engine is a separate chunk loaded on ?engine=navara).
|
||||||
// The lazy chunk with the Navara engine is named navara-engine.
|
// The lazy chunk with the Navara engine is named navara-engine.
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
plugins: [navaraWorkerAssets()],
|
// initialChunkGuard: no three/Navara module in the initial chunks.
|
||||||
|
plugins: [navaraWorkerAssets(), initialChunkGuard()],
|
||||||
output: { chunkFileNames: navaraChunkFileNames },
|
output: { chunkFileNames: navaraChunkFileNames },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+111
-48
@@ -1,17 +1,25 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* The build gate for the Cesium-mode bundle: the main chunk (the module
|
* The main-chunk gate of the Cesium-mode bundle (run after `npm run build`):
|
||||||
* script dist/index.html loads, or the Vite manifest's entry when the build
|
|
||||||
* writes one) and every chunk it imports statically must carry no three or
|
|
||||||
* Navara code; those load only through navaraMode.js's import(). Prints the
|
|
||||||
* sizes (raw and gzip) and fails when a marker is found.
|
|
||||||
*
|
*
|
||||||
* npm run build && node scripts/check-main-chunk.mjs [--dist=dist] [--json]
|
* 1. Composition: the main chunk (the Vite manifest's entry, or the module
|
||||||
|
* script dist/index.html loads) and every chunk it imports statically carry
|
||||||
|
* no three or Navara code (text markers; the build itself also fails on
|
||||||
|
* three/@navaramap module ids in initial chunks, build/initialChunkGuard.js).
|
||||||
|
* 2. Budget, when scripts/main-chunk-baseline.json exists: gzip-9 of the
|
||||||
|
* largest dist/assets/index-*.js may exceed the recorded baseline by at
|
||||||
|
* most its allowance (the phase-4 branch's check; same exports,
|
||||||
|
* checkMainChunk and measureMainChunk, so the two merge into this script).
|
||||||
|
*
|
||||||
|
* Prints the sizes. `--json` prints the whole result; `--dist=DIR` reads
|
||||||
|
* another build.
|
||||||
|
*
|
||||||
|
* npm run build && npm run check:main-chunk
|
||||||
*/
|
*/
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { gzipSync } from 'node:zlib';
|
import { gzipSync } from 'node:zlib';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import {
|
import {
|
||||||
entryFromHtml,
|
entryFromHtml,
|
||||||
entryFromManifest,
|
entryFromManifest,
|
||||||
@@ -19,13 +27,40 @@ import {
|
|||||||
initialChunks,
|
initialChunks,
|
||||||
} from '../src/tooling/mainChunk.js';
|
} from '../src/tooling/mainChunk.js';
|
||||||
|
|
||||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
const ROOT = fileURLToPath(new URL('..', import.meta.url));
|
||||||
const option = (name) =>
|
|
||||||
process.argv.find((arg) => arg.startsWith(`--${name}=`))?.split('=')[1];
|
|
||||||
const dist = path.resolve(root, option('dist') ?? 'dist');
|
|
||||||
const asJson = process.argv.includes('--json');
|
|
||||||
|
|
||||||
function findEntry() {
|
/** The verdict for a main-chunk size against the baseline. */
|
||||||
|
export function checkMainChunk(
|
||||||
|
gzipBytes,
|
||||||
|
{ gzipBytes: baseline, allowanceBytes },
|
||||||
|
) {
|
||||||
|
const limit = baseline + allowanceBytes;
|
||||||
|
const growth = gzipBytes - baseline;
|
||||||
|
return {
|
||||||
|
ok: gzipBytes <= limit,
|
||||||
|
message: `main chunk ${gzipBytes} bytes gzip-9 (${growth >= 0 ? '+' : ''}${growth} over the ${baseline} baseline; limit ${limit})`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** gzip-9 size of the largest dist/assets/index-*.js, with its name. */
|
||||||
|
export function measureMainChunk(distDir) {
|
||||||
|
const assets = path.join(distDir, 'assets');
|
||||||
|
const candidates = fs
|
||||||
|
.readdirSync(assets)
|
||||||
|
.filter((name) => /^index-.*\.js$/.test(name))
|
||||||
|
.map((name) => ({ name, bytes: fs.readFileSync(path.join(assets, name)) }))
|
||||||
|
.sort((a, b) => b.bytes.length - a.bytes.length);
|
||||||
|
if (!candidates.length)
|
||||||
|
throw new Error('no dist/assets/index-*.js: run npm run build first');
|
||||||
|
const [main] = candidates;
|
||||||
|
return {
|
||||||
|
name: main.name,
|
||||||
|
gzipBytes: gzipSync(main.bytes, { level: 9 }).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The entry chunk (relative to dist) and how it was found. */
|
||||||
|
function findEntry(dist) {
|
||||||
const manifestPath = path.join(dist, '.vite', 'manifest.json');
|
const manifestPath = path.join(dist, '.vite', 'manifest.json');
|
||||||
if (fs.existsSync(manifestPath)) {
|
if (fs.existsSync(manifestPath)) {
|
||||||
const entry = entryFromManifest(
|
const entry = entryFromManifest(
|
||||||
@@ -41,13 +76,14 @@ function findEntry() {
|
|||||||
return { entry, via: 'index.html' };
|
return { entry, via: 'index.html' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const read = (file) => {
|
/** Composition of the initial chunks: sizes and forbidden markers. */
|
||||||
|
export function checkComposition(dist) {
|
||||||
|
const read = (file) => {
|
||||||
const absolute = path.join(dist, file);
|
const absolute = path.join(dist, file);
|
||||||
return fs.existsSync(absolute) ? fs.readFileSync(absolute, 'utf8') : null;
|
return fs.existsSync(absolute) ? fs.readFileSync(absolute, 'utf8') : null;
|
||||||
};
|
};
|
||||||
|
const { entry, via } = findEntry(dist);
|
||||||
const { entry, via } = findEntry();
|
const chunks = initialChunks(entry, read).map((file) => {
|
||||||
const chunks = initialChunks(entry, read).map((file) => {
|
|
||||||
const code = read(file);
|
const code = read(file);
|
||||||
if (code == null)
|
if (code == null)
|
||||||
return { file, missing: true, bytes: 0, gzipBytes: 0, markers: [] };
|
return { file, missing: true, bytes: 0, gzipBytes: 0, markers: [] };
|
||||||
@@ -57,38 +93,65 @@ const chunks = initialChunks(entry, read).map((file) => {
|
|||||||
gzipBytes: gzipSync(code).length,
|
gzipBytes: gzipSync(code).length,
|
||||||
markers: forbiddenMarkersIn(code),
|
markers: forbiddenMarkersIn(code),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const main = chunks[0];
|
return {
|
||||||
const failures = chunks.filter(
|
|
||||||
(chunk) => chunk.missing || chunk.markers.length,
|
|
||||||
);
|
|
||||||
const result = {
|
|
||||||
entry,
|
entry,
|
||||||
via,
|
via,
|
||||||
mainBytes: main.bytes,
|
mainBytes: chunks[0].bytes,
|
||||||
mainGzipBytes: main.gzipBytes,
|
mainGzipBytes: chunks[0].gzipBytes,
|
||||||
initialBytes: chunks.reduce((sum, chunk) => sum + chunk.bytes, 0),
|
initialBytes: chunks.reduce((sum, chunk) => sum + chunk.bytes, 0),
|
||||||
chunks,
|
chunks,
|
||||||
ok: failures.length === 0,
|
ok: chunks.every((chunk) => !chunk.missing && !chunk.markers.length),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (asJson) console.log(JSON.stringify(result, null, 2));
|
|
||||||
else {
|
|
||||||
const kb = (bytes) => `${(bytes / 1000).toFixed(2)} kB`;
|
|
||||||
console.log(`[main-chunk] entry ${entry} (via ${via})`);
|
|
||||||
for (const chunk of chunks)
|
|
||||||
console.log(
|
|
||||||
`[main-chunk] ${chunk.file}: ${kb(chunk.bytes)} (gzip ${kb(chunk.gzipBytes)})` +
|
|
||||||
(chunk.missing ? ' MISSING' : '') +
|
|
||||||
(chunk.markers.length ? ` FORBIDDEN: ${chunk.markers.join(', ')}` : ''),
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`[main-chunk] main ${main.bytes} bytes (gzip ${main.gzipBytes}); initial chunks ${result.initialBytes} bytes`,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
result.ok
|
|
||||||
? '[main-chunk] ok: no three or Navara in the main chunk'
|
|
||||||
: '[main-chunk] FAIL',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
process.exitCode = result.ok ? 0 : 1;
|
|
||||||
|
function main() {
|
||||||
|
const option = (name) =>
|
||||||
|
process.argv.find((arg) => arg.startsWith(`--${name}=`))?.split('=')[1];
|
||||||
|
const dist = path.resolve(ROOT, option('dist') ?? 'dist');
|
||||||
|
const composition = checkComposition(dist);
|
||||||
|
const baselinePath = path.join(ROOT, 'scripts/main-chunk-baseline.json');
|
||||||
|
let budget = null;
|
||||||
|
if (fs.existsSync(baselinePath)) {
|
||||||
|
const measured = measureMainChunk(dist);
|
||||||
|
budget = {
|
||||||
|
...measured,
|
||||||
|
...checkMainChunk(
|
||||||
|
measured.gzipBytes,
|
||||||
|
JSON.parse(fs.readFileSync(baselinePath, 'utf8')),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const ok = composition.ok && (budget?.ok ?? true);
|
||||||
|
if (process.argv.includes('--json')) {
|
||||||
|
console.log(JSON.stringify({ composition, budget, ok }, null, 2));
|
||||||
|
} else {
|
||||||
|
const kb = (bytes) => `${(bytes / 1000).toFixed(2)} kB`;
|
||||||
|
console.log(
|
||||||
|
`[check:main-chunk] entry ${composition.entry} (via ${composition.via})`,
|
||||||
|
);
|
||||||
|
for (const chunk of composition.chunks)
|
||||||
|
console.log(
|
||||||
|
`[check:main-chunk] ${chunk.file}: ${kb(chunk.bytes)} (gzip ${kb(chunk.gzipBytes)})` +
|
||||||
|
(chunk.missing ? ' MISSING' : '') +
|
||||||
|
(chunk.markers.length
|
||||||
|
? ` FORBIDDEN: ${chunk.markers.join(', ')}`
|
||||||
|
: ''),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`[check:main-chunk] main ${composition.mainBytes} bytes (gzip ${composition.mainGzipBytes}); initial chunks ${composition.initialBytes} bytes; ` +
|
||||||
|
(composition.ok
|
||||||
|
? 'no three or Navara'
|
||||||
|
: 'three or Navara in the initial chunks'),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
budget
|
||||||
|
? `[check:main-chunk] ${budget.name}: ${budget.message}`
|
||||||
|
: '[check:main-chunk] no scripts/main-chunk-baseline.json: size budget not checked',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!ok) process.exitCode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
||||||
|
main();
|
||||||
@@ -1064,6 +1064,7 @@
|
|||||||
"src/tooling/navaraAssets.test.mjs",
|
"src/tooling/navaraAssets.test.mjs",
|
||||||
"src/tooling/tileCache.test.mjs",
|
"src/tooling/tileCache.test.mjs",
|
||||||
"scripts/check-main-chunk.mjs",
|
"scripts/check-main-chunk.mjs",
|
||||||
"src/tooling/mainChunk.test.mjs",
|
"src/tooling/mainChunkComposition.test.mjs",
|
||||||
"scripts/qa-w1-baseline.mjs"
|
"scripts/qa-w1-baseline.mjs",
|
||||||
|
"src/tooling/initialChunkGuard.test.mjs"
|
||||||
]
|
]
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import {
|
||||||
|
ENGINE_ONLY_MODULES,
|
||||||
|
initialChunkGuard,
|
||||||
|
initialChunkViolations,
|
||||||
|
} from '../../build/initialChunkGuard.js';
|
||||||
|
|
||||||
|
const chunk = (fields) => ({
|
||||||
|
type: 'chunk',
|
||||||
|
isEntry: false,
|
||||||
|
imports: [],
|
||||||
|
moduleIds: [],
|
||||||
|
...fields,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bundle = () => ({
|
||||||
|
'assets/index-A.js': chunk({
|
||||||
|
isEntry: true,
|
||||||
|
imports: ['assets/vendor-B.js'],
|
||||||
|
moduleIds: ['/repo/src/main.js', '/repo/node_modules/cesium/index.js'],
|
||||||
|
}),
|
||||||
|
'assets/vendor-B.js': chunk({
|
||||||
|
moduleIds: ['/repo/node_modules/satellite.js/dist/index.js'],
|
||||||
|
}),
|
||||||
|
'assets/navara-engine-C.js': chunk({
|
||||||
|
isDynamicEntry: true,
|
||||||
|
moduleIds: [
|
||||||
|
'/repo/node_modules/three/build/three.module.js',
|
||||||
|
'/repo/node_modules/@navaramap/three/dist/index.js',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
'assets/style.css': { type: 'asset' },
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lazy chunks may hold three and Navara; initial chunks are clean', () => {
|
||||||
|
assert.deepEqual(initialChunkViolations(bundle()), []);
|
||||||
|
assert.ok(ENGINE_ONLY_MODULES.test('/x/node_modules/three/src/a.js'));
|
||||||
|
assert.ok(
|
||||||
|
ENGINE_ONLY_MODULES.test('C:\\x\\node_modules\\@navaramap\\three\\a.js'),
|
||||||
|
);
|
||||||
|
assert.ok(!ENGINE_ONLY_MODULES.test('/x/node_modules/three-geojson/a.js'));
|
||||||
|
assert.ok(!ENGINE_ONLY_MODULES.test('/x/src/layers/threeD.js'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('three or Navara reached by a static import from the entry is reported', () => {
|
||||||
|
const broken = bundle();
|
||||||
|
broken['assets/vendor-B.js'].moduleIds.push(
|
||||||
|
'/repo/node_modules/three/build/three.core.js',
|
||||||
|
);
|
||||||
|
broken['assets/index-A.js'].moduleIds.push(
|
||||||
|
'/repo/node_modules/@navaramap/three-default-plugin/dist/index.js',
|
||||||
|
);
|
||||||
|
assert.deepEqual(initialChunkViolations(broken), [
|
||||||
|
{
|
||||||
|
chunk: 'assets/index-A.js',
|
||||||
|
module:
|
||||||
|
'/repo/node_modules/@navaramap/three-default-plugin/dist/index.js',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chunk: 'assets/vendor-B.js',
|
||||||
|
module: '/repo/node_modules/three/build/three.core.js',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the plugin fails the build on a violation and passes a clean bundle', () => {
|
||||||
|
const plugin = initialChunkGuard({ log: () => {} });
|
||||||
|
const errors = [];
|
||||||
|
const context = { error: (message) => errors.push(message) };
|
||||||
|
plugin.generateBundle.call(context, {}, bundle());
|
||||||
|
assert.deepEqual(errors, []);
|
||||||
|
const broken = bundle();
|
||||||
|
broken['assets/index-A.js'].moduleIds.push('/r/node_modules/three/a.js');
|
||||||
|
plugin.generateBundle.call(context, {}, broken);
|
||||||
|
assert.equal(errors.length, 1);
|
||||||
|
assert.match(errors[0], /three.*index-A\.js/s);
|
||||||
|
});
|
||||||
@@ -84,3 +84,21 @@ test('three and Navara markers are reported, ordinary engine names are not', ()
|
|||||||
]);
|
]);
|
||||||
assert.ok(FORBIDDEN_MARKERS.length >= 2);
|
assert.ok(FORBIDDEN_MARKERS.length >= 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the script exports the gzip budget check (phase 4 shape) next to the composition check', async () => {
|
||||||
|
const { mkdirSync, mkdtempSync, writeFileSync } = await import('node:fs');
|
||||||
|
const { tmpdir } = await import('node:os');
|
||||||
|
const path = await import('node:path');
|
||||||
|
const { checkMainChunk, measureMainChunk } =
|
||||||
|
await import('../../scripts/check-main-chunk.mjs');
|
||||||
|
const baseline = { gzipBytes: 1000, allowanceBytes: 100 };
|
||||||
|
assert.equal(checkMainChunk(1100, baseline).ok, true);
|
||||||
|
assert.equal(checkMainChunk(1101, baseline).ok, false);
|
||||||
|
const dist = mkdtempSync(path.join(tmpdir(), 'gev-main-chunk-'));
|
||||||
|
mkdirSync(path.join(dist, 'assets'));
|
||||||
|
writeFileSync(path.join(dist, 'assets/index-small.js'), 'x');
|
||||||
|
writeFileSync(path.join(dist, 'assets/index-main.js'), 'x'.repeat(5000));
|
||||||
|
const measured = measureMainChunk(dist);
|
||||||
|
assert.equal(measured.name, 'index-main.js');
|
||||||
|
assert.ok(measured.gzipBytes > 0 && measured.gzipBytes < 5000);
|
||||||
|
});
|
||||||
@@ -46,11 +46,11 @@ test('a build without Navara gets no Navara wasm', (t) => {
|
|||||||
assert.deepEqual(emitted, []);
|
assert.deepEqual(emitted, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('the browser build runs the Navara asset copy as a Rollup plugin', () => {
|
test('the browser build runs the Navara asset copy and the initial-chunk guard as Rollup plugins', () => {
|
||||||
const { build } = createBrowserViteConfig({});
|
const { build } = createBrowserViteConfig({});
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
build.rollupOptions.plugins.map(({ name }) => name),
|
build.rollupOptions.plugins.map(({ name }) => name),
|
||||||
['navara-worker-assets'],
|
['navara-worker-assets', 'gev-initial-chunk-guard'],
|
||||||
);
|
);
|
||||||
assert.equal(build.chunkSizeWarningLimit, 1500);
|
assert.equal(build.chunkSizeWarningLimit, 1500);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user