feat: add TypeScript Go (tsgo) for faster type checking (#17211)

## Summary

- Add `@typescript/native-preview` (tsgo) for dramatically faster type
checking on frontend projects
- Configure tsgo as default for frontend projects (twenty-front,
twenty-ui, twenty-shared, etc.)
- Keep tsc for twenty-server (faster for NestJS decorator-heavy code)
- Fix type imports for tsgo compatibility (DOMPurify, AxiosInstance)
- Remove deprecated `baseUrl` from tsconfigs where safe

## Performance Results

| Project | tsgo | tsc -b | Speedup |
|---------|------|--------|---------|
| **twenty-front** | 1.4s | 60.7s | **43x faster** |
| **twenty-server** | 2m42s | 1m10s | tsc is faster (decorators) |

tsgo excels at modern React/JSX codebases but struggles with
decorator-heavy NestJS backends, so we use the optimal checker for each.

## Usage

```bash
# Default (tsgo for frontend, tsc for backend)
nx typecheck twenty-front
nx typecheck twenty-server

# Force tsc fallback if needed
nx typecheck twenty-front --configuration=tsc

# Force tsgo on backend (slower, not recommended)
nx typecheck twenty-server --configuration=tsgo
```

## Test plan

- [x] `nx typecheck twenty-front` passes with tsgo
- [x] `nx typecheck twenty-server` passes with tsc
- [x] `nx run-many -t typecheck --exclude=fireflies` passes
- [ ] CI tests pass
This commit is contained in:
Félix Malfait
2026-01-19 12:46:34 +01:00
committed by GitHub
parent 42c6a04be6
commit dc93cf4c59
9 changed files with 108 additions and 14 deletions
+2 -2
View File
@@ -97,11 +97,11 @@
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "tsc -b tsconfig.json --incremental"
"command": "tsgo -p tsconfig.json"
},
"configurations": {
"watch": {
"watch": true
"command": "tsgo -p tsconfig.json --watch"
}
},
"dependsOn": ["^build"]
+1
View File
@@ -135,6 +135,7 @@
"@typescript-eslint/eslint-plugin": "^8.39.0",
"@typescript-eslint/parser": "^8.39.0",
"@typescript-eslint/utils": "^8.39.0",
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
"@vitejs/plugin-react-swc": "3.11.0",
"@vitest/browser-playwright": "^4.0.17",
"@vitest/coverage-istanbul": "^4.0.17",
@@ -3,9 +3,8 @@
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react",
"baseUrl": "src",
"paths": {
"@/*": ["*"]
"@/*": ["./src/*"]
}
}
}
+2 -2
View File
@@ -7,9 +7,9 @@
"allowSyntheticDefaultImports": true,
"strict": true,
"types": ["vite/client"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"src/*": ["./src/*"]
}
},
"files": [],
+1 -2
View File
@@ -1,8 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["node"],
"baseUrl": "."
"types": ["node"]
},
"exclude": [
"**/__mocks__/**/*",
+14 -1
View File
@@ -41,7 +41,20 @@
"updateBuildableProjectDepsInPackageJson": true
}
},
"typecheck": {},
"typecheck": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "{projectRoot}",
"command": "tsc -b tsconfig.json --incremental"
},
"configurations": {
"tsgo": {
"command": "tsgo -p tsconfig.json"
}
},
"dependsOn": ["^build"]
},
"start": {
"executor": "nx:run-commands",
"cache": false,
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import DOMPurify from 'dompurify';
import createDOMPurify from 'dompurify';
import { convert } from 'html-to-text';
import { JSDOM } from 'jsdom';
import * as planer from 'planer';
@@ -10,11 +10,11 @@ import { type Email as ParsedEmail } from 'postal-mime';
@Injectable()
export class ImapMessageTextExtractorService {
private readonly jsdomInstance: JSDOM;
private readonly purify: DOMPurify.DOMPurify;
private readonly purify: ReturnType<typeof createDOMPurify>;
constructor() {
this.jsdomInstance = new JSDOM('');
this.purify = DOMPurify(this.jsdomInstance.window);
this.purify = createDOMPurify(this.jsdomInstance.window);
}
extractTextWithoutReplyQuotations(parsed: ParsedEmail): string {
+2 -2
View File
@@ -1,4 +1,4 @@
import { type Axios, type AxiosError } from 'axios';
import { type AxiosError, type AxiosInstance } from 'axios';
const cropRegex = /([w|h])([0-9]+)/;
@@ -24,7 +24,7 @@ export const getCropSize = (value: ShortCropSize): CropSize | null => {
export const getImageBufferFromUrl = async (
url: string,
axiosInstance: Axios,
axiosInstance: AxiosInstance,
): Promise<Buffer> => {
if (!url || typeof url !== 'string' || url.trim().length === 0) {
throw new Error('Invalid URL provided: URL must be a non-empty string');
+82
View File
@@ -25131,6 +25131,87 @@ __metadata:
languageName: node
linkType: hard
"@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260116.1
resolution: "@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260116.1"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260116.1
resolution: "@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260116.1"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260116.1
resolution: "@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260116.1"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260116.1
resolution: "@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260116.1"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260116.1
resolution: "@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260116.1"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260116.1
resolution: "@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260116.1"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260116.1
resolution: "@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260116.1"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview@npm:^7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260116.1
resolution: "@typescript/native-preview@npm:7.0.0-dev.20260116.1"
dependencies:
"@typescript/native-preview-darwin-arm64": "npm:7.0.0-dev.20260116.1"
"@typescript/native-preview-darwin-x64": "npm:7.0.0-dev.20260116.1"
"@typescript/native-preview-linux-arm": "npm:7.0.0-dev.20260116.1"
"@typescript/native-preview-linux-arm64": "npm:7.0.0-dev.20260116.1"
"@typescript/native-preview-linux-x64": "npm:7.0.0-dev.20260116.1"
"@typescript/native-preview-win32-arm64": "npm:7.0.0-dev.20260116.1"
"@typescript/native-preview-win32-x64": "npm:7.0.0-dev.20260116.1"
dependenciesMeta:
"@typescript/native-preview-darwin-arm64":
optional: true
"@typescript/native-preview-darwin-x64":
optional: true
"@typescript/native-preview-linux-arm":
optional: true
"@typescript/native-preview-linux-arm64":
optional: true
"@typescript/native-preview-linux-x64":
optional: true
"@typescript/native-preview-win32-arm64":
optional: true
"@typescript/native-preview-win32-x64":
optional: true
bin:
tsgo: bin/tsgo.js
checksum: 10c0/1469898de5cccd022e728d1f0f6c3de56e58bd388c2732f6b457083ff0deadd4998c262081e512721b4ac014c276807f4e7c51ae94c9d69bbb6c3c9942702cbf
languageName: node
linkType: hard
"@typescript/vfs@npm:^1.6.1":
version: 1.6.2
resolution: "@typescript/vfs@npm:1.6.2"
@@ -57351,6 +57432,7 @@ __metadata:
"@typescript-eslint/eslint-plugin": "npm:^8.39.0"
"@typescript-eslint/parser": "npm:^8.39.0"
"@typescript-eslint/utils": "npm:^8.39.0"
"@typescript/native-preview": "npm:^7.0.0-dev.20260116.1"
"@vitejs/plugin-react-swc": "npm:3.11.0"
"@vitest/browser-playwright": "npm:^4.0.17"
"@vitest/coverage-istanbul": "npm:^4.0.17"