feat(playground): scaffold vite + solid + ts
Fresh playground/ now sits on: - Vite 5 + vite-plugin-solid + Solid.js 1.9 - TypeScript strict (jsx preserve, target ES2022) - index.html → main.tsx → App.tsx with a tiny pushState/popstate router - Design tokens (dark immersive palette, JetBrains Mono) at src/styles/tokens.css - COOP/COEP headers in vite.config.ts (server + preview) so SharedArrayBuffer is available for the C15 + AudioWorklet wiring stream 7 will land. - Debug-probe stub at src/debug/probe.ts: window.__nisps with placeholder methods + __ready=false marker. Stream 10 fills it in. Keeping the install point stable from day one means Playwright tests can rely on the global existing. Stores, pipelines, and primitives ride in the next commits. Stream 8 of the rewrite (meml-911).
This commit is contained in:
parent
d41e4c3e9f
commit
a160b72295
11 changed files with 519 additions and 0 deletions
5
playground/.gitignore
vendored
Normal file
5
playground/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.vite
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
13
playground/index.html
Normal file
13
playground/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||||
|
<meta name="theme-color" content="#0d0d0d" />
|
||||||
|
<title>MEMLNaut Playground</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
playground/package.json
Normal file
23
playground/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "memlnaut-playground",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc --noEmit && vite build",
|
||||||
|
"preview": "vite preview --port 4173",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"test:e2e": "playwright test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"solid-js": "^1.8.22"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.48.0",
|
||||||
|
"@types/node": "^22.7.0",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
|
"vite": "^5.4.10",
|
||||||
|
"vite-plugin-solid": "^2.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
84
playground/src/App.module.css
Normal file
84
playground/src/App.module.css
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
.app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-3);
|
||||||
|
padding: var(--sp-3) var(--sp-4);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
background: var(--bg-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dim {
|
||||||
|
color: var(--fg-mute);
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav button {
|
||||||
|
padding: var(--sp-1) var(--sp-3);
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border-color: var(--accent) !important;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
flex: 1;
|
||||||
|
padding: var(--sp-5);
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: var(--fs-xl);
|
||||||
|
margin: 0;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagline {
|
||||||
|
color: var(--fg-mute);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkList {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
color: var(--fg-dim);
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
margin-top: var(--sp-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notFound {
|
||||||
|
max-width: 540px;
|
||||||
|
margin: var(--sp-6) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notFound h1 {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
71
playground/src/App.tsx
Normal file
71
playground/src/App.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { Component, createSignal, onCleanup, Show } from 'solid-js';
|
||||||
|
import styles from './App.module.css';
|
||||||
|
|
||||||
|
type Route = 'home' | 'unknown';
|
||||||
|
|
||||||
|
function parseRoute(path: string): Route {
|
||||||
|
if (path === '' || path === '/' || path === '/index.html') return 'home';
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
const App: Component = () => {
|
||||||
|
const [route, setRoute] = createSignal<Route>(parseRoute(window.location.pathname));
|
||||||
|
|
||||||
|
const onPop = () => setRoute(parseRoute(window.location.pathname));
|
||||||
|
window.addEventListener('popstate', onPop);
|
||||||
|
onCleanup(() => window.removeEventListener('popstate', onPop));
|
||||||
|
|
||||||
|
const navigate = (path: string) => {
|
||||||
|
if (window.location.pathname === path) return;
|
||||||
|
window.history.pushState({}, '', path);
|
||||||
|
setRoute(parseRoute(path));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={styles.app}>
|
||||||
|
<header class={styles.header}>
|
||||||
|
<strong>MEMLNaut</strong>
|
||||||
|
<span class={styles.dim}>playground</span>
|
||||||
|
<nav class={styles.nav}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class={route() === 'home' ? styles.active : ''}
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
>
|
||||||
|
home
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main class={styles.main}>
|
||||||
|
<Show when={route() === 'home'}>
|
||||||
|
<Home />
|
||||||
|
</Show>
|
||||||
|
<Show when={route() === 'unknown'}>
|
||||||
|
<div class={styles.notFound}>
|
||||||
|
<h1>404</h1>
|
||||||
|
<p>No route at <code>{window.location.pathname}</code>.</p>
|
||||||
|
<p>
|
||||||
|
<a href="/" onClick={(e) => { e.preventDefault(); navigate('/'); }}>back to home</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Home: Component = () => {
|
||||||
|
return (
|
||||||
|
<div class={styles.home}>
|
||||||
|
<h1 class={styles.title}>MEMLNaut Playground</h1>
|
||||||
|
<p class={styles.tagline}>
|
||||||
|
Interactive ML control of audio. SolidJS scaffold — modes coming online in stream 9.
|
||||||
|
</p>
|
||||||
|
<p class={styles.note}>
|
||||||
|
This is a fresh scaffold. Primitives, stores, ML, WASM, and audio engines are added in subsequent commits.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
118
playground/src/debug/probe.ts
Normal file
118
playground/src/debug/probe.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
/**
|
||||||
|
* Debug probe: window.__nisps
|
||||||
|
*
|
||||||
|
* Stream 8 (this stream) installs a stub that returns placeholder values.
|
||||||
|
* Stream 10 wires real ML calls. Keeping the install path stable here means
|
||||||
|
* Playwright tests can rely on `window.__nisps` existing from page load even
|
||||||
|
* before the ML engine boots.
|
||||||
|
*
|
||||||
|
* All methods MUST be synchronous (or return immediately-resolved promises).
|
||||||
|
* The probe deliberately bypasses Solid reactivity so tests get deterministic,
|
||||||
|
* imperative semantics.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface DebugProbe {
|
||||||
|
/** Current 126-element output vector (Float32Array). */
|
||||||
|
getOutputs(): Float32Array;
|
||||||
|
/** Last training loss, or null if no training has occurred. */
|
||||||
|
getLoss(): number | null;
|
||||||
|
/** Flat weight array (~13K floats once wired). */
|
||||||
|
getWeights(): Float32Array;
|
||||||
|
/** Number of training examples currently in the dataset. */
|
||||||
|
getExampleCount(): number;
|
||||||
|
/** Set joystick X/Y in [0,1] and run inference. */
|
||||||
|
setInputs(x: number, y: number): void;
|
||||||
|
/** Trigger thumbs-up RL feedback (train + decay noise). */
|
||||||
|
thumbsUp(): void;
|
||||||
|
/** Trigger thumbs-down RL feedback (move weights + grow noise). */
|
||||||
|
thumbsDown(): void;
|
||||||
|
/** Synchronous training; returns final loss. */
|
||||||
|
train(): number;
|
||||||
|
/** Async training; returns Promise<loss>. */
|
||||||
|
trainAsync(): Promise<number>;
|
||||||
|
/** Randomize weights with current spread. */
|
||||||
|
randomise(): void;
|
||||||
|
/** Clear all training examples. */
|
||||||
|
clearExamples(): void;
|
||||||
|
/** Force a save to localStorage now (no debounce). */
|
||||||
|
saveState(): void;
|
||||||
|
/** Non-destructive loss query against current dataset. */
|
||||||
|
evalLoss(): number | null;
|
||||||
|
/** Batch inference: input is Nx2 array of [x,y] pairs. Output: Float32Array of N*outputSize. */
|
||||||
|
inferBatch(points: ReadonlyArray<readonly [number, number]>): Float32Array;
|
||||||
|
/** Per-layer weight statistics: Float32Array of layerCount * 4 (mean|w|, max|w|, dead%, sat%). */
|
||||||
|
getLayerStats(): Float32Array;
|
||||||
|
/** Marker showing this is a stream-8 stub. Tests can read this to skip when not ready. */
|
||||||
|
readonly __ready: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__nisps?: DebugProbe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_F32 = new Float32Array(0);
|
||||||
|
|
||||||
|
const stubProbe: DebugProbe = {
|
||||||
|
getOutputs() {
|
||||||
|
return EMPTY_F32;
|
||||||
|
},
|
||||||
|
getLoss() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
getWeights() {
|
||||||
|
return EMPTY_F32;
|
||||||
|
},
|
||||||
|
getExampleCount() {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
setInputs(_x: number, _y: number) {
|
||||||
|
/* no-op until ML wired */
|
||||||
|
},
|
||||||
|
thumbsUp() {
|
||||||
|
/* no-op */
|
||||||
|
},
|
||||||
|
thumbsDown() {
|
||||||
|
/* no-op */
|
||||||
|
},
|
||||||
|
train() {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
trainAsync() {
|
||||||
|
return Promise.resolve(0);
|
||||||
|
},
|
||||||
|
randomise() {
|
||||||
|
/* no-op */
|
||||||
|
},
|
||||||
|
clearExamples() {
|
||||||
|
/* no-op */
|
||||||
|
},
|
||||||
|
saveState() {
|
||||||
|
/* no-op */
|
||||||
|
},
|
||||||
|
evalLoss() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
inferBatch(points) {
|
||||||
|
// Return a zero array of the right size for at least the inputs.
|
||||||
|
return new Float32Array(points.length);
|
||||||
|
},
|
||||||
|
getLayerStats() {
|
||||||
|
return EMPTY_F32;
|
||||||
|
},
|
||||||
|
__ready: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install the probe on window. Idempotent.
|
||||||
|
*
|
||||||
|
* Stream 10 will replace this with a fully-wired version. Until then the stub
|
||||||
|
* advertises `__ready === false`, letting tests skip ML-dependent assertions.
|
||||||
|
*/
|
||||||
|
export function installDebugProbe(): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
// Always overwrite — later streams may replace it; the marker prevents stale
|
||||||
|
// probes from passing tests.
|
||||||
|
window.__nisps = stubProbe;
|
||||||
|
}
|
||||||
17
playground/src/main.tsx
Normal file
17
playground/src/main.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* @refresh reload */
|
||||||
|
import { render } from 'solid-js/web';
|
||||||
|
import App from './App';
|
||||||
|
import './styles/tokens.css';
|
||||||
|
import { installDebugProbe } from './debug/probe';
|
||||||
|
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
if (!root) {
|
||||||
|
throw new Error('Root element #root not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install debug probe early. It is a stub for now; stream 10 fills it in
|
||||||
|
// once WASM ML is wired up. Keeping the install path stable from day one
|
||||||
|
// makes Playwright tests insensitive to ordering.
|
||||||
|
installDebugProbe();
|
||||||
|
|
||||||
|
render(() => <App />, root);
|
||||||
123
playground/src/styles/tokens.css
Normal file
123
playground/src/styles/tokens.css
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
/**
|
||||||
|
* Global design tokens for the MEMLNaut playground.
|
||||||
|
* Carries forward the immersive identity: dark canvas, monospace, warm + cool accents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Colors */
|
||||||
|
--bg: #0d0d0d;
|
||||||
|
--bg-1: #141414;
|
||||||
|
--bg-2: #1c1c1c;
|
||||||
|
--bg-3: #242424;
|
||||||
|
--fg: #e8e8e8;
|
||||||
|
--fg-mute: #9a9a9a;
|
||||||
|
--fg-dim: #5a5a5a;
|
||||||
|
--line: #2a2a2a;
|
||||||
|
--line-strong: #3a3a3a;
|
||||||
|
|
||||||
|
--accent: #ff6a00; /* warm primary */
|
||||||
|
--accent-2: #00ccff; /* cool secondary */
|
||||||
|
--accent-3: #ffa860; /* hover warm */
|
||||||
|
--good: #6bc26b;
|
||||||
|
--warn: #f5c45e;
|
||||||
|
--bad: #ef5b5b;
|
||||||
|
--info: #5b9eef;
|
||||||
|
|
||||||
|
--pin-1: rgba(255, 106, 0, 0.25);
|
||||||
|
--pin-2: rgba(0, 204, 255, 0.25);
|
||||||
|
--pin-3: rgba(180, 100, 255, 0.25);
|
||||||
|
--pin-4: rgba(80, 200, 120, 0.25);
|
||||||
|
--pin-5: rgba(255, 200, 80, 0.25);
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, Consolas, monospace;
|
||||||
|
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||||
|
|
||||||
|
--fs-xs: 11px;
|
||||||
|
--fs-sm: 13px;
|
||||||
|
--fs-md: 15px;
|
||||||
|
--fs-lg: 18px;
|
||||||
|
--fs-xl: 24px;
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
--sp-0: 2px;
|
||||||
|
--sp-1: 4px;
|
||||||
|
--sp-2: 8px;
|
||||||
|
--sp-3: 12px;
|
||||||
|
--sp-4: 16px;
|
||||||
|
--sp-5: 24px;
|
||||||
|
--sp-6: 32px;
|
||||||
|
|
||||||
|
/* Radius */
|
||||||
|
--r-1: 4px;
|
||||||
|
--r-2: 8px;
|
||||||
|
--r-3: 14px;
|
||||||
|
--r-pill: 999px;
|
||||||
|
|
||||||
|
/* Motion */
|
||||||
|
--ease: cubic-bezier(.25, .8, .35, 1);
|
||||||
|
--dur-fast: 120ms;
|
||||||
|
--dur-med: 220ms;
|
||||||
|
--dur-slow: 360ms;
|
||||||
|
|
||||||
|
/* Layers */
|
||||||
|
--z-bg: 0;
|
||||||
|
--z-content: 10;
|
||||||
|
--z-overlay: 100;
|
||||||
|
--z-drawer: 200;
|
||||||
|
--z-modal: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #root {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--fs-md);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
background: var(--bg-2);
|
||||||
|
color: var(--fg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--r-1);
|
||||||
|
padding: var(--sp-2) var(--sp-3);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
|
||||||
|
}
|
||||||
|
button:hover:not(:disabled) {
|
||||||
|
background: var(--bg-3);
|
||||||
|
border-color: var(--line-strong);
|
||||||
|
}
|
||||||
|
button:disabled {
|
||||||
|
color: var(--fg-dim);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--accent-2);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
8
playground/src/types.d.ts
vendored
Normal file
8
playground/src/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.module.css' {
|
||||||
|
const classes: Record<string, string>;
|
||||||
|
export default classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.css';
|
||||||
32
playground/tsconfig.json
Normal file
32
playground/tsconfig.json
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"exactOptionalPropertyTypes": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"verbatimModuleSyntax": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"types": ["vite/client", "node"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*", "vite.config.ts"],
|
||||||
|
"exclude": ["node_modules", "dist", "tests/e2e"]
|
||||||
|
}
|
||||||
25
playground/vite.config.ts
Normal file
25
playground/vite.config.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import solid from 'vite-plugin-solid';
|
||||||
|
|
||||||
|
// COOP/COEP headers are required for SharedArrayBuffer (used by C15 + AudioWorklet).
|
||||||
|
// Set on both dev server and the production preview.
|
||||||
|
const crossOriginIsolationHeaders = {
|
||||||
|
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||||
|
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [solid()],
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
headers: crossOriginIsolationHeaders,
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
port: 4173,
|
||||||
|
headers: crossOriginIsolationHeaders,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: 'es2022',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue