镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,61 @@ describe('AngularCompilation', () => {
suppressOutputPathCheck: true,
outDir: undefined,
}),
undefined,
jasmine.any(Map),
);
expect(result.rootNames).toEqual(['/src/main.ts']);
expect(result.compilerOptions.target).toBe(ts.ScriptTarget.ES2022);
expect(result.compilerOptions.inlineSources).toBe(true);
expect(result.warnings.length).toBeGreaterThan(0);
expect(result.tsConfigFiles).toEqual(['tsconfig.json']);
});

it('passes extendedConfigCache to readConfiguration and invalidates entries on config change', async () => {
const compilation = new MockTypeScriptCompilation();
let passedCache: Map<string, ts.ExtendedConfigCacheEntry> | undefined;
const mockReadConfig = jasmine
.createSpy('readConfiguration')
.and.callFake((_project, _options, _host, extendedConfigCache) => {
passedCache = extendedConfigCache;
extendedConfigCache?.set(
'/path/to/tsconfig.base.json',
{} as ts.ExtendedConfigCacheEntry,
);

return {
options: { target: ts.ScriptTarget.ES2020 },
rootNames: ['/src/main.ts'],
errors: [],
};
});
spyOn(TypeScriptCompilation, 'loadCompilerCli').and.resolveTo({
readConfiguration: mockReadConfig,
} as unknown as typeof import('@angular/compiler-cli'));

const result1 = await compilation.testLoadConfiguration('tsconfig.json');
expect(mockReadConfig).toHaveBeenCalledTimes(1);
expect(passedCache).toBeDefined();
expect(passedCache?.has('/path/to/tsconfig.base.json')).toBeTrue();
expect(result1.tsConfigFiles).toEqual(['tsconfig.json', '/path/to/tsconfig.base.json']);

// Invalidation of non-config file should keep extended config cache intact
await compilation.update?.(new Set(['/src/main.ts']));
expect(passedCache?.has('/path/to/tsconfig.base.json')).toBeTrue();

// Invalidation of extended tsconfig file should remove it from cache
await compilation.update?.(new Set(['/path/to/tsconfig.base.json']));
expect(passedCache?.has('/path/to/tsconfig.base.json')).toBeFalse();

// Invalidation with different casing should also remove it from cache
passedCache?.set('/path/to/tsconfig.base.json', {} as ts.ExtendedConfigCacheEntry);
await compilation.update?.(new Set(['/PATH/TO/TSCONFIG.BASE.JSON']));
expect(passedCache?.has('/path/to/tsconfig.base.json')).toBeFalse();

// Subsequent configuration load re-uses the same cache instance
await compilation.testLoadConfiguration('tsconfig.json');
expect(mockReadConfig).toHaveBeenCalledTimes(2);
expect(mockReadConfig.calls.argsFor(1)[3]).toBe(passedCache);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export class AotCompilation extends TypeScriptCompilation {
rootNames,
errors: configurationDiagnostics,
warnings,
tsConfigFiles,
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType);

const useTypeScriptTranspilation =
Expand Down Expand Up @@ -209,28 +210,31 @@ export class AotCompilation extends TypeScriptCompilation {
const componentResourcesDependencies = new Map<string, string[]>();

// Get all files referenced in the TypeScript/Angular program including component resources
const referencedFiles = typeScriptProgram
.getSourceFiles()
.filter((sourceFile) => !angularCompiler.ignoreForEmit.has(sourceFile))
.flatMap((sourceFile) => {
const resourceDependencies = angularCompiler.getResourceDependencies(sourceFile);
componentResourcesDependencies.set(sourceFile.fileName, resourceDependencies);
// Also invalidate Angular diagnostics for a source file if component resources are modified
if (this.#state && hostOptions.modifiedFiles?.size) {
for (const resourceDependency of resourceDependencies) {
if (
hostOptions.modifiedFiles.has(resourceDependency) &&
!/\.(?:css|scss|sass|less)$/i.test(resourceDependency)
) {
this.#state.diagnosticCache.delete(sourceFile);
// Also mark as affected in case changed template affects diagnostics
affectedFiles.add(sourceFile);
const referencedFiles = [
...tsConfigFiles,
...typeScriptProgram
.getSourceFiles()
.filter((sourceFile) => !angularCompiler.ignoreForEmit.has(sourceFile))
.flatMap((sourceFile) => {
const resourceDependencies = angularCompiler.getResourceDependencies(sourceFile);
componentResourcesDependencies.set(sourceFile.fileName, resourceDependencies);
// Also invalidate Angular diagnostics for a source file if component resources are modified
if (this.#state && hostOptions.modifiedFiles?.size) {
for (const resourceDependency of resourceDependencies) {
if (
hostOptions.modifiedFiles.has(resourceDependency) &&
!/\.(?:css|scss|sass|less)$/i.test(resourceDependency)
) {
this.#state.diagnosticCache.delete(sourceFile);
// Also mark as affected in case changed template affects diagnostics
affectedFiles.add(sourceFile);
}
}
}
}

return [sourceFile.fileName, ...resourceDependencies];
});
return [sourceFile.fileName, ...resourceDependencies];
}),
];

this.#state = new AngularCompilationState(
angularProgram,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class JitCompilation extends TypeScriptCompilation {
rootNames,
errors: configurationDiagnostics,
warnings,
tsConfigFiles,
} = await this.loadConfiguration(tsconfig, compilerOptionOverrides, buildType);

if (hostOptions.modifiedFiles) {
Expand Down Expand Up @@ -89,9 +90,10 @@ export class JitCompilation extends TypeScriptCompilation {
createWorkerTransformer(hostOptions.processWebWorker.bind(hostOptions)),
);

const referencedFiles = typeScriptProgram
.getSourceFiles()
.map((sourceFile) => sourceFile.fileName);
const referencedFiles = [
...tsConfigFiles,
...typeScriptProgram.getSourceFiles().map((sourceFile) => sourceFile.fileName),
];

return { compilerOptions, referencedFiles, warnings };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ export interface TransformedConfiguration {
rootNames: string[];
errors: ts.Diagnostic[];
warnings: PartialMessage[];
tsConfigFiles: readonly string[];
}

export abstract class TypeScriptCompilation extends AngularCompilation {
static #angularCompilerCliModule?: typeof ng;
readonly #extendedConfigCache = new Map<string, ts.ExtendedConfigCacheEntry>();

static async loadCompilerCli(): Promise<typeof ng> {
TypeScriptCompilation.#angularCompilerCliModule ??= await import('@angular/compiler-cli');
Expand All @@ -43,24 +45,31 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
rootNames: originalRootNames,
errors,
} = profileSync('NG_READ_CONFIG', () =>
readConfiguration(tsconfig, {
// Angular specific configuration defaults and overrides to ensure a functioning compilation.
suppressOutputPathCheck: true,
outDir: undefined,
sourceMap: false,
declaration: false,
declarationMap: false,
allowEmptyCodegenFiles: false,
annotationsAs: 'decorators',
enableResourceInlining: false,
supportTestBed: false,
supportJitMode: false,
// Disable removing of comments as TS is quite aggressive with these and can
// remove important annotations, such as /* @__PURE__ */ and comments like /* vite-ignore */.
removeComments: false,
}),
readConfiguration(
tsconfig,
{
// Angular specific configuration defaults and overrides to ensure a functioning compilation.
suppressOutputPathCheck: true,
outDir: undefined,
sourceMap: false,
declaration: false,
declarationMap: false,
allowEmptyCodegenFiles: false,
annotationsAs: 'decorators',
enableResourceInlining: false,
supportTestBed: false,
supportJitMode: false,
// Disable removing of comments as TS is quite aggressive with these and can
// remove important annotations, such as /* @__PURE__ */ and comments like /* vite-ignore */.
removeComments: false,
},
undefined,
this.#extendedConfigCache,
),
);

const tsConfigFiles = [toPosixPath(tsconfig), ...this.#extendedConfigCache.keys()];

let rootNames = originalRootNames;
if (compilerOptionOverrides?.rootFiles?.length) {
const rootFilesSet = new Set(
Expand All @@ -87,14 +96,28 @@ export abstract class TypeScriptCompilation extends AngularCompilation {
rootNames,
errors,
warnings,
tsConfigFiles,
};
}

protected readonly sourceFiles = new Map<string, ts.SourceFile>();

protected invalidateFiles(files: Iterable<string>): void {
for (const file of files) {
this.sourceFiles.delete(toPosixPath(file));
const posixFile = toPosixPath(file);
this.sourceFiles.delete(posixFile);

if (this.#extendedConfigCache.size === 0) {
continue;
}

if (this.#extendedConfigCache.delete(posixFile)) {
continue;
}

// Check with lowercased key because TypeScript lowercases the keys
// of the extended config cache on case-insensitive operating systems.
this.#extendedConfigCache.delete(posixFile.toLowerCase());
}
}

Expand Down
Loading