試讀angular源碼第四章:angular模塊及JIT編譯模塊

直接看人話總結html

前言

承接上一章git

該文章項目地址github

文章地址typescript

angular 版本:8.0.0-rc.4json

歡迎看看個人類angular框架bootstrap

文章列表

試讀angular源碼第一章:開場與platformBrowserDynamic數組

試讀angular源碼第二章:引導模塊bootstrapModulepromise

試讀angular源碼第三章:初始化zone瀏覽器

試讀angular源碼第四章:angular模塊及JIT編譯模塊緩存

angular 模塊

官方介紹

NgModule 是一個帶有 @NgModule 裝飾器的類。

@NgModule 的參數是一個元數據對象,用於描述如何編譯組件的模板,以及如何在運行時建立注入器。

它會標出該模塊本身的組件、指令和管道,經過 exports 屬性公開其中的一部分,以便外部組件使用它們。

NgModule 還能把一些服務提供商添加到應用的依賴注入器中。

在以前的例子中,咱們經過 platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.error(err)); 引導初始化時,bootstrapModule 方法傳入的第一個參數就是angular 模塊 NgModule

這裏咱們要先講2個概念:JIT 和 AOT

JIT和AOT

Angular 提供了兩種方式來編譯你的應用:

  1. 即時編譯 (JIT Just-In-Time),它會在運行期間在瀏覽器中編譯你的應用
  2. 預先(AOT Ahead-Of-Time)編譯,它會在構建時編譯你的應用
  • JIT的流程

    • 編譯時
      1. 運行 ngc(angular 封裝的 tsc) 編譯 TypeScript 代碼爲 JavaScript 並提取元數據
      2. 構建項目,作些代碼打包、混淆、壓縮
      3. 部署應用
    • 瀏覽器運行時
      1. 瀏覽器下載 JavaScript 代碼
      2. 啓動 angular 應用
      3. angular 啓動 jit 編譯模式,編譯指令組件模塊提取 ngc 編譯出元數據
      4. 建立各類指令組件模塊的實例,產生視圖
  • AOT的流程

    • 代碼分析階段
      1. 運行 ngc(angular 封裝的 tsc) 編譯應用源代碼輸出編譯出的 angular 目標 Typescript 代碼並**AOT 收集器(collector)**記錄 Angular 裝飾器中的元數據 .metadata.json 文件
      2. ngc 調用 tsc 將目標 Typescript 代碼編譯成 Javascript 代碼
      3. 搖樹優化(Tree shaking)
      4. 構建項目,作些代碼打包、混淆、壓縮
      5. 部署應用
    • 瀏覽器運行時
    1. 瀏覽器下載 JavaScript 代碼
    2. 啓動 angular 應用,產生視圖

參考

但這裏咱們只討論 JIT 模式!

@NgModule

angular/packages/core/src/metadata/ng_module.ts

export interface NgModuleDecorator {
  (obj?: NgModule): TypeDecorator;
  new (obj?: NgModule): NgModule;
}

export interface NgModule {
  providers?: Provider[];
  declarations?: Array<Type<any>|any[]>;
  imports?: Array<Type<any>|ModuleWithProviders<{}>|any[]>;
  exports?: Array<Type<any>|any[]>;
  entryComponents?: Array<Type<any>|any[]>;
  bootstrap?: Array<Type<any>|any[]>;
  schemas?: Array<SchemaMetadata|any[]>;
  id?: string;
  jit?: true;
}

/** * @Annotation * @publicApi */
export const NgModule: NgModuleDecorator = makeDecorator(
    'NgModule', (ngModule: NgModule) => ngModule, undefined, undefined,
    /** * Decorator that marks the following class as an NgModule, and supplies * configuration metadata for it. * * * The `declarations` and `entryComponents` options configure the compiler * with information about what belongs to the NgModule. * * The `providers` options configures the NgModule's injector to provide * dependencies the NgModule members. * * The `imports` and `exports` options bring in members from other modules, and make * this module's members available to others. */
    (type: NgModuleType, meta: NgModule) => SWITCH_COMPILE_NGMODULE(type, meta));
複製代碼

裝飾器 @NgModule 的做用是描述 angular 模塊,並提供 元數據 支持

例如幾個經常使用的元數據:

  1. providers?: Provider[]; 依賴注入系統提供可注入項的重點
    1. 非懶加載模塊定義的 providers 能夠提供給全局任何指令管道服務,至關於 @Injectableroot
    2. 懶加載的模塊有本身的注入器,一般是 app root 注入器的子注入器,在懶加載模塊內爲單例服務
  2. declarations 屬於此模塊的組件,指令和管道的集合
  3. imports 引入其餘模塊的 export
  4. exports 處處給其餘模塊的 imports
  5. bootstrap 引導用的入口組件,一般根模塊和路由懶加載須要設置
  6. entryComponents 入口組件 不經常使用,angular 編譯器會自動將 bootstrap 編譯到裏面

@NgModulemakeDecorator 構造而來:

makeDecorator建立裝飾器

makeDecorator 用來建立 angular 裝飾器,像 @NgModule @Component @Pipe @Directive 都用改方法建立:

angular/packages/core/src/util/decorators.ts

export const ANNOTATIONS = '__annotations__';

export function makeDecorator<T>(
    name: string, 
    props?: (...args: any[]) => any, // 註釋:args 就是裝飾器的參數用來處理裝飾器參數
    parentClass?: any,
    additionalProcessing?: (type: Type<T>) => void, // 註釋:額外的處理
    typeFn?: (type: Type<T>, ...args: any[]) => void) // 註釋:用來處理class的原型
: {new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
  const metaCtor = makeMetadataCtor(props); // 註釋:建立 Metadata 的構造函數

  function DecoratorFactory(...args: any[]): (cls: Type<T>) => any {
    if (this instanceof DecoratorFactory) { // 註釋:經過 args 用來設置默認值 
      metaCtor.call(this, ...args); // 註釋:this就是DecoratorFactory工廠,也就是參數對象
      return this;
    }

    const annotationInstance = new (DecoratorFactory as any)(...args); // 註釋:註解實例實際上就是裝飾器的參數對象
    return function TypeDecorator(cls: Type<T>) { // 註釋:cls就是裝飾器裝飾的類構造函數
      if (typeFn) typeFn(cls, ...args);
      // Use of Object.defineProperty is important since it creates non-enumerable property which
      // prevents the property is copied during subclassing.
      const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
          (cls as any)[ANNOTATIONS] :
          Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
      annotations.push(annotationInstance); // 註釋:將裝飾器的處理結果存在

      if (additionalProcessing) additionalProcessing(cls);

      return cls;
    };
  }

  if (parentClass) {
    DecoratorFactory.prototype = Object.create(parentClass.prototype); // 註釋:使實例 DecoratorFactory 繼承繼承 parentClass
  }

  DecoratorFactory.prototype.ngMetadataName = name; // 註釋:裝飾器名稱會被放在原型屬性 ngMetadataName 上
  (DecoratorFactory as any).annotationCls = DecoratorFactory;
  return DecoratorFactory as any;
}

function makeMetadataCtor(props?: (...args: any[]) => any): any {
  return function ctor(...args: any[]) {
    if (props) {
      const values = props(...args);
      for (const propName in values) {
        this[propName] = values[propName];
      }
    }
  };
}
複製代碼

參數:

  1. name: string 就是裝飾器的名稱
  2. props?: (...args: any[]) => any args 就是裝飾器的參數,props 用來處理裝飾器參數,可用於默認值設置
  3. parentClass?: any 父類,提供給 DecoratorFactory 實例用來繼承
  4. additionalProcessing?: (type: Type<T>) => void 對類構造函數進行額外處理,參數是裝飾器的宿主類的構造函數
  5. typeFn?: (type: Type<T>, ...args: any[]) => void) 在裝飾器的返回函數中,會再次執行下回調函數,參數是類構造函數和參數

在這裏 makeDecorator 基本上作了這幾個事情:

  1. 經過 makeMetadataCtor 建立一個給類構造函數附加初始值的函數 ,本質上是建立 Metadata 的構造函數
  2. 若是 this 是註解工廠 DecoratorFactory 的實例,則經過上面給類構造函數附加初始值的函數,傳入 this 和裝飾器參數 args
  3. 此外則先執行 typeFn 傳入類構造函數和參數,修改類構造函數
  4. 先傳入參數建立註解工廠 DecoratorFactory 的實例 ,註解工廠方法會遞歸執行,直到 this 是註解工廠 DecoratorFactory 的實例 (註解工廠 DecoratorFactory 的實例實際上就是裝飾器的參數對象
  5. 判斷類構造函數是否存在 __annotations__:any[] 屬性,把裝飾器處理結果(註解實例===參數對象)保存在類構造函數的 __annotations__:any[] 屬性數組中,並提供給編譯器 compiler 使用
  6. 最後經過處理 DecoratorFactory 的原型,繼承父類 parentClass 並添加元數據的名字 ngMetadataName

注意這裏:DecoratorFactory.prototype = Object.create(parentClass.prototype);

經過使用 Object.create 避免執行一次 parentClass 來繼承父類

@NgModule總結

  1. 實際上,makeDecorator 的做用就是構造返回一個函數 DecoratorFactory用做 裝飾器,並建立裝飾器工廠 DecoratorFactory 實例
  2. 其實,我以爲 @NgModule 就是一個接受參數並返回函數的方法,裝飾器會把 @NgModule 傳入的元數據對象進行處理並生成註解工廠 DecoratorFactory 的實例掛在到 __annotations__:any 提供給編譯器使用

劃重點:AppModule.__annotations__

最後你們能夠打印下 (AppModule as any).__annotations__ 來進行驗證,這就是存在模塊類上的註解實例。

AppModule.__annotations__

編譯模塊

此處建議結合第二章bootstrapModule一塊兒閱讀

JIT編譯器的服務

先看下以前構建的 JitCompilerFactory 時注入過的服務,這些在後面編譯的時候會大量用到:

angular/packages/platform-browser-dynamic/src/compiler_factory.ts

/** * A set of providers that provide `JitCompiler` and its dependencies to use for * template compilation. */
export const COMPILER_PROVIDERS = <StaticProvider[]>[
  // 註釋:這裏也是一個核心點-編譯反射器
  {provide: CompileReflector, useValue: new JitReflector()},
  // 註釋:ResourceLoader- 資源加載器
  {provide: ResourceLoader, useValue: _NO_RESOURCE_LOADER},
  // 註釋:jit 摘要解析器
  {provide: JitSummaryResolver, deps: []},
  // 註釋:摘要解析器
  {provide: SummaryResolver, useExisting: JitSummaryResolver},
  {provide: Console, deps: []},
  // 註釋:語法分析器
  {provide: Lexer, deps: []},
  // 註釋:解析器器
  {provide: Parser, deps: [Lexer]},
  // 註釋:基本的HTML解析器
  {
    provide: baseHtmlParser,
    useClass: HtmlParser,
    deps: [],
  },
  // 註釋:國際化的HTML解析器
  {
    provide: I18NHtmlParser,
    useFactory: (parser: HtmlParser, translations: string | null, format: string,
                 config: CompilerConfig, console: Console) => {
      translations = translations || '';
      const missingTranslation =
          translations ? config.missingTranslation ! : MissingTranslationStrategy.Ignore;
      // 註釋:new 國際化的HTML解析器
      return new I18NHtmlParser(parser, translations, format, missingTranslation, console);
    },
    deps: [
      baseHtmlParser,
      [new Optional(), new Inject(TRANSLATIONS)],
      [new Optional(), new Inject(TRANSLATIONS_FORMAT)],
      [CompilerConfig],
      [Console],
    ]
  },
  {
    provide: HtmlParser,
    useExisting: I18NHtmlParser,
  },
  // 註釋:模板解析器
  {
    provide: TemplateParser, deps: [CompilerConfig, CompileReflector,
    Parser, ElementSchemaRegistry,
    I18NHtmlParser, Console]
  },
  { provide: JitEvaluator, useClass: JitEvaluator, deps: [] },
  // 註釋:指令規範器
  { provide: DirectiveNormalizer, deps: [ResourceLoader, UrlResolver, HtmlParser, CompilerConfig]},
  // 註釋:元數據解析器
  { provide: CompileMetadataResolver, deps: [CompilerConfig, HtmlParser, NgModuleResolver,
                      DirectiveResolver, PipeResolver,
                      SummaryResolver,
                      ElementSchemaRegistry,
                      DirectiveNormalizer, Console,
                      [Optional, StaticSymbolCache],
                      CompileReflector,
                      [Optional, ERROR_COLLECTOR_TOKEN]]},
  DEFAULT_PACKAGE_URL_PROVIDER,
  // 註釋:樣式編譯器
  { provide: StyleCompiler, deps: [UrlResolver]},
  // 註釋:view 編譯器
  { provide: ViewCompiler, deps: [CompileReflector]},
  // 註釋:NgModule編譯器
  { provide: NgModuleCompiler, deps: [CompileReflector] },
  // 註釋:編譯器配置項目
  { provide: CompilerConfig, useValue: new CompilerConfig()},
  // 註釋:JIT時,Compiler的服務供應商 CompilerImpl
  { provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver,
                                TemplateParser, StyleCompiler,
                                ViewCompiler, NgModuleCompiler,
                                SummaryResolver, CompileReflector, JitEvaluator, CompilerConfig,
                                Console]},
  // 註釋:DOM schema
  { provide: DomElementSchemaRegistry, deps: []},
  // 註釋:Element schema
  { provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
  // 註釋:URL解析器
  { provide: UrlResolver, deps: [PACKAGE_ROOT_URL]},
  // 註釋:指令解析器
  { provide: DirectiveResolver, deps: [CompileReflector]},
  // 註釋:管道解析器
  { provide: PipeResolver, deps: [CompileReflector]},
  // 註釋:模塊解析器
  { provide: NgModuleResolver, deps: [CompileReflector]},
];
複製代碼

講完了 @NgModule,回到以前的文章,看下 bootstrapModule 這個方法如何編譯模塊:

angular/packages/core/src/application_ref.ts

let compileNgModuleFactory:
    <M>(injector: Injector, options: CompilerOptions, moduleType: Type<M>) =>
        Promise<NgModuleFactory<M>> = compileNgModuleFactory__PRE_R3__;

function compileNgModuleFactory__PRE_R3__<M>( injector: Injector, options: CompilerOptions, moduleType: Type<M>): Promise<NgModuleFactory<M>> {
  const compilerFactory: CompilerFactory = injector.get(CompilerFactory);
  const compiler = compilerFactory.createCompiler([options]);
  return compiler.compileModuleAsync(moduleType);
}

@Injectable()
export class PlatformRef {
    ...
   bootstrapModule<M>(moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)| Array<CompilerOptions&BootstrapOptions> = []):Promise<NgModuleRef<M>> {
   const options = optionsReducer({}, compilerOptions);
    return compileNgModuleFactory(this.injector, options, moduleType)
        .then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options));
   }
   ...
}
複製代碼

bootstrapModule 調用了 compileNgModuleFactory 這個方法,而最後其實在 JIT 模式下,其實coreDynamic 提供的 JitCompilerFactory 建立了 CompilerImpl 實例並建立了代理 JitCompiler 去實現真正的編譯

angular/packages/platform-browser-dynamic/src/compiler_factory.ts

export class CompilerImpl implements Compiler {
  ...
  private _delegate: JitCompiler;
  ...
  constructor( injector: Injector, private _metadataResolver: CompileMetadataResolver, templateParser: TemplateParser, styleCompiler: StyleCompiler, viewCompiler: ViewCompiler, ngModuleCompiler: NgModuleCompiler, summaryResolver: SummaryResolver<Type<any>>, compileReflector: CompileReflector, jitEvaluator: JitEvaluator, compilerConfig: CompilerConfig, console: Console) {
    // 註釋:建立 JIT 編譯器
    this._delegate = new JitCompiler(
        _metadataResolver, templateParser, styleCompiler, viewCompiler, ngModuleCompiler,
        summaryResolver, compileReflector, jitEvaluator, compilerConfig, console,
        this.getExtraNgModuleProviders.bind(this));
  }
  compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> { // 註釋:異步建立模塊及其子組件
    return this._delegate.compileModuleAsync(moduleType) as Promise<NgModuleFactory<T>>;
  }
  ...
}
複製代碼

最終由 JitCompiler 執行 compileModuleAsync 方法編譯模塊

JitCompiler

編譯模塊和組件的實際工做是由 CompilerImpl 交由代理 JitCompiler 的方法 compileModuleAsync<T>(moduleType: Type<T>) 完成的:

angular/packages/compiler/src/jit/compiler.ts

export class JitCompiler {
  private _compiledTemplateCache = new Map<Type, CompiledTemplate>();
  private _compiledHostTemplateCache = new Map<Type, CompiledTemplate>();
  private _compiledDirectiveWrapperCache = new Map<Type, Type>();
  private _compiledNgModuleCache = new Map<Type, object>();
  private _sharedStylesheetCount = 0;
  private _addedAotSummaries = new Set<() => any[]>();

  constructor(
      private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
      private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
      private _ngModuleCompiler: NgModuleCompiler, private _summaryResolver: SummaryResolver<Type>,
      private _reflector: CompileReflector, private _jitEvaluator: JitEvaluator,
      private _compilerConfig: CompilerConfig, private _console: Console,
      private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {
        ...
      }
  ...
  compileModuleAsync(moduleType: Type): Promise<object> {
    // 註釋:其實 JTI 編譯在這步作的
    return Promise.resolve(this._compileModuleAndComponents(moduleType, false));
  }

  // 註釋:作了三件事: 
  //  1. 加載模塊 `this._loadModules`
  //  2. 編譯入口組件 `this._compileComponents`
  //  3. 編譯模塊 `this._compileModule`
  private _compileModuleAndComponents(moduleType: Type, isSync: boolean): SyncAsync<object> {
    // 註釋:其實調用的是這步,編譯主模塊和組件
    return SyncAsync.then(this._loadModules(moduleType, isSync), () => {  // 註釋:先加載模塊
      this._compileComponents(moduleType, null); // 註釋:異步有結果以後的回調函數,編譯主模塊上的全部入口組件 
      return this._compileModule(moduleType); // 註釋:返回編譯後的模塊工廠
    });
  }
  ...
}
複製代碼

結合第二章引導模塊總結下:

compileModuleAsync 在這裏只作了三件事:

  1. 加載模塊 this._loadModules
  2. 編譯組件 this._compileComponents
  3. 編譯模塊 this._compileModule

_loadModules加載模塊

angular/packages/compiler/src/jit/compiler.ts

export class JitCompiler {
  ...
  // 註釋:異步加載解析主模塊,也就是 bootstrap 的 ngModule
  private _loadModules(mainModule: any, isSync: boolean): SyncAsync<any> {
    const loading: Promise<any>[] = [];
    // 註釋:從元數據中得到根模塊的 __annotations__ 並格式化
    const mainNgModule = this._metadataResolver.getNgModuleMetadata(mainModule) !;
    // 註釋:過濾 AOT 模塊並異步編加載數據中所有指令組件和和管道
    // 註釋:過濾掉根模塊元數據中的 AOT 模塊
    this._filterJitIdentifiers(mainNgModule.transitiveModule.modules).forEach((nestedNgModule) => {
      // getNgModuleMetadata only returns null if the value passed in is not an NgModule
      const moduleMeta = this._metadataResolver.getNgModuleMetadata(nestedNgModule) !;
      this._filterJitIdentifiers(moduleMeta.declaredDirectives).forEach((ref) => {
        // 註釋:異步編加載數據中所有指令組件和和管道
        const promise =
            this._metadataResolver.loadDirectiveMetadata(moduleMeta.type.reference, ref, isSync);
        if (promise) {
          loading.push(promise);
        }
      });
      this._filterJitIdentifiers(moduleMeta.declaredPipes)
          .forEach((ref) => this._metadataResolver.getOrLoadPipeMetadata(ref));
    });
    // 註釋:最後所有並行 Promise
    return SyncAsync.all(loading);
  }
  ...

  // 註釋:過濾掉根模塊元數據中的 AOT 模塊
  hasAotSummary(ref: Type) { return !!this._summaryResolver.resolveSummary(ref); }

  // 註釋:過濾掉根模塊元數據中的 AOT 模塊
  private _filterJitIdentifiers(ids: CompileIdentifierMetadata[]): any[] {
    return ids.map(mod => mod.reference).filter((ref) => !this.hasAotSummary(ref));
  }
}
複製代碼

_loadModules 接受2個參數:

  1. mainModule: any 模塊類
  2. isSync: boolean 是不是同步加載 在 bootstrapModule 的時候是 false,異步加載

_loadModules 作了什麼?

  1. 首先經過 this._metadataResolver.getNgModuleMetadata 獲取到以前 makeDecorator 在模塊類上建立的靜態屬性 __annotations__ 並編譯模塊的元數據
  2. 調用 this._filterJitIdentifiers 遞歸過濾掉 AOT 模塊
  3. 調用 this._metadataResolver.loadDirectiveMetadata(moduleMeta.type.reference, ref, isSync) 異步加載所有指令組件和和管道的元數據
  4. 所有並行 Promise 並返回異步編譯的結果
  5. 最後全部被導入 AppModule 關聯的模塊的元數據都已經加載進了緩存中,包括了AppModule 開始除了懶加載模塊以外的的整個模塊樹,樹上的全部指令,組件和管道,以及全部的服務

接下來繼續看 _compileModuleAndComponents 在加載完模塊以後,調用了 this._compileComponents 編譯組件:

angular/packages/compiler/src/jit/compiler.ts

export class JitCompiler {
  // 註釋:作了三件事: 
  // 1. 加載模塊 `this._loadModules`
  // 2. 編譯入口組件 `this._compileComponents`
  // 3. 編譯模塊 `this._compileModule`
  private _compileModuleAndComponents(moduleType: Type, isSync: boolean): SyncAsync<object> {
    // 註釋:其實調用的是這步,編譯主模塊和組件
    return SyncAsync.then(this._loadModules(moduleType, isSync), () => {  // 註釋:先加載模塊
      this._compileComponents(moduleType, null); // 註釋:異步有結果以後的回調函數,編譯主模塊上的全部入口組件 
      return this._compileModule(moduleType); // 註釋:返回編譯後的模塊工廠
    });
  }
}
複製代碼

_compileComponents編譯組件

_compileComponents 方法用來編譯根模塊組件的模板:

angular/packages/compiler/src/jit/compiler.ts

export class JitCompiler {
  // 註釋:編譯主模塊上的全部組件
  // 主要目的:拿到被聲明的組件的模板、入口組件的模板,最終拿到了全部涉及的模板,放在 templates 中
  _compileComponents(mainModule: Type, allComponentFactories: object[]|null) {
    // 註釋:獲取主模塊
    const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule) !;
    const moduleByJitDirective = new Map<any, CompileNgModuleMetadata>();
    const templates = new Set<CompiledTemplate>();

    // 註釋:過濾AOT模塊
    const transJitModules = this._filterJitIdentifiers(ngModule.transitiveModule.modules);

    // 註釋:編譯各個模塊的模板,(localMod 是模塊的class)
    transJitModules.forEach((localMod) => {
      const localModuleMeta = this._metadataResolver.getNgModuleMetadata(localMod) !;
      // 註釋:指令和組件都是 declaredDirectives (在angular裏 @Component組件 繼承了 指令@Directive)
      this._filterJitIdentifiers(localModuleMeta.declaredDirectives).forEach((dirRef) => {
        moduleByJitDirective.set(dirRef, localModuleMeta);
        const dirMeta = this._metadataResolver.getDirectiveMetadata(dirRef);
        // 註釋:只編譯組件
        // 註釋:拿到全部的模板,並放在 templates:Set 中
        if (dirMeta.isComponent) {
          templates.add(this._createCompiledTemplate(dirMeta, localModuleMeta));
          if (allComponentFactories) {
            const template =
                this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta);
            templates.add(template);
            allComponentFactories.push(dirMeta.componentFactory as object);
          }
        }
      });
    });

    // 註釋:編譯入口組件的模板
    transJitModules.forEach((localMod) => {
      const localModuleMeta = this._metadataResolver.getNgModuleMetadata(localMod) !;
      this._filterJitIdentifiers(localModuleMeta.declaredDirectives).forEach((dirRef) => {
        const dirMeta = this._metadataResolver.getDirectiveMetadata(dirRef);
        if (dirMeta.isComponent) {
          dirMeta.entryComponents.forEach((entryComponentType) => {
            const moduleMeta = moduleByJitDirective.get(entryComponentType.componentType) !;
            templates.add(
                this._createCompiledHostTemplate(entryComponentType.componentType, moduleMeta));
          });
        }
      });
      localModuleMeta.entryComponents.forEach((entryComponentType) => {
        if (!this.hasAotSummary(entryComponentType.componentType)) {
          const moduleMeta = moduleByJitDirective.get(entryComponentType.componentType) !;
          templates.add(
              this._createCompiledHostTemplate(entryComponentType.componentType, moduleMeta));
        }
      });
    });

    // 註釋:執行 _compileTemplate 編譯模板
    templates.forEach((template) => this._compileTemplate(template));
  }
}
複製代碼

在這裏主要作了下面這幾件事:

  1. this._metadataResolver.getNgModuleMetadata 像以前編譯模板同樣獲取根模塊
  2. this._filterJitIdentifiers 過濾 AOT 模塊
  3. 第一次遍歷,找出全部從根模塊開始的模塊樹上被聲明的組件(declarations),並編譯其模板
  4. 第二次遍歷,找出全部從根模塊開始的模塊樹上入口的組件(entryComponents),並編譯其模板
  5. 最後編譯全部模板

至於如何編譯的模板,以後講組件的時候再說吧。

_compileComponents目的是拿到被聲明的組件的模板、入口組件的模板,最終拿到了全部涉及的模板

_compileModule編譯模塊

angular/packages/compiler/src/jit/compiler.ts

export class JitCompiler {
  ...
  // 註釋:angular 會用 Map 緩存模塊工廠,而且在須要返回編譯的模塊工廠時,優先去緩存中尋找已經被編譯過的模塊工廠
  private _compileModule(moduleType: Type): object {
    // 註釋:從緩存拿到模塊工廠
    let ngModuleFactory = this._compiledNgModuleCache.get(moduleType) !; // 註釋:讀取緩存
    if (!ngModuleFactory) {
      // 註釋:讀取模塊的元數據
      const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType) !;
      // 註釋:調用實例化 JITCompiler 時候傳入方法,建立額外的模塊服務供應商 (在 CompilerImpl 傳入)
      // Always provide a bound Compiler
      const extraProviders = this.getExtraNgModuleProviders(moduleMeta.type.reference);
       // 註釋:建立輸出上下
      const outputCtx = createOutputContext();
      // 註釋:構建編譯結果:是一個對象,只有 ngModuleFactoryVar 這麼一個屬性:ngModuleFactoryVar: "AppModuleNgFactory",內部經過構建服務供應商和模塊的AST,很複雜
      const compileResult = this._ngModuleCompiler.compile(outputCtx, moduleMeta, extraProviders);
      console.log(77777, moduleType, compileResult);
      // 註釋:動態建立出一個模塊的工廠方法
      ngModuleFactory = this._interpretOrJit(
          ngModuleJitUrl(moduleMeta), outputCtx.statements)[compileResult.ngModuleFactoryVar];
      this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
    }
    return ngModuleFactory;
  }
  ...
}
複製代碼

這裏也很簡單:

  1. 先從從緩存拿到模塊工廠函數
  2. 若是不存在工廠函數,則開始建立
  3. 讀取模塊的元數據
  4. 調用實例化 JITCompiler 時候傳入方法,建立額外的模塊服務供應商 (在 CompilerImpl 傳入)
  5. 建立輸出上下
  6. 構建編譯結果:是一個對象,只有 ngModuleFactoryVar 這麼一個屬性,估計是把編譯結果放緩存了ngModuleFactoryVar: "AppModuleNgFactory"
  7. 動態建立出一個模塊的工廠方法並返回

NgModuleCompiler模塊編譯器

模塊編譯器這裏比較複雜:

angular/packages/compiler/src/ng_module_compiler.ts

export class NgModuleCompiler {
  constructor(private reflector: CompileReflector) {}

  compile(
      ctx: OutputContext, ngModuleMeta: CompileNgModuleMetadata,
      extraProviders: CompileProviderMetadata[]): NgModuleCompileResult {
    // 註釋:生成一個關於模塊類及文件位置的對象
    const sourceSpan = typeSourceSpan('NgModule', ngModuleMeta.type);
    // 註釋:得到入口組件的工廠函數,默認就有 <ng-component/> 和 <app-root/>
    const entryComponentFactories = ngModuleMeta.transitiveModule.entryComponents;
    const bootstrapComponents = ngModuleMeta.bootstrapComponents;
    // 註釋:分析模塊及模塊引入的模塊的服務供應商
    const providerParser =
        new NgModuleProviderAnalyzer(this.reflector, ngModuleMeta, extraProviders, sourceSpan);
    // 註釋:這塊是AST了,生成了模塊中全部服務供應商的函數 AST
    const providerDefs =
        [componentFactoryResolverProviderDef(
             this.reflector, ctx, NodeFlags.None, entryComponentFactories)]
            .concat(providerParser.parse().map((provider) => providerDef(ctx, provider)))
            .map(({providerExpr, depsExpr, flags, tokenExpr}) => {
              return o.importExpr(Identifiers.moduleProviderDef).callFn([
                o.literal(flags), tokenExpr, providerExpr, depsExpr
              ]);
            });
    
    // 註釋:這塊是AST了,生成了模塊的 AST
    const ngModuleDef = o.importExpr(Identifiers.moduleDef).callFn([o.literalArr(providerDefs)]);
    const ngModuleDefFactory = o.fn(
        [new o.FnParam(LOG_VAR.name !)], [new o.ReturnStatement(ngModuleDef)], o.INFERRED_TYPE);

    // 註釋:建立一個字符串
    const ngModuleFactoryVar = `${identifierName(ngModuleMeta.type)}NgFactory`;
    // 註釋:保存在上下文中聲明中
    this._createNgModuleFactory(
        ctx, ngModuleMeta.type.reference, o.importExpr(Identifiers.createModuleFactory).callFn([
          ctx.importExpr(ngModuleMeta.type.reference),
          o.literalArr(bootstrapComponents.map(id => ctx.importExpr(id.reference))),
          ngModuleDefFactory
        ]));
    if (ngModuleMeta.id) {
      const id = typeof ngModuleMeta.id === 'string' ? o.literal(ngModuleMeta.id) :
                                                       ctx.importExpr(ngModuleMeta.id);
      const registerFactoryStmt = o.importExpr(Identifiers.RegisterModuleFactoryFn)
                                      .callFn([id, o.variable(ngModuleFactoryVar)])
                                      .toStmt();
      // 註釋:保存在上下文中
      ctx.statements.push(registerFactoryStmt);
    }
    // 註釋:返回編譯結果
    return new NgModuleCompileResult(ngModuleFactoryVar);
  }
  ...
}
複製代碼

這裏作了下面幾件事情:

  1. 生成一個關於模塊類及文件位置的對象
  2. 得到入口組件的工廠函數,默認就有 <ng-component/><app-root/>
  3. 分析模塊及模塊引入的模塊的服務供應商(provide),並生成對應的函數 AST
    provide-ast
  4. 生成模塊的 AST
  5. 最後經過把編譯結果保存在上下文中返回一個做爲 token 的對象

其實我沒太看懂爲何要轉換爲 AST,這裏面留幾個坑

總結

總結下 @NgModule 大概發生了什麼

  1. 在初始化的時候,經過 makeDecorator 生成 @NgModule 註解
  2. @NgModule 經過傳入的參數和反射,生成註解附加在模塊類的靜態屬性 __annotations__ 並提供給 JitCompiler 編譯器使用
  3. bootstrapModule 被調用時候,在 JIT 模式下建立了代理 JitCompiler 去實現真正的編譯
  4. JitCompiler 編譯模塊調用了 compileModuleAsync返回模塊工廠,而且只作了三件事:
    1. 加載模塊 this._loadModules
    2. 編譯組件 this._compileComponents
    3. 編譯模塊 this._compileModule
相關文章
相關標籤/搜索