From dc93cf4c59fc03879857f20750b8651b59b62e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Mon, 19 Jan 2026 12:46:34 +0100 Subject: [PATCH] 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 --- nx.json | 4 +- package.json | 1 + .../browser-extension/tsconfig.json | 3 +- packages/twenty-emails/tsconfig.json | 4 +- packages/twenty-front/tsconfig.build.json | 3 +- packages/twenty-server/project.json | 15 +++- .../imap-message-text-extractor.service.ts | 6 +- packages/twenty-server/src/utils/image.ts | 4 +- yarn.lock | 82 +++++++++++++++++++ 9 files changed, 108 insertions(+), 14 deletions(-) diff --git a/nx.json b/nx.json index 7507b03c82..f584a06908 100644 --- a/nx.json +++ b/nx.json @@ -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"] diff --git a/package.json b/package.json index c531c7a285..adf5e2b661 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/twenty-apps/community/linkedin-browser-extension/browser-extension/tsconfig.json b/packages/twenty-apps/community/linkedin-browser-extension/browser-extension/tsconfig.json index bc8b6bac42..f2d3e657ab 100755 --- a/packages/twenty-apps/community/linkedin-browser-extension/browser-extension/tsconfig.json +++ b/packages/twenty-apps/community/linkedin-browser-extension/browser-extension/tsconfig.json @@ -3,9 +3,8 @@ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "@emotion/react", - "baseUrl": "src", "paths": { - "@/*": ["*"] + "@/*": ["./src/*"] } } } diff --git a/packages/twenty-emails/tsconfig.json b/packages/twenty-emails/tsconfig.json index 90833a0260..02f8335547 100644 --- a/packages/twenty-emails/tsconfig.json +++ b/packages/twenty-emails/tsconfig.json @@ -7,9 +7,9 @@ "allowSyntheticDefaultImports": true, "strict": true, "types": ["vite/client"], - "baseUrl": ".", "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "src/*": ["./src/*"] } }, "files": [], diff --git a/packages/twenty-front/tsconfig.build.json b/packages/twenty-front/tsconfig.build.json index addcf78dfe..c384b58be6 100644 --- a/packages/twenty-front/tsconfig.build.json +++ b/packages/twenty-front/tsconfig.build.json @@ -1,8 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "types": ["node"], - "baseUrl": "." + "types": ["node"] }, "exclude": [ "**/__mocks__/**/*", diff --git a/packages/twenty-server/project.json b/packages/twenty-server/project.json index 6468333df6..600f5defa8 100644 --- a/packages/twenty-server/project.json +++ b/packages/twenty-server/project.json @@ -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, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts index 4342c57550..316adf4b2f 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts @@ -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; constructor() { this.jsdomInstance = new JSDOM(''); - this.purify = DOMPurify(this.jsdomInstance.window); + this.purify = createDOMPurify(this.jsdomInstance.window); } extractTextWithoutReplyQuotations(parsed: ParsedEmail): string { diff --git a/packages/twenty-server/src/utils/image.ts b/packages/twenty-server/src/utils/image.ts index 36da75c005..6941b10c3d 100644 --- a/packages/twenty-server/src/utils/image.ts +++ b/packages/twenty-server/src/utils/image.ts @@ -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 => { if (!url || typeof url !== 'string' || url.trim().length === 0) { throw new Error('Invalid URL provided: URL must be a non-empty string'); diff --git a/yarn.lock b/yarn.lock index 49ca4ccfe1..da98a9c4f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"