iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

1
Modern Web

讓 TypeScript 成為你全端開發的 ACE!系列 第 32

Day 32. 戰線擴張・專案輸出 X 輸出設定 - TypeScript Compiler Output Configurations

https://ithelp.ithome.com.tw/upload/images/20191001/20120614701zplHtQu.png

閱讀本篇文章前,仔細想想看

  1. 為何有些 ES6 的 Feature 諸如 PromiseObject.assign 等東西無法直接在 TypeScript 使用?要如何設定 tsconfig.json 使得 TypeScript 認得這些東西?
  2. 試舉出 JS 圈裡模組的語法規格(Module Speculation)。

如果還沒理解完畢的話,可以先翻看前一篇文章喔!

哎!筆者其實沒想到前一篇光是講 targetlibmodule 這三個設定就耗費幾千字,這一次只能任命繼續推進度下去。

筆者今天ㄧ樣要繼續講跟專案的產出有關的 TypeScript 編譯器設定,直接進入本題。

正文開始

Build 相關的編譯器設定

專案相關設定 Project Related Configurations —— 第二彈

4. 設定專案的主目錄與專案輸出的位置 - rootDir & outDir

這個應該是不太需要說明,就是專案包含的範圍以及 Build 過後專案的位置。不過呢,筆者還是要強調裡面潛藏的一些機制。

首先,必者給讀者看簡單的 Directory 的架構。(如圖一)

https://ithelp.ithome.com.tw/upload/images/20190930/20120614b9mr2aVCDT.png
圖一:測試的專案架構

簡單解釋架構如下:

  • /build 資料夾,通常就是專案被編譯過後輸出的位置
  • /src 資料夾,通常就是專案主要程式碼所在的位置
    • 裡面包含 index.tsmodule.ts 檔案
    • 裡面還包含 /inner-folder 資料夾,存放另一個 index.ts 檔案

如果使用預設的 tsconfig.json 設定並且經過 tsc 編譯結果如圖二。

https://ithelp.ithome.com.tw/upload/images/20190930/20120614xEiG17mjQo.png
圖二:每個 TypeScript 檔案被編譯過後會產生相對應的檔案

重點 1. 預設的 TypeScript 編譯規則

在終端機當前的所在檔案資料夾位置,下達 tsc 指令,且沒有開啟任何 rootDiroutDiroutFile 相關設定,則:

TypeScript 編譯器會找當前所在檔案資料夾位置(包含資料夾本身)裡面所有 TypeScript 檔案,並一個個編譯出原生 JS 成果,其中 —— 每個原生 JS 檔案會與編譯前對應的 TS 檔案位置一模一樣

讀者應該會對 TypeScript 編譯器的這種行為感到習以為常,但同時感到不便 —— 難道就不能把編譯過後的檔案好好匯集在一個地方嗎

這一次筆者來嘗試看看這種設定:

{
  "compilerOptions": {
    /* 略 ... */
    "outDir": "./build",
    "rootDir": "./src",
    /* 略 ... */
  }
}

下達 tsc 編譯過後的結果如圖三。

https://ithelp.ithome.com.tw/upload/images/20190930/20120614m6B1PW1SWw.png
圖三:原來 outDir 這個選項可以控制檔案輸出位置呢!

不過,這裡有個雷點 —— 就算你違反了專案規則,你照樣還是可以編譯出結果。什麼意思呢?

筆者這裡的 rootDir 選項是指專案的檔案應該要被放置的位置 —— 此時的設定為 ./src;如果假設我們在 ./src 外面還有其他 TypeScript 檔案 —— 儘管違反了專案只能放置在 ./src 的規則,下達 tsc 指令依然會建構出結果 —— 不過筆者依然相信讀者不希望出現如圖四的結果。

https://ithelp.ithome.com.tw/upload/images/20190930/20120614MNhX7xPUkx.png
圖四:儘管專案被編譯出來了,但編譯的結果就是怪,連 src 以外的檔案都被編譯到

另外,儘管 TypeScript 編譯器會提醒你違反規則,但我們嫌還要把編譯結果刪掉很麻煩 —— 出錯時不希望它編譯出任何檔案的話,筆者建議可以多加下一個要介紹的設定,甚至可以在每一次初始化 tsconfig.json 時就把這一個設定加進去

5. 只要專案有任何環節出錯,一概不輸出 —— noEmitOnError

這個就很直覺,只要出現錯誤一概不輸出檔案。(實際測試結果如圖五)

https://ithelp.ithome.com.tw/upload/images/20190930/20120614IY9TRUXw10.png
圖五:noEmitOnError 代表有任何錯誤,TypeScript 編譯器就不會輸出任何東西

另外,除了這種狀況,只要程式方面語法也出現警告,同時 TypeScript 設定檔中有啟動 noEmitOnError 也不會有任何輸出 —— 這部分就可以請讀者驗證看看。

讀者試試看

試著故意在 TypeScript 專案裡面隨便寫一行被警告或者是錯誤的程式碼,只要 noEmitOnError 被啟動,任何呼叫 tsc 進行編譯的結果都會沒有輸出。

另外,還有一個選項單純就是 noEmit,代表就算你呼叫 tsc 而且專案沒有出錯,編譯器照樣不會輸出任何東西。通常這只是單純拿來做編譯測試用的選項,因此筆者這裡大概提過後,讀者可以自行試試看、知道有這種功能就好了。

重點 2. TypeScript 編譯器 noEmitOnError 設定

一但將 noEmitOnError 設定啟用,則當編譯過程中出現任何問題:包含專案設定有誤、語法層面有誤等,TypeScript 編譯結果會一概不輸出檔案。

貼心小提示

另外,其實還有一個名為 rootDirs 這個選項 —— 可以提供複數個主目錄(Root Directories),這個讀者也是知道有這個功能就好,需要的時候再來研究或看官方的 Doc

一但你使用了這項功能,筆者建議你可能還會需要深入理解 TypeScript 的 Module Resolution —— 也就是模組的運作機制,因為專案有多個主目錄的情況下,會比較需要注意多個主目錄被結合在一起,不同模組被 import/export 的一些情形喔!

同理 —— baseUrl 這個設定也屬於 Module Resolution 的範疇 —— 本系列認為(或者是筆者自認為 XD),只要 Cover 到一些必要功能的介紹,因此這裡不會延伸討論 Module Resolution 的機制

6. 打包成單個檔案 —— outFile

這個設定看似很好用,不過它有潛藏的條件:

tsconfig.json 中的 module 模組規範的選項只能為 amdsystem 這兩種

有些人可能想說:“這樣真不方便耶!為何這麼麻煩?”(有些人指的也是筆者本人,不過也可能是筆者見識短淺不 EY)

關於這個問題可以參考這個官方 Issue

這跟模組的規範有關,但筆者不太想要涉獵太多這方面的知識 —— 一方面跟本系列沒有太多關聯,另一方面 —— 儘管想要秉持會用而且理解使用該工具背後的哲學,但關於模組的規範,筆者想過之後仍然找不到細探這方面知識的原因。

貼心小提示

筆者認真想過,除非是模組方面規範的研究者或者是有做跟開發編譯器有關的專案,否則研究不同的模組規範實質上沒什麼太大意義,最後還是送讀者一句話:“用 Webpack 可以解決很多事情 XDDDD”

但筆者還是會繼續講跟打包輸出的部分話題,避免有讀者真的還是會用到。

不過筆者還是貼一下該 Issue 討論裡面的重點片段。(如圖六)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614UXLrib7hbg.png
圖六:打包成單一檔案跟 CommonJS 規範似乎有衝突

筆者先示範如何讓 TypeScript 打包出單一檔案結果。以下先給大家看要示範的檔案結構與內容。(如圖七)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614UGg9Z95J5N.png
圖七:其實就是很陽春的範例

先展示一下不將 module 設定為 amdsystem 模式的情形 —— 將 tsconfig.json 裡的 outFile 設定啟動並設定成 build.js,直接編譯之後,會顯示出錯誤訊息。(如圖八)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614Jd4ZgRK9dP.png

筆者這裡另外示範使用 amd 模式下被編譯後的結果。(如圖九)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614YyfyzwMWuP.png
圖九:amd 模式下被編譯過後的檔案

讀者可能意識到,build.js 沒有被放在 outDir 所指定的位置,那是因為 outFileoutDir 兩個設定沒有同時被 Support。(筆者感到傻眼)請參見這個 StackOverflow 帖子,裡面筆者節選重點部分講。

Unfortunately, TypeScript (at least as of version 3.3) does not support both outDir and outFile simultaneously.

(... 略)

(However to simply generate the bundled output in a different directory, yes, outFile: "subdir/bundle.js" will work just fine.)

也就是說,你如果設定 outFile./build/build.js 是可以的!

回過頭來,想要讓這個檔案可以執行,必須使用一個名為 RequireJS 的 Module Loader。如果強行用 node 去執行檔案一定會出錯。(如圖十)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614lx0eGhiBiA.png
圖十:define is not defined,連 NodeJS 也都繞口令嗎?

貼心小提示

讀者可能覺得筆者會開始講如何在 TypeScript 使用 RequireJS 執行 AMD 標準下打包出來的結果 —— 筆者會講到的!

不過必須要等筆者講到 TypeScript Namespaces 才會討論如何執行打包過後的檔案。

重點 2. 輸出專案相關設定 Output Related Configurations

  • rootDir 代表的是專案的檔案必須存放的地方,如果超出 rootDir 指定的範圍,並且沒有啟用 noEmitOnError 選項 —— TypeScript 編譯器照樣會編譯出結果,但也會拋出警告訊息提醒開發者超出 rootDir 規範的範圍

  • outDir 則是指定專案被編譯過後,輸出之結果存放的地方 —— 其中,若專案有樹狀結構資料夾分佈,則會按照該樹狀結構編譯出結果

  • outFile 則是將專案打包成單一檔案,但限制是 module 選項必須為 amdsystem 模式 —— 因為跟模組的規範有關

    • 另外,outFileoutDir 這兩個是完全不相干的設定,所以 outFile 不會在 outDir 指定的資料夾產出結果
    • 若同時出現 outFileoutDir 選項,TypeScript 編譯器裝作沒看見 outDir 這個設定

貼心小提示

儘管更動的機率 —− 筆者認為很小,不過也不排除可能未來版本的 TypeScript 設定檔會改變 outFileoutDir 的行為,但那也可能是跨越大版號才可能出現的結果吧。

小結

筆者認為很雜的內容莫過於 TypeScript 設定檔,一是太多設定讓人覺得不親切、二是專案的輸出/打包應用情境實在多到炸、三是筆者深怕漏掉一些好用的編譯器設定功能,沒有交代給讀者

下一篇筆者會繼續介紹另一些比較實用的 —— TypeScript 編譯器相關 Debug 技巧與功能介紹。


上一篇
Day 31. 戰線擴張・專案監控 X 編譯設定 - TypeScript Compiler Compile Configurations
下一篇
Day 33. 戰線擴張・專案除錯 X 源碼對照 - TypeScript Compiler Debug Techniques
系列文
讓 TypeScript 成為你全端開發的 ACE!51

尚未有邦友留言

立即登入留言