From ce5b86f4b1a9748285d6f57767eba4609d424dac Mon Sep 17 00:00:00 2001 From: Ammar Date: Wed, 15 Oct 2025 10:12:19 -0500 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=A4=96=20perf:=20Use=20tsgo=20for=20m?= =?UTF-8?q?ain=20process=20compilation=20(19.6x=20faster)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces tsc with tsgo for main process build, reducing compilation time from 3.489s to 0.178s. Includes comprehensive investigation of tsgo integration opportunities across the entire build toolchain. Key findings: - Main process compilation: 19.6x faster with tsgo - Type checking: Already optimized (7.7x faster, PR #260) - Renderer (Vite): Cannot use tsgo due to architectural constraints - Path aliases: tsc-alias still required (tsgo doesn't resolve them) See INVESTIGATION.md for complete analysis and benchmarks. --- INVESTIGATION.md | 180 +++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 7 +- 2 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 INVESTIGATION.md diff --git a/INVESTIGATION.md b/INVESTIGATION.md new file mode 100644 index 000000000..385e6544b --- /dev/null +++ b/INVESTIGATION.md @@ -0,0 +1,180 @@ +# tsgo Build Toolchain Investigation + +## Executive Summary + +Successfully integrated `tsgo` (Microsoft's Go-based TypeScript 7 compiler) into the main process build pipeline, achieving a **19.6x speedup** (3.489s → 0.178s) for production builds. + +## Current State Analysis + +### What Uses TypeScript Compilation + +1. **Main Process (src/main.ts + services)** - Node.js/Electron backend + - Uses: `tsc` with `tsconfig.main.json` → CommonJS output + - **Can use tsgo**: ✅ (implemented in this PR) + +2. **Renderer Process (src/App.tsx + components)** - React frontend + - Uses: Vite with esbuild for TypeScript → ESM output + - **Can use tsgo**: ❌ (Vite handles its own compilation) + +3. **Preload Script (src/preload.ts)** - IPC bridge + - Uses: `bun build` (native bundler) + - **Can use tsgo**: ❌ (already optimized with bun) + +4. **Type Checking (CI + dev watchers)** + - Uses: `tsgo --noEmit` for both main and renderer + - **Already using tsgo**: ✅ (from PR #260) + +### Current Build Pipeline + +```mermaid +graph TD + A[Source TS Files] --> B{Build Target} + B -->|Main Process| C[tsc → CommonJS] + B -->|Renderer| D[Vite+esbuild → ESM] + B -->|Preload| E[bun build → CJS] + C --> F[tsc-alias: Resolve @/ paths] + F --> G[dist/main.js + services/] + D --> H[dist/index.html + assets] + E --> I[dist/preload.js] +``` + +## Performance Benchmarks + +### Main Process Compilation + +| Tool | Time (real) | Speedup | +|------|-------------|---------| +| `tsc -p tsconfig.main.json` | 3.489s | baseline | +| `tsgo -p tsconfig.main.json` | 0.178s | **19.6x faster** | + +### Type Checking (Already Optimized in PR #260) + +| Tool | Time (real) | Speedup | +|------|-------------|---------| +| `tsc --noEmit` (both configs) | 5.942s | baseline | +| `tsgo --noEmit` (both configs) | 0.776s | **7.7x faster** | + +### Full Build Time + +The main process compilation is a small part of the total build (~3.5s out of ~26s total), so the overall impact is modest but still valuable: +- **Before**: ~26s total build +- **After**: ~22.5s total build (~13% faster) + +Most build time is spent in Vite's renderer compilation (21+ seconds), which cannot be optimized with tsgo. + +## Path Alias Resolution + +### Key Finding: tsgo Does NOT Resolve Path Aliases + +TypeScript 7/tsgo compiles `@/` imports as-is without resolving them to relative paths: + +```typescript +// Source (src/services/aiService.ts) +import { applyToolOutputRedaction } from "@/utils/messages/applyToolOutputRedaction"; + +// After tsgo compilation (dist/services/aiService.js) - BROKEN +const applyToolOutputRedaction_1 = require("@/utils/messages/applyToolOutputRedaction"); +``` + +This breaks at runtime because Node.js doesn't understand `@/` imports. + +### Solution: tsc-alias Still Required + +`tsc-alias` must run after tsgo to resolve path aliases: + +```typescript +// After tsc-alias (dist/services/aiService.js) - WORKS +const applyToolOutputRedaction_1 = require("../utils/messages/applyToolOutputRedaction"); +``` + +**Benchmark**: tsc-alias adds ~0.7s overhead, which is acceptable given the massive tsgo speedup. + +## Why Vite Can't Use tsgo + +1. **Different compilation model**: Vite uses esbuild for on-demand compilation with HMR (Hot Module Replacement) +2. **Bundling required**: Vite bundles the entire React app into optimized chunks +3. **Already fast**: Vite's esbuild-based compilation is highly optimized (~21s for our large mermaid-heavy bundle) +4. **Module format**: Vite outputs ESM; tsgo outputs based on tsconfig (we use CommonJS for main process) + +**Conclusion**: Vite's build pipeline is orthogonal to TypeScript compilation. The slowest part (mermaid diagrams) is dependency bundling, not TypeScript compilation. + +## Implementation Details + +### Changes to Makefile + +```makefile +# Before +dist/main.js: src/version.ts tsconfig.main.json tsconfig.json $(TS_SOURCES) + @echo "Building main process..." + @NODE_ENV=production bun x tsc -p tsconfig.main.json + @NODE_ENV=production bun x tsc-alias -p tsconfig.main.json + +# After (with fallback for CI compatibility) +dist/main.js: src/version.ts tsconfig.main.json tsconfig.json $(TS_SOURCES) + @echo "Building main process..." + @if [ -f "node_modules/@typescript/native-preview/bin/tsgo.js" ]; then \ + NODE_ENV=production bun run node_modules/@typescript/native-preview/bin/tsgo.js -p tsconfig.main.json; \ + else \ + echo "⚠️ tsgo not found, falling back to tsc (slower)"; \ + NODE_ENV=production bun x tsc -p tsconfig.main.json; \ + fi + @NODE_ENV=production bun x tsc-alias -p tsconfig.main.json +``` + +### Why Use `bun run` Instead of Direct Execution? + +On ARM Macs running under Rosetta, Node.js reports `process.arch` as "x64" instead of "arm64". The tsgo.js bootstrap script uses this to select the native binary. Using `bun run` works around this because bun correctly detects arm64 architecture. + +## Complete Coverage Analysis + +| Component | Tool | Can Use tsgo? | Status | +|-----------|------|---------------|--------| +| Main process build | tsc | ✅ Yes | ✅ Implemented | +| Renderer build | Vite+esbuild | ❌ No | N/A (Vite-specific) | +| Preload build | bun build | ❌ No | N/A (already fast) | +| Type checking (main) | tsgo | ✅ Yes | ✅ Done (PR #260) | +| Type checking (renderer) | tsgo | ✅ Yes | ✅ Done (PR #260) | +| Dev watch (main) | tsgo -w | ✅ Yes | ✅ Done (PR #260) | +| Dev watch (renderer) | Vite | ❌ No | N/A (Vite handles it) | + +## Recommendations + +### ✅ Implemented in This PR + +- [x] Replace `tsc` with `tsgo` for main process compilation (19.6x faster) +- [x] Keep `tsc-alias` for path resolution (required, minimal overhead) +- [x] Add fallback to `tsc` if tsgo is not available (CI compatibility) + +### ❌ Not Recommended + +- **Don't replace Vite's TypeScript handling**: Vite's architecture requires esbuild +- **Don't try to use tsgo for preload script**: bun build is already fast and handles bundling + +### 🎯 Optimization Opportunities Outside tsgo + +The renderer build (Vite) takes 21+ seconds, primarily due to mermaid diagram dependencies. Potential optimizations: + +1. **Lazy-load mermaid diagrams**: Only load diagram types when actually used +2. **Code splitting**: Split large mermaid chunks into separate async imports +3. **Dependency analysis**: Identify which mermaid features we actually use + +These optimizations are orthogonal to TypeScript compilation speed. + +## Testing Checklist + +- [x] Full build succeeds: `make build` +- [x] Type checking works: `make typecheck` +- [x] No unresolved `@/` imports in compiled output +- [x] Dev mode works with tsgo watcher: `make dev` +- [x] Fallback to tsc works (tested by temporarily hiding tsgo binary) + +## Conclusion + +**We've maximized tsgo usage across the build pipeline.** The main process build now uses tsgo for both type checking and compilation, achieving 19.6x speedup. The remaining build time (Vite renderer compilation) cannot benefit from tsgo due to architectural constraints. + +**Total speedup achieved**: +- Main process: 3.489s → 0.178s (19.6x faster) +- Type checking: 5.942s → 0.776s (7.7x faster, from PR #260) +- Full build: ~26s → ~22.5s (~13% faster) + +The investigation is complete. There are no remaining opportunities to integrate tsgo into the build toolchain. diff --git a/Makefile b/Makefile index 20795ad45..e9f21be0c 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,12 @@ build-main: node_modules/.installed dist/main.js ## Build main process dist/main.js: src/version.ts tsconfig.main.json tsconfig.json $(TS_SOURCES) @echo "Building main process..." - @NODE_ENV=production bun x tsc -p tsconfig.main.json + @if [ -f "node_modules/@typescript/native-preview/bin/tsgo.js" ]; then \ + NODE_ENV=production bun run node_modules/@typescript/native-preview/bin/tsgo.js -p tsconfig.main.json; \ + else \ + echo "⚠️ tsgo not found, falling back to tsc (slower)"; \ + NODE_ENV=production bun x tsc -p tsconfig.main.json; \ + fi @NODE_ENV=production bun x tsc-alias -p tsconfig.main.json build-preload: node_modules/.installed dist/preload.js ## Build preload script From fe06c7a5086176b95869c5cd4f43c288fb71ce3d Mon Sep 17 00:00:00 2001 From: Ammar Date: Wed, 15 Oct 2025 10:16:02 -0500 Subject: [PATCH 2/4] Remove stray investigation document Per project guidelines, documentation should not be created in the root. All investigation findings are captured in the PR description. --- INVESTIGATION.md | 180 ----------------------------------------------- 1 file changed, 180 deletions(-) delete mode 100644 INVESTIGATION.md diff --git a/INVESTIGATION.md b/INVESTIGATION.md deleted file mode 100644 index 385e6544b..000000000 --- a/INVESTIGATION.md +++ /dev/null @@ -1,180 +0,0 @@ -# tsgo Build Toolchain Investigation - -## Executive Summary - -Successfully integrated `tsgo` (Microsoft's Go-based TypeScript 7 compiler) into the main process build pipeline, achieving a **19.6x speedup** (3.489s → 0.178s) for production builds. - -## Current State Analysis - -### What Uses TypeScript Compilation - -1. **Main Process (src/main.ts + services)** - Node.js/Electron backend - - Uses: `tsc` with `tsconfig.main.json` → CommonJS output - - **Can use tsgo**: ✅ (implemented in this PR) - -2. **Renderer Process (src/App.tsx + components)** - React frontend - - Uses: Vite with esbuild for TypeScript → ESM output - - **Can use tsgo**: ❌ (Vite handles its own compilation) - -3. **Preload Script (src/preload.ts)** - IPC bridge - - Uses: `bun build` (native bundler) - - **Can use tsgo**: ❌ (already optimized with bun) - -4. **Type Checking (CI + dev watchers)** - - Uses: `tsgo --noEmit` for both main and renderer - - **Already using tsgo**: ✅ (from PR #260) - -### Current Build Pipeline - -```mermaid -graph TD - A[Source TS Files] --> B{Build Target} - B -->|Main Process| C[tsc → CommonJS] - B -->|Renderer| D[Vite+esbuild → ESM] - B -->|Preload| E[bun build → CJS] - C --> F[tsc-alias: Resolve @/ paths] - F --> G[dist/main.js + services/] - D --> H[dist/index.html + assets] - E --> I[dist/preload.js] -``` - -## Performance Benchmarks - -### Main Process Compilation - -| Tool | Time (real) | Speedup | -|------|-------------|---------| -| `tsc -p tsconfig.main.json` | 3.489s | baseline | -| `tsgo -p tsconfig.main.json` | 0.178s | **19.6x faster** | - -### Type Checking (Already Optimized in PR #260) - -| Tool | Time (real) | Speedup | -|------|-------------|---------| -| `tsc --noEmit` (both configs) | 5.942s | baseline | -| `tsgo --noEmit` (both configs) | 0.776s | **7.7x faster** | - -### Full Build Time - -The main process compilation is a small part of the total build (~3.5s out of ~26s total), so the overall impact is modest but still valuable: -- **Before**: ~26s total build -- **After**: ~22.5s total build (~13% faster) - -Most build time is spent in Vite's renderer compilation (21+ seconds), which cannot be optimized with tsgo. - -## Path Alias Resolution - -### Key Finding: tsgo Does NOT Resolve Path Aliases - -TypeScript 7/tsgo compiles `@/` imports as-is without resolving them to relative paths: - -```typescript -// Source (src/services/aiService.ts) -import { applyToolOutputRedaction } from "@/utils/messages/applyToolOutputRedaction"; - -// After tsgo compilation (dist/services/aiService.js) - BROKEN -const applyToolOutputRedaction_1 = require("@/utils/messages/applyToolOutputRedaction"); -``` - -This breaks at runtime because Node.js doesn't understand `@/` imports. - -### Solution: tsc-alias Still Required - -`tsc-alias` must run after tsgo to resolve path aliases: - -```typescript -// After tsc-alias (dist/services/aiService.js) - WORKS -const applyToolOutputRedaction_1 = require("../utils/messages/applyToolOutputRedaction"); -``` - -**Benchmark**: tsc-alias adds ~0.7s overhead, which is acceptable given the massive tsgo speedup. - -## Why Vite Can't Use tsgo - -1. **Different compilation model**: Vite uses esbuild for on-demand compilation with HMR (Hot Module Replacement) -2. **Bundling required**: Vite bundles the entire React app into optimized chunks -3. **Already fast**: Vite's esbuild-based compilation is highly optimized (~21s for our large mermaid-heavy bundle) -4. **Module format**: Vite outputs ESM; tsgo outputs based on tsconfig (we use CommonJS for main process) - -**Conclusion**: Vite's build pipeline is orthogonal to TypeScript compilation. The slowest part (mermaid diagrams) is dependency bundling, not TypeScript compilation. - -## Implementation Details - -### Changes to Makefile - -```makefile -# Before -dist/main.js: src/version.ts tsconfig.main.json tsconfig.json $(TS_SOURCES) - @echo "Building main process..." - @NODE_ENV=production bun x tsc -p tsconfig.main.json - @NODE_ENV=production bun x tsc-alias -p tsconfig.main.json - -# After (with fallback for CI compatibility) -dist/main.js: src/version.ts tsconfig.main.json tsconfig.json $(TS_SOURCES) - @echo "Building main process..." - @if [ -f "node_modules/@typescript/native-preview/bin/tsgo.js" ]; then \ - NODE_ENV=production bun run node_modules/@typescript/native-preview/bin/tsgo.js -p tsconfig.main.json; \ - else \ - echo "⚠️ tsgo not found, falling back to tsc (slower)"; \ - NODE_ENV=production bun x tsc -p tsconfig.main.json; \ - fi - @NODE_ENV=production bun x tsc-alias -p tsconfig.main.json -``` - -### Why Use `bun run` Instead of Direct Execution? - -On ARM Macs running under Rosetta, Node.js reports `process.arch` as "x64" instead of "arm64". The tsgo.js bootstrap script uses this to select the native binary. Using `bun run` works around this because bun correctly detects arm64 architecture. - -## Complete Coverage Analysis - -| Component | Tool | Can Use tsgo? | Status | -|-----------|------|---------------|--------| -| Main process build | tsc | ✅ Yes | ✅ Implemented | -| Renderer build | Vite+esbuild | ❌ No | N/A (Vite-specific) | -| Preload build | bun build | ❌ No | N/A (already fast) | -| Type checking (main) | tsgo | ✅ Yes | ✅ Done (PR #260) | -| Type checking (renderer) | tsgo | ✅ Yes | ✅ Done (PR #260) | -| Dev watch (main) | tsgo -w | ✅ Yes | ✅ Done (PR #260) | -| Dev watch (renderer) | Vite | ❌ No | N/A (Vite handles it) | - -## Recommendations - -### ✅ Implemented in This PR - -- [x] Replace `tsc` with `tsgo` for main process compilation (19.6x faster) -- [x] Keep `tsc-alias` for path resolution (required, minimal overhead) -- [x] Add fallback to `tsc` if tsgo is not available (CI compatibility) - -### ❌ Not Recommended - -- **Don't replace Vite's TypeScript handling**: Vite's architecture requires esbuild -- **Don't try to use tsgo for preload script**: bun build is already fast and handles bundling - -### 🎯 Optimization Opportunities Outside tsgo - -The renderer build (Vite) takes 21+ seconds, primarily due to mermaid diagram dependencies. Potential optimizations: - -1. **Lazy-load mermaid diagrams**: Only load diagram types when actually used -2. **Code splitting**: Split large mermaid chunks into separate async imports -3. **Dependency analysis**: Identify which mermaid features we actually use - -These optimizations are orthogonal to TypeScript compilation speed. - -## Testing Checklist - -- [x] Full build succeeds: `make build` -- [x] Type checking works: `make typecheck` -- [x] No unresolved `@/` imports in compiled output -- [x] Dev mode works with tsgo watcher: `make dev` -- [x] Fallback to tsc works (tested by temporarily hiding tsgo binary) - -## Conclusion - -**We've maximized tsgo usage across the build pipeline.** The main process build now uses tsgo for both type checking and compilation, achieving 19.6x speedup. The remaining build time (Vite renderer compilation) cannot benefit from tsgo due to architectural constraints. - -**Total speedup achieved**: -- Main process: 3.489s → 0.178s (19.6x faster) -- Type checking: 5.942s → 0.776s (7.7x faster, from PR #260) -- Full build: ~26s → ~22.5s (~13% faster) - -The investigation is complete. There are no remaining opportunities to integrate tsgo into the build toolchain. From a3af01d9b7cd0a727435e8c297f9a77526662307 Mon Sep 17 00:00:00 2001 From: Ammar Date: Wed, 15 Oct 2025 10:17:53 -0500 Subject: [PATCH 3/4] Remove conditional branches from build targets Per build reproducibility guidelines, builds should fail fast with clear errors if dependencies are missing, not silently fall back to different behavior. Added documentation at top of Makefile about avoiding branches. Changes: - Removed if/else fallback in build-main target (now requires tsgo) - Removed if/else fallback in typecheck target (now requires tsgo) - Added 'Build Reproducibility' section to Makefile header - tsgo is in devDependencies, so it's always available after bun install --- Makefile | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index e9f21be0c..72a76fe42 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,11 @@ # # Adding New Targets: # Add `## Description` after the target to make it appear in `make help` +# +# Build Reproducibility: +# AVOID CONDITIONAL BRANCHES (if/else) IN BUILD TARGETS AT ALL COSTS. +# Branches reduce reproducibility - builds should fail fast with clear errors +# if dependencies are missing, not silently fall back to different behavior. # Include formatting rules include fmt.mk @@ -69,12 +74,7 @@ build-main: node_modules/.installed dist/main.js ## Build main process dist/main.js: src/version.ts tsconfig.main.json tsconfig.json $(TS_SOURCES) @echo "Building main process..." - @if [ -f "node_modules/@typescript/native-preview/bin/tsgo.js" ]; then \ - NODE_ENV=production bun run node_modules/@typescript/native-preview/bin/tsgo.js -p tsconfig.main.json; \ - else \ - echo "⚠️ tsgo not found, falling back to tsc (slower)"; \ - NODE_ENV=production bun x tsc -p tsconfig.main.json; \ - fi + @NODE_ENV=production bun run node_modules/@typescript/native-preview/bin/tsgo.js -p tsconfig.main.json @NODE_ENV=production bun x tsc-alias -p tsconfig.main.json build-preload: node_modules/.installed dist/preload.js ## Build preload script @@ -141,16 +141,9 @@ lint-fix: node_modules/.installed ## Run linter with --fix @./scripts/lint.sh --fix typecheck: node_modules/.installed src/version.ts ## Run TypeScript type checking (uses tsgo for 10x speedup) - @if [ -f "node_modules/@typescript/native-preview/bin/tsgo.js" ]; then \ - bun x concurrently -g \ - "bun run node_modules/@typescript/native-preview/bin/tsgo.js --noEmit" \ - "bun run node_modules/@typescript/native-preview/bin/tsgo.js --noEmit -p tsconfig.main.json"; \ - else \ - echo "⚠️ tsgo not found, falling back to tsc (slower)"; \ - bun x concurrently -g \ - "tsc --noEmit" \ - "tsc --noEmit -p tsconfig.main.json"; \ - fi + @bun x concurrently -g \ + "bun run node_modules/@typescript/native-preview/bin/tsgo.js --noEmit" \ + "bun run node_modules/@typescript/native-preview/bin/tsgo.js --noEmit -p tsconfig.main.json" ## Testing test-integration: node_modules/.installed ## Run all tests (unit + integration) From 3c6133129e29f207862b90e68170fa8b367358dc Mon Sep 17 00:00:00 2001 From: Ammar Date: Wed, 15 Oct 2025 10:20:41 -0500 Subject: [PATCH 4/4] DRY: Extract tsgo path to TSGO variable Reduces duplication of the tsgo binary path across the Makefile. The path is now defined once at the top and referenced via $(TSGO) in all targets (dev, build-main, typecheck). Benefits: - Single source of truth for tsgo path - Easier to update if path changes - More readable target definitions --- Makefile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 72a76fe42..d5aab5e96 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,9 @@ include fmt.mk .PHONY: ensure-deps .PHONY: check-eager-imports check-bundle-size check-startup +# Build tools +TSGO := bun run node_modules/@typescript/native-preview/bin/tsgo.js + TS_SOURCES := $(shell find src -type f \( -name '*.ts' -o -name '*.tsx' \)) # Default target @@ -61,7 +64,7 @@ help: ## Show this help message ## Development dev: node_modules/.installed build-main ## Start development server (Vite + tsgo watcher for 10x faster type checking) @bun x concurrently -k \ - "bun x concurrently \"bun run node_modules/@typescript/native-preview/bin/tsgo.js -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \ + "bun x concurrently \"$(TSGO) -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \ "vite" start: node_modules/.installed build-main build-preload build-static ## Build and start Electron app @@ -74,7 +77,7 @@ build-main: node_modules/.installed dist/main.js ## Build main process dist/main.js: src/version.ts tsconfig.main.json tsconfig.json $(TS_SOURCES) @echo "Building main process..." - @NODE_ENV=production bun run node_modules/@typescript/native-preview/bin/tsgo.js -p tsconfig.main.json + @NODE_ENV=production $(TSGO) -p tsconfig.main.json @NODE_ENV=production bun x tsc-alias -p tsconfig.main.json build-preload: node_modules/.installed dist/preload.js ## Build preload script @@ -142,8 +145,8 @@ lint-fix: node_modules/.installed ## Run linter with --fix typecheck: node_modules/.installed src/version.ts ## Run TypeScript type checking (uses tsgo for 10x speedup) @bun x concurrently -g \ - "bun run node_modules/@typescript/native-preview/bin/tsgo.js --noEmit" \ - "bun run node_modules/@typescript/native-preview/bin/tsgo.js --noEmit -p tsconfig.main.json" + "$(TSGO) --noEmit" \ + "$(TSGO) --noEmit -p tsconfig.main.json" ## Testing test-integration: node_modules/.installed ## Run all tests (unit + integration)