使用 Babel、Flow、ESLint 改善網頁或 JavaScript 程式專案

JavaScript 是網頁前端必備的技術,也可以在網頁後端或其他領域使用,有著豐富的生態系;然而,JavaScript 語言本身的缺陷也一直為人詬病 (參考這裡)。以繼續留在 JavaScript 生態圈的前提下改善這些缺點,使用 JavaScript 轉譯器就成了一個常見的選項 (參考這裡)。本文介紹以 Babel 為中心的工具組合,用來改善 JavaScript 的 code base。

為什麼使用 Babel、Flow、ESLint

我們來看一下這三個專案的核心功能如下:

  • Babel: 可以用 ES6 (ECMAScript 2015) 以後的新語法寫 JavaScript 程式碼,並將其轉為等效的 ES5 (EMCAScript 2009) 程式碼
  • Flow: 在 JavaScript 程式碼中進行型別檢查,可搭配 Babel 或單獨使用
  • ESLint: 可對 ES6 (ECMAScript 2015) 後的 JavaScript 程式碼進行靜態程式碼檢查

簡而言之,這個工具組合用新的 JavaScript 語法特性改善現有的 JavaScript 程式碼;透過這個工具組合,我們可以用更好的 JavaScript 語法來寫應用程式,而將原有的 ES5 視為一種執行環境 (runtime environment)。現在主流的瀏覽器都已經實作 ES5 的特性,並且逐漸實作 ES6 以後的功能,不用擔心相容性的問題。

筆者先前也嘗試過 CoffeeScript 或 Dart 等 JavaScript 轉譯器,但後來偏好 Babel。這是因為 CoffeeScript 等大部分的 JavaScript 轉譯器的語法和 JavaScript 本身不相容,可以用來撰寫新的專案,但無法直接套在原有的專案上;相較來說,只要透過幾行指令,就可以把 Babel 套用在現有的專案上,可以在不破壞現有的 code base 的前提下漸進式引入新的語法;此外,如果要用 Babel 寫新的專案也是可以的。

為什麼不使用 TypeScript

TypeScript 是另一個知名的 JavaScript 轉譯器,語法上和 JavaScript 相容,流行度更勝 Babel。然而,我們不用 TypeScript 而用 Babel,這比較屬於個人偏好而沒有絕對的對錯,我們也可以用和本文相同的思維,把專案中有關 JavaScript 的 code base 逐漸轉為 TypeScript。那麼,要如何選擇呢?TypeScript 支援更多的語法特性而 Babel 更貼近原生的 JavaScript,所以,依照自己喜好的風格來選擇即可。

建立 NPM 設定檔

一開始要先用 npm (Node.js 套件管理工具) 初始化一些設定檔,程式會詢問我們一些問題:

$ npm init
(省略一些訊息 )

Press ^C at any time to quit.
package name: (myapp)
version: (1.0.0)
description: My demo app.
entry point: (index.js)
test command:
git repository:
keywords: myapp
author: Michael Chen
license: (ISC)

不用太糾結於如何回答這些問題,這些填入的內容事後都可以在專案設定檔中修改。

npm init 原本是要用來建置 Node.js 套件或 Node.js 應用程式專案的指令,但不代表我們一定要用 Node.js 寫網頁程式。基本上 npm init 指令只是在專案中多加入幾個驅動 Node.js 相關的設定檔,包括 package.jsonpackage-lock.json 等,但和專案本身使用的語言無關。

npm init 初始化專案後,我們就可以在專案中加入一些 Node.js 套件。本文許多指令也是在專案中加入額外的 Node.js 套件,用這些套件來改善專案中 JavaScript 部分的程式碼。

加入 Babel 支援

Babel 是支援 ES6+ 的 JavaScript 轉譯器。透過以下指令在專案中加入 Babel 套件:

$ npm install --save-dev babel-cli

設定 package.json,用 Babel 轉換 JavaScript 程式碼:

"scripts": {
    "build": "babel src -d lib",
    ...
},

在這個設定下,我們輸入 npm run build 時,NPM 會將 src 中的 ES6+ 程式碼轉成等效的 ES5 程式碼,並放在 lib 中;檔案名稱不會更動,例如, src/app.js 會轉為 lib/app.js 。實際在使用 Babel 時,我們會將現有的 JavaScript 程式碼從 lib 搬移到 src ,之後編輯位於 src 的程式碼,將 lib 內的程式碼視為自動產生的靜態 assets;由於檔案名稱不會更動,我們不需要更動專案其他的部分。

Babel 預設情形下會將程式碼原封不動地拷貝到目標位置,實際轉換程式碼的功能會透過外掛 (plugins) 來達成。babel-preset-env 是一個 Babel 的外掛的套組 (set),會自動引入最新版的 JavaScript 語法。輸入以下指令來安裝該套件:

$ npm install --save-dev babel-preset-env

另外要設置 .babelrc 以啟用該 preset:

{
    "presets": ["env"]
}

實際上線的 JavaScript 程式會經過壓縮 (minification) 的動作,主要用意是縮小程式碼所占的空間,傳輸上更快一些;另外可以讓程式碼相對更難追蹤。透過 babel-minify 可達成這項功能;透過以下指令來安裝該套件:

$ npm install babel-minify --save-dev

由於程式碼經壓縮後會變得較難追蹤,在開發階段不會急著把程式壓縮,最後程式要上線前再壓縮即可。可參考以下內容來設置 .babelrc

{
    "presets": ["env"],
    "env": {
        "production": {
          "presets": ["minify"]
        }
    }
}

在類 Unix 系統系統上輸入以下指令即可轉換並壓縮 JavaScript 程式碼:

$ BABEL_ENV=production npm run build

在 Windows 上則要略為修改指令如下:

C:\path\to\project>SET BABEL_ENV=production&&npm run build

(選擇性) 加入 Flow 支援

Flow 的用意是在 JavaScript 中加入型別檢查,由於這個功能是選擇性的,讀者可自行決定要不要在專案中加入 flow。Babel 預設不會辨識 flow 相關的程式碼,要透過額外的 preset:

$ npm install --save-dev babel-cli babel-preset-flow

同時要設置 .babelrc 以啟用 flow 相關的 preset:

{
    "presets": ["env", "flow"]
}

上述套件會解析 flow 相關的代碼,在產出的 assets 中將其抹除,因為實際上線的 JavaScript 環境無法辨識 flow 的型別註解相關語法。TypeScript 程式在上線時也會將型別註解的部分抹除,道理是相同的。

上述的 preset 不會檢查型別,要另外要安裝 flow-bin 才有實際檢查型別的功能:

$ npm install --save-dev flow-bin

同時要設置 package.json

"scripts": {
    "flow": "flow",
    ...
},

第一次使用 flow 時要在專案中初始化:

$ npm run flow init

之後就可以直接用 flow 檢查代碼:

$ npm run flow

本文篇幅較短,故未介紹 flow 的語法,請各位讀者自行參考這裡的說明。

(選擇性) 使用 ESLint 檢查 JavaScript 語法

ESLint 是一個 JavaScript 的靜態程式碼檢查軟體 (linter),對於補強 JavaScript 的工程性有相當幫助。ESLint 的好處是可辨識 ES6+ 代碼;此外,ESLint 不會有太強烈的風格上的建議,可用在各類型的專案上。

透過以下指令安裝 ESLint:

$ npm install --save-dev eslint babel-eslint

第一次使用 ESLint 時要先初始化,按照自己的使用情境來回答即可:

$ node_modules\.bin\eslint --init
? How would you like to configure ESLint? Answer questions about your style
? Which version of ECMAScript do you use? ES2015
? Are you using ES6 modules? No
? Where will your code run? Browser
? Do you use CommonJS? No
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Double
? What line endings do you use? Windows
? Do you require semicolons? Yes
? What format do you want your config file to be in? JavaScript

由於我們採局部 (local) 安裝,故我們從 node_modules 中呼叫 ESLint 的終端機指令。

package.json 中啟用 ESLint:

"scripts": {
    "lint": "eslint src/*.js",
    ...
},

ESLint 的用法較多,或許有機會日後會在其他文章介紹。

Git 或其他版本控制的使用者

記得要在專案中加入相關的 .gitignore 或其他同性質檔案,以免引不不必要的檔案。

下篇具有可攜性的命令稿 shebang