Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type * as ng from '@angular/compiler-cli';
import type { PartialMessage } from 'esbuild';
import { profileSync } from '../../esbuild/profiling';
import type { AngularHostOptions } from '../angular-host';
import type { CompilerOptionOverrides } from './compiler-options';

export interface EmitFileResult {
filename: string;
Expand Down Expand Up @@ -37,6 +38,7 @@ export interface AngularCompilationResult {
externalStylesheets?: ReadonlyMap<string, string>;
templateUpdates?: ReadonlyMap<string, string>;
componentResourcesDependencies?: ReadonlyMap<string, readonly string[]>;
warnings?: readonly PartialMessage[];
}

export enum DiagnosticModes {
Expand Down Expand Up @@ -82,7 +84,7 @@ export abstract class AngularCompilation {
abstract initialize(
tsconfig: string,
hostOptions: AngularHostOptions,
compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions,
compilerOptionOverrides?: CompilerOptionOverrides,
): Promise<AngularCompilationResult>;

emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@

import ts from 'typescript';
import type { AngularHostOptions } from '../angular-host';
import { transformCompilerOptions } from './compiler-options';
import { TypeScriptCompilation } from './typescript-compilation';
import {
AngularCompilation,
AngularCompilationResult,
DiagnosticModes,
NoopCompilation,
TypeScriptCompilation,
createAngularCompilation,
} from './index';

Expand Down Expand Up @@ -62,13 +63,24 @@ describe('AngularCompilation', () => {
it('initializes with empty referencedFiles and compiler options', async () => {
const compilation = new NoopCompilation();
const mockHostOptions = {} as AngularHostOptions;
const result = await compilation.initialize('tsconfig.json', mockHostOptions, (opts) => ({
...opts,
customOption: true,
}));
const result = await compilation.initialize('tsconfig.json', mockHostOptions);

expect(result.referencedFiles).toEqual([]);
expect(result.compilerOptions['customOption']).toBe(true);
expect(result.compilerOptions).toBeDefined();
});

it('initializes with CompilerOptionOverrides object', async () => {
const compilation = new NoopCompilation();
const mockHostOptions = {} as AngularHostOptions;
const result = await compilation.initialize('tsconfig.json', mockHostOptions, {
sourcemap: true,
enableHmr: true,
});

expect(result.referencedFiles).toEqual([]);
expect(result.compilerOptions.inlineSources).toBe(true);
expect(result.compilerOptions.inlineSourceMap).toBe(true);
expect(result.compilerOptions['_enableHmr']).toBe(true);
});

it('throws when calling emitAffectedFiles', () => {
Expand Down Expand Up @@ -184,4 +196,160 @@ describe('AngularCompilation', () => {
);
});
});

describe('transformCompilerOptions', () => {
it('does not mutate the input compiler options object', () => {
const originalOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ES2020,
module: ts.ModuleKind.CommonJS,
};
const originalCopy = { ...originalOptions };

transformCompilerOptions(ts, originalOptions);

expect(originalOptions).toEqual(originalCopy);
});

it('sets target to ES2022 and useDefineForClassFields to false when target is undefined', () => {
const { compilerOptions, warnings } = transformCompilerOptions(
ts,
{ module: ts.ModuleKind.ES2022 },
undefined,
'tsconfig.json',
);

expect(compilerOptions.target).toBe(ts.ScriptTarget.ES2022);
expect(compilerOptions.useDefineForClassFields).toBe(false);
expect(warnings.length).toBe(1);
expect(warnings[0].text).toContain(
"TypeScript compiler options 'target' and 'useDefineForClassFields'",
);
expect(warnings[0].location?.file).toBe('tsconfig.json');
});

it('preserves existing useDefineForClassFields if target < ES2022', () => {
const { compilerOptions, warnings } = transformCompilerOptions(
ts,
{
target: ts.ScriptTarget.ES2020,
useDefineForClassFields: true,
module: ts.ModuleKind.ES2022,
},
undefined,
'tsconfig.json',
);

expect(compilerOptions.target).toBe(ts.ScriptTarget.ES2022);
expect(compilerOptions.useDefineForClassFields).toBe(true);
expect(warnings.length).toBe(1);
});

it('sets compilationMode to full and warns when compilationMode is partial', () => {
const { compilerOptions, warnings } = transformCompilerOptions(ts, {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ES2022,
compilationMode: 'partial',
});

expect(compilerOptions.compilationMode).toBe('full');
expect(warnings.length).toBe(1);
expect(warnings[0].text).toContain('Angular partial compilation mode is not supported');
});

it('configures incremental and tsBuildInfoFile when cachePath is provided', () => {
const { compilerOptions } = transformCompilerOptions(
ts,
{ target: ts.ScriptTarget.ES2022 },
{ cachePath: '/tmp/cache' },
);

expect(compilerOptions.incremental).toBe(true);
expect(compilerOptions.tsBuildInfoFile).toContain('.tsbuildinfo');
});

it('sets incremental to false when cachePath is not provided or incremental is false', () => {
const { compilerOptions: opt1 } = transformCompilerOptions(
ts,
{ target: ts.ScriptTarget.ES2022 },
undefined,
);
expect(opt1.incremental).toBe(false);

const { compilerOptions: opt2 } = transformCompilerOptions(
ts,
{ target: ts.ScriptTarget.ES2022, incremental: false },
{ cachePath: '/tmp/cache' },
);
expect(opt2.incremental).toBe(false);
});

it('sets module to ES2022 and warns when module < ES2015', () => {
const { compilerOptions, warnings } = transformCompilerOptions(ts, {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.CommonJS,
});

expect(compilerOptions.module).toBe(ts.ModuleKind.ES2022);
expect(warnings.length).toBe(1);
expect(warnings[0].text).toContain(
"TypeScript compiler options 'module' values 'CommonJS', 'UMD'",
);
});

it('warns when isolatedModules is enabled with emitDecoratorMetadata', () => {
const { warnings } = transformCompilerOptions(ts, {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ES2022,
isolatedModules: true,
emitDecoratorMetadata: true,
});

expect(warnings.length).toBe(1);
expect(warnings[0].text).toContain(
"TypeScript compiler option 'isolatedModules' may prevent",
);
});

it('synchronizes customConditions when moduleResolution is Bundler or module is Preserve', () => {
const { compilerOptions: bundlerOptions } = transformCompilerOptions(
ts,
{ target: ts.ScriptTarget.ES2022, moduleResolution: ts.ModuleResolutionKind.Bundler },
{ customConditions: ['development'] },
);
expect(bundlerOptions.customConditions).toEqual(['development']);

const { compilerOptions: preserveOptions } = transformCompilerOptions(
ts,
{ target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.Preserve },
{ customConditions: ['development'] },
);
expect(preserveOptions.customConditions).toEqual(['development']);
});

it('applies override options correctly', () => {
const { compilerOptions } = transformCompilerOptions(
ts,
{ target: ts.ScriptTarget.ES2022, isolatedModules: true },
{
sourcemap: true,
preserveSymlinks: true,
externalRuntimeStyles: true,
enableHmr: true,
instrumentForCoverage: true,
includeTestMetadata: true,
},
);

expect(compilerOptions.inlineSources).toBe(true);
expect(compilerOptions.inlineSourceMap).toBe(true);
expect(compilerOptions.preserveSymlinks).toBe(true);
expect(compilerOptions.externalRuntimeStyles).toBe(true);
expect(compilerOptions['_enableHmr']).toBe(true);
expect(compilerOptions['_useTypeScriptTranspilation']).toBe(true);
expect(compilerOptions.supportTestBed).toBe(true);
expect(compilerOptions.supportJitMode).toBe(true);
expect(compilerOptions.noEmitOnError).toBe(false);
expect(compilerOptions.composite).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import type * as ng from '@angular/compiler-cli';
import type { PartialMessage } from 'esbuild';
import assert from 'node:assert';
import { relative } from 'node:path';
import ts from 'typescript';
Expand All @@ -26,6 +27,7 @@ import {
DiagnosticModes,
EmitFileResult,
} from './angular-compilation';
import { CompilerOptionOverrides, transformCompilerOptions } from './compiler-options';
import { collectHmrCandidates } from './hmr-candidates';
import { TypeScriptCompilation } from './typescript-compilation';
import { printSourceFileWithMap } from './typescript-printer';
Expand Down Expand Up @@ -64,7 +66,7 @@ export class AotCompilation extends TypeScriptCompilation {
async initialize(
tsconfig: string,
hostOptions: AngularHostOptions,
compilerOptionsTransformer?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions,
compilerOptionOverrides?: CompilerOptionOverrides,
): Promise<AngularCompilationResult> {
// Dynamically load the Angular compiler CLI package
const { NgtscProgram, OptimizeFor } = await AngularCompilation.loadCompilerCli();
Expand All @@ -75,8 +77,13 @@ export class AotCompilation extends TypeScriptCompilation {
rootNames,
errors: configurationDiagnostics,
} = await this.loadConfiguration(tsconfig);
const compilerOptions =
compilerOptionsTransformer?.(originalCompilerOptions) ?? originalCompilerOptions;

const { compilerOptions, warnings } = transformCompilerOptions(
ts,
originalCompilerOptions,
compilerOptionOverrides,
tsconfig,
);

const useTypeScriptTranspilation =
(compilerOptions['_useTypeScriptTranspilation'] as boolean | undefined) ??
Expand Down Expand Up @@ -245,6 +252,7 @@ export class AotCompilation extends TypeScriptCompilation {
externalStylesheets: hostOptions.externalStylesheets,
templateUpdates,
componentResourcesDependencies,
warnings,
};
}

Expand Down
Loading