:::info 🛣 Long way to go ~ :::
Angular Packages
Zone
Angular 使用了 Zone.js,借鉴于 Dart 语言的 Zones(多作用域 Context)。
在 Angular 中,https://github.com/angular/angular/blob/master/packages/core/src/zone/ng_zone.ts 通过 NgZone 的设计,封装 NgZone 提供
run
:在 AngularZone 中,提供统一捕获和 Trace Error 的机制(对于异步任务)runOutsideAngular
:不在 AngularZone 中,一般是 UI 无关,和 Angular 框架无关的操作(UI 渲染和错误捕获),这样可以提升 Angular 性能。
Compiler
:::warning 🚧 后续再展开细节,编译器的包是比较复杂的,基本一个 View 编译都是几 K 代码,近期没有时间研究。 :::
- JIT Compiler
- AoT Compiler
两者 Compiler 的主要在时序调用上的差异:
主要为 Angular 模块、组件等编译和连接服务。核心仅仅透出了使用 API 的声明,但是最复杂的实现过程,在 Compiler Package 中。涉及到:HTML Template 编译、加载、注入等等。
ServiceWorker
:::warning 🚧 后续再展开,近期没有时间研究。 :::
ServiceWorker
of NgModule- 封装了主要的 ServiceWorker 的业务功能实现
Platforms
:::info Angular 的多平台架构设计还是比较有意思的, :::
多平台架构:
一个 Platform
定义了一个应用如何和它的宿主环境(HostEnvironment)交互的方式 & 能力。比如:渲染、多任务调度、安全处理。这需要一层统一的抽象,以便于 Angular 的代码可以做好跨平台的设计。
Angular 提供了如下的平台包:
platform-browser
(runs in the browser’s main UI thread and uses the offline template compiler),platform-browser-dynamic
(runs in the browser’s main UI thread and uses the runtime template compiler),platform-webworker
(runs in a web worker and uses the offline compiler),platform-webworker-dynamic
(runs in a web worker and uses the runtime
template compiler) and
platform-server
(runs in the server and can uses either the offline or runtime
template compiler)
几个包之间的代码共享架构:
这套共享的结构和 React 的结构分层有点异曲同工之妙(Share 层是共享层):
Platform Browser Package
PlatformBrowser 对外抛出的 DOM 相关的关键 API:
- DOM 相关
- Browser 相关
- Security 安全相关
Dynamic Browser Pakcage
在 PlatformBrowser 的基础上,增加了:JitCompilerFactory => Compiler
的能力。see Compiler。
Angular LanguageService
Angular 还为 VSCode 实现了一套 LanguageService …
Common
HttpClient
https://github.com/angular/angular/tree/master/packages/common/http
i18n
https://github.com/angular/angular/tree/master/packages/common/locales
CommonDirectives
https://github.com/angular/angular/tree/master/packages/common/src/directives
Location
https://github.com/angular/angular/blob/master/packages/common/src/location/location.ts
Common Pipes
https://github.com/angular/angular/tree/master/packages/common/src/pipes
Core
From one demo to know core API Design
:::info 👨🏻💻 从最小的环路去理解 :::
:::success ✅ 整个 Angular Bootstrap 过程设计:WIP :::
👇 开始:
https://codesandbox.io/s/arno-angular-demo-xofde?file=/src/main.ts
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { AppModule } from "./app/app.module";
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.log(err));
platformBrowserDynamic()
此函数是由core.createPlatformFactory()
完成生成。这里是最关键的模块化启动点。
核心类和对象梳理:
Entry
总入口:[/src/core.ts](https://github.com/angular/angular/blob/master/packages/core/src/core.ts)
metadata.ts
核心 Decorator 入口 👈 可以找到 AngularCore 对外抛出的 核心元数据信息标注 APIlinker.ts
为模板模块编译 & 连接器相关
唯一的外置依赖,是用于 Polyfill 语言层的工具库:⚙️ tslib(TS Runtime Function)
NgModule decorator
- 比较抽象地使用了 makeDecorator 的抽象工厂函数。
- makeDecorator 实现了 Angular 核心类的 decorator 函数工厂,里面将 decorator 的元信息 —— annotationInstance 存储在了当前类的 annoction 属性里,便于后续做 DI 或者其它操作的时候读取。
- 实现了简单版本的
reflect-metadata
Rx.js based EventEmitter
Rx.js Based EventEmitter 后续可以参考这里面的相关实现 eventEmitter.ts
View
WIP
Utils
:::info Utils 里面类似 Google Closure 的基础 API,可以学习其「优雅实现」🎉 :::
ForwardRef
使用 ForwardRef 来表示引用一种还未实例化的引用关系。 👈 这块可以在自己实现 DI 框架的时候关注其时序问题的解决方案。
/**
* Allows to refer to references which are not yet defined.
*
* For instance, `forwardRef` is used when the `token` which we need to refer to for the purposes of
* DI is declared, but not yet defined. It is also used when the `token` which we use when creating
* a query is not yet defined.
*
* @usageNotes
* ### Example
* {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref'}
* @publicApi
*/
export function forwardRef(forwardRefFn: ForwardRefFn): Type<any> {
(<any>forwardRefFn).__forward_ref__ = forwardRef;
(<any>forwardRefFn).toString = function() {
return stringify(this());
};
return (<Type<any>><any>forwardRefFn);
}
/** Checks whether a function is wrapped by a `forwardRef`. */
export function isForwardRef(fn: any): fn is() => any {
return typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
fn.__forward_ref__ === forwardRef;
}
Global
对全局 Global 的声明 GET:https://github.com/angular/angular/blob/master/packages/core/src/util/global.ts
后续安全地获取 global 变量可以参考 👆 函数的实现 🆒。
MacroTask and MicroTask
MicroTask 挺有意思的:SourceCode。
可以看一下后续部分几个时序函数的理解,加深对 MacroTask / MicroTask 的理解:
- setTimeOut / setInterval
- requestIdlCallback
- requestAnimatonFramework
- promise.then
- generatorFuncation / iterator.next()
- Node.js 中的 nextTick()
然后配合 Zone.js 中调度相关的函数做深入理解。
Decorator Factory
[core/utils/decorators](https://github.com/angular/angular/blob/master/packages/core/src/util/decorators.ts)
👆 这块 API 的设计可以让框架层对 Decorator 的驾驭更上一层:
- 比如 ngModule Decorator 的制作过程,会更加「入戏」一些。
- 本质上来说是实现了支撑 Angular 运行时 decorator 元信息的存储和读取,类似于
reflect-metadata
这个库的 Angular 实现。
Sanitization
安全相关:https://github.com/angular/angular/tree/master/packages/core/src/sanitization
Render
V2
Render 在平台 (Platforms) 的关系:
V3
:::danger ⚠️ IVY & Render3 是最关键的设计了,后续可以重点关注一下。 :::
- 渲染相关
Interface
严格的依赖倒置遵循者:https://github.com/angular/angular/blob/master/packages/core/src/interface/lifecycle_hooks.ts
LifeCycleHook 均需要显式声明 implemented。
DI System
https://github.com/angular/angular/tree/master/packages/core/src/di
:::info 💡 Angular 的 DI 系统里面,Injector 使用的 InjectToken 不需要手动命名而是使用了类本身来保持唯一性。 :::
Angular DI 系统的特点:
- Injector 分层结构 Combo 树形依赖
- 异步控制,独立 Injecotr
- 初始化分层(PlatformInjector、ModuleInjector、ComponentInjector 等等)
- 同样类型的依赖(多实例)通过 injector 分层隔离,同时通过 修饰符灵活控制注入时的依赖析出的 方式
DI 的基础运行时序
decorator
显式声明依赖(provideServices),同时 Services 显式声明对别的服务的依赖,使用deps
字段声明- 初始化 Platform / Module / Component 等类型的时候,会创建一个依附的 injector,以顶层的
StaticInjector
的构造函数为例:Compiler、AppModule- 使用一个 Map 来记录依赖对象
recursivelyProcessProviders
是 灵魂 👻 方法,用来分析依赖关系网
constructor(
providers: StaticProvider[], parent: Injector = Injector.NULL, source: string|null = null) {
this.parent = parent;
this.source = source;
const records = this._records = new Map<any, Record>();
records.set(
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
records.set(
INJECTOR, <Record>{token: INJECTOR, fn: IDENT, deps: EMPTY, value: this, useNew: false});
this.scope = recursivelyProcessProviders(records, providers);
}
- 我们接着来关注
recursivelyProcessProviders
函数的简化版本实现: ```typescript function recursivelyProcessProviders(records: Map, provider: StaticProvider): string| null { let scope: string|null = null; provider = resolveForwardRef(provider); if (Array.isArray(provider)) { // if we have an array recurse into the array for (let i = 0; i < provider.length; i++) {
} } else if (provider && typeof provider === ‘object’ && provider.provide) { // At this point we have what looks like a provider: {provide: ?, ….} let token = resolveForwardRef(provider.provide); const resolvedProvider = resolveProvider(provider); } records.set(token, resolvedProvider); return scope; }scope = recursivelyProcessProviders(records, provider[i]) || scope;
// 解析当前的依赖类型和注入类型 function resolveProvider(provider: SupportedProvider): Record { const deps = computeDeps(provider); let fn: Function = IDENT; let value: any = EMPTY; let useNew: boolean = false; let provide = resolveForwardRef(provider.provide); if (USE_VALUE in provider) { // We need to use USE_VALUE in provider since provider.useValue could be defined as undefined. value = (provider as ValueProvider).useValue; } else if ((provider as FactoryProvider).useFactory) { fn = (provider as FactoryProvider).useFactory; } else if ((provider as ExistingProvider).useExisting) { // Just use IDENT } else if ((provider as StaticClassProvider).useClass) { useNew = true; fn = resolveForwardRef((provider as StaticClassProvider).useClass); } else if (typeof provide == ‘function’) { useNew = true; fn = provide; } // 依赖 Map 中,描述一个 InjectToken 的依赖四元组 return {deps, fn, useNew, value}; }
// 生成关系依赖网和修改作用域符 function computeDeps(provider: StaticProvider): DependencyRecord[] { let deps: DependencyRecord[] = EMPTY; const providerDeps: any[] = (provider as ExistingProvider & StaticClassProvider & ConstructorProvider).deps; if (providerDeps && providerDeps.length) { deps = []; for (let i = 0; i < providerDeps.length; i++) { let options = OptionFlags.Default; let token = resolveForwardRef(providerDeps[i]); if (Array.isArray(token)) { for (let j = 0, annotations = token; j < annotations.length; j++) { const annotation = annotations[j]; if (annotation instanceof Optional || annotation == Optional) { options = options | OptionFlags.Optional; } else if (annotation instanceof SkipSelf || annotation == SkipSelf) { options = options & ~OptionFlags.CheckSelf; } else if (annotation instanceof Self || annotation == Self) { options = options & ~OptionFlags.CheckParent; } else if (annotation instanceof Inject) { token = (annotation as Inject).token; } else { token = resolveForwardRef(annotation); } } } deps.push({token, options}); } } else if ((provider as ExistingProvider).useExisting) { const token = resolveForwardRef((provider as ExistingProvider).useExisting); deps = [{token, options: OptionFlags.Default}]; } return deps; }
<br />值得一提的是,经过 DI 修饰符比如:`@Optional()` 或者 `@SkipSelf()` 修饰的依赖,其注入的 Provider 函数都会被外部包裹一层 `XXXDecoratorFactory`,用于依赖注入修饰的识别,这块挺有意思的。本质上是:
- @classParamDecortor(OriginalClassService)
- => ClassParamDectoratorFactory()
- => DecoratedClass
- => DecoratedClassInstance
通过上述的算法分析,运算生成依赖树,并记录关键规则在 Injector 的 records 中。最后在通过 Injector 的 get 函数执行依赖分析和析出算法,简化实现如下:
```typescript
get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
get(token: any, notFoundValue?: any): any;
get(token: any, notFoundValue?: any, flags: InjectFlags = InjectFlags.Default): any {
const records = this._records;
let record = records.get(token);
let lastInjector = setCurrentInjector(this);
try {
return tryResolveToken(token, record, records, this.parent, notFoundValue, flags);
} catch (e) {
return catchInjectorError(e, token, 'StaticInjectorError', this.source);
} finally {
setCurrentInjector(lastInjector);
}
}
// 核心算法,递归根据 record 四元组析出注入实例
function resolveToken(
token: any, record: Record|undefined|null, records: Map<any, Record|null>, parent: Injector,
notFoundValue: any, flags: InjectFlags): any {
let value;
if (record && !(flags & InjectFlags.SkipSelf)) {
// If we don't have a record, this implies that we don't own the provider hence don't know how
// to resolve it.
value = record.value;
if (value == CIRCULAR) {
throw Error(NO_NEW_LINE + 'Circular dependency');
} else if (value === EMPTY) {
record.value = CIRCULAR;
let obj = undefined;
let useNew = record.useNew;
let fn = record.fn;
let depRecords = record.deps;
let deps = EMPTY;
if (depRecords.length) {
deps = [];
for (let i = 0; i < depRecords.length; i++) {
const depRecord: DependencyRecord = depRecords[i];
const options = depRecord.options;
const childRecord =
options & OptionFlags.CheckSelf ? records.get(depRecord.token) : undefined;
deps.push(tryResolveToken(
// Current Token to resolve
depRecord.token,
// A record which describes how to resolve the token.
// If undefined, this means we don't have such a record
childRecord,
// Other records we know about.
records,
// If we don't know how to resolve dependency and we should not check parent for it,
// than pass in Null injector.
!childRecord && !(options & OptionFlags.CheckParent) ? Injector.NULL : parent,
options & OptionFlags.Optional ? null : Injector.THROW_IF_NOT_FOUND,
InjectFlags.Default));
}
}
record.value = value = useNew ? new (fn as any)(...deps) : fn.apply(obj, deps);
}
} else if (!(flags & InjectFlags.Self)) {
value = parent.get(token, notFoundValue, InjectFlags.Default);
} else if (!(flags & InjectFlags.Optional)) {
value = Injector.NULL.get(token, notFoundValue);
} else {
value = Injector.NULL.get(token, typeof notFoundValue !== 'undefined' ? notFoundValue : null);
}
return value;
}
Extra
是否应该使用 path 作为 Alias in projects?
"paths": { "selenium-webdriver": ["./node_modules/@types/selenium-webdriver/index.d.ts"], "rxjs/*": ["./node_modules/rxjs/*"], "@angular/*": ["./packages/*"], "zone.js/*": ["./packages/zone.js/*"], "angular-in-memory-web-api": ["./packages/misc/angular-in-memory-web-api/index.ts"] }
@angular/upgrade
也是神奇 … 提供了类似运行时的升级兼容方案 … 良心啊- 良心注释
:::success
👨🏻💻 U1S1:Angular 是我目前读过的「最干净」的源代码库,里面的注释和设计是相当完备的,非常值得开原框架学习。
他们对 typedoc
的规范和使用极为用心。
:::
- Angular theta 的 含义,Angular 代码里面有很多 theta 开头的方法,其实是用于标记「私有」的变量,private field,不愿意作为公共 API 暴露出去的方法。
- 对
publicApi
的严格管控,在注释上都会增加「@publicApi
」的注释提醒,同时使用了工具:Tsickle
以及TS-API-Guardian
去防止文件循环引用以及对外 API 暴露的控制检测,保证在社区开发中公共 API 暴露的稳定性。
Reference
- Reading Angular Source Code
- Angular Theta
- Zone.js 源码初探
- 神书:AngularSourceCode Digest 👈 收好不谢