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

直接看人話總結git

前言

今天 angularv8的正式版發了,可是除了路由懶加載那裏沒以爲有啥大變化,有點小失望github

承接上一章typescript

該文章項目地址bootstrap

文章地址api

angular 版本:8.0.0-rc.4數組

歡迎看看個人類angular框架緩存

文章列表

試讀angular源碼第一章:開場與platformBrowserDynamicapp

試讀angular源碼第二章:引導模塊bootstrapModule框架

試讀angular源碼第三章:初始化zone異步

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

引導模塊

Angular 應用是模塊化的,它擁有本身的模塊化系統,稱做 NgModule

關於 NgModule

一個 NgModule 就是一個容器,用於存放一些內聚的代碼塊,這些代碼塊專一於某個應用領域、某個工做流或一組緊密相關的功能。

它能夠包含一些組件、服務提供商或其它代碼文件,其做用域由包含它們的 NgModule 定義。 它還能夠導入一些由其它模塊中導出的功能,並導出一些指定的功能供其它 NgModule 使用。

每一個 Angular 應用都至少有一個 NgModule 類,也就是根模塊,它習慣上命名爲 AppModule,並位於一個名叫 app.module.ts 的文件中。

引導這個根模塊就能夠啓動你的應用

當 bootstrap(引導)根模塊以後,NgModule 會繼而實例化元數據中 bootstrap

bootstrap 應用的主視圖,稱爲根組件。它是應用中全部其它視圖的宿主。只有根模塊才應該設置這個 bootstrap 屬性

bootstrapModule

bootstrapModule 是在上一節 platformBrowserDynamic() 返回的平臺實例 PlatformRef 中的一個方法,用於引導啓動實例根模塊。

angular/packages/core/src/application_ref.ts

@Injectable()
export class PlatformRef {
  ...
  bootstrapModule<M>(
      moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)|
      Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> {
    // 註釋:bootstrapModule` 首先經過 `optionsReducer` 遞歸 reduce 將編譯器選項 `compilerOptions` 拍平爲對象
    const options = optionsReducer({}, compilerOptions);
    // 註釋:這裏獲取到編譯後的模塊工廠,而後返回給 bootstrapModuleFactory建立模塊
    return compileNgModuleFactory(this.injector, options, moduleType)
        .then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options));
  }
  ...
}
複製代碼

bootstrapModule 接受2個參數:

  1. moduleType: Type<M> 根模塊
  2. compilerOptions: (CompilerOptions&BootstrapOptions)| Array<CompilerOptions&BootstrapOptions> = [] 編譯器選項,默認是空數組

Type

這裏有個頗有意思的 typescript 寫法:Type<M>

angular/packages/core/src/interface/type.ts

export interface Type<T> extends Function { new (...args: any[]): T; }
複製代碼

接口 Type 繼承 Function ,其實 Type<T> 能夠說是 class 的類型

在這裏,bootstrapModule

  1. 首先經過 optionsReducer 遞歸 reduce 將編譯器選項 compilerOptions 拍平爲對象
  2. 而後調用了 compileNgModuleFactory 傳入平臺實例的注射器 injector ,編譯器選項和要引導實例化的根模塊。

compileNgModuleFactory

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>> {
  // 註釋:其實就是平臺coreDynamic 的服務商 JitCompilerFactory
  const compilerFactory: CompilerFactory = injector.get(CompilerFactory);
  // 註釋:調用 JitCompilerFactory 建立編譯器實例 CompilerImpl
  const compiler = compilerFactory.createCompiler([options]);
  // 註釋:異步建立 ngmodule 模塊工廠 (CompilerImpl 經過代理 CompilerImpl 去編譯)
  return compiler.compileModuleAsync(moduleType);
}
複製代碼

compileNgModuleFactory 在這裏其實就是 compileNgModuleFactory__PRE_R3__

  1. 在這裏,先經過平臺實例 PlatformRef 的注射器 injector 獲取了編譯器實例,其實也就是 coreDynamic 提供的 JitCompilerFactory
  2. 而後調用 JIT 編譯器工廠 JitCompilerFactorycreateCompiler 方法,建立編譯器 Compiler 實例 CompilerImpl
  3. 最後經過編譯器 Compiler 實例 CompilerImpl 異步編譯給定的 NgModule 及其全部組件

coreDynamic 提供的 JitCompilerFactory 調用 createCompiler 建立編譯器實例 Compiler 的時候,實際上是在這裏注入的服務供應商 CompilerImpl

因此最後建立了的編譯器實例 Compiler 實際上是 CompilerImpl

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

{ provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver....]}
複製代碼

CompilerImpl

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

export class CompilerImpl implements Compiler {
  private _delegate: JitCompiler;
  public readonly injector: Injector;
  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));
    this.injector = injector;
  }
  ...
  // 註釋:異步建立模塊及其子組件
  compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> {
    return this._delegate.compileModuleAsync(moduleType) as Promise<NgModuleFactory<T>>;
  }
  ...
}
複製代碼

因此 compileNgModuleFactory 在異步建立模塊工廠和組件 compiler.compileModuleAsync(moduleType) 時,其實調用的是 CompilerImpl 實例 的 compileModuleAsync

而在 JTT 編譯器實例化的時候,會實例一個 JitCompiler 當作代理去編譯,因此實際上異步建立模塊工廠和組件這個方法具體是由 JitCompiler 實例的方法 compileModuleAsync 執行的:

JitCompilerJIT編譯器

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 調用了 _compileModuleAndComponents,並返回一個 Promise

這裏邏輯比較複雜,大概講下,具體的在後面 angular模塊 的時候再詳細講解,很好理解:

  1. 加載模塊:私有方法 _compileModuleAndComponents調用了 this._loadModules,異步加載解析主模塊,也就是 bootstrapModulengModule
  2. 編譯組件:在異步加載主模塊以後,執行後面的回調函數,經過私有方法 _compileComponents 編譯主模塊上的全部組件,並經過 _compileTemplate 編譯模板(這步先跳過,後面講到編譯組件的時候會講)
  3. 編譯模塊:最後經過私有方法 _compileModule 返回value 是編譯過的模塊工廠的 Promise
  4. Promise 會調用下面的異步方法 then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options))

兩次導入同一個模塊

這裏有個地方也頗有意思,官網上的模塊常見問題上有這樣的一個問題:

若是我兩次導入同一個模塊會怎麼樣?

答案裏有一句:當三個模塊全都導入模塊'A'時,Angular 只會首次遇到時加載一次模塊'A',以後就不會這麼作了,以前一直不知道爲何,此次看到了這樣的一段代碼:

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

export class JitCompiler {
   ...
   private _compileModule(moduleType: Type): object {
     // 註釋:從緩存中得到編譯過的模塊
    let ngModuleFactory = this._compiledNgModuleCache.get(moduleType) !;
    if (!ngModuleFactory) {
      const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType) !;
      // Always provide a bound Compiler
      const extraProviders = this.getExtraNgModuleProviders(moduleMeta.type.reference);
      const outputCtx = createOutputContext();
      const compileResult = this._ngModuleCompiler.compile(outputCtx, moduleMeta, extraProviders);
      ngModuleFactory = this._interpretOrJit(
          ngModuleJitUrl(moduleMeta), outputCtx.statements)[compileResult.ngModuleFactoryVar];
      this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory);
    }
    return ngModuleFactory;
   }
   ...
}
複製代碼

angular 會用 Map 緩存模塊,而且在須要返回編譯的模塊工廠時,優先去緩存中尋找已經被編譯過的模塊

bootstrapModuleFactory

angular/packages/core/src/application_ref.ts

@Injectable()
export class PlatformRef {
  ...
  bootstrapModule<M>(
      moduleType: Type<M>, compilerOptions: (CompilerOptions&BootstrapOptions)|
      Array<CompilerOptions&BootstrapOptions> = []): Promise<NgModuleRef<M>> {
    // 註釋:bootstrapModule` 首先經過 `optionsReducer` 遞歸 reduce 將編譯器選項 `compilerOptions` 拍平爲對象
    const options = optionsReducer({}, compilerOptions);
    // 註釋:這裏獲取到編譯後的模塊工廠,而後返回給 bootstrapModuleFactory建立模塊
    return compileNgModuleFactory(this.injector, options, moduleType)
        .then(moduleFactory => this.bootstrapModuleFactory(moduleFactory, options));
  }
  ...
}
複製代碼

回一下上面,bootstrapModule 方法調用了 compileNgModuleFactory 返回一個 value 是 ngModuleFactory 模塊工廠的 Promise

接下來在 Promisethen 方法裏調用了 bootstrapModuleFactory

bootstrapModuleFactory引導模塊的工廠方法

angular/packages/core/src/application_ref.ts

@Injectable()
export class PlatformRef {
  ...
   bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):
      Promise<NgModuleRef<M>> {
    // Note: We need to create the NgZone _before_ we instantiate the module,
    // as instantiating the module creates some providers eagerly.
    // So we create a mini parent injector that just contains the new NgZone and
    // pass that as parent to the NgModuleFactory.
    const ngZoneOption = options ? options.ngZone : undefined;
    const ngZone = getNgZone(ngZoneOption);
    const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}];
    // Attention: Don't use ApplicationRef.run here,
    // as we want to be sure that all possible constructor calls are inside `ngZone.run`!
    return ngZone.run(() => {
      const ngZoneInjector = Injector.create(
          {providers: providers, parent: this.injector, name: moduleFactory.moduleType.name});
      const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);
      const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
      if (!exceptionHandler) {
        throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
      }
      moduleRef.onDestroy(() => remove(this._modules, moduleRef));
      ngZone !.runOutsideAngular(
          () => ngZone !.onError.subscribe(
              {next: (error: any) => { exceptionHandler.handleError(error); }}));
      return _callAndReportToErrorHandler(exceptionHandler, ngZone !, () => {
        const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);
        initStatus.runInitializers();
        return initStatus.donePromise.then(() => {
          this._moduleDoBootstrap(moduleRef);
          return moduleRef;
        });
      });
    });
   }
   ...
}
複製代碼

這裏作的事情也很少:

  1. 首先獲判斷下是否存在配置,默認咱們啓動的時候沒有配置,因此返回的是 NgZoneNgZone放到下一節講):

angular/packages/core/src/application_ref.ts

function getNgZone(ngZoneOption?: NgZone | 'zone.js' | 'noop'): NgZone {
  let ngZone: NgZone;

  if (ngZoneOption === 'noop') {
    ngZone = new NoopNgZone();
  } else {
    ngZone = (ngZoneOption === 'zone.js' ? undefined : ngZoneOption) ||
        new NgZone({enableLongStackTrace: isDevMode()});
  }
  return ngZone;
}
複製代碼
  1. 接下來 angualr 建立了一個包含 ngZone 的 providers,做爲根模塊的父注入器

angular/packages/core/src/application_ref.ts

// 註釋:會被 onInvoke 執行
const ngZoneInjector = Injector.create(
       {providers: providers, parent: this.injector, name: moduleFactory.moduleType.name});
   const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);
複製代碼
  1. 調用 ngZone.run ,啓動 ngZone讓全部的 angular 程序跑在這個 zone 上下文環境裏
  2. ngZone.run 啓動 zone 以後,建立一個初始的注入器,並使用該注入器做爲根模塊的父注入器建立根模塊實例
  3. 處理錯誤並返回

總結

這裏面內容很少,用人話總結下:

  1. bootstrapModule 會先合併配置並調用編譯模塊的工廠函數 compileNgModuleFactory 開始編譯模塊
  2. compileNgModuleFactory 經過平臺實例 PlatformRef 的注射器 injector 獲取 JIT編譯器工廠 JitCompilerFactory,JIT 編譯器工廠 JitCompilerFactory 又經過 createCompiler 方法,建立編譯器 Compiler 實例 CompilerImpl,並開始編譯根模塊和全部的組件,CompilerImpl 調用 JitCompiler JIT 編譯實例 最後實際上編譯是JitCompiler去編譯的
  3. JitCompiler 加載模塊 => 編譯組件 => 編譯模塊
  4. 異步編譯根模塊和全部的組件並放入緩存中,最後返回 value 是模塊工廠 NgModuleFactoryPromise
  5. 而後在 Promise.then() 裏調用 bootstrapModuleFactory
  6. bootstrapModuleFactory 建立 NgZone 實例並開始運行 zone讓全部的 angular 程序跑在這個 zone 上下文環境裏
  7. 開始運行 zone ,建立根模塊的父注入器 injector 並實例化模塊工廠建立模塊實例 NgModuleRef
相關文章
相關標籤/搜索