http://www.cnblogs.com/ztwBlog/p/6209759.htmlcss
這是我用來進行實驗的代碼,它是基於quickstart項目,並根據aot文檔修改獲得的。各位能夠用它來進行探索,也能夠本身基於quickstart進行修改(我的建議後者)。html
2018年2月17日更新:最近又作了2個小Demo用來研究Angular的編譯和打包,基於Angular5,一個使用rollup,一個使用webpack,(rollup目前沒法作到Angular的lazy loading)。不只項目文件結構很是簡潔,並且使用ngc(Angular compiler)的輸出做爲打包的輸入,這意味着:你不只能夠修改ts代碼而後查看ngc輸出有何變化,並且能夠修改ngc輸出而後查看最終的應用會如何運行,相似於玩「彙編」的感受,我相信這能加深學習者對Angular的理解甚至開啓源碼學習之路。前端
Angular應用由許多組件、指令、管道等組成,而且每一個組件有本身的HTML模板,它們按照Angular規定的語法進行組織。然而Angular的語法並不能被瀏覽器直接理解。爲了讓瀏覽器能運行咱們寫的項目,這些組件、指令、管道和HTML模板必須先被Angular編譯器編譯成瀏覽器可執行的Javascript。webpack
這個問題至關於:「爲何不讓用戶像之前同樣,寫瀏覽器能直接執行的JS代碼?」git
對於Angular來講,簡練的js代碼執行起來不高效(從時間、內存、文件大小的角度),高效的js代碼寫起來不簡練。爲了讓Angular既易於書寫又能擁有極高的效率,咱們能夠先用一種簡練的Angular語法表達咱們語義,而後讓編譯器根據咱們寫的源代碼編譯出同等語義的、真正用來執行的、但難以閱讀和手寫的js代碼。程序員
內存、文件大小的效率提高比較容易理解,Angular編譯器會輸出儘量優化、簡潔(犧牲可讀性)的代碼。時間上的效率提高很大程度來自於Angular2的變化檢測代碼對於Javascript虛擬機更友好,簡單來講就是爲 每一個組件都生成一段本身的變化檢測代碼,直接對這個組件的每個綁定逐一檢查,而不是像AngularJS同樣,對全部組件都同一個 通用的檢測算法。能夠閱讀 參考資料5的 Why we need compilation in Angular? 段落。
普通的typescript項目須要用typescript編譯器(tsc)來編譯,而ngc是專用於Angular項目的tsc替代者。它內部封裝了tsc,還額外增長了用於Angular的選項、輸出額外的文件。
截圖自ng-conf視頻,除以上三種輸出以外ngc還能夠產生ngfactory
、ngstyle
文件。如視頻中所說,圖中三種輸出是Angular library(第三方庫,好比Angular Material)須要發佈的,ngfactory
、ngstyle
應該由library的使用者在編譯本身的Angular項目的時候產生(tsconfig
中的angularCompilerOptions.skipTemplateCodegen
字段能夠控制AOT是否產生這2種文件)。github
根據 最新的講座,在AOT模式下輸出的是ts代碼而不是js代碼。在JIT模式下直接輸出js代碼。tsc讀取tsconfig配置文件的
compilerOptions
部分,ngc讀取angularCompilerOptions
部分。webAngular文檔:There is actually only one Angular compiler. The difference between AOT and JIT is a matter of timing and tooling.
Angular編譯有兩種:Ahead-of-time (AOT) 和 just-in-time (JIT)。可是實際上使用的是同一個編譯器,AOT和JIT的區別只是編譯的時機和編譯所使用的工具庫。算法Angular文檔對.metadata.json的解釋。
.metadata.json
文件是Angular編譯器產生的,它用json的形式記錄了源.ts
中decorator信息、依賴注入信息,從而Angular二次編譯時再也不須要從.ts
中提取metadata(從而不須要.ts
源代碼的參與)。二次編譯的情形:第三方庫做者進行第一次編譯,產生圖中展現的三種文件併發布(不須要發佈.ts
源代碼),而後,庫的用戶將這些庫文件與本身的項目一塊兒編譯(第二次編譯),產生可運行的應用。若是你是Angular library的開發者而且但願你的library支持用戶進行AOT,那麼你須要發佈.metadata.json
和.js
文件,不然,你不須要.metadata.json
。typescript
JIT通常經歷的步驟:
tsc
將Typescript代碼(包括咱們寫的,以及Angular框架、Angular編譯器代碼)編譯成JavaScript代碼。Angular啓動,Angular調用Angular編譯器,將Angular源代碼(Javascript代碼)編譯成瀏覽器真正執行的Javascript目標代碼(也就是後面會講的NgFactories
)。
Angular的啓動源於main.js(由main.ts編譯獲得)的執行。
NgFactories
),產生了咱們看到的應用。AOT通常經歷的步驟:
用ngc
編譯應用,其中包括兩步:
NgFactories
)。這一步是Angular編譯的核心,咱們在後文仔細研究。後面將反覆說起「AOT步驟2.1」。ngc
調用tsc
將應用的Typescript代碼編譯成Javascript代碼(包括2.1產生的、咱們寫的源代碼、Angular框架的Typescript代碼)。將ts編譯爲js的過程當中,能發現Angular程序中的類型錯誤,好比class沒有定義a屬性你卻去訪問它。
哪些代碼是須要編譯的?根據 tsconfig-aot.json的"files"字段,以app.module.ts
和main.ts
爲起點,直接或間接import
的全部.ts
都須要編譯。固然,Lazy loading module因爲沒有被import
而不會被加入bundle中,可是 Angular AOT Webpack 插件會智能地找到Lazy loading module並將它編譯成另一個bundle。
搖樹優化(Tree shaking),將沒有用的代碼刪掉。
Angular文檔:Tree shaking and AOT compilation are separate steps. Tree shaking can only target JavaScript code(目前的工具只能對Javascript代碼進行搖樹優化). AOT compilation converts more of the application to JavaScript, which in turn makes more of the application "tree shakable".
如下是發生在客戶端(用戶瀏覽器)的步驟:
NgFactories
的Javascript代碼,所以Angular直接用它們來建立各類組件的實例,產生了咱們看到的應用。Angular編譯器輸入NgModule,編譯其中的entryComponents指定的那些組件。對每一個entryComponents都產生對應的ComponentFactory類型,保存在一個ComponentFactoryResolver類型中。最後輸出NgModuleFactory類型。
咱們知道,組件的模板中能夠引用別的組件,從而構成了 組件樹。entryComponents就是組件樹的 根節點,每個entryComponents都引伸出一顆組件樹。編譯器從一個entryComponent出發,就能編譯到組件樹中的全部組件。雖然編譯器爲 每一個組件都生成了工廠函數,可是隻須要將 entryComponents的工廠函數保存在ComponentFactoryResolver對象中就夠了,由於 父組件工廠在建立實例的時候會遞歸調用子組件的工廠。所以運行時只須要調用根組件的工廠函數,就能獲得一顆組件樹。爲何產生的都是類型而不是對象?由於編譯是靜態的,編譯器只能依賴於靜態的數據(編譯器只是靜態地提取分析decorators和metadata;編譯器不會執行源代碼、也不知道咱們定義的那些函數是幹什麼的),而且產生靜態的結果(輸出客戶端要執行代碼),只有類型這種靜態的信息可以用代碼來表示。而對象是動態的,它是運行時在內存中的一段數據,不能用ts/js代碼來表示。
NgModules是編譯組件的上下文:編譯一個組件的時候,除了須要本組件的模板和metadata信息,編譯器還須要知道當前NgModule中聲明的其餘組件、指令、管道,由於在這個組件的template中可能使用它們。因此,不像AngularJS,組件、指令、管道不是全局有效的,只有聲明(declare)了它們的NgModule,或者import它們所在的NgModule,才能使用它們,不然編譯報錯。這有助於在大型項目中隔離功能模塊、防止命名(selector)衝突。
在運行時,Angular會使用NgModuleFactory建立出模塊的實例:NgModuleRef。
在NgModuleRef中有一個重要的屬性:componentFactoryResolver,它就是剛纔那個ComponentFactoryResolver類型的實例,給它一個組件類(類型在運行時的形態,即function),它會給你返回對應的ComponentFactory類型實例。
NgFactories
是瀏覽器真正執行的代碼(若是是Typescript形式的,則須要先編譯成Javascript)。每一個組件、NgModule都會生成對應的工廠。組件工廠中包含了建立組件、渲染組件——這涉及DOM操做、執行變化檢測——獲取oldValue和newValue並對比、銷燬組件的邏輯。當須要產生某個組件的實例的時候,Angular用組件工廠來實例化一個組件對象。NgModule
實例也是Angular用NgModule factory來建立的。
Angular文檔:JIT compilation generates these same NgFactories in memory where they are largely invisible. AOT compilation reveals them as separate, physical files.
其實不管是AOT仍是JIT,angular-complier都輸出NgFactories
,只不過AOT產生的輸出到*.ngfactory.ts
文件中,JIT產生的輸出到客戶端內存中。Angular文檔:Each component factory creates an instance of the component at runtime by combining the original class file and a JavaScript representation of the component's template. Note that the original component class is still referenced internally by the generated factory.
每個component factory能夠在運行時建立組件的實例,經過組合組件類(好比classAppComponent
)和組件模板的JavaScript表示。注意,在*.ngfactory.ts
中,仍然引用源文件中的組件類(見下例)。
這是步驟2.1產生的其中一個文件app.component.ngfactory.ts
:
/** * @fileoverview This file is generated by the Angular template compiler. * Do not edit. * @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride} */ /* tslint:disable */ import * as i0 from './app.component.css.shim.ngstyle'; import * as i1 from '@angular/core'; import * as i2 from '../../../src/app/app.component'; import * as i3 from '@angular/common'; import * as i4 from '@angular/forms'; import * as i5 from './child1.component.ngfactory'; import * as i6 from '../../../src/app/child1.component'; const styles_AppComponent:any[] = [i0.styles]; export const RenderType_AppComponent:i1.RendererType2 = i1.ɵcrt({encapsulation:0,styles:styles_AppComponent, data:{}}); function View_AppComponent_1(_l:any):i1.ɵViewDefinition { return i1.ɵvid(0,[(_l()(),i1.ɵeld(0,(null as any),(null as any),1,'h1',([] as any[]), (null as any),(null as any),(null as any),(null as any),(null as any))),(_l()(), i1.ɵted((null as any),['This is heading']))],(null as any),(null as any)); } function View_AppComponent_2(_l:any):i1.ɵViewDefinition { return i1.ɵvid(0,[(_l()(),i1.ɵeld(0,(null as any),(null as any),1,'div',([] as any[]), (null as any),(null as any),(null as any),(null as any),(null as any))),(_l()(), i1.ɵted((null as any),['','']))],(null as any),(_ck,_v) => { const currVal_0:any = _v.context.$implicit; _ck(_v,1,0,currVal_0); }); } export function View_AppComponent_0(_l:any):i1.ɵViewDefinition { return i1.ɵvid(0,[(_l()(),i1.ɵeld(0,(null as any),(null as any),1,'button',([] as any[]), (null as any),[[(null as any),'click']],(_v,en,$event) => { var ad:boolean = true; var _co:i2.AppComponent = _v.component; if (('click' === en)) { const pd_0:any = ((<any>_co.toggleHeading()) !== false); ad = (pd_0 && ad); } return ad; },(null as any),(null as any))),(_l()(),i1.ɵted((null as any),['Toggle Heading'])), (_l()(),i1.ɵted((null as any),['\n'])),(_l()(),i1.ɵand(16777216,(null as any), (null as any),1,(null as any),View_AppComponent_1)),i1.ɵdid(16384,(null as any), 0,i3.NgIf,[i1.ViewContainerRef,i1.TemplateRef],{ngIf:[0,'ngIf']},(null as any)), (_l()(),i1.ɵted((null as any),['\n\n'])),(_l()(),i1.ɵeld(0,(null as any),(null as any), 1,'h3',([] as any[]),(null as any),(null as any),(null as any),(null as any), (null as any))),(_l()(),i1.ɵted((null as any),['List of Heroes'])),(_l()(), i1.ɵted((null as any),['\n'])),(_l()(),i1.ɵand(16777216,(null as any),(