/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/compiler/src/render3/r3_module_compiler.ts
434 строки
14 KB
Alex Rickabaugh
refactor(compiler): add support for compiling NgModules under isolatedDeclarations
23 май 2026, 00:00
Не верифицирован
23 май 2026, 00:00
06b004e
Код
Авторство
О чём код?
/** * @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.dev/license */ import {R3DeclareNgModuleFacade} from '../compiler_facade_interface'; import * as o from '../output/output_ast'; import {Identifiers as R3} from './r3_identifiers'; import {jitOnlyGuardedExpression, R3CompiledExpression, R3Reference, refsToArray} from './util'; import {DefinitionMap} from './view/util'; /** * How the selector scope of an NgModule (its declarations, imports, and exports) should be emitted * as a part of the NgModule definition. */ export enum R3SelectorScopeMode { /** * Emit the declarations inline into the module definition. * * This option is useful in certain contexts where it's known that JIT support is required. The * tradeoff here is that this emit style prevents directives and pipes from being tree-shaken if * they are unused, but the NgModule is used. */ Inline, /** * Emit the declarations using a side effectful function call, `ɵɵsetNgModuleScope`, that is * guarded with the `ngJitMode` flag. * * This form of emit supports JIT and can be optimized away if the `ngJitMode` flag is set to * false, which allows unused directives and pipes to be tree-shaken. */ SideEffect, /** * Don't generate selector scopes at all. * * This is useful for contexts where JIT support is known to be unnecessary. */ Omit, } /** * The type of the NgModule meta data. * - Global: Used for full and partial compilation modes which mainly includes R3References. * - Local: Used for the local compilation mode which mainly includes the raw expressions as appears * in the NgModule decorator. */ export enum R3NgModuleMetadataKind { Global, Local, Isolated, } interface R3NgModuleMetadataCommon { kind: R3NgModuleMetadataKind; /** * An expression representing the module type being compiled. */ type: R3Reference; /** * How to emit the selector scope values (declarations, imports, exports). */ selectorScopeMode: R3SelectorScopeMode; /** * The set of schemas that declare elements to be allowed in the NgModule. */ schemas: R3Reference[] | null; /** Unique ID or expression representing the unique ID of an NgModule. */ id: o.Expression | null; } /** * Metadata required by the module compiler in full/partial mode to generate a module def (`ɵmod`) * for a type. */ export interface R3NgModuleMetadataGlobal extends R3NgModuleMetadataCommon { kind: R3NgModuleMetadataKind.Global; /** * An array of expressions representing the bootstrap components specified by the module. */ bootstrap: R3Reference[]; /** * An array of expressions representing the directives and pipes declared by the module. */ declarations: R3Reference[]; /** * Those declarations which should be visible to downstream consumers. If not specified, all * declarations are made visible to downstream consumers. */ publicDeclarationTypes: o.Expression[] | null; /** * An array of expressions representing the imports of the module. */ imports: R3Reference[]; /** * Whether or not to include `imports` in generated type declarations. */ includeImportTypes: boolean; /** * An array of expressions representing the exports of the module. */ exports: R3Reference[]; /** * Whether to generate closure wrappers for bootstrap, declarations, imports, and exports. */ containsForwardDecls: boolean; } /** * Metadata required by the module compiler in local mode to generate a module def (`ɵmod`) for a * type. */ export interface R3NgModuleMetadataLocal extends R3NgModuleMetadataCommon { kind: R3NgModuleMetadataKind.Local; /** * The output expression representing the bootstrap components specified by the module. */ bootstrapExpression: o.Expression | null; /** * The output expression representing the declarations of the module. */ declarationsExpression: o.Expression | null; /** * The output expression representing the imports of the module. */ importsExpression: o.Expression | null; /** * The output expression representing the exports of the module. */ exportsExpression: o.Expression | null; /** * Local compilation mode always requires scope to be handled using side effect function calls. */ selectorScopeMode: R3SelectorScopeMode.SideEffect; } /** * Metadata required by the module compiler to generate a module def (`ɵmod`) for a type. */ /** * Metadata required by the module compiler in isolated mode to generate a module def (`ɵmod`) for a * type. */ export interface R3NgModuleMetadataIsolated extends R3NgModuleMetadataCommon { kind: R3NgModuleMetadataKind.Isolated; /** * Isolated declarations mode always omits scope in the generated JS definition. */ selectorScopeMode: R3SelectorScopeMode.Omit; /** * The output expression representing the imports of the module. */ importsExpression: o.Expression | null; /** * The output expression representing the exports of the module. */ exportsExpression: o.Expression | null; } export type R3NgModuleMetadata = | R3NgModuleMetadataGlobal | R3NgModuleMetadataLocal | R3NgModuleMetadataIsolated; /** * The shape of the object literal that is passed to the `ɵɵdefineNgModule()` call. */ interface R3NgModuleDefMap { /** * An expression representing the module type being compiled. */ type: o.Expression; /** * An expression evaluating to an array of expressions representing the bootstrap components * specified by the module. */ bootstrap?: o.Expression; /** * An expression evaluating to an array of expressions representing the directives and pipes * declared by the module. */ declarations?: o.Expression; /** * An expression evaluating to an array of expressions representing the imports of the module. */ imports?: o.Expression; /** * An expression evaluating to an array of expressions representing the exports of the module. */ exports?: o.Expression; /** * A literal array expression containing the schemas that declare elements to be allowed in the * NgModule. */ schemas?: o.LiteralArrayExpr; /** * An expression evaluating to the unique ID of an NgModule. * */ id?: o.Expression; } /** * Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`. */ export function compileNgModule(meta: R3NgModuleMetadata): R3CompiledExpression { const statements: o.Statement[] = []; const definitionMap = new DefinitionMap<R3NgModuleDefMap>(); definitionMap.set('type', meta.type.value); // 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) { // If requested to emit scope information inline, pass the `declarations`, `imports` and // `exports` to the `ɵɵdefineNgModule()` call directly. if (meta.declarations.length > 0) { definitionMap.set('declarations', refsToArray(meta.declarations, meta.containsForwardDecls)); } if (meta.imports.length > 0) { definitionMap.set('imports', refsToArray(meta.imports, meta.containsForwardDecls)); } if (meta.exports.length > 0) { definitionMap.set('exports', refsToArray(meta.exports, meta.containsForwardDecls)); } } else if (meta.selectorScopeMode === R3SelectorScopeMode.SideEffect) { // In this mode, scope information is not passed into `ɵɵdefineNgModule` as it // would prevent tree-shaking of the declarations, imports and exports references. Instead, it's // patched onto the NgModule definition with a `ɵɵsetNgModuleScope` call that's guarded by the // `ngJitMode` flag. const setNgModuleScopeCall = generateSetNgModuleScopeCall(meta); if (setNgModuleScopeCall !== null) { statements.push(setNgModuleScopeCall); } } else { // Selector scope emit was not requested, so skip it. } if (meta.schemas !== null && meta.schemas.length > 0) { definitionMap.set('schemas', o.literalArr(meta.schemas.map((ref) => ref.value))); } if (meta.id !== null) { definitionMap.set('id', meta.id); // Generate a side-effectful call to register this NgModule by its id, as per the semantics of // NgModule ids. statements.push( o.importExpr(R3.registerNgModuleType).callFn([meta.type.value, meta.id]).toStmt(), ); } const expression = o .importExpr(R3.defineNgModule) .callFn([definitionMap.toLiteralMap()], undefined, true); const type = createNgModuleType(meta); return {expression, type, statements}; } /** * This function is used in JIT mode to generate the call to `ɵɵdefineNgModule()` from a call to * `ɵɵngDeclareNgModule()`. */ export function compileNgModuleDeclarationExpression(meta: R3DeclareNgModuleFacade): o.Expression { const definitionMap = new DefinitionMap<R3NgModuleDefMap>(); definitionMap.set('type', new o.WrappedNodeExpr(meta.type)); if (meta.bootstrap !== undefined) { definitionMap.set('bootstrap', new o.WrappedNodeExpr(meta.bootstrap)); } if (meta.declarations !== undefined) { definitionMap.set('declarations', new o.WrappedNodeExpr(meta.declarations)); } if (meta.imports !== undefined) { definitionMap.set('imports', new o.WrappedNodeExpr(meta.imports)); } if (meta.exports !== undefined) { definitionMap.set('exports', new o.WrappedNodeExpr(meta.exports)); } if (meta.schemas !== undefined) { definitionMap.set('schemas', new o.WrappedNodeExpr(meta.schemas)); } if (meta.id !== undefined) { definitionMap.set('id', new o.WrappedNodeExpr(meta.id)); } return o.importExpr(R3.defineNgModule).callFn([definitionMap.toLiteralMap()]); } export function createNgModuleType(meta: R3NgModuleMetadata): o.ExpressionType { if (meta.kind === R3NgModuleMetadataKind.Local) { return new o.ExpressionType(meta.type.value); } if (meta.kind === R3NgModuleMetadataKind.Isolated) { return new o.ExpressionType( o.importExpr(R3.NgModuleDeclaration, [ new o.ExpressionType(meta.type.type), o.NONE_TYPE, meta.importsExpression ? o.expressionType(meta.importsExpression) : o.NONE_TYPE, meta.exportsExpression ? o.expressionType(meta.exportsExpression) : o.NONE_TYPE, ]), ); } const { type: moduleType, declarations, exports, imports, includeImportTypes, publicDeclarationTypes, } = meta; return new o.ExpressionType( o.importExpr(R3.NgModuleDeclaration, [ new o.ExpressionType(moduleType.type), publicDeclarationTypes === null ? tupleTypeOf(declarations) : tupleOfTypes(publicDeclarationTypes), includeImportTypes ? tupleTypeOf(imports) : o.NONE_TYPE, tupleTypeOf(exports), ]), ); } /** * Generates a function call to `ɵɵsetNgModuleScope` with all necessary information so that the * transitive module scope can be computed during runtime in JIT mode. This call is marked pure * such that the references to declarations, imports and exports may be elided causing these * 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; bootstrap: o.Expression; }>(); if (meta.kind === R3NgModuleMetadataKind.Global) { if (meta.declarations.length > 0) { scopeMap.set('declarations', refsToArray(meta.declarations, meta.containsForwardDecls)); } } else if (meta.kind === R3NgModuleMetadataKind.Local) { if (meta.declarationsExpression) { scopeMap.set('declarations', meta.declarationsExpression); } } if (meta.kind === R3NgModuleMetadataKind.Global) { if (meta.imports.length > 0) { scopeMap.set('imports', refsToArray(meta.imports, meta.containsForwardDecls)); } } else if (meta.kind === R3NgModuleMetadataKind.Local) { if (meta.importsExpression) { scopeMap.set('imports', meta.importsExpression); } } if (meta.kind === R3NgModuleMetadataKind.Global) { if (meta.exports.length > 0) { scopeMap.set('exports', refsToArray(meta.exports, meta.containsForwardDecls)); } } else if (meta.kind === R3NgModuleMetadataKind.Local) { if (meta.exportsExpression) { scopeMap.set('exports', meta.exportsExpression); } } if (meta.kind === R3NgModuleMetadataKind.Local && meta.bootstrapExpression) { scopeMap.set('bootstrap', meta.bootstrapExpression); } if (Object.keys(scopeMap.values).length === 0) { return null; } // setNgModuleScope(...) const fnCall = new o.InvokeFunctionExpr( /* fn */ o.importExpr(R3.setNgModuleScope), /* args */ [meta.type.value, scopeMap.toLiteralMap()], ); // (ngJitMode guard) && setNgModuleScope(...) const guardedCall = jitOnlyGuardedExpression(fnCall); // function() { (ngJitMode guard) && setNgModuleScope(...); } const iife = new o.FunctionExpr(/* params */ [], /* statements */ [guardedCall.toStmt()]); // (function() { (ngJitMode guard) && setNgModuleScope(...); })() const iifeCall = new o.InvokeFunctionExpr(/* fn */ iife, /* args */ []); return iifeCall.toStmt(); } function tupleTypeOf(exp: R3Reference[]): o.Type { const types = exp.map((ref) => o.typeofExpr(ref.type)); return exp.length > 0 ? o.expressionType(o.literalArr(types)) : o.NONE_TYPE; } function tupleOfTypes(types: o.Expression[]): o.Type { const typeofTypes = types.map((type) => o.typeofExpr(type)); return types.length > 0 ? o.expressionType(o.literalArr(typeofTypes)) : o.NONE_TYPE; }