位元詩人 技術雜談:使用 Babel、Flow、ESLint 改善網頁或 JavaScript 程式專案

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

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 本身不相容;雖然這些轉譯器可以用來撰寫新的專案,但無法直接套在現有的 JavaScript 專案上。

相較來說,只要透過幾行指令,就可以把 Babel 套用在現有的專案上,可以在不破壞現有的專案程式碼的前提下漸進式重構 (refactor) 新的 JavaScript 語法特性。如果要用 Babel 寫全新的專案當然也是可以的。

為什麼不使用 TypeScript

TypeScript 是另一個知名的 JavaScript 轉譯器,語法上和 JavaScript 相容,流行度更勝 Babel。然而,我們不用 TypeScript 而用 Babel,這比較屬於個人偏好而沒有絕對的對錯。

我們也可以用和本文相同的思維,把專案中有關 JavaScript 的專案程式碼逐漸轉為 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: Michelle 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 部分的程式碼。有許多和網頁程式相關的工具都是運行在 Node.js 環境中,不論專案本身使用什麼語言,加入一些網頁工具可以增加程式人的生產力。

在 NPM 專案中加入 Babel 支援

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

$ npm install --save-dev @babel/core @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 的程式碼。引入 Babel 後,我們將 lib 內的程式碼視為自動產生的靜態資源 (assets),不會去手動編輯這些樣案;由於檔案名稱不會更動,我們不需要更動專案其他的部分。

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

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

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

{
    "presets": ["@babel/preset-env"]
}

(選擇性) 支援 Internet Explorer 等舊瀏覽器

上一節的設置,是針對主流瀏覽器來設置。當時 ES6 才剛起步,故要把運行環境鎖在 ES5。不過,我們可以稍微調整一下 Babel 相關設定,就可以讓專案的前端 JavaScript 程式碼支援 Internet Explorer 等有一定市佔率的舊瀏覽器。

我們要額外安裝 core-js 套件:

$ npm install --save core-js@3

目前 core-js 在版本 2 及版本 3 的過渡期。除非有什麼好的理由,不用刻意守在舊版本。

另外要安裝 polyfill 套件:

npm install --save @babel/polyfill

在 JavaScript 生態圈中,polyfill 的意思是重新實作一些新瀏覽器的特性,讓舊瀏覽器也能使用到這些特性。安裝後記得要將 node_modules/@babel/polyfill/dist/polyfill.min.js 拷貝到專案的靜態資源中,因為 Babel 轉出的 JavaScript 程式碼要透過 polyfill 補足相關功能才能順利在舊瀏覽器上執行。

參考以下 .babelrc 設定:

{
    "presets": [
        ["@babel/preset-env", 
        {
            "targets": {
                "ie": "9"
            },
            "modules": false,
            "useBuiltIns": "entry",
            "corejs": 3
        }]
    ]
}

考量市佔率,支援到 Internet Explorer 9 應該是足夠了。有需要支援更低版本的 IE 的讀者請自行調整設定檔。

因為我們的專案在前端運行,不需要使用 modules。

我們開啟 builtin 功能,Babel 就會認定 polyfill 相關特性已經載入;實際上要由我們自己另行載入 polyfill 函式庫。

使用 "entry" 代表 Babel 認定只會載入一次 polyfill。因為我們的前端 JavaScript 程式碼可能分散在好幾個檔案中,我們把整個網頁當成是一個整體,在同一個頁面中只需載入一次 polyfill 即可。

(選擇性) 壓縮輸出的程式碼

實際上線的 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 或其他同性質檔案,以免引不不必要的檔案。