從頭建立一套 web-based 服務架構 (4) 環境變數、ESLint 與 Babel

昨天在初始化專案架構時,除了使用 Gulp 作為 task runner 外,還運用了如 ESLint、Babel、Webpack 等工具,也利用 NODE_ENV 這個環境變數進行開發階段或 production 階段的區分。接下來幾篇文章會針對各個部分進行討論。

關於環境變數(environment variables)和 NODE_ENV

專案的 config 處理方式因人而異,在這裡我們不對設定值做太多預先假設( i.e. 不寫死在程式碼裡面),讓這些設定能夠於 deploy 階段被動態宣告(註1)。在 Node.js 開發環境中,這點可以藉由 process.env.* 取得執行時期的環境變數來達成。昨天介紹的 gulp tasks 並沒有針對環境變數做太多額外的處理(除了對 NODE_ENV 做 fallback),而是由更外層進行調配。

使用方式的例子如:

NODE_ENV=production node src/server

也可以寫在 npm scripts 裡面:

"scripts": {
    "production": "NODE_ENV=production PORT=3000 node src/server"
},

然後用 npm run production 啟動。

dotenv 是另一個常用的好套件,他能夠在 runtime 讀取專案中 .env 檔案的 key=value 設定內容,並注入到 process.env 物件中。dotenv 的使用前提是避免 .env 成為程式碼的一部分( i.e. 加入 .gitignore),所以記得要在 deploy 程序中加進寫入 .env 檔案的步驟。

由於本次專案預計以製作 Docker image 的形式佈署,直接由指令注入的方式會更方便,因此就不採用 dotenv。若是專案有使用 foreverpm2 做程式佈署,dotenv 會是很適合使用的工具(雖然 pm2 本身也有自帶環境變數管理的機制)。

除了 NODE_ENV 外,有哪些設定值也適合放在環境變數呢?主要的思考方向,還是要以 deploy 時期的變化來做考量。例如,PORT 就是個容易在不同環境下變動的設定,像是在線上機器使用 80 port(註2)而在本機開發時使用不需 sudo 權限的 port。

若此服務用到了其他外部服務,這些外部服務的連線設定也適合由環境變數做指定。以本專案的情況,Frontend server 會需要知道 API server 的位置以向 database 獲取資料,因此 API server 的 URL 就會放在環境變數,而非寫死在程式碼內。

ESLint

Linting 對 JavaScript 這種 dynamic language 的開發相當重要,能夠在 pre runtime 直接找出潛在的 bug 和不好的寫作風格,減少 debug 時間和降低 runtime 出錯的風險。早期比較常用的 Linting 工具是由 Douglas Crockford 開發的 JSLint,不過現在則是以功能較為全面的 superset 「ESLint」為主。

設定 ESLint 的方式是於專案根目錄撰寫 .eslintrc 檔案讓 ESLint 工具讀取,可以用 JSON 或 YAML 格式撰寫。附帶一提,其實 ESLint 官網不建議使用 .eslintrc 的命名,建議可以在檔案名稱後主動加上格式附檔名以利 ESLint 進行解析,例如 .eslintrc.json.eslintrc.yaml

在 ESLint 設定中,可以選擇不同的 parser、plugin 和 rule set,可以高度化客製不同專案習慣的 linting 風格。以下大致介紹此專案使用的 ESLint 設定:

  • parser 使用 babel-eslint。其實也可以不指定並採用預設的 ESLint parser,因為目前內建的 parser 已經支援 ES6 以後的語法;不過因為如 decorator 等只有 Babel 支援的語法仍舊需要使用 babel-eslint 進行解析,本專案就不採用內建 parser
  • ESLint 設定擋是可以擴充的,本專案的設定即基於知名的 Airbnb ESLint config
  • rules 根據個人喜好,更改一些預設的設定值,並加入 React.js 的 ESLint rule set

不過,比起設定一堆 linting stuffs,最重要的還是開發者自身良好的寫作習慣(coding conventions)。 針對 JavaScript 的 coding convention,作者個人非常推薦閱讀 Airbnb JavaScript Style Guide 以及詳讀 JavaScript: The Good Parts 一書。

Babel

由於主流的瀏覽器對於 ES6 語法的支援度仍然不高,將使用 ES6 撰寫的 JS 程式碼 transpile 回 ES5 的工作是目前前端開發的重點之一。Babel 大概是目前使用率最高的 transpiler 了,得益於他的 plugin 生態系,現在幾乎各種 JS ecosystem 的東西都能用 Babel 轉回 ES5,當然也包括撰寫 React.js 慣用的 JSX 語法。Babel 在本專案有兩個使用途徑:

  1. 藉由整合為 Webpack 的一個 loader,讓 frontend code 直接轉為 ES5
  2. 使用 babel-register 讓 server-side 的程式碼直接由 Babel 轉換成 ES5 執行

雖然目前在 server 使用 Babel 做 transpiling 的效益沒有太大(因為 Node 6.x 已經達到 90% 以上的 ES6 語法支援率),但仍有幾個特定語法是非得使用 transpiling 做轉換不可的,尤其是新的 import 指令(註3)或還未定案的 ES next 語法系列( e.g. decorator 或 Object spread)。

和 ESLint 類似,Babel 會在專案根目錄讀取 .babelrc 檔案並取得 Babel 的設定。大部分狀況下,.babelrc 只需要指定想使用的 preset 規則、並安裝相對應的套件即可使用:

{
      "presets": ["react", "es2015", "stage-0"]
}

Preset 是預先綁定好的 plugin set,因此不需額外在 plugins 宣告 presets 已經有的 plugin,這是比較 top-down 的作法。想要以 bottom-up 的方式設定 Babel 的話,可以自行挑選需要 transpile 的語法所對應的 plugin 並加入 ”plugins” 陣列中。在這幾個 preset 中,比較特殊的是 babel-preset-stage-0,這個 preset 涵括了一些比較熱門的 ES next 語法,由於這些語法未來變動規格的可能性比較高,因此要斟酌使用。

要注意的是 presetsplugins 的宣告順序會影響 transpile 結果。以上面的設定為例,Babel 會依照 babel-preset-stage-0babel-preset-es2015babel-preset-react 的順序轉換程式碼;而 plugin 則是以宣告的順序做轉換。如果發現 Babel 對 transpile 過程有抱怨的話,可以檢查一下 .babelrc 看是否有可能是因為轉換順序造成的 bug。


註1:可參考著名的 12-factor app 系列對 config 的看法

註2:這其實是不好的使用情境,比較好的作法是搭配其他服務做 port forwarding,例如 nginx、Apache HTTP Server 或 Docker 的 -p 功能

註3:import 預期短時間內不會被原生的 Node.js 支援,可以參考本篇文章的解說

results matching ""

    No results matching ""