1. 程式人生 > 其它 >介紹Angular 工程化,構建物件,編譯器

介紹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 來實現模板裡面的語法型別檢測的。