Skip to content

Fix generateTrace with newer TypeScript versions and build: false #730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 11, 2022
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,10 +347,10 @@ yarn add --dev @types/webpack

## Profiling types resolution

Starting from TypeScript 4.1.0, you can profile long type checks by
When using TypeScript 4.3.0 or newer you can profile long type checks by
setting "generateTrace" compiler option. This is an instruction from [microsoft/TypeScript#40063](https://github.com/microsoft/TypeScript/pull/40063):

1. Set "generateTrace": "{folderName}" in your `tsconfig.json`
1. Set "generateTrace": "{folderName}" in your `tsconfig.json` (under `compilerOptions`)
2. Look in the resulting folder. If you used build mode, there will be a `legend.json` telling you what went where.
Otherwise, there will be `trace.json` file and `types.json` files.
3. Navigate to [edge://tracing](edge://tracing) or [chrome://tracing](chrome://tracing) and load `trace.json`
Expand Down
3 changes: 3 additions & 0 deletions src/typescript/worker/lib/program/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getConfigFilePathFromProgram, getParsedConfig } from '../config';
import { updateDiagnostics, getDiagnosticsOfProgram } from '../diagnostics';
import { emitDtsIfNeeded } from '../emit';
import { createCompilerHost } from '../host/compiler-host';
import { startTracingIfNeeded, stopTracingIfNeeded } from '../tracing';
import { typescript } from '../typescript';

let compilerHost: ts.CompilerHost | undefined;
Expand All @@ -16,6 +17,7 @@ export function useProgram() {
compilerHost = createCompilerHost(parsedConfig);
}
if (!program) {
startTracingIfNeeded(parsedConfig.options);
program = typescript.createProgram({
rootNames: parsedConfig.fileNames,
options: parsedConfig.options,
Expand All @@ -26,6 +28,7 @@ export function useProgram() {

updateDiagnostics(getConfigFilePathFromProgram(program), getDiagnosticsOfProgram(program));
emitDtsIfNeeded(program);
stopTracingIfNeeded(program);
}

export function invalidateProgram(withHost = false) {
Expand Down
41 changes: 24 additions & 17 deletions src/typescript/worker/lib/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,44 @@ import { getConfigFilePathFromCompilerOptions } from './config';
import { typescript } from './typescript';
import { config } from './worker-config';

// write this type as it's available only starting from TypeScript 4.1.0
// these types are internal in TypeScript, so reproduce them here
type TracingMode = 'project' | 'build' | 'server';
interface Tracing {
startTracing(configFilePath: string, traceDirPath: string, isBuildMode: boolean): void;
stopTracing(typeCatalog: unknown): void;
dumpLegend(): void;
startTracing?: (tracingMode: TracingMode, traceDir: string, configFilePath?: string) => void;

tracing?: {
stopTracing(): void;
dumpLegend(): void;
};
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tracing: Tracing | undefined = (typescript as any).tracing;
const traceableTypescript: Tracing = typescript as any;

export function startTracingIfNeeded(compilerOptions: ts.CompilerOptions) {
if (compilerOptions.generateTrace && tracing) {
tracing.startTracing(
getConfigFilePathFromCompilerOptions(compilerOptions),
compilerOptions.generateTrace as string,
config.build
if (
typeof compilerOptions.generateTrace === 'string' &&
typeof traceableTypescript.startTracing === 'function'
) {
traceableTypescript.startTracing(
config.build ? 'build' : 'project',
compilerOptions.generateTrace,
getConfigFilePathFromCompilerOptions(compilerOptions)
);
}
}

export function stopTracingIfNeeded(program: ts.BuilderProgram) {
export function stopTracingIfNeeded(program: ts.Program | ts.BuilderProgram) {
const compilerOptions = program.getCompilerOptions();

if (compilerOptions.generateTrace && tracing) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
tracing.stopTracing((program.getProgram() as any).getTypeCatalog());
if (
typeof compilerOptions.generateTrace === 'string' &&
typeof traceableTypescript.tracing?.stopTracing === 'function'
) {
traceableTypescript.tracing.stopTracing();
}
}

export function dumpTracingLegendIfNeeded() {
if (tracing) {
tracing.dumpLegend();
}
traceableTypescript.tracing?.dumpLegend();
}
40 changes: 40 additions & 0 deletions test/e2e/type-script-tracing.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import path from 'path';

import { extractWebpackErrors } from './driver/webpack-errors-extractor';

describe('TypeScript Tracing', () => {
it.each([
{ build: false, typescript: '~4.3.0' },
{ build: true, typescript: '~4.3.0' },
{ build: false, typescript: '~4.6.0' },
{ build: true, typescript: '~4.6.0' },
])('can generate trace files for %p', async ({ build, ...dependencies }) => {
await sandbox.load(path.join(__dirname, 'fixtures/typescript-basic'));
await sandbox.install('yarn', { ...dependencies });

// enable tracing
await sandbox.patch(
'tsconfig.json',
'"outDir": "./dist"',
'"outDir": "./dist",\n"generateTrace": "./traces"'
);

await sandbox.write(
'fork-ts-checker.config.js',
`module.exports = ${JSON.stringify({ typescript: { build } })};`
);

const webpackResult = await sandbox.exec('yarn webpack --mode=development');
const errors = extractWebpackErrors(webpackResult);
expect(errors).toEqual([]);

expect(await sandbox.exists('dist')).toEqual(true);

expect(await sandbox.list('./traces')).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: expect.stringMatching(/types.*\.json/) }),
expect.objectContaining({ name: expect.stringMatching(/trace.*\.json/) }),
])
);
});
});