iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

1
Modern Web

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

Day 33. 戰線擴張・專案除錯 X 源碼對照 - TypeScript Compiler Debug Techniques

https://ithelp.ithome.com.tw/upload/images/20191001/201206148FYCOGmYAF.png

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

  1. 如何確保出現錯誤時,防止 TypeScript 編譯器產出專案結果?
  2. 描述 rootDiroutDiroutFile 三種設定。
  3. outFile 的使用時有什麼限制?跟 outDir 差別在哪?

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

今天要講一些跟除錯相關的東西。

讀者可能會問:“奇怪,在 VSCode 上面 TypeScript 不是會幫你檢測語法?難道還有其他除錯的方法?”

方法其實超多種,只是有沒有被發現或有沒有用對罷了,想要提升作業效率 —— 除錯技巧也是一門可以被優化的地方。

事實上還有另一個好用的東西叫做 Source Map —— 筆者就不多說,請讀者繼續看下去。

另外,今天也會統整通常在 JS 專案上 Debug 的途徑有哪些~

以下直接正文開始吧~

專案除錯技巧 Project Debug Technique

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

沒錯,我們還沒講完專案相關設定,但這是最後一彈了!XD

簡易環境建置 —— 使用 lite-server

但在我們講今天的專案設定前,筆者必須進行簡單的環境建置動作。

讀者可以進到任何想要測試專案的檔案資料夾位置:首先將 package.json 進行初始化的動作,並且下載一個名為 lite-server 的套件 —— 專門快速 Host 簡單的環境讓 HTML/CSS/JS 檔案協作。

// 進到任何資料夾,也可以臨時 mkdir 創建再進去
$ cd PATH_TO_TEST_DIR

// 初始化 package.json
$ npm init -y

// 下載 lite-server 套件
$ npm install lite-server --save-dev

下載完後,進到 package.json,將 scripts 選項改成:

{
  /* 略... */
  "scripts": {
    "dev": "lite-server"
  },
  /* 略... */
}

因此每一次執行 npm run dev 會啟動 lite-server

圖一是目前打該編輯器應該出現的結果。

https://ithelp.ithome.com.tw/upload/images/20191001/20120614zNmqijV4Bp.png
圖一:初始化專案並下載 lite-server

好的,在當前檔案資料夾,筆者建立簡單的 index.html 檔案並填入一些程式碼。

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

以下是對 HTML 檔案的說明:

  • 有一個按鈕被綁定 ID 為 click-me-btn,等等會註冊事件更新被按到的次數
  • 有一個 P 元素裡面有 Span 負責紀錄被按到的次數
  • 引入 ./build/index.js 檔案(該檔案目前是還未編譯狀態,請繼續看下去)

另外,初始化 TypeScript 專案、建立 /src/build 資料夾以及新增 index.ts/src 裡:

// 初始化 tsconfig.json
$ tsc --init

// 建立 /src 與 /build
$ mkdir src
$ mkdir build

// 新增 ./src/index.ts
$ touch ./src/index.ts

筆者簡單在 index.ts 填入一些程式碼,負責將該按鈕註冊簡單的事件並更新按鈕被按到的次數。

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

不過以上的程式碼,TypeScript 會告訴你 —— $btn$counter 可能為 null。(如圖二)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614jMRESRJO7W.png
圖二:元素在進行查找的時候,可能會找不到

於是有幾種寫法可以解決,一種是直接對那些 document.getElementById 進行積極註記的動作。

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

不過筆者傾向於另一種寫法 —— 使用 Type Guard 進行過濾的動作,因為之前在複合型別篇章提過 —— 通常遇到 union 型別,普遍解決方式就是要設定 Type Guard 進行型別限縮。

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

這樣的寫法也比較合理的理由是:如果對 A | null 進行強行註記為 A 這個型別的話,就等同於你忽略了它可能是 null 的機率,造成開發過程中產出 Bug 的風險 —— 通常在這裡會變成 null 的情形,不外乎可能是元素的 ID 打錯字或者是單純 getElementById 裡面的元素 ID 打錯字,所以要讓 TypeScript 自動幫我們監測這些問題的存在。

使用 Type Guard 就會逼迫我們寫出錯誤情形的應變對策(在這裡是直接丟出錯誤訊息),後續出錯就知道問題在哪,馬上可以去查 Bug~!

另外,剛剛解完前面的問題後,TypeScript 還會提醒你 innerText 不能接收 number 型別,必須為 string。(如圖三)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614EfIfs21oxR.png
圖三:TypeScript 的好處就是,它會提醒你正確的使用方法

所以必須將程式碼那一行改成:

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

以下是完整的程式碼:

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

讀者試試看

這裡筆者想要問讀者簡單的問題,如果你還記得在本系列一開始提到型別的推論與註記的機制,為何筆者沒有對 index.ts 裡的範例程式碼的某些變數進行積極註記(Annotation)的動作?

這裡要請讀者複習必須要積極註記型別的時機:

  • 什麼時候必須積極註記?
  • 什麼時候則不需要就可以藉由型別系統的推論(Inference)就會自動幫我們監控各種變數或參數型別?
  • 什麼時候需要用到 Type Guard?

最後,在 tsconfig.json 裡修改一下 rootDiroutDir 這兩個選項設定,環境建置就完成了。

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

直接進行編譯檔案的動作並啟動 lite-server

// 編譯檔案
$ tsc

// 執行 lite-server
$ npm run dev

圖四是執行的結果,可以看到每次按按鈕,都會新增計數次數。

https://i.imgur.com/TiEk5CM.gif
圖四:執行結果

通常 Debug 會有幾種方式,以下一個接一個討論。

除錯技巧 1. 使用 debugger 關鍵字

這是 JavaScript 在瀏覽器本來就有的功能,可以在程式碼植入 Debugger 並且查找不同變數狀態。可能讀者在學原生 JS 時早就知道這個技巧,但筆者想要提醒的是 —— TypeScript 也可以使用 debugger

於是,我們將 index.ts 更改一下並重新編譯 —— 使用 tsc

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

當你編譯過後檔案,lite-server 會偵測到 ./build/index.js 改變,自動重新對瀏覽器刷新,因此不必重新整理視窗。

回到剛剛的程式碼,我們是在按鈕註冊事件裡加入 debugger —— 也就是說,每一次按按鈕我們都會暫停 JS 的執行並且可以自由測試。(如圖五)

https://i.imgur.com/Wt2YZ8A.gif
圖五:每一次按到按鈕會在 debugger 註記的地方暫停執行,並且可以檢視此時的變數狀況!

另外,必須注意到的是圖六這個畫面。

https://ithelp.ithome.com.tw/upload/images/20191001/20120614VRCHOTuw7G.png
圖六:這是觸發 debugger 時的畫面

筆者必須將以上畫面得出的資訊下重點:

重點 1. 使用 debugger 關鍵字

debugger 為 JavaScript 本身就擁有的除錯 Feature,當注入 debugger 在程式碼的任何地方,只要瀏覽器執行到就會停在該地方,並自動將瀏覽器的開發者工具跳到原始碼觸發 debugger 的位置。

另外,此時的瀏覽器開發者工具的 Console 裡紀錄的變數狀態會跟 debugger 所在的區域同步 —— 也就是說,你可以在執行的過程檢視任何變數的變化甚至中途進行竄改、呼叫方法執行的動作。

在 TypeScript 檔案裡也可以使用 debugger,編譯過後也可以使用。

貼心小提示

不同的瀏覽器可能會對 debugger 或各種開發者工具提供的 Debug 功能有不同的行為 —— 但行為應該要差不多ㄧ致,就是操作介面的長相可能不ㄧ樣罷了。

以上的範例是使用 Google Chrome 作為瀏覽器進行測試,因此以下的所有範例也都會以同個瀏覽器作示範喔!

除錯技巧 2. 直接在開發者工具部分,標註程式碼執行斷點 Breakpoint

既然知道開發者工具有 Source 這個功能讓我們可以得知原始碼存放位置,當然不僅僅只有 debugger 可以這樣做 —— 我們可以翻閱原始碼直接進行斷點標註,使得被標注的位置,只要被執行到也會跟 debugger 有同樣效果。

因此筆者將剛剛的 index.ts 恢復到原本狀態並重新編譯。

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

重新編譯過後,lite-server 會自動刷新頁面。這一次筆者示範如何在程式碼內設定斷點:打開瀏覽器的開發者工具後並且找到 Source 這個 Tab。(如圖七)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614daAwxd6r1c.png
圖七:打開開發者工具進入到 Source 介面,它會自動連結到 index.js 檔案

頁面跟剛剛差不多,但這一次翻閱 index.js 途中,可以對任意一行程式碼標註斷點。(如圖八)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614IFmOpmFis9.png
圖八:在 index.js 裡的 console.log 這一行直接標註

如果筆者直接按了按鈕,ㄧ樣就會在被標注的那一行停止執行喔!(如圖九)

https://i.imgur.com/SqpfIg0.gif
圖九:設定斷點的效果跟 debugger 差不多呢!

重點 2. 翻閱原始碼進行除錯

通常開啟瀏覽器的開發者工具,其中 Source 的部分會存放該網頁從 Server 端取得的靜態資源,可能包含 JS 檔案、圖片檔等等 —— 想要檢閱完整的原始碼可以在這個地方搜尋。

另外,檢閱原始碼的過程中,可以對 JS 檔案標註斷點位置 —— 在執行的過程當中若執行到斷點,會停止執行並且 Console 狀態會鎖定目前的程式碼執行到的變數狀態

除錯技巧 3. 使用 TypeScript 編譯器設定 —— sourceMap

另外,有時候編譯過後的 JS 檔案在複雜的情況下會讓人看不懂,比如*列舉型別被編譯的結果就是ㄧ團噁心的 IIFE 配上物件的屬性互相指派的動作*。(如果讀者還記得列舉型別篇章討論到的東西的話)

如果不希望在檢索 JS 程式碼的過程中看到那一大串冗長的編譯結果,於是 TypeScript 編譯器設定提供一個很好的方式:使用 sourceMap

筆者這邊就把 tsconfig.json 裡,將 sourceMap 選項開啟。

{
  "compilerOptions": {
    /* 略... */
    "sourceMap": true
    /* 略... */  
  }
}

並且經過 TypeScript 編譯結果如圖十。

https://ithelp.ithome.com.tw/upload/images/20191001/20120614jkoz0rzDQ5.png
圖十:經過編譯後會發現,多出了一個 .js.map 檔案

這個 .map 檔案是所謂的 Source Map —— 代表的是連結編譯 TypeScript 檔案後與編譯前的關聯檔

其實任何專案的 minifieduglified 的結果通常也會搭配一個 Source Map,負責連結轉換前的原始碼的樣貌。

也就是說,有了 index.js.map,瀏覽器就會知道原本的 TypeScript 檔案內容會長什麼樣子

因此打開瀏覽器並進到 Source 介面,你會發現多了 src 這個資料夾,存放了 index.ts 檔案。(如圖十一)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614aOZIKNftvI.png
圖十一:Source 介面裡面有一個名為 src/index.ts 的檔案

打開內容後,ㄧ樣可以在 index.ts 設定斷點,我們照樣可以進行 Debug 動作。(設定斷點如圖十二;實際測試如圖十三)

https://ithelp.ithome.com.tw/upload/images/20191001/20120614rbhWAolBQE.png
圖十二:我們也可以在 index.ts 裡面設定斷點呢!

https://i.imgur.com/F7Nv9Sr.gif
圖十三:也可以藉由 TypeScript 的原始檔案進行除錯,sourceMap 實在是很方便呢!

重點 3. TypeScript 編譯器 Source Map 的產出

若將 tsconfig.json 裡的 sourceMap 啟用,每次編譯後的 JS 檔案都會附帶一個 Source Map 檔案。

Source Map 檔案的主要目的是建立編譯前後的連結資訊 —— 因此只要擁有編譯後的檔案與對應的 Source Map 檔案,就可以回推原始檔的模樣。

藉由 Source Map 的建立,可以在開發者工具 —— 不需要檢視編譯過後產生的 JS 檔,可以用原本的 TypeScript 檔案進行除錯

小結

基本上,筆者最近三篇都在講專案相關方面的編譯器設定,這些都是學習 TypeScript 建議必須知道的編譯機制。

不過光是這一篇,為了鋪 sourceMap 設定的梗,實是有些辛苦;但一次把常見的除錯技巧列出來,對未來開發上具有加成的效益。

往後讀者若建構更複雜的專案可能也會需要撥時間研究更多內部的設定資訊,但通常筆者 Cover 到的設定應該是足夠了,其他的可能 —— 通常也會想到將 TypeScript 與 Webpack 結合建立一系列的編譯程序等等。(請等待 Day 38.)

反正,接下來筆者要進到另一個很好用的編譯設定系列 —— 也就是筆者自己認定所謂的語法檢測相關的編譯設定(Syntatic Checks Related Configs.)。

讀者云:“編譯器設定還沒講完啊!?”

對・就是這樣

下一篇再見!


上一篇
Day 32. 戰線擴張・專案輸出 X 輸出設定 - TypeScript Compiler Output Configurations
下一篇
Day 34. 戰線擴張・專案語法 X 嚴格把關 - TypeScript Compiler Syntatic Checks Configurations
系列文
讓 TypeScript 成為你全端開發的 ACE!51

尚未有邦友留言

立即登入留言