试读angular源码第二章:引导模块bootstrapModule
栏目: JavaScript · 发布时间: 6年前
内容简介:今天 angularv8的正式版发了,但是除了动态路由那里没觉得有啥大变化,有点小失望
今天 angularv8的正式版发了,但是除了动态路由那里没觉得有啥大变化,有点小失望
引导模块
Angular 应用是模块化的,它拥有自己的模块化系统,称作 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
:
-
首先通过
optionsReducer递归 reduce 将编译器选项compilerOptions拍平为对象 。 -
然后调用了
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__
-
在这里,先
通过平台实例
PlatformRef的注射器injector获取了编译器实例,其实也就是coreDynamic提供的JitCompilerFactory -
然后调用 JIT 编译器工厂
JitCompilerFactory的createCompiler方法,创建编译器Compiler实例CompilerImpl -
最后通过编译器
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 的源代码,很好理解:
-
私有方法
_compileModuleAndComponents先 调用了this._loadModules,异步加载解析主模块,也就是bootstrapModule的ngModule -
在异步加载主模块之后,执行后面的回调函数,通过私有方法
_compileComponents编译主模块上的所有组件和指令 ,并通过_compileTemplate编译模板,这步先跳过,感兴趣可以自行去看 -
最后通过私有方法
_compileModule返回value 是编译过的模块工厂的Promise -
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
,
接下来在 Promise
的 then
方法里调用了 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;
});
});
});
}
复制代码
这里做的事情也不多:
-
首先获判断下是否存在配置, 默认我们启动的时候没有配置
,所以返回的是
NgZone(NgZone放到下一节讲):
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;
}
复制代码
-
接下来 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);
复制代码
-
调用
ngZone.run,启动ngZone并 让所有的 angular 程序跑在这个zone上下文环境里 -
在
ngZone.run启动 zone 之后,创建一个初始的注入器,并使用该注入器作为根模块的父注入器创建根模块实例 - 处理错误并返回
总结
这里面内容不多,用人话总结下:
-
bootstrapModule会先合并配置并调用编译模块的工厂函数compileNgModuleFactory开始编译模块 -
compileNgModuleFactory通过平台实例PlatformRef的注射器injector获取 JIT编译器工厂JitCompilerFactory,JIT 编译器工厂JitCompilerFactory又通过createCompiler方法,创建编译器Compiler实例CompilerImpl,并开始编译根模块和所有的组件,CompilerImpl调用JitCompilerJIT 编译实例 最后实际上编译是JitCompiler去编译的 -
异步编译根模块和所有的组件
, 并放入缓存中
,最后返回 value 是模块工厂
NgModuleFactory的Promise -
然后在
Promise.then()里调用bootstrapModuleFactory -
bootstrapModuleFactory创建 NgZone 实例并开始运行 zone , 让所有的 angular 程序跑在这个zone上下文环境里 -
开始运行 zone ,
创建根模块的父注入器
injector并实例化模块工厂创建模块实例NgModuleRef
以上所述就是小编给大家介绍的《试读angular源码第二章:引导模块bootstrapModule》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 试读angular源码第一章:开场与platformBrowserDynamic
- 试读angular源码第四章:angular模块及JIT编译模块
- 【源码阅读】AndPermission源码阅读
- ReactNative源码解析-初识源码
- 【源码阅读】Gson源码阅读
- Spring源码系列:BeanDefinition源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Cracking the Coding Interview
Gayle Laakmann McDowell / CareerCup / 2015-7-1 / USD 39.95
Cracking the Coding Interview, 6th Edition is here to help you through this process, teaching you what you need to know and enabling you to perform at your very best. I've coached and interviewed hund......一起来看看 《Cracking the Coding Interview》 这本书的介绍吧!