开场来个自我介绍
angular 源码阅读
文章列表
试读angular源码第一章:开场与platformBrowserDynamic
试读angular源码第二章:引导模块bootstrapModule
关于为什么写这么一个项目
声明:仅仅为个人阅读源码的理解,不一定完全正确,还需要大佬的指点。
其实市面上很多关于 vue和react 的源码阅读,但是基本上没有看到关于 angular 系统性地源码阅读。
而且大部分人一听说 angular 就会本能地避开。
但其实不是的,在我眼里 angular 只是套用了很多后端已有的概念,比如 DI,比如 AOT 等。
之前我写过一个类 angular 的框架 InDiv,基本上实现了大多数 ng 的装饰器。
而且在写这个项目的时候,我从 angular 上学到了很多。
这次,则希望通过阅读 angular 的源代码,学习到更多谷歌在设计模式上的运用,学习到更多代码优化和结构的运用。
也有一点私心,希望更多人说 ng大法好 ,哈哈。
前提
希望看之前读者能先了解一下 typescripy 和 angular 的基础概念,因为文章里会出现大量的 DI,服务商啊这类词
项目结构
项目下只有三个文件夹: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
*
* @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});
createPlatform(Injector.create({providers: injectedProviders, name: desc}));
}
}
return assertPlatform(marker);
};
}
复制代码
该方法接受三个参数:
-
parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null
返回父平台工厂实例的方法 -
name: string
平台的名字 -
providers: StaticProvider[] = []
DI的服务提供者 -
首先通过
InjectionToken
创建一个Platform: ${name}
的值提供商 -
然后返回一个方法,接受服务提供者
extraProviders?: StaticProvider[]
,返回一个平台实例PlatformRef
createPlatformFactory
返回的方法
- 获取当前平台实例
- 如果当前平台实例不存在并且不存在
AllowMultipleToken
这个允许多个令牌的服务提供者- 父级平台工厂方法
parentPlatformFactory
存在,则合并服务提供商并递归调用parentPlatformFactory
- 父级平台工厂方法
parentPlatformFactory
不存在,则使用注入器创建实例方法Injector.create
创建实例平台实例并用createPlatform
设置为全局的平台实例
- 父级平台工厂方法
- 调用
assertPlatform
确认 IOC 容器中存在 该marker
的平台实例并返回
所以创建平台实例的顺序上,应该是 合并 browserDynamic 的 provider => 合并 coreDynamic 的 provider => 合并 provider 并创建 core
大概用人话描述就是:
- 判断是否已经创建过了
- 判断是否有父
Factory
- 如果有父
Factory
就把调用Factory
时传入的Provider
和调用createPlatformFactory
传入的Provider
合并,然后调用父Factory
- 如果没有父
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);
const inits = injector.get(PLATFORM_INITIALIZER, null);
if (inits) inits.forEach((init: any) => init());
return _platform;
}
复制代码
_platform
是全局的唯一平台实例。
创建平台实例关键方法,传入服务注入器实例 injector
返回平台实例:
- 确认全局的平台实例存在,状态不是被销毁,并且不存在多个平台实例
- 从注入器中获取平台实例
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_OPTIONS
和 PLATFORM_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实现:
PLATFORM_INITIALIZER
是初始化需要执行的方法集合 这个很重要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();
}
复制代码
BrowserDomAdapter.makeCurrent();
通过BrowserDomAdapter
的静态方法实例化一个BrowserDomAdapter
全局DOM适配器 ,具体就是实现并封装了一些在浏览器端的方法,具体的可以看angular/packages/platform-browser/src/browser/browser_adapter.ts
中的class BrowserDomAdapter extends GenericBrowserDomAdapter
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
又传入了
- 来自
@angular/core
的 平台核心platformCore
- 平台名
coreDynamic
- 2个静态服务提供者:编译选项
COMPILER_OPTIONS
和platformDynamic
的JIT编译器工厂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_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()},
{provide: ResourceLoader, useValue: _NO_RESOURCE_LOADER},
{provide: JitSummaryResolver, deps: []},
{provide: SummaryResolver, useExisting: JitSummaryResolver},
{provide: Console, deps: []},
{provide: Lexer, deps: []},
{provide: Parser, deps: [Lexer]},
{
provide: baseHtmlParser,
useClass: HtmlParser,
deps: [],
},
{
provide: I18NHtmlParser,
useFactory: (parser: HtmlParser, translations: string | null, format: string,
config: CompilerConfig, console: Console) => {
translations = translations || '';
const missingTranslation =
translations ? config.missingTranslation ! : MissingTranslationStrategy.Ignore;
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]},
{ provide: ViewCompiler, deps: [CompileReflector]},
{ provide: NgModuleCompiler, deps: [CompileReflector] },
{ provide: CompilerConfig, useValue: new CompilerConfig()},
{ provide: Compiler, useClass: CompilerImpl, deps: [Injector, CompileMetadataResolver,
TemplateParser, StyleCompiler,
ViewCompiler, NgModuleCompiler,
SummaryResolver, CompileReflector, JitEvaluator, CompilerConfig,
Console]},
{ provide: DomElementSchemaRegistry, deps: []},
{ provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
{ 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的服务提供者
PLATFORM_ID
平台idPlatformRef
在这里PlatformRef
被加入了injector
并在后续的createPlatformFactory
中通过createPlatform(Injector.create({providers: injectedProviders, name: desc}));
平台实例会被实例化TestabilityRegistry
可测试性注册表 测试相关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>> {
const options = optionsReducer({}, compilerOptions);
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
就是平台实例的类,有一些方法和属性等,例如几个关键的方法
bootstrapModule
引导根模块的方法bootstrapModuleFactory
实例模块的工厂方法,会运行 zone.js 并监听事件destroy
销毁平台实例的方法
这个我们放到后文去说吧
总结
调用 platformBrowserDynamic()
并生成平台实例 PlatformRef
时大概经历了这些:
- 调用
createPlatformFactory
合并平台browserDynamic
的providers
并触发父级平台coreDynamic
的平台工厂函数 平台browserDynamic
提供了PLATFORM_INITIALIZER
平台初始化函数和BrowserDomAdapter
全局DOM适配器这个服务供应商 - 调用
createPlatformFactory
合并平台coreDynamic
的providers
并触发父级平台core
的平台工厂函数 平台coreDynamic
提供了JitCompilerFactory
运行时编译器,JitCompilerFactory
又通过创建COMPILER_PROVIDERS
创建了编译器实例 所以@angular/platform-browser-dynamic
提供 JIT运行时 编译 - 平台
core
提供了PlatformRef
平台实例这个服务供应商 - 由于平台
core
无父级平台,调用Injector.create
创建PlatformRef
实例,并赋值给全局唯一的平台实例_platform
- 在
createPlatform
创建PlatformRef
的时候,实例化一个BrowserDomAdapter
全局DOM适配器 ,具体就是实现并封装了一些在浏览器端的方法 - 最后断言,确认存在
PlatformRef
实例,并返回PlatformRef
实例
所以大概,@angular/platform-browser-dynamic
提供了运行时编译,实现并封装了浏览器方法