介紹Angular 工程化,構建物件,編譯器
介紹Angular 工程化,構建物件,編譯器
這篇文章從巨集觀的角度認識Angular 的工程(project),構建物件(build target),編譯器(compiler)。包括如下內容
- 工程檔案的主要構成
- 構建物件的配置
- 編譯器的步驟與職責
Angular的工程(project)
一般而言,Angular的工作目錄結構如下:
my-app |---- tsconfig.json angular.json package.json projects |----- project1 project2 src |----- xxxx
特指angular.json檔案。下面看看這個工程檔案的主要幾個部分。先列出其中使用的github 連結。
-
工程檔案Schema 也就是
$schema
欄位指定的值。用來描述Angular 工作空間的多專案的結構。其實就是當前angular.json 的約束。 - configSchema這個Schema就是控制當我們工程裡面的元件的風格,樣式的型別scss/css 等,同時也會控制ng new 自動產生的元件的風格。
簡單樣例如下。
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",//schema "version": 1, // 你的程式的版本 "newProjectRoot": "projects", // 表示如果使用ng new 建立一個新的project,它的根目錄在projects下面,可以任意指定 "projects":{ "project1":{}, // 第一個project "project2":{} // 第二個project }, "defaultProject":"project1", // 預設是那個project "schematics":{}, // configSchema, 連結如上,控制組件風格的。 "cli":{"analytics": false} // 這個可以忽略,例如這個選項就是決定是否跟Angular Team 分析你專案的使用分析。 }
我們再次深入看看某個project 的配置資訊。
還是看下面的這個簡單的例子,下面的配置是上面配置中project1的值。 先列出其中用到的一些連結
- 這個JSON的schema,注意:不同的專案型別(projectType),(例如:application, library等),參考github裡面不同的資料夾內的schema檔案。
{ "root":"xxx", // 表示這個project 的根目錄。 "sourcrRoot":"xxx/xxx", // 這個表示這個project 的src 的目錄。注意跟上面的區分, src 裡面放置了main.ts app.module 等。 "projectType":"application",// 專案的型別,application library 等。 "prefix":"",// 元件selector 的字首。 "schematics":{}, // 與上一節中的configSchema 一樣。控制組件風格的。 "architect":{}, // 這個是核心,會定義這個工程有哪些編譯模式。下節細談. }
再來看看architect裡面的配置資訊
architect裡面定義了各種各樣的構建物件(build target),那麼什麼是build target 呢?最直接的認識,ng build
,ng serve
,這兩個命令裡面的build,serve就是構建物件。我們可以把它們理解成環境變數+構建工具的組合。
首先,Angular 保留了這些構建物件
{
"architect": {
"build": {},
"serve": {},
"e2e" : {},
"test": {},
"lint": {},
"extract-i18n": {},
"server": {},
"app-shell": {},
"mybuildTarget":{}, // 這個是自定義的構建物件,任意名字。
}
}
我們也可以自己定義任意名字的構建物件,然後我們可以在package.json 中這麼使用它 ng run mybuildTarget
(這個我沒有實驗過),不過大部分情況下,我們只會用保留build Target。那麼每一個build Target 擁有三個重要的欄位 builder,options,configurations
-
builder 這個裡面是清單,給兩個例子
- {"builder":"@angular-devkit/build-angular:browser"} 這個構建物件代表編譯生產庫的網頁應用
- {"builder":@angular-devkit/build-angular:dev-server} 這個構建物件代表本地開發的網頁應用
- {builder:@angular-devkit/build-angular:ng-packagr} 這個構建物件代表編譯共享庫
-
options 描述與物件的project 工程相關的屬性,例如下面的例子
{
"options":{// 配置選項
"outputPath":'dist/xxx',
"index":'xxx/src/index.html',
"main":"xxx/src/main.ts",
"polyfills":'xxx/src/polyfill.ts',
"tsConfig":'xxx/src/tsconfig.app.json',
"assets":[assetPath1,assetPath2,...], // {"glob":xxx,"input":xxx,"output":xxx},需要複製到dist目錄的assets
"styles":[stylePath1,stylePath2,...],// {"glob":xxx,"input":xxx,"output":xxx},需要新增到index.html的樣式檔案,
"scripts":[script1,script2,xxx],// 需要新增到index.html的js 檔案。
}
}
- configurations 則表示這個構建物件有哪些環境配置選項,例如Dev, Production, UAT, QA 等,它的內容與options類似,用於複寫options 的內容。給個例子
{
"configurations":{ // 典型的用法是 `ng build -c=production/development/qa/uat`
"production":{}, // 生成環境的編譯配置
"development":{}, // 本地開發環境的配置
"qa":{}, // 測試環境的配置
"uat":{}, // uat測試環境的配置
"demo":{
"fileReplacements":[
{"replace":'xxx/environment.ts','with':'xxx/environment.prod.ts'} // 編譯前替換檔案
],
//options 裡面大部分的配置,這個地方都可以替換。
}
}
}
那麼將上面的配置資訊用起來就是
-
ng run buildTarget -c=configurationName
或者99%的使用場景是 -
ng build -c=configurationName
使用configurationName來編譯build 編譯物件,也就是編譯應用。 -
ng serve -c=configurationName
使用configurationName來編譯serve 編譯物件,也就是本地開發環境。
編譯IVY
Angular 編譯器模型與步驟
Create Programe(TS) ---> Analysis(Angular) ---> Resolve(Angular) ---> Type Checking(TS) ---> Emit (TS)
TS: TSC中的步驟
- Create Programe, 從tsconfig.json 出發 遞迴解析檔案,import modules,除了ts本身的,angular 一些檔案匯入,ngFactoryResolve 等。
- Analysis 遞迴查詢檔案樹中的所有類,找到Angular相關的類,元件等。並且以類為單元,嘗試做一些獨立的解析。不會去做類之間操作的事情,這一步可以理解為對類做一些元資料的解析生成。Isolate,這些類的解析完成後,下一步會利用他們做成軟體結構的解析。
- Resolve 這個階段是從一個元件樹的角度去理解依賴關係,做一些優化的策略
- Type Checking 這一步會去檢查有沒有型別錯誤,以及Component 表示式中有沒有型別錯誤。
- Emit 這一步是最昂貴的一步,將typescript 轉換成js. 產生Angular Component 類的產生js 程式碼。
編譯器的模型(Model)與功能(Feature)
功能(Feature)
- NgModule Scopes, Scope 中宣告template 中元件查詢的作用域 (decleration, exports)
- Partial evaluation, import 中的內容可以是原生的Array, funtion等,對於一些其他的例如使用到document.body.scrollwitdh 的屬性,可以在編譯的時候跳過,等等
- Template type-checking, 解析模板中的型別。
AOT(Ahead-Of-Time Compiler)
首先使用這個模式的好處與原因
- 快速的渲染載入,由於Angular的框架程式碼已經是編譯好搖樹過的js,瀏覽器中直接執行,無需再編譯
- 更少的非同步請求,IVY編譯器通過inline的方式,集成了html template + css,減少了請求數量
- 更小的框架包體積,IVY在編譯階段搖樹的方式去除了不用的程式碼
- 更早的檢測到模板中的錯誤
- 更好的安全性
Angular 提供了兩者編譯器 JIT(Just In Time) vs AOT(Ahead Of Time)
-
JIT: 在瀏覽器中的runtime中編譯你的應用。Angular8 之前預設的編譯器, 也叫ViewEngine
-
AOT: 在編譯打包階段編譯應用, Angular9 開始的預設編譯器,也叫IVY。
Angular.json中的aot:true
可以控制編譯器的選擇。 -
AOT 友好的程式碼
- factoryfunction 必須是一個有名方法,不能是一個匿名的或者箭頭函式
- 模板中的變數不能是私有變數
- 模板中的函式呼叫的前面必須要匹配
-
code Analysis (AOT collector)
- AOT collector所認識的是js的一個子集. 也就是說在decorator(例如:@Component, @NgModule等) 中所以寫的表示式就是js的一個子集。
- 這個子集Expression syntax limitations
- 不能使用箭頭函式,必須使用exporeted named function.
- 程式碼摺疊(Code Folding),編譯器引用exported symbols. 然而collector 可以執行部分表示式,將結果記錄到meta.json中。這個過程叫做fold,也就是程式碼摺疊。
-
code generation/Resolve
- 編譯器只能支援某些類的建立,支援核心的decorator,支援返回表示式的巨集函式(靜態函式或者函式)的呼叫
- Metadata rewriting, 對於provider 裡面的語義物件,編譯器有特殊的邏輯,能夠使用箭頭函式,沒有匯出的物件,只能是某些特殊寫法。
-
Template type checking
它是通過typescript 來實現模板裡面的語法型別檢測的。