網站上線前一定會歷經各個測試環境 (sit / uat) 的試煉,因此需定義出符合各環境的各項參數值,本文提供兩種方式來搭配使用,應可符合絕大多數需要調整環境參數的情境。
前言
以 vue-cli 建立的專案來說,在 webpack 設定中會區分 development 及 production 兩種建置 config 供開發者使用,我們可以透過這兩種模式來建立對應的參數檔,供我們在「開發」及「打包」時各自調用不同的參數。
系統上線前一定會歷經 sit 及 uat 的洗禮, 各環境對參數需求會不一樣 ( 如佈署根目錄位置 ),其餘 webpack 打包方式都相同,所以我們可以從 production 延伸出去來訂定各測試環境的參數檔。
vue-cli: 2.9.2
webpack: 3.6.0
環境切分
通常網站從開發到上線會經歷幾個過程,因此較常態性的切分方式如下:
- development (開發, hot reload)
- production (打包, build)
- local - 本機測試
- sit - 系統整合測試
- uat - 用戶接受度測試
- prod - 線上正式版
參數位置考量
組態區分就是用於調整「相同變數」在「不同環境」套用「不同內容」,以此來符合各環境預期的參數需求。一般來說參數定義位置約略會區分為以下兩類,後續會分別使用這兩種方式進行實作說明。
- 透過 build script 傳入
打包時才決定的參數,不異動代碼就可因應 web server 環境隨時 build 出符合所需的站台設定。
- 定義在 constant 檔案
固定不變的參數,可以預先依環境定義數值。
理解環境判斷機制
透過 vue-cli 建立的專案會區分 development 及 production 兩種 webpack 設置,因此在建置時會各自叫用 build 資料夾下的 webpack.dev.conf.js
及 webpack.prod.conf.js
設定檔,並且同時載入 config 資料夾下對應的 dev.env.js
及 prod.env.js
環境變數。
環境變數 dev.env.js
及 prod.env.js
中有各自定義的變數值 (ex. NODE_ENV )
/* dev.env.js */
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})
/* prod.env.js */
module.exports = {
NODE_ENV: '"production"'
}
由於 webpack.dev.conf.js 及 webpack.prod.conf.js 設定檔都有用 webpack.DefinePlugin
將各自載入的環境變數設定在 process.env
之下,所以我們才可以在程式中使用那些「文字」判斷目前建置環境。
為什麼說是以「文字」來做判斷,原因是 webpack.DefinePlugin
是透過打包後以「文字取代」的方式來重新定義所謂的參數值;以設定物件為例,該物件內所有 key
都會被定義在 process.env 之下,因此在打包時會將程式中所有 process.env.key 關鍵字串「取代」為 value
值,所以我們才可透過 isDev 判斷式來得知目前執行環境為何。示意如下。
- DefinePlugin: "process.env": {NODE_ENV: '"development"'} (同下行設定)
DefinePlugin: "process.env.NODE_ENV": '"development"'
- 原始代碼: const isDev = process.env.NODE_ENV === 'development'
- 打包輸出: const isDev = "development" === 'development'
建立各環境常數檔
在熟悉判斷環境變數的機制後,接著新增 constant 資料夾,並建立 development.js 及 production.js 兩個環境的常數檔 (最好依照 process.env.NODE_ENV 環境名稱命名,避免不需要的判斷邏輯)。
/* constant/development.js */
export default {
environment: process.env.NODE_ENV,
apiUrl: 'http://127.0.0.1:9999/'
}
/* constant/production.js */
import baseConstant from './development'
let constant = baseConstant
// 僅調整與 dev 有差別的部分就好
constant = { ...constant, apiUrl: 'http://11.22.33.44/api/' }
export default constant
會在 constant/index.js
中依當下環境決定 export 出哪個檔案:
- development: 會載入
constant/development.js
作為常數檔 - production: 會載入
constant/production.js
作為常數檔
/* constant/index.js */
// 依據建置環境選擇對應變數定義檔 (webpack.DefinePlugin)
const envConstant = require('./' + process.env.NODE_ENV + '.js').default
export default envConstant
使用端只需 import constant 即可,裡面的 constant.apiUrl
參數就會依照環境的不同而切換數值。
import constant from 'constant'
console.log(constant.apiUrl)
// in develop:
// http://127.0.0.1:9999/
// in production:
// http://11.22.33.44/api/
透過 Build Script 傳入參數
在開發初期有一些參數會比較不穩定,並且隨著佈署環境的不同而有變動的可能性;再者協助佈署的人員可能又不是開發人員,因此最快的方式就是透過 build script 將參數傳入,可以在不須異動 constant 檔案的情況下滿足佈署人員立即置換參數的需求。
先來分析一下需求,筆者需要傳入以下三個參數,作用分別如下所示:
-
mode: 放置建置哪種類型的 Production 環境 (local/sit/uat/prod)
-
rootPath: 放置佈署時 Web Server 根目錄路徑 (虛擬目錄)
-
noDecrypt: 是否執行資料解密動作,因為測試環境並無加解密機制 (true/false)
預期的 build script 如下
使用 command-line-args 解析參數
接著開始實做參數傳入及接收邏輯,筆者使用 command-line-args 這個套件來 parse 傳入參數,可以預先定義資料名稱及格式,在結構化這些參數後會比較好操作,為程式帶來較高的可讀性。
在執行打包作業時,會統一呼叫 build/build.js
這隻檔案,因此參數會從這邊傳入與取得;在接收到資料後馬上存放至 node 環境變數 process.env 物件中,後續會依據這個 node 環境變數做接續的處理。
// 設定建置環境
process.env.NODE_ENV = 'production'
// 定義 build.js 的傳入參數
const optionDefinitions = [
{ name: 'mode', type: String},
{ name: 'rootPath', type: String },
{ name: 'noDecrypt', type: Boolean, defaultValue: false }
]
// 接收與格式化傳入參數
const commandLineArgs = require('command-line-args')
const options = commandLineArgs(optionDefinitions)
// 將參數存入 process.env 全域變數
process.env.MODE = options.mode
process.env.ROOT_PATH = options.rootPath
process.env.NO_DECRYPT = options.noDecrypt
// 取得建置時期的建置參數
var webpackConfig = require(`./webpack.prod.conf`)
// 使用 webpackConfig 執行打包作業
// ...
設定 webpack.DefinePlugin
先前有提到如果要將環境變數放置到程式中,必須透過 webpack.DefinePlugin 來定義;由於我們多了一些從 build script 來的參數,因此在引入 webpack.DefinePlugin 的 webpack.prod.conf.js
必須微調,將新增的參數一併加入其中,示意程式如下。
'use strict'
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
// 固定的建置環境設定值
let env = require('../config/prod.env')
// 加入外部控制的資訊
// [*] 使用 JSON.stringify 目的在於將 'str' 轉為 '"str"' 後
// 透過 define plugin 才可正確取代 process.env.XXX 代碼為 "str" 字串代碼
// [*] 資料來源 process.env 會強制把屬性值都設為字串,所以在轉換 boolean 時要注意
const {MODE, ROOT_PATH, NO_DECRYPT} = process.env
env = {...env, MODE: JSON.stringify(MODE),
ROOT_PATH: JSON.stringify(ROOT_PATH),
NO_DECRYPT: NO_DECRYPT ==='true' ? true : false}
const webpackConfig = merge(baseWebpackConfig, {
// ... 略 ...
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
})
// ... 略 ...
]
})
// ... 略 ...
JSON.stringify()
方法來處理字串資料,舉個簡單的例子來說明會比較清楚;若程式中有段邏輯為 if (process.env.Target === 'web')
時,在兩種設定下會有不同的結果喔!- 在 webpack.DefinePlugin 中設定 process.env.Target: 'web'
取代後會變成if (web === 'web')
然後報錯,因有未定義的 web 變數!
- 在 webpack.DefinePlugin 中設定 process.env.Target: JSON.stringify('web') 等同 '"web"'
取代後會變成if ("web" === 'web')
,就是我們需要的結果。
'"production"'
, 或者使用 JSON.stringify('production')
。
切出不同環境下的 production 常數
接著就可以透過 process.env 取得透過 build script 中傳入的參數,所以就可以開始將此變數埋入須要透過外部調整的邏輯中。以下圖為例,就可以在先前我們產生的 constant/production.js
常數檔中透過 MODE 區分出不同佈署環境所需的參數值,讓 production 常數檔的應用性又更加靈活了。
透過 build script 傳入的 rootPath
也可直接埋入,終於可以直接調整 build script 來因應佈署環境了。
設定 Build Script 清單
完成上述組態分割動作後,可以統一在 package.json 中定義一些 build script 指令,讓其他人在不知道細節的情況下,也可以輕鬆打包出符合環境需求的站台檔案。
參考資訊
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !