【1-2】開發語言

GitBook 鏈接:Learn Angular · GitBook

Github Repo:trotyl/learn-angular

當我們說到 Angular 的開發語言的時候,我們可能指:

  1. Angular 源代碼所使用的語言;
  2. 基於 Angular 的項目所需要使用的語言。

大部分時候我們往往指代的是後者。

Angular 項目本身是用 TypeScript[1] 或 Dart[2] 開發的。咦,為什麼是「或」?其實在 2.0.0-rc.5 版本之前,Angular 的 TypeScript 版本和 Dart 版本是共用的同一個 Code Base[3],絕大部分公共代碼採用 TypeScript 編寫,並編譯[4] 成 Dart 代碼,另有少部分非共用代碼分別使用 TypeScript 和 Dart 實現。

之後由於各種原因,Angular 的 Dart 版本不再與 TypeScript 版本共用代碼,從而成為了獨立實現。不過事實上,除了 Google 的內部產品外,很少有其它項目會使用 Dart 版本的 Angular 來進行開發。

因此,在本書中,如無特殊說明,所有用到 Angular 的地方均指代 TypeScript 實現的版本。如果有 Dart 版本明顯不一致的地方,會在腳註中表明(前提是我知道的話)。

在說到 TypeScript 之前,不得不提及的一門語言是 AtScript[5]。

Angular 的一個重要設計理念就是 Declarative(聲明式) 編程,為了落實這一理念,Angular 在 API 設計上的一個重大改進就是很大程度上基於[6] @SomethingAnnotation(註解)/Decorator(裝飾器) 語法。

那麼,這個語法到底是 Annotation 還是 Decorator 呢?

事實上,在 Angular 早期設計的時候,ES6 都還不知道是不是 ES2015,更別說之後的各種語言提案。因此,AtScript 自行擴展一個叫做 Metadata Annotation[7] 的語法,用於附加額外的 Metadata,實現也非常簡單:

/* AtScript */@Component()class MyApp { server: Server; @Bind(name) name: string @Event(foo) fooFn: Function @Inject() constructor(@parent server: Server) { } greet(): string { }}

等價於:

/* JavaScript in ES2015 */class MyApp() { constructor(server) { } greet(): string { }}MyApp.properties = { server: { is: Server }, name: { is: String, annotate: [new Bind(name] }, fooFn: { is: Function, annotate: [new Event(foo)] },}MyApp.annotations = [ new Component(), new Inject(),]MyApp.parameters = [ { is: Server, annotate: [parent] },]MyApp.prototype.greet.returns = String

可以看出,所有的標註信息都被附加到運行時[8] 中,即具備 Type Introspection(內省) 的能力。當然,這麼做僅僅是附加信息,本身並不能提供任何功能,而是由其它使用該內容的代碼根據相應的數據來動態確定行為。這種方式非常類似於 Java 的 Annotation 或者 C# 的 Attribute

隨後在 NG-Conf 2015 上,Angular 團隊宣布了遷移至 TypeScript 的消息[9]。在 Angular 團隊與 TypeScript 團隊的合作計劃中,TypeScript 將增加 Metadata Annotation 的語法以及對應的 Introspection API[10]。

不過,這件事最後並沒有發生。隨著 ES2015 的正式發布,JavaScript 語言開始進入穩定的持續迭代發展階段,TypeScript 也不再接受新的語言特性,而是僅僅提供對 JavaScript 語言特性的支持以及提供相應的類型檢查。於是 TypeScript 最後增加了對語法相似(但是語義完全不同)的 Decorator 特性的支持(Decorator 本身是 JavaScript 的語言提案,並不是 TypeScript 的擴展內容),而 Angular 也將相應的 API 改用 Decorator 實現[11]。不過對於一般用戶而言,這個重大的改動似乎並沒有什麼實際影響(以及在 2015 年的時候實際上也沒有多少用戶存在)。

此外,為了解決 Angular 需要運行時獲取構造函數參數信息的問題(關於 Dependency Injection 的內容會在之後的部分覆蓋),TypeScript 提供了一個新的編譯器選項 emitDecoratorMetadata,為具備 Decorator的 Class 暴露構造函數參數信息,默認情況下是基於 Metadata Reflection API 所實現的,後者是一個還不是語言提案的「提案」。

於是現在我們解決了第一個問題,Angular(的我們所關心的那個實現)是使用 TypeScript 所開發的。那麼接下來的另一個問題是,基於 Angular 的項目是否需要使用 TypeScript 開發呢?

是也不是。在上一節中我們已經嘗試過使用 Pure JavaScript 來開發一個簡單的 Angular 應用,所以使用 JavaScript 來開發在技術上是切實可行的。但是我們知道,TypeScript 具備很多優勢,例如提供了編譯時的靜態類型檢查,提供了最新的(以及提案中的)的 JavaScript 語言特性的轉譯支持,提供了完善的語言服務集成等等。

不過其實這些都不是重點,最重要的地方時,Angular 的靜態編譯工具是基於 TypeScript 封裝實現的,也就是說,在不使用 TypeScript 工具鏈[12] 的情況下,便無法在開發時使用 Angular 的模版編譯器[13],從而無法構建出適合生產環境使用的發行版本。

所以說,就目前的客觀事實下,如果想用 Angular 開發實際項目,那麼就應該使用 TypeScript。

為此,我們現在就開始將我們上一節中完成 的 Hello World 項目遷移到使用 TypeScript 的方式。

首先,我們將 JavaScript 文件從 HTML 文件中分離,命名為 main.js,內容為:

/* main.js */class AppComponent { }AppComponent.annotations = [ new ng.core.Component({ selector: main, template: <h1>Hello Angular</h1>, })]class AppModule { }AppModule.annotations = [ new ng.core.NgModule({ imports: [ ng.platformBrowser.BrowserModule, ], declarations: [ AppComponent, ], bootstrap: [ AppComponent, ], })]ng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(AppModule)

相應的 HTML 中的內容為:

<!DOCTYPE html><title>Hello Angular</title><main>TODO</main><script>Reflect.getOwnMetadata = () => {}</script><script src="https://unpkg.com/zone.js@0.8.10/dist/zone.js"></script><script src="https://unpkg.com/rxjs@5.4.0/bundles/Rx.js"></script><script src="https://unpkg.com/@angular/core@4.1.3/bundles/core.umd.js"></script><script src="https://unpkg.com/@angular/common@4.1.3/bundles/common.umd.js"></script><script src="https://unpkg.com/@angular/compiler@4.1.3/bundles/compiler.umd.js"></script><script src="https://unpkg.com/@angular/platform-browser@4.1.3/bundles/platform-browser.umd.js"></script><script src="https://unpkg.com/@angular/platform-browser-dynamic@4.1.3/bundles/platform-browser-dynamic.umd.js"></script><script src="./main.js"></script>

現在,我們有了單獨的 JavaScript 文件。不過,將所有代碼放在一個 JavaScript 文件中顯然不利於後期維護,為此我們藉助自 ES2015 開始引入的 Module 特性,將 main.js 拆分為多個 Module 形式的 JavaScript 文件:

  • 將 AppComponent 的相關內容提取到 app.component.js 中;
  • 將 AppModule 的相關內容提取到 app.module.js 中;
  • 將剩下的內容保留在 main.js 中。

之後我們得到:

/* app.component.js */class AppComponent { }AppComponent.annotations = [ new ng.core.Component({ selector: main, template: <h1>Hello Angular</h1>, })]export { AppComponent }/* app.module.js */import { AppComponent } from ./app.component.jsclass AppModule { }AppModule.annotations = [ new ng.core.NgModule({ imports: [ ng.platformBrowser.BrowserModule, ], declarations: [ AppComponent, ], bootstrap: [ AppComponent, ], })]export { AppModule }/* main.js */import { AppModule } from ./app.module.jsng.platformBrowserDynamic.platformBrowserDynamic().bootstrapModule(AppModule)

<!DOCTYPE html><title>Hello Angular</title><main>TODO</main><script>Reflect.getOwnMetadata = () => {}</script><script src="https://unpkg.com/zone.js@0.8.10/dist/zone.js"></script><script src="https://unpkg.com/rxjs@5.4.0/bundles/Rx.js"></script><script src="https://unpkg.com/@angular/core@4.1.3/bundles/core.umd.js"></script><script src="https://unpkg.com/@angular/common@4.1.3/bundles/common.umd.js"></script><script src="https://unpkg.com/@angular/compiler@4.1.3/bundles/compiler.umd.js"></script><script src="https://unpkg.com/@angular/platform-browser@4.1.3/bundles/platform-browser.umd.js"></script><script src="https://unpkg.com/@angular/platform-browser-dynamic@4.1.3/bundles/platform-browser-dynamic.umd.js"></script><script src="./main.js" type="module"></script>

和上一節中不同,現在我們確實需要使用到 開發工具 級別的瀏覽器了,選項有:

  1. 安裝最新版本的 Chrome Canary[14] (>= 60.0),進入 chrome://flags,開啟 Experimental Web Platform features 這個開關;
  2. 安裝最新版本的 Firefox Beta / Firefox Developer / Firefox Nightly[15] (>= 54.0),進入 about:config,開啟 dom.moduleScripts.enabled 這個開關;
  3. 安裝最新版本的 Safari[16] (>= 10.1),什麼準備操作也不用做。

然後再次用剛剛準備好的瀏覽器打開我們的 index.html 文件,發現出現了一條報錯(以 Chrome 為例):

Access to Script at file:///Users/zjyu/GitBook/Library/Import/learn-angular/code-examples/001-002/step-002/main.js from origin null has been blocked by CORS policy: Invalid response. Origin null is therefore not allowed access.

這是因為使用 file:// 協議的時候對於 Origin(域) 的判斷上會有些問題,任何一個 Web 前端工程師都應該知道相應的解決方案 —— 開一個 Server。

我們可以使用 yarn global add http-server[17] 來快速安裝一個靜態文件伺服器(如果有其它的 Server 或者其它的包管理器,自行調整即可,對結果沒有影響)。

這時我們在 index.html 所在的路徑使用 http-server 啟動一個伺服器,然後在瀏覽器中訪問 localhost:8080/(以自己的實際埠為準),又一次得到了同樣的內容:

Hello Angular

這樣就完成了 模塊化 的過程,不過需要注意的是,目前為止我們使用的都是能夠直接在瀏覽器中運行的沒有使用任何預處理的普通的 JavaScript

如果我們熟悉 ES Module 的話,為了美觀,我們可以把 export 部分[18] inline 化,得到:

/* app.component.js */export class AppComponent { }AppComponent.annotations = [ new ng.core.Component({ selector: main, template: <h1>Hello Angular</h1>, })]/* app.module.js */import { AppComponent } from ./app.component.jsexport class AppModule { }AppModule.annotations = [ new ng.core.NgModule({ imports: [ ng.platformBrowser.BrowserModule, ], declarations: [ AppComponent, ], bootstrap: [ AppComponent, ], })]

這樣代碼組織上會顯得更加簡潔。

之後,我們更進一步,藉助預處理工具,把外部依賴也改用 Module 的形式引入,並且最後只引入一個 JavaScript 文件。首先將 JavaScript 文件修改為:

/* app.component.js */import { Component } from @angular/coreexport class AppComponent { }AppComponent.annotations = [ new Component({ selector: main, template: <h1>Hello Angular</h1>, })]/* app.module.js */import { NgModule } from @angular/coreimport { BrowserModule } from @angular/platform-browserimport { AppComponent } from ./app.componentexport class AppModule { }AppModule.annotations = [ new NgModule({ imports: [ BrowserModule, ], declarations: [ AppComponent, ], bootstrap: [ AppComponent, ], })]/* main.js */import { platformBrowserDynamic } from @angular/platform-browser-dynamicimport { AppModule } from ./app.moduleplatformBrowserDynamic().bootstrapModule(AppModule)

這樣,我們就可以告別冗長的 ng.moduleName.localName 全局訪問形式了,代碼更為直觀。

接下來我們還需要進行打包,為了和之後的內容接軌,我們使用 Webpack[19] 來作為打包工具。

我們可以使用 yarn global add webpack 在全局安裝 Webpack 的命令行工具。

然後使用 webpack main.js bundle.js,即指定 main.js 為入口文件,bundle.js 為打包後的輸出文件。這是我們會看到一些錯誤:

ERROR in ./main.jsModule not found: Error: Cant resolve @angular/platform-browser-dynamic in /Users/zjyu/GitBook/Library/Import/learn-angular/code-examples/001-002/step-004 @ ./main.js 1:0-74ERROR in ./app.module.jsModule not found: Error: Cant resolve @angular/core in /Users/zjyu/GitBook/Library/Import/learn-angular/code-examples/001-002/step-004 @ ./app.module.js 1:0-40 @ ./main.jsERROR in ./app.module.jsModule not found: Error: Cant resolve @angular/platform-browser in /Users/zjyu/GitBook/Library/Import/learn-angular/code-examples/001-002/step-004 @ ./app.module.js 2:0-57 @ ./main.jsERROR in ./app.component.jsModule not found: Error: Cant resolve @angular/core in /Users/zjyu/GitBook/Library/Import/learn-angular/code-examples/001-002/step-004 @ ./app.component.js 1:0-41 @ ./app.module.js @ ./main.js

簡單地說,就是我們缺少了 @angular/core@angular/platform-browser@angular/platform-browser-dynamic 這幾個包,雖然我們在 index.html 中手動引入了,但是 Webpack 僅僅根據 JavaScript 文件進行處理,是無法知曉的。事實上,這三個僅僅是我們所直接引用的,還有很多背後所依賴的 Packages。

為了簡單起見,我們直接給出完整的安裝列表:

yarn add @angular/core@4.1.3 @angular/common@4.1.3 @angular/compiler@4.1.3 @angular/platform-browser@4.1.3 @angular/platform-browser-dynamic@4.1.3 rxjs@5.4.0

然後重新使用打包命令:

webpack main.js bundle.js

成功得到打包後的 bundle.js 文件,隨後我們將 index.html 中的內容改為:

<!DOCTYPE html><title>Hello Angular</title><main>TODO</main><script>Reflect.getOwnMetadata = () => {}</script><script src="https://unpkg.com/zone.js@0.8.10/dist/zone.js"></script><script src="./bundle.js"></script>

啟動伺服器,刷新瀏覽器,又能重新見到我們的 Hello Angular 內容。

剩下的內容還有什麼呢?對了,遷移到 TypeScript 版本。

首先,我們先遷移到 TypeScript 的工具鏈上,不過要注意,在更改後綴名之前,我們仍然使用的是 JavaScript 語言。全局安裝 TypeScript CLI 工具的命令為:

yarn global add typescript@2.3.2

接著在不改動 JavaScript 文件的前提下,先試試 tsc 命令的效果:

tsc main.js

當然,我們還是繼續秉承 EDD 的開發方式,我們現在出現的錯誤是:

error TS6054: File main.js has unsupported extension. The only supported extensions are .ts, .tsx, .d.ts.

這裡是說,main.js 並不符合 TypeScript 的後綴要求。不過,我們本來也就用的不是 TypeScript 文件,所以加上 allowJs 選項:

tsc --allowJs main.js

現在錯誤變了,為:

error TS5055: Cannot write file /Users/zjyu/GitBook/Library/Import/learn-angular/code-examples/001-002/step-005/app.component.js because it would overwrite input file. Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig.error TS5055: Cannot write file /Users/zjyu/GitBook/Library/Import/learn-angular/code-examples/001-002/step-005/app.module.js because it would overwrite input file. Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig.error TS5055: Cannot write file main.js because it would overwrite input file. Adding a tsconfig.json file will help organize projects that contain both TypeScript and JavaScript files. Learn more at https://aka.ms/tsconfig.

這又是什麼意思呢?

簡單地說,我們知道 TypeScript 文件會被編譯為 JavaScript 文件,編譯後後綴名由 .ts 變為 .js。那麼如果直接編譯 .js 文件呢?直接把源文件覆蓋掉么?這樣顯然是不行的,為此我們需要指定輸出目錄:

tsc --allowJs --outDir dist main.js

輸出到 dist 目錄的話,總不用擔心把源文件給覆蓋掉了,不過現在又有一些其它問題:

node_modules/@angular/common/src/directives/ng_class.d.ts(48,34): error TS2304: Cannot find name Set.node_modules/@angular/compiler/src/aot/compiler.d.ts(48,32): error TS2304: Cannot find name Map.node_modules/@angular/compiler/src/compile_metadata.d.ts(369,20): error TS2304: Cannot find name Set.node_modules/@angular/compiler/src/compile_metadata.d.ts(371,28): error TS2304: Cannot find name Set.node_modules/@angular/compiler/src/compile_metadata.d.ts(373,15): error TS2304: Cannot find name Set.node_modules/@angular/compiler/src/compile_metadata.d.ts(375,23): error TS2304: Cannot find name Set.node_modules/@angular/compiler/src/compile_metadata.d.ts(377,17): error TS2304: Cannot find name Set.node_modules/@angular/compiler/src/compile_metadata.d.ts(379,25): error TS2304: Cannot find name Set.node_modules/@angular/compiler/src/output/output_ast.d.ts(444,63): error TS2304: Cannot find name Set.node_modules/@angular/core/src/change_detection/differs/default_iterable_differ.d.ts(28,32): error TS2304: Cannot find name Iterable.node_modules/@angular/core/src/change_detection/differs/default_keyvalue_differ.d.ts(24,16): error TS2304: Cannot find name Map.node_modules/@angular/core/src/change_detection/differs/default_keyvalue_differ.d.ts(32,16): error TS2304: Cannot find name Map.node_modules/@angular/core/src/change_detection/differs/iterable_differs.d.ts(15,48): error TS2304: Cannot find name Iterable.node_modules/@angular/core/src/change_detection/differs/keyvalue_differs.d.ts(23,18): error TS2304: Cannot find name Map.node_modules/@angular/core/src/di/reflective_provider.d.ts(87,123): error TS2304: Cannot find name Map.node_modules/@angular/core/src/di/reflective_provider.d.ts(87,165): error TS2304: Cannot find name Map.node_modules/@angular/platform-browser/src/browser/browser_adapter.d.ts(79,33): error TS2304: Cannot find name Map.node_modules/@angular/platform-browser/src/dom/dom_adapter.d.ts(97,42): error TS2304: Cannot find name Map.node_modules/@angular/platform-browser/src/dom/shared_styles_host.d.ts(11,30): error TS2304: Cannot find name Set.node_modules/@angular/platform-browser/src/dom/shared_styles_host.d.ts(22,30): error TS2304: Cannot find name Set.node_modules/rxjs/Observable.d.ts(69,60): error TS2693: Promise only refers to a type, but is being used as a value here.

雖然看著很長,其實信息只有一點,就是缺少一些 ES2015 中新增內容的類型定義。事實上,TypeScript 不僅支持編譯到 JavaScript,還能設定不同的 JavaScript 級別,默認為 ES5。然後,在輸出 ES5 的情況下,TypeScript 會發現我們使用了 ES2015 的內容,由於這些內容不是語法,只是 API,所以是不會通過轉譯實現的,而是要自行添加相應的 Polyfill。

TypeScript 為我們提供了兩種內置的解決方案,一種是保持輸出級別為 ES5,但是告訴 TypeScript 我們能夠自行解決 Polyfill 的問題:

tsc --allowJs --outDir dist --lib es2015,dom main.js

這裡通過 lib 選項指定需要引入的類型定義文件[20]。另一種方案是將輸出級別改為 ES2015,如果我們的目標平台較為先進的話:

tsc --allowJs --outDir dist --target es2015 main.js

我們這裡選擇後者,因為僅僅是作為教學目的。

由於 TypeScript 自帶了對最新(以及比最新還要更新)的 JavaScript 語言特性,我們現在可以直接在 JavaScript 文件中使用更多的語法糖,例如 Decorator。

我們將 JavaScript 文件修改為使用 Decorator 的版本[21]:

/* app.component.js */import { Component } from @angular/core@Component({ selector: main, template: <h1>Hello Angular</h1>,})export class AppComponent { }/* app.module.js */import { NgModule } from @angular/coreimport { BrowserModule } from @angular/platform-browserimport { AppComponent } from ./app.component@NgModule({ imports: [ BrowserModule, ], declarations: [ AppComponent, ], bootstrap: [ AppComponent, ],})export class AppModule { }

相比於手動屬性賦值而言,這樣又更加簡潔直觀了不少。

然而當我們再次嘗試使用 tsc 編譯的時候,發現了新的錯誤:

app.component.js(7,14): error TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the experimentalDecorators option to remove this warning.app.module.js(16,14): error TS1219: Experimental support for decorators is a feature that is subject to change in a future release. Set the experimentalDecorators option to remove this warning.

這裡是在說 Decorator 默認是不提供支持的,想要用的話自己加開關。所以很簡單,加上開關就好了:

tsc --allowJs --outDir dist --target es2015 --experimentalDecorators main.js

成功編譯文件。然後重新使用 Webpack 打包,注意路徑的改動:

webpack dist/main.js bundle.js

刷新瀏覽器,我們在瀏覽器中看到了一個錯誤:

Uncaught TypeError: Reflect.defineMetadata is not a function

這個是因為我們在上一節中提供了一個假的 Metadata Reflection API,因為當時還不需要用到。不過現在是真的需要了,為此我們加上對應的 Polyfill:

<!DOCTYPE html><title>Hello Angular</title><main>TODO</main><script src="https://unpkg.com/core-js@2.4.1/client/shim.js"></script><script src="https://unpkg.com/zone.js@0.8.10/dist/zone.js"></script><script src="./bundle.js"></script>

重新看到了我們的 Hello Angular

最後,我們將 JavaScript 文件改成 TypeScript 文件,並不需要改動內容,僅僅是修改後綴名為 .ts

/* app.component.ts */import { Component } from @angular/core@Component({ selector: main, template: <h1>Hello Angular</h1>,})export class AppComponent { }/* app.module.ts */import { NgModule } from @angular/coreimport { BrowserModule } from @angular/platform-browserimport { AppComponent } from ./app.component@NgModule({ imports: [ BrowserModule, ], declarations: [ AppComponent, ], bootstrap: [ AppComponent, ],})export class AppModule { }/* main.ts */import { platformBrowserDynamic } from @angular/platform-browser-dynamicimport { AppModule } from ./app.moduleplatformBrowserDynamic().bootstrapModule(AppModule)

同樣使用 tsc 編譯,不過已經不需要 allowJsourDir 選項了:

tsc --target es2015 --experimentalDecorators main.ts

不過這次又有點小問題:

app.component.ts(1,27): error TS2307: Cannot find module @angular/core.app.module.ts(1,26): error TS2307: Cannot find module @angular/core.app.module.ts(2,31): error TS2307: Cannot find module @angular/platform-browser.main.ts(1,40): error TS2307: Cannot find module @angular/platform-browser-dynamic.

為什麼明明沒有動 node_modules 目錄,就找不到相應的模塊了呢?這其實是 TypeScript 一個歷史遺留問題,我們簡單增加一個參數即可解決:

tsc --target es2015 --experimentalDecorators --moduleResolution node main.ts

隨後重新打包:

webpack main.js bundle.js

刷新瀏覽器,發現一切正常。現在我們就成功地將整個項目 Script 形式的單文件 JavaScript 逐步遷移成了 Module 形式的多文件 TypeScript。不過目前我們 並沒有用到任何 TypeScript 語言 的內容,僅僅是將 JavaScript 文件改了後綴名而已。

可能的疑惑

為什麼 Dart 版本的 Angular 沒有流行起來?

因為 Dart 沒有流行起來。

現在是否還有辦法使用 AtScript?

Github 上有一個 PlayGround 的 Repo:angular/atscript-playground。

Angular 的編譯器是如何工作的?

會在後文中覆蓋。

既然 AOT 編譯的要求是 TypeScript 工具和 Decorator 語法,那是否可以對使用 Decorator 語法的 JavaScript 文件進行 AOT 編譯?

理論上可行,Decorator 本身是(提案中的)JavaScript 語言特性,但是 TypeScript 工具對 JavaScript 文件的支持(Salsa)與 TypeScript 文件的支持略有差異,需要使用額外的構建步驟將 .js 文件重命名為 .ts 文件,另外可能還需要設置忽略相應的類型檢查錯誤。

另外,不建議在沒有相關實力的情況下主動踩坑。

既然 JIT 編譯也會在運行時生成相應的 JavaScript 文件,那是否可以將瀏覽器中所生成的 JavaScript 文件拷貝出來當做源碼使用,從而避免運行時編譯?

理論上可行,JIT 編譯除了輸出的語言級別和使用的模塊機制外,與 AOT 編譯的結果並無本質差異。但這樣做會導致模版中的內容無法被正確地進行類型檢查,可能產生不必要的錯誤隱患。

另外,不建議在沒有相關實力的情況下主動踩坑。

明明 Edge 瀏覽器也提供了 ES Module 支持,為什麼不給出相應的選項?

因為我現在手上用的是 Mac。

為什麼不在瀏覽器原生模塊化的步驟中把外部依賴也使用 Module 的形式引入?

HTML 規範所實現的 ES Module 的 Runtime Semantics: HostResolveImportedModule 過程與目前所有的模塊使用方式都不兼容,詳情參考:為什麼 ES Module 的瀏覽器支持沒有意義 - 知乎專欄。

為什麼 file 協議會有跨域問題?

Web 開發基礎不在本書的覆蓋範圍內。請自行搜索其它外部資源。

ES Module 到底有多少種 import 和 export 語法?

JavaScript 語言基礎不在本書的覆蓋範圍內。請自行搜索其它外部資源。

為什麼要用 Webpack 做示例?

因為 CLI 用的就是 Webpack。

為什麼 TypeScript 工具的 JavaScript 支持部分叫做 Salsa?

內部項目代號,大家後來習慣了就都這麼叫。

哪裡能查到 TypeScript CLI 的所有編譯器選項?

這裡:Compiler Options · TypeScript。

1. TypeScript 是 Microsoft 推出的一門基於 JavaScript 語言擴展的類 JavaScript 語言,用於增強 JavaScript 工程中的靜態類型檢查效果。官網為:TypeScript - JavaScript that scales,語言規範為:TypeScript Language Specification(不是最新)。在本文中,我們可能不嚴格區分 TypeScript 這門語言與其 官方實現。

2. Dart 是 Google 推出的一門通用編程語言,主要面向 Web 開發。官網為:Dart programming language | Dart,語言規範為:Standard ECMA-408。在本文中,我們可能不嚴格區分 Dart 這門語言與其 官方實現。

3. 分離前 Dart 版實現的源碼也位於 angular/angular 的 GitHub 當中,同時 dart-lang/angular2 充當編譯後的純 Dart 代碼的存檔。之後即作為 Dart 實現的源碼 Repo 使用。

4. Angular 團隊自行開發了 TypeScript 到 Dart 的編譯工具,詳情參考:Angular 2 Dart Transformer,相應的實現在 angular/ts2dart。

5. AtScript 是專門為開發 Angular 所設計的語言,因此在 Angular 團隊決定遷移到 TypeScript 之後,該語言即被宣布廢棄。官網在 AtScript Primer,擴展名為 .ats

6. 正如我們在第一節中嘗試過的那樣,Angular 並不要求使用 Decorator 語法,只是在使用該語法的情況下能夠大量提高代碼可讀性,提高開發效率。

7. Annotation 並不僅僅表示 @Something 這樣的內容,這種情況下通常譯作「註解」,AtScript 中叫做 Metadata Annotation;而 name: string 這樣的內容一般叫做 Type Annotation,譯作「類型標註」。

8. AtScript 的一個獨特的功能就是運行時的類型檢查,這點和 TypeScript 純粹的編譯時檢查不同,即便是直接使用編譯後的 JavaScript 代碼也同樣能保證類型安全。

9. Twitter 鏈接為:ng-conf on Twitter: "AtScript is Typescript #ngconf"。

10. 原計劃中的 TypeScript Introspection API 設計文檔:TypeScript Introspection API。

11. AtScript 原有的 Metadata Annotation 的功能基本可以通過 JavaScript 的 Decorator 模擬實現,改動後的 Re-design 文檔可以參見:Decorators vs Metadata Annotations。

12. 更確切地說 AOT 編譯的限制還有必須使用 Decorator 語法。

13. 對於 Angular 而言,在開發時預先編譯模版內容叫做 AOT(Ahead-of-time)編譯,在運行時編譯模版內容叫做 JIT(Just-in-time)編譯,如無特殊說明,本文中的編譯方式均指代 Angular 模版編譯器的編譯方式。

14. Chome Canary 的下載地址:Chrome Browser。

15. Firefox Nightly 的下載地址:Try New Browser Features in Pre-Release Versions | Firefox。

16. Safari 的下載地址:Apple - Support - Downloads。

17. Yarn 是一款 Facebook 推出的包管理器,基於 NPM Registry,相比 NPM 而言對功能和性能進行了一些增強。官網為:Yarn。

18. 就語言規範的定義而言,importexport 這類語法形式構成的內容並不屬於 Statement(語句)

19. Webpack 是一個通用的 JavaScript 模塊打包器,官網為:webpack。

20. TypeScript 編譯器的 lib 選項僅僅添加的是類型定義,用於通過類型檢查,並不會添加實際的運行時內容。

21. 將靜態屬性改為 Decorator 的過程前後文件的語義是發生了變化的,在 JavaScript 語言層面並不等價,只是在 Angular 的功能實現上基本等價。

trotyl.gitbooks.io/lear
推薦閱讀:

【MMR-A】Angular 去除對 Metadata Reflection API 的依賴
AngularJS、React 真的被淘汰了嗎?
PC 前端是不是沒希望了?
Angular 項目 國際化方案

TAG:Angular? | TypeScript |