From 4914912e59ba7dc4be0b97f21bcd39b513b9ff39 Mon Sep 17 00:00:00 2001 From: hfcRed <101019309+hfcRed@users.noreply.github.com> Date: Sat, 18 Jul 2026 03:30:08 -0400 Subject: [PATCH] Improve dev server performance on startup and navigation (#3233) --- README.md | 24 ++- app/form/SendouForm.tsx | 8 +- app/form/fields.ts | 12 ++ app/form/utils.ts | 14 +- e2e/helpers/playwright-form.ts | 4 +- package.json | 3 +- pnpm-lock.yaml | 275 ++++++++++++++++++++++++++++----- scripts/setup.ts | 4 +- vite.config.ts | 101 +++++++++--- 9 files changed, 368 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 2fe289f06..ba87f4ba9 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Another key objective is to bridge the gap between casual and competitive player ### Prerequisites - [Git](https://git-scm.com/) -- [Node.js v22](https://nodejs.org/en) +- [Node.js v24](https://nodejs.org/en) (see [.nvmrc](./.nvmrc) for the exact version) - [pnpm](https://pnpm.io/installation) Optionally [nvm](https://github.com/nvm-sh/nvm) can be convenient for managing multiple Node.js installs @@ -73,7 +73,7 @@ pnpm --version You should see something like: ``` -v22.13.0 +v24.18.0 git version 2.39.5 (Apple Git-154) 10.33.0 ``` @@ -114,6 +114,26 @@ docker compose up -d Minio admin UI to manage uploaded photos should be up and running at http://localhost:9001 +#### Windows performance tips + +The dev server does many small file operations which are slower on Windows by default. Two optional tweaks can improve performance: + +**Windows Defender exclusions** + +Excluding the project folder and the pnpm store from Windows Defender speeds up installs, dev server startup and first navigations. In a PowerShell ran as administrator: + +```powershell +Add-MpPreference -ExclusionPath "C:\path\to\sendou.ink" +Add-MpPreference -ExclusionPath "$(pnpm store path)" +``` + +Be aware that excluded folders are not scanned at all, and compromised npm packages can land in `node_modules`. This should not be much of an issue since pnpm does not allow running dependency lifecycle scripts unless explicitly allowed, and exact dependency versions are being pinned by the lockfile, but it should be kept in mind. +Undo at any time with `Remove-MpPreference -ExclusionPath "..."`. + +**Dev Drive** + +Alternatively, keep the repository on a [Dev Drive](https://learn.microsoft.com/en-us/windows/dev-drive/). On a Dev Drive, Defender scans files asynchronously instead of blocking, so you keep most of the speedup without giving up scanning entirely. + ## Contributing - **Developers**: Read [CONTRIBUTING.md](./CONTRIBUTING.md) diff --git a/app/form/SendouForm.tsx b/app/form/SendouForm.tsx index 230558919..a7ba62125 100644 --- a/app/form/SendouForm.tsx +++ b/app/form/SendouForm.tsx @@ -9,9 +9,9 @@ import type { z } from "zod"; import { FormMessage } from "~/components/FormMessage"; import { SubmitButton } from "~/components/SubmitButton"; import { FormField as FormFieldComponent } from "./FormField"; -import { formRegistry } from "./fields"; +import { getFormFieldMetadata } from "./fields"; import styles from "./SendouForm.module.css"; -import type { FormField, TypedFormFieldComponent } from "./types"; +import type { TypedFormFieldComponent } from "./types"; import { buildFieldPath, errorMessageId, @@ -625,9 +625,7 @@ function buildInitialValues( const result: Record = {}; for (const [key, fieldSchema] of Object.entries(schema.shape)) { - const formField = formRegistry.get(fieldSchema as z.ZodType) as - | FormField - | undefined; + const formField = getFormFieldMetadata(fieldSchema as z.ZodType); const defaultValue = defaultValues?.[key as keyof typeof defaultValues]; if (defaultValue !== undefined) { diff --git a/app/form/fields.ts b/app/form/fields.ts index 788b5d67a..ca2933772 100644 --- a/app/form/fields.ts +++ b/app/form/fields.ts @@ -36,6 +36,18 @@ import type { export const formRegistry = z.registry(); +/** + * Looks up a schemas form field metadata. Needed to bypass the + * registrys deep generic `get` signature which causes + * "Type instantiation is excessively deep" errors. + */ +export function getFormFieldMetadata(schema: z.ZodType): FormField | undefined { + const registry = formRegistry as { + get(schema: z.ZodType): FormField | undefined; + }; + return registry.get(schema); +} + export type RequiresDefault = T & { _requiresDefault: true; }; diff --git a/app/form/utils.ts b/app/form/utils.ts index 7de3c3227..74380f06c 100644 --- a/app/form/utils.ts +++ b/app/form/utils.ts @@ -1,5 +1,5 @@ import type { z } from "zod"; -import { formRegistry } from "./fields"; +import { getFormFieldMetadata } from "./fields"; import type { FormField } from "./types"; function infoMessageId(fieldId: string) { @@ -94,12 +94,6 @@ export function setNestedValue( }; } -// Casting away the registry's deep generic signature avoids "Type instantiation -// is excessively deep" errors when looking up field metadata by schema. -const typedRegistry = formRegistry as { - get(schema: z.ZodType): FormField | undefined; -}; - /** * The default value object for a `fieldset` field, built from each sub-field's * own `initialValue` (e.g. a `select`'s first option). Returns `{}` for @@ -113,7 +107,7 @@ export function fieldsetDefaults( const shape = fieldsetMeta.fields.shape as Record; const result: Record = {}; for (const [key, fieldSchema] of Object.entries(shape)) { - const fieldMeta = typedRegistry.get(fieldSchema); + const fieldMeta = getFormFieldMetadata(fieldSchema); if (fieldMeta) result[key] = fieldMeta.initialValue; } return result; @@ -142,7 +136,7 @@ export function seedArrayItemDefaults( const itemSchema = getNestedSchema(schema, itemPath); if (!itemSchema) return values; - const itemMeta = typedRegistry.get(itemSchema); + const itemMeta = getFormFieldMetadata(itemSchema); if (itemMeta?.type !== "fieldset") return values; const existing = getNestedValue(values, itemPath) as @@ -228,7 +222,7 @@ export function validateField( // array) belongs to that child — attributing it to the parent would surface // the wrong message at the wrong field. Other composite fields (e.g. a custom // tuple) render as a single control, so their nested issues belong to them. - const fieldMeta = typedRegistry.get(fieldSchema); + const fieldMeta = getFormFieldMetadata(fieldSchema); const childrenRenderOwnErrors = fieldMeta?.type === "array" || fieldMeta?.type === "fieldset"; const issue = childrenRenderOwnErrors diff --git a/e2e/helpers/playwright-form.ts b/e2e/helpers/playwright-form.ts index ad49f0db0..6c32df8c5 100644 --- a/e2e/helpers/playwright-form.ts +++ b/e2e/helpers/playwright-form.ts @@ -3,7 +3,7 @@ import * as path from "node:path"; import { fileURLToPath } from "node:url"; import { expect, type Page } from "@playwright/test"; import type { z } from "zod"; -import { formRegistry } from "~/form/fields"; +import { getFormFieldMetadata } from "~/form/fields"; import type { FormField } from "~/form/types"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -81,7 +81,7 @@ export function createFormHelpers( const getFieldMetadata = (name: string): FormField | undefined => { const fieldSchema = schema.shape[name]; if (!fieldSchema) return undefined; - return formRegistry.get(fieldSchema) as FormField | undefined; + return getFormFieldMetadata(fieldSchema as z.ZodType); }; const getLabel = (name: string): string => { diff --git a/package.json b/package.json index 9c9b8eaa4..db75831c5 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "deploy": "pnpm install --frozen-lockfile && pnpm run build", "build": "react-router build", "dev": "cross-env DB_PATH=db.sqlite3 pnpm run migrate up && pnpm run setup && react-router dev --host", + "dev:fast": "react-router dev --host", "dev:sentry": "NODE_OPTIONS='--import ./instrument.server.mjs' pnpm dev", "dev:prod": "cross-env DB_PATH=db-prod.sqlite3 VITE_PROD_MODE=true react-router dev --host", "start": "pnpm run migrate up && NODE_ENV=production NODE_OPTIONS='--import ./instrument.server.mjs' react-router-serve ./build/server/index.js", @@ -118,7 +119,7 @@ "knip": "6.25.0", "ley": "0.8.1", "sql-formatter": "15.8.2", - "typescript": "6.0.3", + "typescript": "7.0.2", "vite": "8.1.3", "vite-node": "6.0.0", "vite-plugin-babel": "1.7.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b87a7370c..d48bad81d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,16 +47,16 @@ importers: version: 3.12.2 '@react-router/node': specifier: 8.1.0 - version: 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + version: 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2) '@react-router/serve': specifier: 8.1.0 - version: 8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + version: 8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2) '@remix-run/form-data-parser': specifier: 0.17.4 version: 0.17.4 '@sentry/react-router': specifier: 10.64.0 - version: 10.64.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: 10.64.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) '@tldraw/tldraw': specifier: 3.12.1 version: 3.12.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -86,7 +86,7 @@ importers: version: 4.0.3 i18next: specifier: 26.3.4 - version: 26.3.4(typescript@6.0.3) + version: 26.3.4(typescript@7.0.2) i18next-browser-languagedetector: specifier: 8.2.1 version: 8.2.1 @@ -155,7 +155,7 @@ importers: version: 7.2.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react-i18next: specifier: 17.0.8 - version: 17.0.8(i18next@26.3.4(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) + version: 17.0.8(i18next@26.3.4(typescript@7.0.2))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2) react-router: specifier: 8.1.0 version: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -173,7 +173,7 @@ importers: version: 3.4.1(remix-auth@4.2.0) remix-i18next: specifier: 8.0.0 - version: 8.0.0(i18next@26.3.4(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) + version: 8.0.0(i18next@26.3.4(typescript@7.0.2))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) slugify: specifier: 1.6.9 version: 1.6.9 @@ -201,7 +201,7 @@ importers: version: 1.61.1 '@react-router/dev': specifier: 8.1.0 - version: 8.1.0(@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.1.3(@types/node@26.1.1)(jiti@2.7.0)(yaml@2.9.0)) + version: 8.1.0(@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2)(vite@8.1.3(@types/node@26.1.1)(jiti@2.7.0)(yaml@2.9.0)) '@types/better-sqlite3': specifier: 7.6.13 version: 7.6.13 @@ -245,8 +245,8 @@ importers: specifier: 15.8.2 version: 15.8.2 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 vite: specifier: 8.1.3 version: 8.1.3(@types/node@26.1.1)(jiti@2.7.0)(yaml@2.9.0) @@ -2676,6 +2676,126 @@ packages: '@types/web-push@3.6.4': resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@use-gesture/core@10.3.1': resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} @@ -4370,9 +4490,9 @@ packages: types-ramda@0.31.0: resolution: {integrity: sha512-vaoC35CRC3xvL8Z6HkshDbi6KWM1ezK0LHN0YyxXWUn9HKzBNg/T3xSGlJZjCYspnOD3jE7bcizsp0bUXZDxnQ==} - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true uc.micro@2.1.0: @@ -5801,7 +5921,7 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@react-router/dev@8.1.0(@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.1.3(@types/node@26.1.1)(jiti@2.7.0)(yaml@2.9.0))': + '@react-router/dev@8.1.0(@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2)(vite@8.1.3(@types/node@26.1.1)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/generator': 7.29.7 @@ -5810,7 +5930,7 @@ snapshots: '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 - '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2) '@remix-run/node-fetch-server': 0.13.3 babel-dead-code-elimination: 1.0.12 chokidar: 5.0.0 @@ -5829,34 +5949,34 @@ snapshots: react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) semver: 7.8.4 tinyglobby: 0.2.17 - valibot: 1.4.1(typescript@6.0.3) + valibot: 1.4.1(typescript@7.0.2) vite: 8.1.3(@types/node@26.1.1)(jiti@2.7.0)(yaml@2.9.0) optionalDependencies: - '@react-router/serve': 8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) - typescript: 6.0.3 + '@react-router/serve': 8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2) + typescript: 7.0.2 transitivePeerDependencies: - babel-plugin-macros - supports-color - '@react-router/express@8.1.0(express@5.2.1)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': + '@react-router/express@8.1.0(express@5.2.1)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2)': dependencies: - '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2) express: 5.2.1 react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 - '@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': + '@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2)': dependencies: '@remix-run/node-fetch-server': 0.13.3 react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 - '@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': + '@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2)': dependencies: - '@react-router/express': 8.1.0(express@5.2.1)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) - '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@react-router/express': 8.1.0(express@5.2.1)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2) + '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2) '@remix-run/node-fetch-server': 0.13.3 compression: 1.8.1 express: 5.2.1 @@ -6058,11 +6178,11 @@ snapshots: '@sentry/conventions': 0.15.1 '@sentry/core': 10.64.0 - '@sentry/react-router@10.64.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': + '@sentry/react-router@10.64.0(@opentelemetry/core@2.9.0(@opentelemetry/api@1.9.1))(@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/instrumentation': 0.220.0(@opentelemetry/api@1.9.1) - '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@7.0.2) '@sentry/browser': 10.64.0 '@sentry/cli': 2.58.6 '@sentry/conventions': 0.15.1 @@ -7294,6 +7414,66 @@ snapshots: dependencies: '@types/node': 26.1.1 + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + '@use-gesture/core@10.3.1': {} '@use-gesture/react@10.3.1(react@19.2.7)': @@ -7930,9 +8110,9 @@ snapshots: picomatch: 4.0.4 yargs: 17.7.2 - i18next@26.3.4(typescript@6.0.3): + i18next@26.3.4(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 iconv-lite@0.7.2: dependencies: @@ -8604,16 +8784,16 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - react-i18next@17.0.8(i18next@26.3.4(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3): + react-i18next@17.0.8(i18next@26.3.4(typescript@7.0.2))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@7.0.2): dependencies: '@babel/runtime': 7.29.2 html-parse-stringify: 3.0.1 - i18next: 26.3.4(typescript@6.0.3) + i18next: 26.3.4(typescript@7.0.2) react: 19.2.7 use-sync-external-store: 1.6.0(react@19.2.7) optionalDependencies: react-dom: 19.2.7(react@19.2.7) - typescript: 6.0.3 + typescript: 7.0.2 react-is@16.13.1: {} @@ -8690,9 +8870,9 @@ snapshots: remix-auth@4.2.0: {} - remix-i18next@8.0.0(i18next@26.3.4(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)): + remix-i18next@8.0.0(i18next@26.3.4(typescript@7.0.2))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)): dependencies: - i18next: 26.3.4(typescript@6.0.3) + i18next: 26.3.4(typescript@7.0.2) react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) require-directory@2.1.1: {} @@ -9021,7 +9201,28 @@ snapshots: dependencies: ts-toolbelt: 9.6.0 - typescript@6.0.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 uc.micro@2.1.0: {} @@ -9060,9 +9261,9 @@ snapshots: util-deprecate@1.0.2: {} - valibot@1.4.1(typescript@6.0.3): + valibot@1.4.1(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 vary@1.1.2: {} diff --git a/scripts/setup.ts b/scripts/setup.ts index 7ead2456d..d4896c12d 100644 --- a/scripts/setup.ts +++ b/scripts/setup.ts @@ -1,4 +1,3 @@ -import { seed } from "~/db/seed"; import { db } from "~/db/sql"; import { logger } from "~/utils/logger"; import { seedImages } from "./seed-images"; @@ -10,6 +9,9 @@ async function main() { if (dbEmpty) { logger.info("🌱 Seeding database..."); try { + // Dynamically imported so we skip transforming the large + // seed import graph if the database is already seeded + const { seed } = await import("~/db/seed"); await seed(); logger.info("Database seeded successfully"); } catch (err) { diff --git a/vite.config.ts b/vite.config.ts index 7e3b625cf..c970e7952 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,9 +5,14 @@ import babel from "vite-plugin-babel"; export default defineConfig((config) => { const env = loadEnv(config.mode, process.cwd(), ""); + const isBuild = config.command === "build"; return { server: { port: Number(env.PORT) || 5173, + warmup: { + clientFiles: ["./app/entry.client.tsx", "./app/root.tsx"], + ssrFiles: ["./app/entry.server.tsx"], + }, }, ssr: { noExternal: ["react-charts", "react-use"], @@ -33,25 +38,31 @@ export default defineConfig((config) => { }, }, reactRouter(), - babel({ - include: /\.[jt]sx?$/, - babelConfig: { - presets: ["@babel/preset-typescript"], - plugins: [["babel-plugin-react-compiler", {}]], - }, - }), - sentryReactRouter( - { - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - telemetry: false, - unstable_sentryVitePluginOptions: { - applicationKey: "sendou-ink", - }, - }, - config, - ), + // React Compiler and Sentry are skipped in dev where their per-module + // transform cost outweighs their value. + ...(isBuild + ? [ + babel({ + include: /\.[jt]sx?$/, + babelConfig: { + presets: ["@babel/preset-typescript"], + plugins: [["babel-plugin-react-compiler", {}]], + }, + }), + sentryReactRouter( + { + org: process.env.SENTRY_ORG, + project: process.env.SENTRY_PROJECT, + authToken: process.env.SENTRY_AUTH_TOKEN, + telemetry: false, + unstable_sentryVitePluginOptions: { + applicationKey: "sendou-ink", + }, + }, + config, + ), + ] + : []), ], test: { @@ -73,6 +84,58 @@ export default defineConfig((config) => { }, optimizeDeps: { exclude: ["@sentry/react-router"], + // Dependencies which are only imported by specific route modules. + // Pre-bundling them at startup avoids mid-session re-optimization + // and full page reloads on first navigations. + include: [ + "@date-fns/tz", + "@dnd-kit/core", + "@dnd-kit/modifiers", + "@dnd-kit/sortable", + "@dnd-kit/utilities", + "@epic-web/cachified", + "@internationalized/date", + "@remix-run/form-data-parser", + "@tldraw/tldraw", + "@zumer/snapdom", + "better-sqlite3", + "chart.js", + "compressorjs", + "date-fns/locale/da", + "date-fns/locale/de", + "date-fns/locale/en-US", + "date-fns/locale/es", + "date-fns/locale/fr", + "date-fns/locale/fr-CA", + "date-fns/locale/he", + "date-fns/locale/it", + "date-fns/locale/ja", + "date-fns/locale/ko", + "date-fns/locale/nl", + "date-fns/locale/pl", + "date-fns/locale/pt-BR", + "date-fns/locale/ru", + "date-fns/locale/zh-CN", + "edmonds-blossom-fixed", + "i18next-browser-languagedetector", + "i18next-http-backend", + "jsoncrush", + "kysely", + "kysely/helpers/sqlite", + "markdown-to-jsx", + "neverthrow", + "openskill", + "partysocket", + "qrcode.react", + "react-chartjs-2", + "react-flip-toolkit", + "remeda", + "remix-auth", + "remix-auth-oauth2", + "remix-i18next", + "sql-formatter", + "swr/immutable", + ], }, }; });