Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"recommendations": [
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint"
"dbaeumer.vscode-eslint",
"typescriptteam.native-preview"
]
}
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"files.eol": "\n",
"postman.settings.dotenv-detection-notification-visibility": false,
// Use the native TypeScript compiler (tsgo, @typescript/native-preview) as the
// editor language server. Requires the "TypeScript (Native Preview)" extension.
// Every typecheck target now runs tsgo, so the whole workspace — incl. apps/backend
// (node16) — is tsgo-compatible.
"typescript.experimental.useTsgo": true,
}
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Standard Nest module layout (`words`, `user`, `auth`, `supabase`, `config`, `com

Env is validated at startup (`src/config/env.validation.ts`); missing vars exit the process with a readable message instead of failing on the first request. See `apps/backend/README.md`.

Database types are generated, not hand-written: `generate-types` in `apps/backend` regenerates `database.types.ts` and copies it into `packages/api`. Do not edit those files, and note `lint` runs with `--fix` — the generated types are eslint-ignored so it cannot reformat them.
Database types are generated, not hand-written: `generate-types` in `apps/backend` regenerates `database.types.ts` and copies it into `packages/api`. Do not edit those files (they are eslint-ignored, so `lint:fix` cannot reformat them). Backend `lint` is check-only; use `lint:fix` to autofix — same split as every other package.

**The API contract is generated too.** Backend DTOs → Swagger → `packages/api/src/schemas/openapi.json` → `api.ts` → SDK → `apps/web`. After changing a DTO or a handler's return type, run `pnpm --filter app-backend generate:api-schemas` (needs a valid root `.env`) and commit the regenerated schemas, otherwise `apps/web` typechecks against a stale contract.

Expand Down
5 changes: 3 additions & 2 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
"start": "nest start",
"start:dev": "nest start --watch",
"start:prod": "node dist/main.js",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"typecheck": "tsc --noEmit",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint:fix": "npm run lint -- --fix",
"typecheck": "tsgo --noEmit",
"generate:api-schemas": "ts-node --project tsconfig.json scripts/generate-api-schemas.ts",
"generate-types": "ts-node ./scripts/generate-db-types.ts",
"test:unit": "vitest",
Expand Down
4 changes: 2 additions & 2 deletions apps/backend/test/nest-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export const supabaseStub = {
export const createTestApp = async (
supabase: unknown = supabaseStub,
): Promise<INestApplication> => {
const { AppModule } = await import("../src/app.module");
const { SupabaseService } = await import("../src/supabase/supabase.service");
const { AppModule } = await import("../src/app.module.js");
const { SupabaseService } = await import("../src/supabase/supabase.service.js");

const moduleRef = await Test.createTestingModule({ imports: [AppModule] })
.overrideProvider(SupabaseService)
Expand Down
7 changes: 3 additions & 4 deletions apps/backend/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"module": "node16",
"moduleResolution": "node16",
"target": "ES2021",
"lib": ["ES2021"],
"declaration": true,
Expand All @@ -14,11 +14,10 @@
"strictNullChecks": true,
"noImplicitAny": true,
"strictBindCallApply": true,
"forceConsistentCasingInFileNames": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": false,
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"resolveJsonModule": true,
"types": ["node"],
Expand Down
2 changes: 1 addition & 1 deletion apps/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"build": "storybook build -o dist-storybook",
"storybook": "storybook dev --no-open -p 6006",
"typecheck": "tsc --noEmit"
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@heroui/system": "^2.4.23",
Expand Down
6 changes: 2 additions & 4 deletions apps/storybook/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
"include": [
".",
".storybook/**/*",
],
"compilerOptions": {
"baseUrl": ".",
}
"../../types/assets.d.ts",
]
}
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"start:prod": "cross-env NODE_ENV=production tsx src/server/main.ts",
"stylelint": "stylelint \"**/*.{css,scss}\" --config ../../stylelint.config.mjs",
"stylelint:fix": "pnpm run stylelint -- --fix",
"typecheck": "tsc --build ./tsconfig.json",
"typecheck": "tsgo --build ./tsconfig.json",
"knip": "knip --config ./knip.config.ts"
},
"dependencies": {
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/locales/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"**/*.json"
],
"compilerOptions": {
"baseUrl": ".",
"resolveJsonModule": true,
}
}
1 change: 0 additions & 1 deletion apps/web/src/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
}
],
"compilerOptions": {
"baseUrl": ".",
// TODO: Fix relative server paths
// "paths": {
// "@/*": ["./*"],
Expand Down
5 changes: 1 addition & 4 deletions apps/web/src/shared/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@
],
"include": [
"."
],
"compilerOptions": {
"baseUrl": ".",
}
]
}
4 changes: 1 addition & 3 deletions apps/web/src/ui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
],
"include": [
".",
"typings.d.ts",
"../../../../types/assets.d.ts",
"../vite-env.d.ts"
],
"exclude": [
"**/*.test.*",
"**/*.test.*",
"**/*.stories.*",
"**/__stories__",
Expand All @@ -23,7 +22,6 @@
}
],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"./*"
Expand Down
4 changes: 0 additions & 4 deletions apps/web/src/ui/typings.d.ts

This file was deleted.

1 change: 1 addition & 0 deletions apps/web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"compilerOptions": {
"rootDir": ".",
"outDir": "./.typecheck",
"noEmit": true,
},
"include": [
"*.config.ts",
Expand Down
74 changes: 74 additions & 0 deletions docs/tsgo-benchmark.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Typecheck: `tsc` → `tsgo` migration (`@typescript/native-preview`)

Type checking was moved from classic `tsc` to the native `tsgo` compiler
(`@typescript/native-preview`, TypeScript 7 preview). Speedup measurements below.

## Results (hyperfine / Measure-Command, Windows 11, warm FS cache)

| Target | tsc | tsgo | Speedup |
|---|---|---|---|
| `apps/web` (`--build`, project references) | 4.13 s | 0.86 s | **4.77×** |
| `packages/uikit` (`--noEmit`) | 2.37 s | 0.39 s | **6.07×** |
| `packages/class-names` (`--noEmit`, small) | 0.83 s | 0.21 s | **3.86×** |
| **All 19 migrated targets** (sum, without nx) | **22.35 s** | **5.06 s** | **4.42×** |

A full type-check pass over the migrated packages dropped from ~22 s to ~5 s
(~17 s saved). Smaller packages gain less — there the fixed process-startup cost
dominates.

Methodology: `hyperfine -w 1 -r 5` for single targets (for `apps/web` — `-r 3`,
clearing `.typecheck` before each run); the aggregate is the sum of pure compiler
time across all 19 targets, averaged over 2 runs after warmup, without the nx
wrapper. Both compilers run against the same tsconfigs.

## What changed

- **All 20** typecheck scripts switched to `tsgo` (`tsc --noEmit` → `tsgo --noEmit`;
`apps/web`: `tsc --build` → `tsgo --build`).
- `@typescript/native-preview` added to the root devDependencies. `typescript`/`tsc`
is kept — it remains the runtime compiler for `tsx`, `nest build`, `vitest`,
`ts-node`, `vite`.
- **IDE:** the native tsgo language server is enabled — `.vscode/settings.json` →
`typescript.experimental.useTsgo: true`, and the `typescriptteam.native-preview`
extension is added to the recommendations (`.vscode/extensions.json`). Works in
both VS Code and Cursor.

### Config changes required by TypeScript 7

tsgo is TS7, which removed a few options that the project actually used:

- **`baseUrl` removed.** Dropped from every tsgo target (it was redundant: `paths`
work without it and resolve relative to the tsconfig directory). Verified green on
`tsc 5.7`.
- **Style side-effect imports** (`import "./x.scss"`) now require an ambient module
declaration (error TS2882), which `tsc` silently tolerated. Added a shared
`types/assets.d.ts` (`declare module "*.scss" | "*.css"`), wired via `include` into
the packages whose programs compile UI sources directly or transitively through
`@languages-learner/uikit` (`uikit`, `data-source`, `storybook`, `apps/web/src/ui`).

### `apps/backend` modernized for TS7

`apps/backend` used `moduleResolution: "node"`, which was removed in TS7. It was
switched to `module: node16` + `moduleResolution: node16`. The only source change:
dynamic `import()` calls in `test/nest-app.ts` require the `.js` extension (ESM
resolution): `import("../src/app.module")` → `import("../src/app.module.js")`.
Verified nothing broke:

- `tsgo --noEmit` and `tsc --noEmit` — green;
- 64 unit/e2e tests (`vitest`/`swc`) pass — the runtime resolves `.js` → `.ts`;
- `nest build` still emits CommonJS (`module: node16` for a CJS package without
`"type": "module"` yields `require`/`exports`).

### Incidental cleanups

- `apps/web/tsconfig.json`: added `noEmit: true` — the solution project no longer
emits config-file JS into `.typecheck/`.
- `apps/web/src/ui/tsconfig.json`: removed the duplicated `"**/*.test.*"` in `exclude`.
- `apps/backend/tsconfig.json`: `forceConsistentCasingInFileNames` aligned to `true`
(consistent with the base config).

## Next (out of scope for this migration)

- **Nx cache for `typecheck`** (`targetDefaults` in `nx.json`) — not configured yet,
so type checking is recomputed for every project on each run. Orthogonal to tsgo;
would let unchanged projects be skipped. Planned as a separate phase.
50 changes: 48 additions & 2 deletions nx.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json"
}
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"sharedGlobals": [
"{workspaceRoot}/pnpm-lock.yaml",
"{workspaceRoot}/tsconfig.base.json",
"{workspaceRoot}/tsconfig.base.composite.json",
"{workspaceRoot}/types/**/*"
]
},
"targetDefaults": {
"typecheck": {
"cache": true,
"inputs": ["default", "^default"],
"outputs": []
},
"lint": {
"cache": true,
"inputs": [
"default",
"^default",
"{workspaceRoot}/eslint.config.mjs",
"{workspaceRoot}/prettier.config.mjs"
],
"outputs": []
},
"test:unit:ci": {
"cache": true,
"inputs": [
"default",
"^default",
"{workspaceRoot}/vitest.config.ts",
"{workspaceRoot}/vitest.shims.d.ts"
],
"outputs": []
},
"stylelint": {
"cache": true,
"inputs": ["default", "{workspaceRoot}/stylelint.config.mjs"],
"outputs": []
},
"circular-deps": {
"cache": true,
"inputs": ["default"],
"outputs": []
}
}
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@
"devDependencies": {
"@formatjs/cli": "^6.7.1",
"@languages-learner/har-sanitizer": "workspace:*",
"@playwright/experimental-ct-react": "1.52.0",
"@tanstack/eslint-config": "^0.3.3",
"@types/node": "^22.15.29",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.2.3",
"@typescript/native-preview": "7.0.0-dev.20260707.2",
"@vitest/browser": "^3.1.4",
"cross-env": "^7.0.3",
"eslint": "^9.39.1",
Expand Down Expand Up @@ -68,8 +70,7 @@
"ts-node": "11.0.0-beta.1",
"typescript": "~5.7.2",
"typescript-eslint": "^8.26.1",
"vitest": "^3.1.4",
"@playwright/experimental-ct-react": "1.52.0"
"vitest": "^3.1.4"
},
"pnpm": {
"overrides": {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"lint": "eslint --concurrency=auto .",
"lint:fix": "npm run lint -- --fix",
"typecheck": "tsc --noEmit"
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@supabase/supabase-js": "2.49.4"
Expand Down
5 changes: 1 addition & 4 deletions packages/api/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@
],
"include": [
"./src",
],
"compilerOptions": {
"baseUrl": ".",
}
]
}
2 changes: 1 addition & 1 deletion packages/app-core-tests-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"lint": "eslint --concurrency=auto .",
"lint:fix": "npm run lint -- --fix",
"typecheck": "tsc --noEmit"
"typecheck": "tsgo --noEmit"
},
"devDependencies": {
"@playwright/test": "1.52.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/app-integration-tests-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"lint": "eslint --concurrency=auto .",
"lint:fix": "npm run lint -- --fix",
"typecheck": "tsc --noEmit"
"typecheck": "tsgo --noEmit"
},
"devDependencies": {
"@languages-learner/playwright-utils": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion packages/class-names/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lint:fix": "npm run lint -- --fix",
"test:unit": "vitest",
"test:unit:ci": "vitest --run",
"typecheck": "tsc --noEmit"
"typecheck": "tsgo --noEmit"
},
"devDependencies": {
"@bem-react/classname": "^1.7.0",
Expand Down
5 changes: 1 addition & 4 deletions packages/class-names/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@
],
"include": [
".",
],
"compilerOptions": {
"baseUrl": ".",
}
]
}
2 changes: 1 addition & 1 deletion packages/component-core-tests-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"lint": "eslint --concurrency=auto .",
"lint:fix": "npm run lint -- --fix",
"typecheck": "tsc --noEmit"
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@playwright/experimental-ct-react": "1.52.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/data-source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"lint": "eslint --concurrency=auto .",
"lint:fix": "npm run lint -- --fix",
"typecheck": "tsc --noEmit"
"typecheck": "tsgo --noEmit"
},
"dependencies": {
"@gravity-ui/data-source": "^0.6.1",
Expand Down
Loading
Loading