-
Notifications
You must be signed in to change notification settings - Fork 12k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(@angular-devkit/build-angular): improve sharing of TypeScript com…
…pilation state between various esbuild instances during rebuilds This commit improves the logic to block and share a TypeScript results across multiple esbuild instances (browser and server builds) which fixes an issue that previously during rebuilds in some cases did not block the build correctly.
- Loading branch information
1 parent
99d9037
commit 8981d8c
Showing
3 changed files
with
153 additions
and
16 deletions.
There are no files selected for viewing
93 changes: 93 additions & 0 deletions
93
...kit/build_angular/src/builders/application/tests/behavior/typescript-rebuild-lazy_spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import type { logging } from '@angular-devkit/core'; | ||
import { concatMap, count, firstValueFrom, timeout } from 'rxjs'; | ||
import { buildApplication } from '../../index'; | ||
import { OutputHashing } from '../../schema'; | ||
import { APPLICATION_BUILDER_INFO, BASE_OPTIONS, describeBuilder } from '../setup'; | ||
|
||
describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { | ||
beforeEach(async () => { | ||
await harness.modifyFile('src/tsconfig.app.json', (content) => { | ||
const tsConfig = JSON.parse(content); | ||
tsConfig.files = ['main.server.ts', 'main.ts']; | ||
|
||
return JSON.stringify(tsConfig); | ||
}); | ||
|
||
await harness.writeFiles({ | ||
'src/lazy.ts': `export const foo: number = 1;`, | ||
'src/main.ts': `export async function fn () { | ||
const lazy = await import('./lazy'); | ||
return lazy.foo; | ||
}`, | ||
'src/main.server.ts': `export { fn as default } from './main';`, | ||
}); | ||
}); | ||
|
||
describe('Behavior: "Rebuild both server and browser bundles when using lazy loading"', () => { | ||
it('detect changes and errors when expected', async () => { | ||
harness.useTarget('build', { | ||
...BASE_OPTIONS, | ||
watch: true, | ||
namedChunks: true, | ||
outputHashing: OutputHashing.None, | ||
server: 'src/main.server.ts', | ||
ssr: true, | ||
}); | ||
|
||
const builderAbort = new AbortController(); | ||
const buildCount = await firstValueFrom( | ||
harness.execute({ outputLogsOnFailure: true, signal: builderAbort.signal }).pipe( | ||
timeout(20_000), | ||
concatMap(async ({ result, logs }, index) => { | ||
switch (index) { | ||
case 0: | ||
expect(result?.success).toBeTrue(); | ||
|
||
// Add valid code | ||
await harness.appendToFile('src/lazy.ts', `console.log('foo');`); | ||
|
||
break; | ||
case 1: | ||
expect(result?.success).toBeTrue(); | ||
|
||
// Update type of 'foo' to invalid (number -> string) | ||
await harness.writeFile('src/lazy.ts', `export const foo: string = 1;`); | ||
|
||
break; | ||
case 2: | ||
expect(result?.success).toBeFalse(); | ||
expect(logs).toContain( | ||
jasmine.objectContaining<logging.LogEntry>({ | ||
message: jasmine.stringMatching( | ||
`Type 'number' is not assignable to type 'string'.`, | ||
), | ||
}), | ||
); | ||
|
||
// Fix TS error | ||
await harness.writeFile('src/lazy.ts', `export const foo: string = "1";`); | ||
|
||
break; | ||
case 3: | ||
expect(result?.success).toBeTrue(); | ||
|
||
builderAbort.abort(); | ||
break; | ||
} | ||
}), | ||
count(), | ||
), | ||
); | ||
|
||
expect(buildCount).toBe(4); | ||
}); | ||
}); | ||
}); |
46 changes: 46 additions & 0 deletions
46
packages/angular_devkit/build_angular/src/tools/esbuild/angular/compilation-state.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
export class SharedTSCompilationState { | ||
#pendingCompilation = true; | ||
#resolveCompilationReady: (() => void) | undefined; | ||
#compilationReadyPromise: Promise<void> | undefined; | ||
|
||
get waitUntilReady(): Promise<void> { | ||
if (!this.#pendingCompilation) { | ||
return Promise.resolve(); | ||
} | ||
|
||
this.#compilationReadyPromise ??= new Promise((resolve) => { | ||
this.#resolveCompilationReady = resolve; | ||
}); | ||
|
||
return this.#compilationReadyPromise; | ||
} | ||
|
||
markAsReady(): void { | ||
this.#resolveCompilationReady?.(); | ||
this.#compilationReadyPromise = undefined; | ||
this.#pendingCompilation = false; | ||
} | ||
|
||
markAsInProgress(): void { | ||
this.#pendingCompilation = true; | ||
} | ||
|
||
dispose(): void { | ||
this.markAsReady(); | ||
globalSharedCompilationState = undefined; | ||
} | ||
} | ||
|
||
let globalSharedCompilationState: SharedTSCompilationState | undefined; | ||
|
||
export function getSharedCompilationState(): SharedTSCompilationState { | ||
return (globalSharedCompilationState ??= new SharedTSCompilationState()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters