從頭建立一套 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。若是專案有使用 forever 或 pm2 做程式佈署,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 在本專案有兩個使用途徑:
- 藉由整合為 Webpack 的一個 loader,讓 frontend code 直接轉為 ES5
- 使用
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 語法,由於這些語法未來變動規格的可能性比較高,因此要斟酌使用。
要注意的是 presets
和 plugins
的宣告順序會影響 transpile 結果。以上面的設定為例,Babel 會依照 babel-preset-stage-0
、babel-preset-es2015
、babel-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 支援,可以參考本篇文章的解說