试读angular源码第二章:引导模块bootstrapModule

栏目: JavaScript · 发布时间: 4年前

内容简介:今天 angularv8的正式版发了,但是除了动态路由那里没觉得有啥大变化,有点小失望

今天 angularv8的正式版发了,但是除了动态路由那里没觉得有啥大变化,有点小失望

承接上一章

项目地址

文章地址

angular 版本:8.0.0-rc.4

欢迎看看我的类angular框架

引导模块

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
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 接受2个参数:

moduleType: Type<M>
compilerOptions: (CompilerOptions&BootstrapOptions)| Array<CompilerOptions&BootstrapOptions> = []

这里有个很有意思的 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>> {
  const compilerFactory: CompilerFactory = injector.get(CompilerFactory);
  const compiler = compilerFactory.createCompiler([options]);
  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 创建编译器实例的时候,其实是在这里注入了服务供应商 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) {
    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 执行 的:

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> {
    return Promise.resolve(this._compileModuleAndComponents(moduleType, false)); // 注释:其实 JTI 编译在这步做的
  }

  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
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
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 返回一个 value 是 ngModuleFactory 模块工厂的 Promise

接下来在 Promisethen 方法里调用了 bootstrapModuleFactory

angular/packages/core/src/application_ref.ts
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
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. 异步编译根模块和所有的组件并放入缓存中 ,最后返回 value 是模块工厂 NgModuleFactoryPromise
  4. 然后在 Promise.then() 里调用 bootstrapModuleFactory
  5. bootstrapModuleFactory 创建 NgZone 实例并开始运行 zone 让所有的 angular 程序跑在这个 zone 上下文环境里
  6. 开始运行 zone , 创建根模块的父注入器 injector 并实例化模块工厂创建模块实例 NgModuleRef

以上所述就是小编给大家介绍的《试读angular源码第二章:引导模块bootstrapModule》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

面向对象葵花宝典:思想、技巧与实践

面向对象葵花宝典:思想、技巧与实践

李运华 编著 / 电子工业出版社 / 2015-12 / 69

《面向对象葵花宝典:思想、技巧与实践》系统地讲述了面向对象技术的相关内容,包括面向对象的基本概念、面向对象开发的流程、面向对象的各种技巧,以及如何应用面向对象思想进行架构设计。在讲述相关知识或技术的时候,除了从“是什么”这个角度进行介绍外,更加着重于从“为什么”和“如何用”这两个角度进行剖析,力争让读者做到“知其然,并知其所以然”,从而达到在实践中既能正确又能优秀地应用面向对象的相关技术和技巧。 ......一起来看看 《面向对象葵花宝典:思想、技巧与实践》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具