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

直接看人話總結html

開場來個自我介紹

angular 源碼閱讀vue

項目地址react

該文章項目地址git

angular 版本:8.0.0-rc.4github

歡迎看看個人類angular框架typescript

文章列表

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

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

試讀angular源碼第三章:初始化zone設計模式

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

關於爲何寫這麼一個項目

聲明:僅僅爲我的閱讀源碼的理解,不必定徹底正確,還須要大佬的指點。

其實市面上不少關於 vue和react 的源碼閱讀,可是基本上沒有看到關於 angular 系統性地源碼閱讀。

並且大部分人一據說 angular 就會本能地避開。

angular三連

但其實不是的,在我眼裏 angular 只是套用了不少後端已有的概念,好比 DI,好比 AOT 等。

以前我寫過一個類 angular 的框架 InDiv,基本上實現了大多數 ng 的裝飾器。

並且在寫這個項目的時候,我從 angular 上學到了不少。

此次,則但願經過閱讀 angular 的源代碼,學習到更多谷歌在設計模式上的運用,學習到更多代碼優化和結構的運用。

也有一點私心,但願更多人說 ng大法好 ,哈哈。

一塊兒學習angular

前提

但願看以前讀者能先了解一下 typescripy 和 angular 的基礎概念,由於文章裏會出現大量的 DI,服務商啊這類詞

  1. typescript
  2. angular文檔

angular的基礎架構

項目結構

項目下只有三個文件夾:angular docs 和 my-demo

- angular: 註釋版angular的ts源代碼
- docs: 文檔位置
- my-demo: 啓動的一個demo項目
複製代碼

經過 tsconfig 把 angular 別名設置到 angular這個文件夾,來閱讀下 ts 版本的源碼。

啓動app

在瀏覽器端,每一個 angular app都是從 main.ts 開始的。

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));
複製代碼

至於啓動項目,都是這一行 platformBrowserDynamic().bootstrapModule(AppModule) 開始的。

在 angular 的世界中,全部的app都是由 platformBrowserDynamic() 提供的 bootstrapModule 方法引導根模塊或主模塊啓動的。

platform平臺

angular 抽象出 platform,來實現跨平臺。

實例化 angular 根模塊的 bootstrapModule 的方法在瀏覽器端來自 @angular/platform-browser-dynamic

其實除了 @angular/platform-browser-dynamic 以外還有 @angular/platform-browser

這兩個模塊的主要區別是編譯方式的不一樣, platform-browser-dynamic 提供 JIT 編譯,也就是說編譯在瀏覽器內完成,而 platform-browser 提供 AOT 編譯,編譯在本地完成。

至於區別

platformBrowserDynamic

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

/** * @publicApi */
export const platformBrowserDynamic = createPlatformFactory(
    platformCoreDynamic, 'browserDynamic', INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS);
複製代碼

platformBrowserDynamic 方法很簡單,就是調用建立平臺的工廠方法 createPlatformFactory 返回的一個返回值是平臺實例 PlatformRef 的函數

createPlatformFactory

angular/packages/core/src/application_ref.ts

/**
 * Creates a factory for a platform
 * 
 * 1. 判斷是否已經建立過了
 * 2. 判斷是否有父 `Factory`
 * 3. 若是有父 `Factory` 就把調用 `Factory` 時傳入的 `Provider` 和調用 `createPlatformFactory` 傳入的 `Provider` 合併,而後調用父 `Factory`
 * 4. 若是沒有父 `Factory` ,先建立一個 `Injector` ,而後去建立 `PlatformRef` 實例
 *
 * @publicApi
 */
export function createPlatformFactory(
    parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null,
    name: string, providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) =>
    PlatformRef {
  const desc = `Platform: ${name}`;
  const marker = new InjectionToken(desc);
  return (extraProviders: StaticProvider[] = []) => {
    let platform = getPlatform();
    // 註釋:判斷是否存在平臺實例
    if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
      if (parentPlatformFactory) {
        // 註釋:調用父平臺方法
        parentPlatformFactory(
            providers.concat(extraProviders).concat({provide: marker, useValue: true}));
      } else {
        const injectedProviders: StaticProvider[] =
            providers.concat(extraProviders).concat({provide: marker, useValue: true});
        // 註釋:Injector.create建立平臺實例,並獲取設置爲全局平臺實例
        createPlatform(Injector.create({providers: injectedProviders, name: desc}));
      }
    }
    return assertPlatform(marker);
  };
}
複製代碼

該方法接受三個參數:

  1. parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null 返回父平臺工廠實例的方法

  2. name: string 平臺的名字

  3. providers: StaticProvider[] = [] DI的服務提供者

  4. 首先經過 InjectionToken 建立一個 Platform: ${name}值提供商

  5. 而後返回一個方法,接受服務提供者 extraProviders?: StaticProvider[],返回一個平臺實例 PlatformRef

createPlatformFactory 返回的方法

  1. 獲取當前平臺實例
  2. 若是當前平臺實例不存在而且不存在 AllowMultipleToken 這個容許多個令牌的服務提供者
    1. 父級平臺工廠方法 parentPlatformFactory 存在,則合併服務提供商並遞歸調用 parentPlatformFactory
    2. 父級平臺工廠方法 parentPlatformFactory 不存在,則使用注入器建立實例方法 Injector.create 建立實例平臺實例並用 createPlatform 設置爲全局的平臺實例
  3. 調用 assertPlatform 確認 IOC 容器中存在 該 marker 的平臺實例並返回

因此建立平臺實例的順序上,應該是 合併 browserDynamic 的 provider => 合併 coreDynamic 的 provider => 合併 provider 並建立 core

大概用人話描述就是:

  1. 判斷是否已經建立過了
  2. 判斷是否有父 Factory
  3. 若是有父 Factory 就把調用 Factory 時傳入的 Provider 和調用 createPlatformFactory 傳入的 Provider 合併,而後調用父 Factory
  4. 若是沒有父 Factory ,先建立一個 Injector ,而後去建立 PlatformRef 實例

createPlatform

angular/packages/core/src/application_ref.ts

let _platform: PlatformRef;

/** * Creates a platform. * Platforms have to be eagerly created via this function. * * @publicApi */
export function createPlatform(injector: Injector): PlatformRef {
  if (_platform && !_platform.destroyed &&
      !_platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
    throw new Error(
        'There can be only one platform. Destroy the previous one to create a new one.');
  }
  _platform = injector.get(PlatformRef);
  // 註釋:初始化平臺時將執行的函數,平臺browserDynamic提供
  const inits = injector.get(PLATFORM_INITIALIZER, null);
  if (inits) inits.forEach((init: any) => init());
  return _platform;
}
複製代碼

_platform 是全局的惟一平臺實例。

建立平臺實例關鍵方法,傳入服務注入器實例 injector 返回平臺實例:

  1. 確認全局的平臺實例存在,狀態不是被銷燬,而且不存在多個平臺實例
  2. 從注入器中獲取平臺實例
  3. injector.get(PLATFORM_INITIALIZER, null) 獲取初始化平臺時須要執行的函數並執行

回過頭看 platformBrowserDynamic

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

/** * @publicApi */
export const platformBrowserDynamic = createPlatformFactory(
    platformCoreDynamic, 'browserDynamic', INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS);
複製代碼

重點來了:INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS

這個 providers 究竟提供了什麼服務?

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

/** * @publicApi */
export const INTERNAL_BROWSER_DYNAMIC_PLATFORM_PROVIDERS: StaticProvider[] = [
  INTERNAL_BROWSER_PLATFORM_PROVIDERS, // 註釋:此處會注入初始化的幾個方法
  {
    provide: COMPILER_OPTIONS,
    useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl, deps: []}]},
    multi: true
  },
  {provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
];
複製代碼

除了 COMPILER_OPTIONSPLATFORM_ID,大概重點就是 INTERNAL_BROWSER_PLATFORM_PROVIDERS 了吧。

INTERNAL_BROWSER_PLATFORM_PROVIDERS 來自 @angular/platform-browser

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

export const INTERNAL_BROWSER_PLATFORM_PROVIDERS: StaticProvider[] = [
  {provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID},
  {provide: PLATFORM_INITIALIZER, useValue: initDomAdapter, multi: true}, // 註釋:初始化的方法
  {provide: PlatformLocation, useClass: BrowserPlatformLocation, deps: [DOCUMENT]},
  {provide: DOCUMENT, useFactory: _document, deps: []},
];
複製代碼

@angular/platform-browser 提供了一些瀏覽器端的ng實現:

  1. PLATFORM_INITIALIZER 是初始化須要執行的方法集合 這個很重要
  2. DOCUMENT 瀏覽器端的 document_document 工廠方法返回 document

在上面,createPlatform 的時候,會 const inits = injector.get(PLATFORM_INITIALIZER, null); if (inits) inits.forEach((init: any) => init()); 依次執行 PLATFORM_INITIALIZER 注入的工廠方法。

那麼來看看 initDomAdapter 吧:

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

export function initDomAdapter() {
  BrowserDomAdapter.makeCurrent();
  BrowserGetTestability.init();
}
複製代碼
  1. BrowserDomAdapter.makeCurrent(); 經過 BrowserDomAdapter 的靜態方法實例化一個 BrowserDomAdapter 全局DOM適配器 ,具體就是實現並封裝了一些在瀏覽器端的方法,具體的能夠看 angular/packages/platform-browser/src/browser/browser_adapter.ts 中的 class BrowserDomAdapter extends GenericBrowserDomAdapter
  2. BrowserGetTestability.init(); 則是初始化 angular 的測試,這個就沒看了

回過頭看下,在建立 platformBrowserDynamic 時候,傳入了返回父平臺實例的方法 platformCoreDynamic

platformCoreDynamic

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

import {COMPILER_OPTIONS, CompilerFactory, PlatformRef, StaticProvider, createPlatformFactory, platformCore} from '@angular/core';
import {JitCompilerFactory} from './compiler_factory';

/** * A platform that included corePlatform and the compiler. * * @publicApi */
export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [
  {provide: COMPILER_OPTIONS, useValue: {}, multi: true},
  {provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS]},
]);
複製代碼

platformCoreDynamic 又傳入了

  1. 來自 @angular/core 的 平臺核心 platformCore
  2. 平臺名 coreDynamic
  3. 2個靜態服務提供者:編譯選項 COMPILER_OPTIONSplatformDynamicJIT編譯器工廠 JitCompilerFactory

JitCompilerFactory

重點來了

一塊兒看下 JitCompilerFactory

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

/** * @publicApi */
export class JitCompilerFactory implements CompilerFactory {
  private _defaultOptions: CompilerOptions[];

  /* @internal */
  constructor(defaultOptions: CompilerOptions[]) {
    const compilerOptions: CompilerOptions = {
      useJit: true,
      defaultEncapsulation: ViewEncapsulation.Emulated,
      missingTranslation: MissingTranslationStrategy.Warning,
    };

    this._defaultOptions = [compilerOptions, ...defaultOptions];
  }
  createCompiler(options: CompilerOptions[] = []): Compiler {
    const opts = _mergeOptions(this._defaultOptions.concat(options));
    const injector = Injector.create([
      // 註釋:編譯器 Compiler 在這裏被替換成 CompilerImpl 
      COMPILER_PROVIDERS, {
        provide: CompilerConfig,
        useFactory: () => {
          return new CompilerConfig({
            // let explicit values from the compiler options overwrite options
            // from the app providers
            useJit: opts.useJit,
            jitDevMode: isDevMode(),
            // let explicit values from the compiler options overwrite options
            // from the app providers
            defaultEncapsulation: opts.defaultEncapsulation,
            missingTranslation: opts.missingTranslation,
            preserveWhitespaces: opts.preserveWhitespaces,
          });
        },
        deps: []
      },
      opts.providers !
    ]);
    return injector.get(Compiler);
  }
}
複製代碼

編譯器在 COMPILER_PROVIDERS 做爲服務提供商被提供給注射器這裏很重要,後面的編譯器會大量用到

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]},
];
複製代碼

最後,其實也是建立了一個 injector,而後獲取了 編譯器實例 Compiler,因此:

大概就是 @angular/platform-browser-dynamic 提供 JIT 編譯 的緣由了吧。

platformCore

angular/packages/core/src/platform_core_providers.ts

import {PlatformRef, createPlatformFactory} from './application_ref';
import {PLATFORM_ID} from './application_tokens';
import {Console} from './console';
import {Injector, StaticProvider} from './di';
import {TestabilityRegistry} from './testability/testability';

const _CORE_PLATFORM_PROVIDERS: StaticProvider[] = [
  // Set a default platform name for platforms that don't set it explicitly.
  {provide: PLATFORM_ID, useValue: 'unknown'},
  // 註釋:在這裏 PlatformRef 被加入了 injector 並在 createPlatformFactory 中實例化
  {provide: PlatformRef, deps: [Injector]},
  {provide: TestabilityRegistry, deps: []},
  {provide: Console, deps: []},
];

/** * This platform has to be included in any other platform * * @publicApi */
export const platformCore = createPlatformFactory(null, 'core', _CORE_PLATFORM_PROVIDERS);
複製代碼

platformCore 則是建立了一個返回根平臺工廠實例的方法,並設置了4個基礎的DI的服務提供者

  1. PLATFORM_ID 平臺id
  2. PlatformRef 在這裏 PlatformRef 被加入了 injector 並在後續的 createPlatformFactory 中經過 createPlatform(Injector.create({providers: injectedProviders, name: desc})); 平臺實例會被實例化
  3. TestabilityRegistry 可測試性註冊表 測試相關
  4. Console 頗有意思 angular 把 Console 做爲服務注入了DI,可是 Console 只實現了 log和warn兩個方法

PlatformRef

angular/packages/core/src/application_ref.ts

@Injectable()
export class PlatformRef {
  private _modules: NgModuleRef<any>[] = [];
  private _destroyListeners: Function[] = [];
  private _destroyed: boolean = false;

  /** @internal */
  constructor(private _injector: Injector) {}

  bootstrapModuleFactory<M>(moduleFactory: NgModuleFactory<M>, options?: BootstrapOptions):
      Promise<NgModuleRef<M>> {
        ...
  }

  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));
  }

  private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
    ...
  }

  onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }

  get injector(): Injector { return this._injector; }

  destroy() {
    if (this._destroyed) {
      throw new Error('The platform has already been destroyed!');
    }
    this._modules.slice().forEach(module => module.destroy());
    this._destroyListeners.forEach(listener => listener());
    this._destroyed = true;
  }

  get destroyed() { return this._destroyed; }
}
複製代碼

PlatformRef 就是平臺實例的類,有一些方法和屬性等,例如幾個關鍵的方法

  1. bootstrapModule 引導根模塊的方法
  2. bootstrapModuleFactory 實例模塊的工廠方法,會運行 zone.js 並監聽事件
  3. destroy 銷燬平臺實例的方法

這個咱們放到後文去說吧

總結

調用 platformBrowserDynamic() 並生成平臺實例 PlatformRef 時大概經歷了這些:

  1. 調用 createPlatformFactory 合併平臺 browserDynamicproviders 並觸發父級平臺 coreDynamic 的平臺工廠函數 平臺 browserDynamic 提供了 PLATFORM_INITIALIZER 平臺初始化函數和 BrowserDomAdapter 全局DOM適配器這個服務供應商
  2. 調用 createPlatformFactory 合併平臺 coreDynamicproviders 並觸發父級平臺 core 的平臺工廠函數 平臺 coreDynamic 提供了 JitCompilerFactory 運行時編譯器,JitCompilerFactory 又經過建立 COMPILER_PROVIDERS 建立了編譯器實例 因此 @angular/platform-browser-dynamic 提供 JIT運行時 編譯
  3. 平臺 core 提供了 PlatformRef 平臺實例這個服務供應商
  4. 因爲平臺 core 無父級平臺,調用 Injector.create 建立 PlatformRef 實例,並賦值給全局惟一的平臺實例 _platform
  5. createPlatform 建立 PlatformRef 的時候,實例化一個 BrowserDomAdapter 全局DOM適配器 ,具體就是實現並封裝了一些在瀏覽器端的方法
  6. 最後斷言,確認存在 PlatformRef 實例,並返回 PlatformRef 實例

因此大概,@angular/platform-browser-dynamic 提供了運行時編譯,實現並封裝了瀏覽器方法

相關文章
相關標籤/搜索