承接上文。筆者以前將一個angular項目的啓動過程分爲了兩步: 建立平臺獲得 PlatformRef
,以及執行平臺引用提供的方法編譯根模塊 AppModule
。本文就將着眼於建立好的平臺,從angular的茫茫源代碼中看看整個AppModule的編譯過程。html
從外界使用的 bootstrapModule
方法入手。首先angular把皮球踢給了私有方法 _bootstrapModuleWithZone
,而後皮球又踢給了 compileModuleAsync
方法,這個方法比較調皮,直接進入是一個抽象方法,說明它有一個繼承而且在前面做爲服務被注入了,它就是!在建立爸爸平臺 platformCoreDynamic
時注入的編譯器工廠中提供的 createCompiler
方法返回的編譯器實例,也就是 JitCompiler
。進入 JitCompiler
果不其然找到了 compileModuleAsync
方法的實現。bootstrap
// 啓動過程當中就是用這個異步編譯模塊 compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> { return Promise.resolve(this._compileModuleAndComponents(moduleType, false)); }
壞消息是皮球如今踢給了私有方法 _compileModuleAndComponents
:promise
// 這個就是最底層編譯模塊的方法 private _compileModuleAndComponents<T>( moduleType: Type<T>, isSync: boolean ): SyncAsync<NgModuleFactory<T>> { // 至關於 把第一個參數的結果做爲第二個表達式的參數 // 但在這裏只是保證兩個參數順序執行 // 最後作了三件事 _loadModules _compileComponents _compileModule return SyncAsync.then(this._loadModules(moduleType, isSync), () => { // 加載好模塊後編譯組件 this._compileComponents(moduleType, null); // 編譯模塊並返回 return this._compileModule(moduleType); }); }
稍微感覺一下這個 SyncAsync 對象,很厲害:緩存
export const SyncAsync = { // 斷言同步 也就是說 若是是個Promise 那就拋出 不是的話直接返回 assertSync: <T>(value: SyncAsync<T>): T => { if (isPromise(value)) { throw new Error(`Illegal state: value cannot be a promise`); } return value; }, // 除了適配普通Promise外還能處理非Promise的狀況 將value做爲參數傳入cb then: <T, R>( value: SyncAsync<T>, cb: (value: T) => R | Promise<R>| SyncAsync<R> ): SyncAsync<R> => { // 若是是Promise則繼續這個promise(用cb接上),不然的話直接執行cb 仍是至關於接上 強行接上不是Promise的狀況 return isPromise(value) ? value.then(cb) : cb(value); }, all: <T>(syncAsyncValues: SyncAsync<T>[]): SyncAsync<T[]> => { // 只要裏面有Promise 那就執行 Promise.all(並行執行這些Promise) 一個都沒有嘛那就直接返回 return syncAsyncValues.some(isPromise) ? Promise.all(syncAsyncValues) : syncAsyncValues as T[]; } };
如今看來,模塊編譯的重頭戲就在這三個方法裏面了:框架
_loadModules 負責加載模塊元數據 _compileComponents 負責編譯組件 _compileModule 負責編譯模塊
加載過程是這樣的:異步
private _loadModules(mainModule: any, isSync: boolean): SyncAsync<any> { const loading: Promise<any>[] = []; // 最終會把全部任務放到這裏 用SyncAsync 串起來執行 // 拿到模塊元數據 裏面包含了全部模塊 以及模塊涉及的服務 指令 管道 const mainNgModule = this._metadataResolver.getNgModuleMetadata(mainModule) !; // 遍歷元數據中的模塊 this._filterJitIdentifiers(mainNgModule.transitiveModule.modules).forEach((nestedNgModule) => { // getNgModuleMetadata only returns null if the value passed in is not an NgModule // 遞歸獲取模塊元數據的萬惡之源 getNgModuleMetadata const moduleMeta = this._metadataResolver.getNgModuleMetadata(nestedNgModule) !; // 遍歷這些模塊的元數據拿到其聲明組件的元數據 this._filterJitIdentifiers(moduleMeta.declaredDirectives).forEach((ref) => { const promise = this._metadataResolver.loadDirectiveMetadata(moduleMeta.type.reference, ref, isSync); // 這貨會把指令元數據(包括組件)放到緩存 返回的是null if (promise) { loading.push(promise); } }); // 遍歷這些模塊元數據拿到其聲明管道的元數據 this._filterJitIdentifiers(moduleMeta.declaredPipes) .forEach((ref) => this._metadataResolver.getOrLoadPipeMetadata(ref)); }); return SyncAsync.all(loading); }
表面上代碼量還不算大,不過裏面還踢了幾回皮球,並且存在遞歸,工做量很是恐怖,所作的事情以下:ide
@NgModule({})
中的配置信息)至此全部與AppModule關聯的模塊的全部元數據都已經加載進了緩存中,包括了從 AppModule 展開的整個模塊樹,樹上的全部指令和管道的配置,以及全部的服務。this
沒有看明白的最後幾行代碼:code
let evalResult: any; if (!this._compilerConfig.useJit) { evalResult = interpretStatements(outputContext.statements); } else { evalResult = jitStatements( // 拼接獲得具體工廠方法 templateJitUrl(template.ngModule.type, template.compMeta), outputContext.statements); } const viewClass = evalResult[compileResult.viewClassVar]; const rendererType = evalResult[compileResult.rendererTypeVar]; // 到這一步 模板、用到的管道、樣式表都已經處理好了 還建立了應該是組件的工廠方法 // 可是最後這個compile 好像把這裏獲得的 rendererType 一一賦值到了組件元數據中 細節待研究 template.compiled(viewClass, rendererType);
不明覺厲的 jitStatements 方法:component
function evalExpression( sourceUrl: string, ctx: EmitterVisitorContext, vars: {[key: string]: any} ): any { let fnBody = `${ctx.toSource()}\n//# sourceURL=${sourceUrl}`; const fnArgNames: string[] = []; const fnArgValues: any[] = []; for (const argName in vars) { // 遍歷添加變量 fnArgNames.push(argName); fnArgValues.push(vars[argName]); } if (isDevMode()) { // using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise // E.g. ``` // function anonymous(a,b,c // /**/) { ... }``` // We don't want to hard code this fact, so we auto detect it via an empty function first. const emptyFn = new Function(...fnArgNames.concat('return null;')).toString(); const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1; fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, sourceUrl, headerLines).toJsComment()}`; } return new Function(...fnArgNames.concat(fnBody))(...fnArgValues); } // 路徑 語句 好像是組合了一個新的function // 看來是用來造工廠方法的 export function jitStatements(sourceUrl: string, statements: o.Statement[]): {[key: string]: any} { const converter = new JitEmitterVisitor(); // 新的發射訪問器 const ctx = EmitterVisitorContext.createRoot(); // 新的發射訪問器上下文 converter.visitAllStatements(statements, ctx); // 訪問全部語句 converter.createReturnStmt(ctx); // 建立返回語句 return evalExpression(sourceUrl, ctx, converter.getArgs()); // 路徑 上下文 參數 獲得一個動態建立的方法 }
Anyway, all in all, whatever, 如今已經編譯好了組件,放在了緩存裏面。
前面執行 _loadModules 僅是加載了整個模塊樹的元數據,如今要正式編譯它們了:
private _compileModule<T>(moduleType: Type<T>): NgModuleFactory<T> { // 從緩存拿到模塊工廠 let ngModuleFactory = this._compiledNgModuleCache.get(moduleType) !; if (!ngModuleFactory) { // 緩存中沒有 const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType) !; // 從新拿到模塊元數據 // Always provide a bound Compiler const extraProviders = [this._metadataResolver.getProviderMetadata(new ProviderMeta( Compiler, {useFactory: () => new ModuleBoundCompiler(this, moduleMeta.type.reference)}))]; const outputCtx = createOutputContext(); // 建立輸出上下文 const compileResult = this._ngModuleCompiler.compile(outputCtx, moduleMeta, extraProviders); // 執行編譯 if (!this._compilerConfig.useJit) { ngModuleFactory = interpretStatements(outputCtx.statements)[compileResult.ngModuleFactoryVar]; } else { // ----------------- ngModuleFactory = jitStatements( // 傳入路徑和語句 獲得一個object key爲字符串 value爲 生成的工廠方法 ngModuleJitUrl(moduleMeta), outputCtx.statements, )[compileResult.ngModuleFactoryVar]; // 從這個object中拿到 key爲模塊變量的值 看來是造了一個工廠方法 } this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory); // 設置工廠方法的緩存 } return ngModuleFactory; }
看上去還算比較明瞭,作的事情以下:
獲得模塊的工廠方法以後,就跟以前平臺的建立過程鏈接上了,使用這個工廠建立出模塊引用,並注入一個NgZone以完成整個模塊的啓動。
比較掃興的地方有這些:
已知的情報有這些: