Files
upstream-tldraw/internal/scripts/profile-typescript.ts
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

293 lines
9.3 KiB
TypeScript

import { execFileSync } from 'child_process'
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'fs'
import { join } from 'path'
import { REPO_ROOT } from './lib/file'
import { nicelog } from './lib/nicelog'
interface ProfileResults {
timestamp: string
duration: number
fileCount: number
errorCount: number
tsbuildInfoSize: number
traceStats?: {
totalTime: number
longestFiles: Array<{ file: string; duration: number }>
}
}
async function main() {
nicelog('🔍 Starting TypeScript profiling...\n')
// Allow targeting a specific workspace, default to apps/dotcom/client
const targetWorkspace = process.argv[2] || 'apps/dotcom/client'
const workspacePath = join(REPO_ROOT, targetWorkspace)
if (!existsSync(join(workspacePath, 'tsconfig.json'))) {
console.error(`❌ No tsconfig.json found in ${targetWorkspace}`)
console.error(`Usage: yarn profile-typescript [workspace-path]`)
console.error(`Example: yarn profile-typescript packages/editor`)
process.exit(1)
}
nicelog(` Profiling workspace: ${targetWorkspace}\n`)
const outputDir = join(REPO_ROOT, '.ts-profile')
const traceDir = join(outputDir, 'trace')
const reportPath = join(outputDir, 'profile-report.md')
// Create output directory
if (!existsSync(outputDir)) {
mkdirSync(outputDir, { recursive: true })
}
const results: ProfileResults = {
timestamp: new Date().toISOString(),
duration: 0,
fileCount: 0,
errorCount: 0,
tsbuildInfoSize: 0,
}
// Step 1: Count files being processed
nicelog('📊 Step 1/5: Counting TypeScript files...')
try {
const listFilesOutput = execFileSync(
join(REPO_ROOT, 'node_modules/.bin/tsc'),
['--listFiles', '--noEmit'],
{ cwd: workspacePath, encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }
)
results.fileCount = listFilesOutput.split('\n').filter((line) => line.trim()).length
nicelog(` Found ${results.fileCount} files\n`)
} catch {
nicelog(' ⚠️ Could not count files (non-fatal)\n')
}
// Step 2: Calculate tsbuildinfo cache size
nicelog('📦 Step 2/5: Calculating build cache size...')
const findTsbuildInfo = (dir: string): string[] => {
const results: string[] = []
try {
const entries = readdirSync(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = join(dir, entry.name)
if (entry.isDirectory()) {
if (entry.name !== 'node_modules' && entry.name !== '.git') {
results.push(...findTsbuildInfo(fullPath))
}
} else if (entry.name.endsWith('.tsbuildinfo')) {
results.push(fullPath)
}
}
} catch {
// Ignore permission errors
}
return results
}
const tsbuildInfoFiles = findTsbuildInfo(REPO_ROOT)
results.tsbuildInfoSize = tsbuildInfoFiles.reduce((total, file) => {
try {
const stats = readFileSync(file)
return total + stats.length
} catch {
return total
}
}, 0)
nicelog(
` Found ${tsbuildInfoFiles.length} cache files (${(results.tsbuildInfoSize / 1024 / 1024).toFixed(2)} MB)\n`
)
// Step 3: Run extended diagnostics
nicelog('⏱️ Step 3/5: Running extended diagnostics...')
const startTime = Date.now()
try {
const diagnosticsOutput = execFileSync(
join(REPO_ROOT, 'node_modules/.bin/tsc'),
['--extendedDiagnostics', '--noEmit'],
{ cwd: workspacePath, encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }
)
results.duration = Date.now() - startTime
// Save full diagnostics output
writeFileSync(join(outputDir, 'extended-diagnostics.txt'), diagnosticsOutput)
nicelog(` Completed in ${(results.duration / 1000).toFixed(2)}s\n`)
} catch (err: any) {
results.duration = Date.now() - startTime
if (err.stdout) {
writeFileSync(join(outputDir, 'extended-diagnostics.txt'), err.stdout)
}
// Count errors from stderr
if (err.stderr) {
const errorLines = err.stderr.split('\n').filter((line: string) => line.includes('error TS'))
results.errorCount = errorLines.length
}
nicelog(
` Completed in ${(results.duration / 1000).toFixed(2)}s (with ${results.errorCount} errors)\n`
)
}
// Step 4: Generate trace file
nicelog('🔬 Step 4/5: Generating trace file...')
try {
if (existsSync(traceDir)) {
rmSync(traceDir, { recursive: true, force: true })
}
mkdirSync(traceDir, { recursive: true })
const tscPath = join(REPO_ROOT, 'node_modules/.bin/tsc')
execFileSync(tscPath, ['--generateTrace', traceDir, '--noEmit'], {
cwd: workspacePath,
encoding: 'utf-8',
maxBuffer: 50 * 1024 * 1024,
stdio: 'pipe',
})
const traceFile = join(traceDir, 'trace.json')
if (existsSync(traceFile)) {
nicelog(` ✅ Trace generated at ${traceFile}\n`)
nicelog(` Open chrome://tracing and load this file\n`)
// Try to parse trace for basic stats
try {
const trace = JSON.parse(readFileSync(traceFile, 'utf-8'))
if (Array.isArray(trace)) {
const checkEvents = trace.filter((e: any) => e.name === 'checkSourceFile')
const sorted = checkEvents
.sort((a: any, b: any) => (b.dur || 0) - (a.dur || 0))
.slice(0, 10)
results.traceStats = {
totalTime: checkEvents.reduce((sum: number, e: any) => sum + (e.dur || 0), 0) / 1000,
longestFiles: sorted.map((e: any) => ({
file: e.args?.path || 'unknown',
duration: (e.dur || 0) / 1000,
})),
}
}
} catch {
// Trace parsing is optional
}
}
} catch (err: any) {
nicelog(` ⚠️ Could not generate trace: ${err.message}\n`)
if (err.stderr) {
nicelog(` Error output: ${err.stderr.slice(0, 200)}\n`)
}
}
// Step 5: Generate markdown report
nicelog('📝 Step 5/5: Generating report...')
const report = generateReport(results, outputDir, traceDir)
writeFileSync(reportPath, report)
nicelog(` ✅ Report saved to ${reportPath}\n`)
// Print summary
console.log('\n' + '='.repeat(80))
console.log('📊 TypeScript Profile Summary')
console.log('='.repeat(80))
console.log(`Files processed: ${results.fileCount.toLocaleString()}`)
console.log(`Type check duration: ${(results.duration / 1000).toFixed(2)}s`)
console.log(`Type errors: ${results.errorCount}`)
console.log(`Build cache size: ${(results.tsbuildInfoSize / 1024 / 1024).toFixed(2)} MB`)
if (results.traceStats) {
console.log(`Total check time: ${results.traceStats.totalTime.toFixed(2)}ms (from trace)`)
}
console.log('\n📄 Full report: ' + reportPath)
console.log('🔬 Chrome trace: ' + join(traceDir, 'trace.json'))
console.log('='.repeat(80) + '\n')
}
function generateReport(results: ProfileResults, outputDir: string, traceDir: string): string {
const lines: string[] = []
lines.push('# TypeScript profiling report')
lines.push('')
lines.push(`**Generated:** ${new Date(results.timestamp).toLocaleString()}`)
lines.push('')
lines.push('## Summary')
lines.push('')
lines.push('| Metric | Value |')
lines.push('|--------|-------|')
lines.push(`| Files processed | ${results.fileCount.toLocaleString()} |`)
lines.push(`| Type check duration | ${(results.duration / 1000).toFixed(2)}s |`)
lines.push(`| Type errors | ${results.errorCount} |`)
lines.push(`| Build cache size | ${(results.tsbuildInfoSize / 1024 / 1024).toFixed(2)} MB |`)
if (results.traceStats) {
lines.push(
`| Total type checking time | ${results.traceStats.totalTime.toFixed(2)}ms (from trace) |`
)
}
lines.push('')
if (results.traceStats && results.traceStats.longestFiles.length > 0) {
lines.push('## Slowest files to type check')
lines.push('')
lines.push('| File | Duration |')
lines.push('|------|----------|')
for (const { file, duration } of results.traceStats.longestFiles) {
const shortPath = file.replace(REPO_ROOT, '.')
lines.push(`| \`${shortPath}\` | ${duration.toFixed(2)}ms |`)
}
lines.push('')
}
if (results.errorCount > 0) {
lines.push('## Type errors')
lines.push('')
lines.push(
`Found ${results.errorCount} type errors. See \`extended-diagnostics.txt\` for details.`
)
lines.push('')
}
lines.push('## How to analyze')
lines.push('')
lines.push('### Chrome tracing')
lines.push('')
lines.push('1. Open Chrome/Edge browser')
lines.push('2. Navigate to `chrome://tracing`')
lines.push(`3. Load file: \`${join(traceDir, 'trace.json')}\``)
lines.push('4. Use WASD keys to navigate, search for specific files')
lines.push('')
lines.push('### Extended diagnostics')
lines.push('')
lines.push(
`Full TypeScript diagnostics output saved to \`${join(outputDir, 'extended-diagnostics.txt')}\``
)
lines.push('')
lines.push('## Recommendations')
lines.push('')
lines.push('### If performance is slow:')
lines.push('')
lines.push('1. **Clear build caches:**')
lines.push(' ```bash')
lines.push(' find . -name "*.tsbuildinfo" -not -path "*/node_modules/*" -delete')
lines.push(' ```')
lines.push('')
lines.push('2. **Restart TypeScript server in your IDE**')
lines.push('')
lines.push('3. **Check for files with long check times in the Chrome trace**')
lines.push('')
lines.push('4. **Exclude test files from main compilation:**')
lines.push(' Add to `tsconfig.json`:')
lines.push(' ```json')
lines.push(' "exclude": ["**/__test__/**", "**/*.test.ts"]')
lines.push(' ```')
lines.push('')
lines.push('5. **Add to compiler options for faster dependency checking:**')
lines.push(' ```json')
lines.push(' "assumeChangesOnlyAffectDirectDependencies": true')
lines.push(' ```')
lines.push('')
return lines.join('\n')
}
main().catch((err) => {
console.error('❌ Unexpected error:', err)
process.exit(1)
})