[Vue] 跟著 Vue 闖蕩前端世界 - 16 各環境組態切分技巧

網站上線前一定會歷經各個測試環境 (sit / uat) 的試煉,因此需定義出符合各環境的各項參數值,本文提供兩種方式來搭配使用,應可符合絕大多數需要調整環境參數的情境。

前言


以 vue-cli 建立的專案來說,在 webpack 設定中會區分 development 及 production 兩種建置 config 供開發者使用,我們可以透過這兩種模式來建立對應的參數檔,供我們在「開發」及「打包」時各自調用不同的參數。

系統上線前一定會歷經 sit 及 uat 的洗禮, 各環境對參數需求會不一樣 ( 如佈署根目錄位置 ),其餘 webpack 打包方式都相同,所以我們可以從 production 延伸出去來訂定各測試環境的參數檔。

vue-cli: 2.9.2
webpack: 3.6.0

 

環境切分


通常網站從開發到上線會經歷幾個過程,因此較常態性的切分方式如下:

  1. development (開發, hot reload)
  2. production (打包, build)
     
    1. local - 本機測試
    2. sit - 系統整合測試
    3. uat - 用戶接受度測試
    4. prod - 線上正式版

 

在使用 vue-cli 建立專案後,對於 development 及 production 的 webpack 設置都已具備了,因此筆者將以此架構為基礎,向後延伸說明如何實作,依據各環境需求建立出對應參數取得機制。

 

 

參數位置考量


組態區分就是用於調整「相同變數」在「不同環境」套用「不同內容」,以此來符合各環境預期的參數需求。一般來說參數定義位置約略會區分為以下兩類,後續會分別使用這兩種方式進行實作說明。

  1. 透過 build script 傳入
    打包時才決定的參數,不異動代碼就可因應 web server 環境隨時 build 出符合所需的站台設定。
     
  2. 定義在 constant 檔案
    固定不變的參數,可以預先依環境定義數值。

 

 

理解環境判斷機制


透過 vue-cli 建立的專案會區分 development 及 production 兩種 webpack 設置,因此在建置時會各自叫用 build 資料夾下的 webpack.dev.conf.jswebpack.prod.conf.js 設定檔,並且同時載入 config 資料夾下對應的 dev.env.jsprod.env.js 環境變數。

 

環境變數  dev.env.jsprod.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 檔案的情況下滿足佈署人員立即置換參數的需求。
 

先來分析一下需求,筆者需要傳入以下三個參數,作用分別如下所示:

  1. mode: 放置建置哪種類型的 Production 環境 (local/sit/uat/prod)

  2. rootPath: 放置佈署時 Web Server 根目錄路徑 (虛擬目錄)

  3. noDecrypt: 是否執行資料解密動作,因為測試環境並無加解密機制 (true/false)

 

預期的 build script 如下

$ node build/build.js --mode=local --rootPath=/boo/ --noDecrypt

 

 

使用 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') ,就是我們需要的結果。
注意,因為 DefinePlugin 會直接執行文本替換,給定的值必須包含字符串本身內的實際引號。通常,有兩種方式來達到這個效果,使用 '"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 指令,讓其他人在不知道細節的情況下,也可以輕鬆打包出符合環境需求的站台檔案。

 

 

參考資訊


command-line-args

DefinePlugin

webpack.DefinePlugin 使用介绍

 

 


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !