refactor(compiler-cli): move NgModule bootstrap definition to runtime in local compilation mode (#51767)

Today in local compilation mode the NgModule bootstrap definition is moved as it is into the runtime `ɵɵdefineNgModule`. This runtime was initially made for AoT full compilation mode and assumes that the bootstrap info is already flattened and resolved. This is not the case in local compilation where the bootstrap is the raw expression coming from the NgModule decorator and can be a nested array. To get around this problem we move the bootstrap along with other scope info (e.g., declarations, imports, exports) to the runtime`ɵɵsetNgModuleScope` to be further analyzed and flattened in runtime.

PR Close #51767
This commit is contained in:
Payam Valadkhan
2023-09-05 15:19:39 -04:00
committed by Pawel Kozlowski
parent 1d0fc42fc7
commit fbf3ac247b
2 changed files with 65 additions and 11 deletions
@@ -562,5 +562,55 @@ runInEachFileSystem(() => {
expect(text).toContain('either inline it or move it to a separate file');
});
});
describe('ng module bootstrap def', () => {
it('should include the bootstrap definition in ɵɵsetNgModuleScope instead of ɵɵdefineNgModule',
() => {
env.write('test.ts', `
import {NgModule} from '@angular/core';
import {App} from './some-where';
@NgModule({
declarations: [App],
bootstrap: [App],
})
export class AppModule {
}
`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents)
.toContain(
'AppModule.ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: AppModule });');
expect(jsContents)
.toContain(
'ɵɵsetNgModuleScope(AppModule, { declarations: [App], bootstrap: [App] }); })();');
});
it('should include no bootstrap definition in ɵɵsetNgModuleScope if the NgModule has no bootstrap field',
() => {
env.write('test.ts', `
import {NgModule} from '@angular/core';
import {App} from './some-where';
@NgModule({
declarations: [App],
})
export class AppModule {
}
`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents)
.toContain(
'AppModule.ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: AppModule });');
expect(jsContents)
.toContain('ɵɵsetNgModuleScope(AppModule, { declarations: [App] }); })();');
});
});
});
});
@@ -204,15 +204,11 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3CompiledExpression
const definitionMap = new DefinitionMap<R3NgModuleDefMap>();
definitionMap.set('type', meta.type.value);
// Assign bootstrap definition
if (meta.kind === R3NgModuleMetadataKind.Global) {
if (meta.bootstrap.length > 0) {
definitionMap.set('bootstrap', refsToArray(meta.bootstrap, meta.containsForwardDecls));
}
} else {
if (meta.bootstrapExpression) {
definitionMap.set('bootstrap', meta.bootstrapExpression);
}
// Assign bootstrap definition. In local compilation mode (i.e., for
// `R3NgModuleMetadataKind.LOCAL`) we assign the bootstrap field using the runtime
// `ɵɵsetNgModuleScope`.
if (meta.kind === R3NgModuleMetadataKind.Global && meta.bootstrap.length > 0) {
definitionMap.set('bootstrap', refsToArray(meta.bootstrap, meta.containsForwardDecls));
}
if (meta.selectorScopeMode === R3SelectorScopeMode.Inline) {
@@ -321,8 +317,12 @@ export function createNgModuleType(meta: R3NgModuleMetadata): o.ExpressionType {
* symbols to become tree-shakeable.
*/
function generateSetNgModuleScopeCall(meta: R3NgModuleMetadata): o.Statement|null {
const scopeMap = new DefinitionMap<
{declarations: o.Expression, imports: o.Expression, exports: o.Expression}>();
const scopeMap = new DefinitionMap<{
declarations: o.Expression,
imports: o.Expression,
exports: o.Expression,
bootstrap: o.Expression
}>();
if (meta.kind === R3NgModuleMetadataKind.Global) {
if (meta.declarations.length > 0) {
@@ -354,6 +354,10 @@ function generateSetNgModuleScopeCall(meta: R3NgModuleMetadata): o.Statement|nul
}
}
if (meta.kind === R3NgModuleMetadataKind.Local && meta.bootstrapExpression) {
scopeMap.set('bootstrap', meta.bootstrapExpression);
}
if (Object.keys(scopeMap.values).length === 0) {
return null;
}