fix(ui): make fixture bundle browser-safe, fast and cacheable
The UI fixture bundle crashed at runtime, so every test waited out its 45s timeout: - server-only modules leaked into the bundle and referenced node built-ins, the process global, and validated env at import time - the previous platform: 'node' fix kept esbuild happy but emitted require() calls that cannot run in the browser Fix it at the bundle boundary: - alias node:* imports to e2e/ui/node-stubs.mjs (shape-compatible stubs, no real I/O) - inject e2e/ui/process-shim.mjs for the process global - define process.env.SKIP_ENV_VALIDATION=1 so src/env.ts skips parsing in the fixture (same escape hatch vitest uses) Also make the fixture server fast to start: - build JS and Tailwind CSS in parallel - cache built assets in node_modules/.cache/nexst-ui-fixture keyed on a content hash of src/, fixtures, stubs and the server file - startup drops from ~1.7s to ~220ms on cache hits
This commit is contained in:
1 parent
c9b363d60d
commit
1dd584cd82
27 files changed
+334
-67
No files matched your search
+162
-11
@@ -1,6 +1,8 @@
|
||||
// Stubs for Node.js built-ins used by server-only modules that leak into the
|
||||
// UI fixture bundle. These keep esbuild from failing on `node:*` imports
|
||||
// while providing shape-compatible exports so the bundle stays loadable.
|
||||
// Nothing here performs real I/O; it only exists so module evaluation in the
|
||||
// browser does not crash on import of server-only helpers.
|
||||
|
||||
export class AsyncLocalStorage {
|
||||
enterWith() {}
|
||||
@@ -13,24 +15,173 @@ export class AsyncLocalStorage {
|
||||
}
|
||||
}
|
||||
|
||||
export function createHash() {
|
||||
return {
|
||||
const cryptoApi = {
|
||||
createHash: () => ({
|
||||
update: () => ({
|
||||
digest: () => "",
|
||||
}),
|
||||
};
|
||||
}
|
||||
}),
|
||||
randomUUID: () => "00000000-0000-0000-0000-000000000000",
|
||||
randomBytes: (size) => new Uint8Array(size),
|
||||
createHmac: () => ({
|
||||
update: () => ({
|
||||
digest: () => "",
|
||||
}),
|
||||
}),
|
||||
timingSafeEqual: () => true,
|
||||
createCipheriv: () => ({
|
||||
update: () => new Uint8Array(0),
|
||||
final: () => new Uint8Array(0),
|
||||
}),
|
||||
createDecipheriv: () => ({
|
||||
update: () => new Uint8Array(0),
|
||||
final: () => new Uint8Array(0),
|
||||
}),
|
||||
webcrypto: globalThis.crypto,
|
||||
};
|
||||
|
||||
export function randomUUID() {
|
||||
return "00000000-0000-0000-0000-000000000000";
|
||||
}
|
||||
export const {
|
||||
createHash,
|
||||
randomUUID,
|
||||
randomBytes,
|
||||
createHmac,
|
||||
timingSafeEqual,
|
||||
createCipheriv,
|
||||
createDecipheriv,
|
||||
webcrypto,
|
||||
} = cryptoApi;
|
||||
|
||||
export async function readFile() {
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
export const appendFile = async () => {};
|
||||
export const mkdir = async () => {};
|
||||
export const readdir = async () => [];
|
||||
export const readFile = async () => new Uint8Array(0);
|
||||
export const rename = async () => {};
|
||||
export const stat = async () => ({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 0,
|
||||
mtimeMs: 0,
|
||||
});
|
||||
export const unlink = async () => {};
|
||||
export const writeFile = async () => {};
|
||||
export const rm = async () => {};
|
||||
export const mkdtemp = async () => "/tmp/fixture-stub";
|
||||
|
||||
export const path = {
|
||||
export const promises = {
|
||||
appendFile,
|
||||
mkdir,
|
||||
readdir,
|
||||
readFile,
|
||||
rename,
|
||||
stat,
|
||||
unlink,
|
||||
writeFile,
|
||||
rm,
|
||||
mkdtemp,
|
||||
};
|
||||
|
||||
export const existsSync = () => false;
|
||||
export const readFileSync = () => new Uint8Array(0);
|
||||
export const writeFileSync = () => {};
|
||||
export const readdirSync = () => [];
|
||||
export const mkdirSync = () => {};
|
||||
export const rmSync = () => {};
|
||||
export const mkdtempSync = () => "/tmp/fixture-stub";
|
||||
export const appendFileSync = () => {};
|
||||
export const statSync = () => ({
|
||||
isDirectory: () => false,
|
||||
isFile: () => true,
|
||||
size: 0,
|
||||
});
|
||||
|
||||
const pathApi = {
|
||||
join: (...parts) => parts.join("/"),
|
||||
resolve: (...parts) => parts.join("/"),
|
||||
dirname: (value) => value.split("/").slice(0, -1).join("/"),
|
||||
basename: (value) => value.split("/").pop() ?? "",
|
||||
extname: (value) => {
|
||||
const base = value.split("/").pop() ?? "";
|
||||
const index = base.lastIndexOf(".");
|
||||
return index > 0 ? base.slice(index) : "";
|
||||
},
|
||||
relative: (_from, to) => to,
|
||||
normalize: (value) => value,
|
||||
isAbsolute: (value) => value.startsWith("/"),
|
||||
sep: "/",
|
||||
delimiter: ":",
|
||||
};
|
||||
|
||||
export const {
|
||||
join,
|
||||
resolve,
|
||||
dirname,
|
||||
basename,
|
||||
extname,
|
||||
relative,
|
||||
normalize,
|
||||
isAbsolute,
|
||||
sep,
|
||||
delimiter,
|
||||
} = pathApi;
|
||||
|
||||
export const path = pathApi;
|
||||
|
||||
const osApi = {
|
||||
hostname: () => "localhost",
|
||||
tmpdir: () => "/tmp",
|
||||
platform: () => "linux",
|
||||
homedir: () => "/tmp",
|
||||
};
|
||||
|
||||
export const { hostname, tmpdir, platform, homedir } = osApi;
|
||||
|
||||
export const promisify = (fn) => fn;
|
||||
export const isIP = () => 0;
|
||||
export const lookup = async () => ({ address: "127.0.0.1", family: 4 });
|
||||
export const execFile = () => {};
|
||||
export const execFileSync = () => "";
|
||||
export const spawnSync = () => ({ status: 0, stdout: "", stderr: "" });
|
||||
export const fileURLToPath = (value) => String(value);
|
||||
export const createServer = () => ({
|
||||
listen: () => {},
|
||||
close: () => {},
|
||||
on: () => {},
|
||||
address: () => ({ port: 0 }),
|
||||
});
|
||||
|
||||
// Single default export: all node:* aliases point at this one file, and the
|
||||
// merged object keeps `import crypto from "node:crypto"`, `import path from
|
||||
// "node:path"` and `import os from "node:os"` usable after aliasing.
|
||||
export default {
|
||||
...cryptoApi,
|
||||
...pathApi,
|
||||
...osApi,
|
||||
appendFile,
|
||||
mkdir,
|
||||
readdir,
|
||||
readFile,
|
||||
rename,
|
||||
stat,
|
||||
unlink,
|
||||
writeFile,
|
||||
rm,
|
||||
mkdtemp,
|
||||
promises,
|
||||
existsSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
readdirSync,
|
||||
mkdirSync,
|
||||
rmSync,
|
||||
mkdtempSync,
|
||||
appendFileSync,
|
||||
statSync,
|
||||
promisify,
|
||||
isIP,
|
||||
lookup,
|
||||
execFile,
|
||||
execFileSync,
|
||||
spawnSync,
|
||||
fileURLToPath,
|
||||
createServer,
|
||||
};
|
||||
Reference in new issue
Block a user