Update project tooling
This commit is contained in:
parent
f0fb4fc86b
commit
3c21291d72
14 changed files with 1469 additions and 198 deletions
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"enabledPlugins": {
|
||||
"frontend-design@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name: Deploy to Pages
|
||||
name: Check & deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -25,20 +25,38 @@ jobs:
|
|||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: |
|
||||
npm ci
|
||||
npx playwright install --with-deps chromium
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint -- --check || true
|
||||
- name: Test
|
||||
run: |
|
||||
npm run lint:check
|
||||
npm run format:check
|
||||
npm run typecheck
|
||||
npm run typecheck:e2e
|
||||
npm test
|
||||
|
||||
- name: Typecheck
|
||||
run: npm run typecheck
|
||||
- name: Test E2E
|
||||
run: |
|
||||
npm run test:e2e
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
run: |
|
||||
npm run build
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: |
|
||||
playwright-report/
|
||||
test-results/
|
||||
retention-days: 7
|
||||
|
||||
- name: Copy build to host pages mount
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
apt update && apt install -y rsync
|
||||
mkdir -p /pages
|
||||
rsync -a --delete dist/ /pages/fleeting-garden
|
||||
rsync -a --delete dist/ /pages/fleeting
|
||||
|
|
|
|||
44
.gitignore
vendored
44
.gitignore
vendored
|
|
@ -1,45 +1,5 @@
|
|||
# Dependency directory
|
||||
node_modules
|
||||
modules/
|
||||
ts-node--*/
|
||||
rss.xml
|
||||
|
||||
dist
|
||||
|
||||
# Logs
|
||||
logs
|
||||
test-results
|
||||
.DS_Store
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.ssh
|
||||
*.ppk
|
||||
v8-compile-cache-0/
|
||||
Thumbs.db
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
bin
|
||||
ts-node
|
||||
|
||||
# Personal Scripts
|
||||
*.bat
|
||||
*.ssh
|
||||
*.sh
|
||||
!system.min.js
|
||||
|
||||
# Editors
|
||||
.vscode
|
||||
.markdownlint.json
|
||||
|
||||
# Build Files
|
||||
temp
|
||||
*.js
|
||||
*.map
|
||||
!webpack.*
|
||||
|
|
|
|||
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
22
|
||||
22.13.0
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@
|
|||
"endOfLine": "lf",
|
||||
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
|
||||
"importOrder": ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "", "^[./]"],
|
||||
"importOrderTypeScriptVersion": "5.0.0"
|
||||
"importOrderTypeScriptVersion": "6.0.3"
|
||||
}
|
||||
|
|
|
|||
4
definitions.d.ts
vendored
4
definitions.d.ts
vendored
|
|
@ -1,4 +0,0 @@
|
|||
declare module '*.wgsl?raw' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
265
e2e/app.spec.ts
Normal file
265
e2e/app.spec.ts
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
import { test as base, expect, type Page } from '@playwright/test';
|
||||
|
||||
const canvasName = 'Interactive generative garden canvas';
|
||||
|
||||
interface BrowserDiagnostics {
|
||||
browserFailures: Array<string>;
|
||||
consoleErrors: Array<string>;
|
||||
}
|
||||
|
||||
const isLocalUrl = (url: string) => {
|
||||
const { hostname } = new URL(url);
|
||||
return hostname === '127.0.0.1' || hostname === 'localhost';
|
||||
};
|
||||
|
||||
const collectLocalBrowserFailures = (page: Page) => {
|
||||
const failures: Array<string> = [];
|
||||
|
||||
page.on('requestfailed', (request) => {
|
||||
if (!isLocalUrl(request.url())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const failure = request.failure();
|
||||
failures.push(`${request.method()} ${request.url()} ${failure?.errorText}`);
|
||||
});
|
||||
page.on('response', (response) => {
|
||||
if (response.status() < 400 || !isLocalUrl(response.url())) {
|
||||
return;
|
||||
}
|
||||
|
||||
failures.push(`${response.status()} ${response.url()}`);
|
||||
});
|
||||
|
||||
return failures;
|
||||
};
|
||||
|
||||
const test = base.extend<{ browserDiagnostics: BrowserDiagnostics }>({
|
||||
browserDiagnostics: [
|
||||
async ({ page }, use) => {
|
||||
const browserFailures = collectLocalBrowserFailures(page);
|
||||
const consoleErrors: Array<string> = [];
|
||||
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error') {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
|
||||
await use({ browserFailures, consoleErrors });
|
||||
|
||||
expect(consoleErrors).toEqual([]);
|
||||
expect(browserFailures).toEqual([]);
|
||||
},
|
||||
{ auto: true },
|
||||
],
|
||||
});
|
||||
|
||||
const disableWebGpu = async (page: Page) => {
|
||||
await page.addInitScript(() => {
|
||||
Object.defineProperty(navigator, 'gpu', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
test('starts the WebGPU garden and accepts drawing input', async ({ page }) => {
|
||||
await page.addInitScript((expectedCanvasName) => {
|
||||
const captureState = { count: 0 };
|
||||
Object.defineProperty(window, '__fleetingGardenPointerCaptures', {
|
||||
configurable: true,
|
||||
value: captureState,
|
||||
});
|
||||
|
||||
const originalSetPointerCapture = Element.prototype.setPointerCapture;
|
||||
Element.prototype.setPointerCapture = function setPointerCapture(pointerId) {
|
||||
if (
|
||||
this instanceof HTMLCanvasElement &&
|
||||
this.getAttribute('aria-label') === expectedCanvasName
|
||||
) {
|
||||
captureState.count += 1;
|
||||
}
|
||||
|
||||
return originalSetPointerCapture.call(this, pointerId);
|
||||
};
|
||||
}, canvasName);
|
||||
|
||||
await page.goto('/');
|
||||
const startButton = page.getByRole('button', { exact: true, name: 'Start' });
|
||||
await expect(startButton).toBeVisible();
|
||||
await expect(startButton).toBeEnabled({ timeout: 30_000 });
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(page.locator('body')).not.toHaveClass(/is-loading/, {
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
await expect(page.getByRole('alert')).toHaveCount(0);
|
||||
await expect(page.getByRole('toolbar', { name: 'Garden toolbar' })).toBeVisible();
|
||||
|
||||
const canvas = page.getByRole('img', { name: canvasName });
|
||||
await expect(canvas).toBeVisible();
|
||||
const canvasSize = await canvas.evaluate((element) => {
|
||||
const canvasElement = element as HTMLCanvasElement;
|
||||
return {
|
||||
height: canvasElement.height,
|
||||
width: canvasElement.width,
|
||||
};
|
||||
});
|
||||
expect(canvasSize.width).toBeGreaterThan(0);
|
||||
expect(canvasSize.height).toBeGreaterThan(0);
|
||||
|
||||
const box = await canvas.boundingBox();
|
||||
expect(box).not.toBeNull();
|
||||
if (!box) {
|
||||
return;
|
||||
}
|
||||
|
||||
await page.mouse.move(box.x + box.width * 0.2, box.y + box.height * 0.5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(box.x + box.width * 0.8, box.y + box.height * 0.5, {
|
||||
steps: 16,
|
||||
});
|
||||
await page.mouse.up();
|
||||
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(
|
||||
window as unknown as {
|
||||
__fleetingGardenPointerCaptures?: { count: number };
|
||||
}
|
||||
).__fleetingGardenPointerCaptures?.count ?? 0
|
||||
)
|
||||
)
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
(
|
||||
window as unknown as {
|
||||
__fleetingGardenBrushPasses?: number;
|
||||
}
|
||||
).__fleetingGardenBrushPasses ?? 0
|
||||
)
|
||||
)
|
||||
.toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('shows a clear fallback when WebGPU is unavailable', async ({ page }) => {
|
||||
await disableWebGpu(page);
|
||||
await page.goto('/');
|
||||
|
||||
await expect(page).toHaveTitle('Fleeting Garden');
|
||||
await expect(page.getByRole('img', { name: canvasName })).toBeVisible();
|
||||
await expect(page.getByRole('toolbar', { name: 'Garden toolbar' })).toBeVisible();
|
||||
await expect(page.locator('body')).not.toHaveClass(/is-loading/);
|
||||
|
||||
const fallback = page.getByRole('alert');
|
||||
await expect(fallback).toContainText('Fleeting Garden needs WebGPU');
|
||||
await expect(fallback).toContainText('webgpu-unsupported');
|
||||
});
|
||||
|
||||
test('syncs the selected vibe with the URI', async ({ page }) => {
|
||||
await disableWebGpu(page);
|
||||
await page.goto('/?vibe=Aurora%20Mycelium');
|
||||
|
||||
await expect(page).toHaveURL(/vibe=aurora-mycelium/);
|
||||
|
||||
await page.getByRole('button', { name: 'Next vibe' }).click();
|
||||
await expect(page).toHaveURL(/vibe=velvet-observatory/);
|
||||
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL(/vibe=aurora-mycelium/);
|
||||
});
|
||||
|
||||
test('keeps audio focus outlines scoped to the active control', async ({ page }) => {
|
||||
await disableWebGpu(page);
|
||||
await page.goto('/');
|
||||
await expect(page.locator('body')).not.toHaveClass(/is-loading/);
|
||||
|
||||
const audioControl = page.locator('.audio-control');
|
||||
const soundButton = page.getByRole('button', { name: /audio/i });
|
||||
const volumeSlider = page.getByRole('slider', { name: 'Master volume' });
|
||||
|
||||
await soundButton.click();
|
||||
await expect(audioControl).toHaveCSS('outline-style', 'none');
|
||||
await expect(soundButton).toHaveCSS('outline-style', 'none');
|
||||
|
||||
await page.mouse.click(10, 10);
|
||||
for (let tabIndex = 0; tabIndex < 12; tabIndex += 1) {
|
||||
await page.keyboard.press('Tab');
|
||||
const activeClass = await page.evaluate(() =>
|
||||
String(document.activeElement?.className ?? '')
|
||||
);
|
||||
if (activeClass.includes('sound')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await expect(soundButton).toBeFocused();
|
||||
await expect(soundButton).toHaveCSS('outline-style', 'solid');
|
||||
await expect(soundButton).toHaveCSS('outline-offset', '-4px');
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(volumeSlider).toBeFocused();
|
||||
await expect(volumeSlider).toHaveCSS('outline-style', 'solid');
|
||||
await expect(volumeSlider).toHaveCSS('outline-offset', '-4px');
|
||||
});
|
||||
|
||||
test('keeps the config overlay scrollable and dismissible on mobile', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 390, height: 640 });
|
||||
await page.goto('/');
|
||||
|
||||
const startButton = page.getByRole('button', { exact: true, name: 'Start' });
|
||||
await expect(startButton).toBeEnabled({ timeout: 30_000 });
|
||||
await startButton.click();
|
||||
await expect(page.locator('body')).not.toHaveClass(/is-loading/, {
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
const settingsButton = page.getByRole('button', { name: 'Show config overlay' });
|
||||
await settingsButton.click();
|
||||
|
||||
const pane = page.locator('.config-pane');
|
||||
const closeButton = page.locator('.config-pane-close');
|
||||
await expect(pane).toBeVisible();
|
||||
await expect(closeButton).toBeVisible();
|
||||
|
||||
const paneMetrics = await pane.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = window.getComputedStyle(element);
|
||||
return {
|
||||
bottom: rect.bottom,
|
||||
clientHeight: element.clientHeight,
|
||||
overflowY: style.overflowY,
|
||||
scrollHeight: element.scrollHeight,
|
||||
top: rect.top,
|
||||
viewportHeight: window.innerHeight,
|
||||
viewportWidth: window.innerWidth,
|
||||
width: rect.width,
|
||||
};
|
||||
});
|
||||
|
||||
expect(paneMetrics.top).toBeGreaterThanOrEqual(0);
|
||||
expect(paneMetrics.bottom).toBeLessThanOrEqual(paneMetrics.viewportHeight);
|
||||
expect(Math.round(paneMetrics.width)).toBe(Math.round(paneMetrics.viewportWidth * 0.8));
|
||||
expect(paneMetrics.scrollHeight).toBeGreaterThan(paneMetrics.clientHeight);
|
||||
expect(['auto', 'scroll']).toContain(paneMetrics.overflowY);
|
||||
|
||||
await pane.evaluate((element) => {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
});
|
||||
await expect
|
||||
.poll(() => pane.evaluate((element) => element.scrollTop))
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
await closeButton.click();
|
||||
await expect(pane).toBeHidden();
|
||||
await expect(settingsButton).toHaveAttribute('aria-expanded', 'false');
|
||||
});
|
||||
30
eslint.config.js
Normal file
30
eslint.config.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import js from '@eslint/js';
|
||||
import prettierConfig from 'eslint-config-prettier';
|
||||
import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['node_modules/**', 'dist/**', 'public/**'],
|
||||
},
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
prettierConfig,
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'prefer-const': 'error',
|
||||
},
|
||||
}
|
||||
);
|
||||
1176
package-lock.json
generated
1176
package-lock.json
generated
File diff suppressed because it is too large
Load diff
37
package.json
37
package.json
|
|
@ -1,22 +1,28 @@
|
|||
{
|
||||
"name": "webgpu-seed",
|
||||
"name": "fleeting-garden",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "A WebGPU-powered slime-mold-meets-territory-control simulation.",
|
||||
"description": "Tend it while you can. The garden returns to weather either way. A WebGPU drawing toy in your browser.",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --fix \"src/**/*.ts\" && prettier --write \"src/**/*.{ts,scss,json,html}\"",
|
||||
"lint:check": "eslint . && npm run unused:check",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write \"index.html\" \"public/manifest.webmanifest\" \"src/**/*.{ts,scss,json,html}\" \"e2e/**/*.ts\" \".forgejo/workflows/*.yml\" \"*.{json,js,ts,md}\" \".prettierrc\"",
|
||||
"format:check": "prettier --check \"index.html\" \"public/manifest.webmanifest\" \"src/**/*.{ts,scss,json,html}\" \"e2e/**/*.ts\" \".forgejo/workflows/*.yml\" \"*.{json,js,ts,md}\" \".prettierrc\"",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"typecheck:e2e": "tsc --noEmit --project tsconfig.playwright.json",
|
||||
"test": "vitest run",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:watch": "vitest",
|
||||
"generate-icons": "pwa-assets-generator",
|
||||
"update": "ncu"
|
||||
"unused:check": "knip --production --files --dependencies && knip --exports --include-entry-exports",
|
||||
"generate-icons": "pwa-assets-generator"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=22.13.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -33,23 +39,28 @@
|
|||
"browserslist": [
|
||||
"supports webgpu and last 2 years"
|
||||
],
|
||||
"dependencies": {
|
||||
"gl-matrix": "^3.4.4"
|
||||
"knip": {
|
||||
"ignoreFiles": [
|
||||
"pwa-assets.config.ts"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@tweakpane/core": "~2.0.5",
|
||||
"@types/node": "^25.6.0",
|
||||
"@vite-pwa/assets-generator": "^1.0.2",
|
||||
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||
"@webgpu/types": "^0.1.69",
|
||||
"browserslist": "^4.28.2",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"eslint": "^10.3.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-unused-imports": "^4.4.1",
|
||||
"gl-matrix": "^3.4.4",
|
||||
"globals": "^17.6.0",
|
||||
"knip": "^6.14.1",
|
||||
"lightningcss": "^1.32.0",
|
||||
"npm-check-updates": "^22.1.0",
|
||||
"prettier": "^3.8.3",
|
||||
"sass": "^1.99.0",
|
||||
"typescript": "^6.0.3",
|
||||
|
|
@ -57,5 +68,9 @@
|
|||
"vite": "^8.0.10",
|
||||
"vite-plugin-singlefile": "^2.3.3",
|
||||
"vitest": "^4.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@plausible-analytics/tracker": "^0.4.5",
|
||||
"tweakpane": "~4.0.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
37
playwright.config.ts
Normal file
37
playwright.config.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const port = 4173;
|
||||
const baseURL = `https://127.0.0.1:${port}`;
|
||||
const isCi = Boolean(process.env.CI);
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
fullyParallel: true,
|
||||
forbidOnly: isCi,
|
||||
retries: isCi ? 2 : 0,
|
||||
workers: 1,
|
||||
reporter: isCi ? [['list'], ['html', { open: 'never' }]] : 'list',
|
||||
use: {
|
||||
baseURL,
|
||||
ignoreHTTPSErrors: true,
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: `npm run build && npm run preview -- --host 127.0.0.1 --port ${port}`,
|
||||
ignoreHTTPSErrors: true,
|
||||
reuseExistingServer: false,
|
||||
timeout: 120_000,
|
||||
url: baseURL,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
launchOptions: {
|
||||
args: ['--enable-unsafe-webgpu'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -9,14 +9,13 @@
|
|||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"include": ["src/**/*", "definitions.d.ts", "vite.config.ts"]
|
||||
"include": ["src/**/*", "pwa-assets.config.ts", "vite.config.ts"]
|
||||
}
|
||||
|
|
|
|||
7
tsconfig.playwright.json
Normal file
7
tsconfig.playwright.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["playwright.config.ts", "e2e/**/*.ts"]
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||
import browserslist from 'browserslist';
|
||||
import browserslistToEsbuild from 'browserslist-to-esbuild';
|
||||
import { browserslistToTargets } from 'lightningcss';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { viteSingleFile } from 'vite-plugin-singlefile';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
const cssTargets = browserslistToTargets(browserslist());
|
||||
const esbuildTargets = browserslistToEsbuild();
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [viteSingleFile()],
|
||||
export default defineConfig(({ command }) => ({
|
||||
plugins: [viteSingleFile(), ...(command === 'serve' ? [basicSsl()] : [])],
|
||||
css: {
|
||||
transformer: 'lightningcss',
|
||||
lightningcss: {
|
||||
|
|
@ -14,16 +17,14 @@ export default defineConfig({
|
|||
},
|
||||
},
|
||||
build: {
|
||||
target: 'es2022',
|
||||
cssCodeSplit: false,
|
||||
target: esbuildTargets,
|
||||
cssMinify: 'lightningcss',
|
||||
assetsInlineLimit: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
server: {
|
||||
open: true,
|
||||
host: true,
|
||||
},
|
||||
test: {
|
||||
environment: 'node',
|
||||
include: ['src/**/*.test.ts'],
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue