說明: 本文代碼實現以vue爲主,其餘框架思路相同html
咱們用vue-cli生成項目後,vue給咱們僅提供了一個啓動的命令和一個打包命令(若是你選擇了單元測試等其餘選項,還會有其餘命令,但本篇文章咱們不討論它),這個啓動命令是開發環境的,打包打的是生產環境的包。vue
可是若是咱們還有測試環境以及預發佈環境呢,這兩個環境都是線上的,測試環境的接口、預發佈環境的接口以及生產環境的接口都是不相同的,但除了開發環境外其餘環境都是線上的,這個時候一個npm run build
彷佛有點不夠用了呢,由於咱們運行npm run build
永遠打的是生產環境包(也就是說發佈到線上後你向後臺請求的是生產環境的接口)。固然你也能夠每次都手動打包,打包以前改一下接口地址,可是這樣彷佛有點low啊,並且若是你除了要改接口地址外還有其它部分須要改呢,這樣就有點麻煩了。node
因此咱們場景一的目標,就是給不一樣的環境配置一個不一樣的打包命令,而後打不一樣環境的包,解決每次打包前修改配置,這樣還有個好處就是若是你司採用Jenkins類的自動化部署工具,那麼咱們就能夠提供不一樣的命令給Jenkins,而後完全解放雙手。linux
好,不說廢話,咱們直接開始解決問題;webpack
1,項目安裝cross-env
,cross-env是node的一個設置和使用環境變量的腳本;nginx
npm install cross-env -D
git
2,在項目的package.json
文件中,把scripts
對象的build
字段的值改成如下代碼,實際就是修改npm run build
命令。同時再添加npm run build:test
命令和npm run build:pre
命令。github
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"build": "cross-env env_config=prod node build/build.js",
"build:test": "cross-env env_config=test node build/build.js",
"build:pre": "cross-env env_config=pre node build/build.js"
},
複製代碼
簡單說一下,與原文件相比,咱們改了build
字段的值,同時添加了build:test
和build:pre
屬性,這樣作以後至關於咱們加了npm run build:test
和npm run build:pre
命令,我準備當運行npm run build
時打生產環境包,運行npm run build:test
時打測試環境包,npm run build:pre
打預發佈環境包。web
與原命令相比,我在node build/build.js
前加了cross-env env_config=prod
這點內容,這段東西主要在設置環境變量,能夠在/build/build.js
文件內console.log('查看環境變量-------->', process.env.env_config)
,而後運行打包命令時會在控制檯打印出來。redis
3,在項目的/build/build.js
文件內找到const spinner = ora('building for production...')
這行代碼,將其改成const spinner = ora(`正在打${process.env.env_config}環境包...`)
,改這個主要是爲了打包的時候方便知道正在打那個環境的。
4,在項目的/config/prod.env.js
文件內,將其內容修改。
'use strict'
module.exports = {
prod: {NODE_ENV: '"production"'},
test: {NODE_ENV: '"testing"'},
pre: {NODE_ENV: '"pre-release"'},
}
複製代碼
5,繼續在項目的/build/webpack.prod.conf.js
文件內找到
new webpack.DefinePlugin({
'process.env': env
})
複製代碼
這段代碼,而後將其改成
new webpack.DefinePlugin({
'process.env': env[process.env.env_config]
})
複製代碼
6,2.x修改完成,而後測試一波,在main.js
中寫個判斷打印出來看一波。
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
if (process.env.NODE_ENV === 'production') {
console.log('生產環境');
} else if(process.env.NODE_ENV === 'testing') {
console.log('測試環境');
} else if(process.env.NODE_ENV === 'pre-release') {
console.log('預發佈環境');
}
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
複製代碼
而後npm run build:pre
打個包,而後線上環境布一下,而後在控制檯應該能夠打印出來預發佈環境
;
以上判斷代碼僅爲測試效果,實際開發過程當中,應該對請求進行單獨封裝,對請求域名設爲變量,根據環境不一樣給變量設置不一樣的值。
1,在項目根目錄下新建3個文件,.env.test
、.env.prod
、.env.pre
,在文件內分別寫入NODE_ENV = testing
、NODE_ENV = production
、NODE_ENV = pre-release
。
2,在項目的package.json
文件中,把scripts
對象的build
字段的值改成如下代碼,實際就是修改npm run build
命令。同時再添加npm run build:test
命令和npm run build:pre
命令。(本段文字Ctrl C
自2.x第二步)
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build --mode prod",
"build:test": "vue-cli-service build --mode test",
"build:pre": "vue-cli-service build --mode pre",
"lint": "vue-cli-service lint"
}
複製代碼
3,完成2.X的第6步
4,3.x的實現比2.x簡化了不少,官方文檔說的很詳細【傳送門】,可是咱們在項目內加了3個文件,每一個文件又僅有一行代碼,這就讓人感受很low了,若是有七八個環境咱們不是須要加七八個文件,做爲一個強迫症患者,怎麼能容忍這種狀況。因此在場景二中,咱們會嘗試使用一個文件來區分多個環境。
基於場景一的狀況下,咱們目前開發了一套系統,如今計劃拿這套系統去售賣,但每一個公司都有不一樣的需求,咱們的系統只能知足大部分功能,並不能徹底知足全部公司。
這時候有超能力的公司就但願能基於咱們個人系統再作些個性化的定製功能,這種狀況下咱們能夠以咱們通用系統爲基本新起一個工程作定製化開發,或者在通用工程上再拉一個子分支作開發,但有定製化需求的公司數量不多還好,若是有幾十個呢,若是通用工程作了迭代或者改了bug,那麼不可避免會出現咱們可能須要維護好多套類似類型的代碼,那這個工程量就是很是巨大的,費時費力,咱們場景二的目標就是解決這個問題。
因此若是咱們能夠把全部的定製化都寫在通用版本的項目中,而後根據不一樣的打包命令能夠打包出不一樣的系統,那麼咱們就能夠只維護一套代碼,從而完美解決問題。
具體的咱們須要爲每個定製化版本寫一個配置文件,當咱們打包的時候引用不一樣的配置文件打不一樣的生產包。
備註: 實際開發過程當中若有遇到這種場景,各個功能模塊必定要解耦,解耦後就能夠根據功能模塊組合出各類功能不一的系統,必定要解耦,這點也很重要
假設: 因爲實際項目不一樣,因此定個假設來解決問題。假設咱們咱們這個工程有10個頁面(實際開發過程當中應該是模塊),其中5個頁面是通用的,5個頁面是A、B、C、D、E五家公司定製的,同時每家定製版要求系統上有他們公司logo、頁面title加上公司名稱。接下來咱們解決這個問題
1,先在項目新建10個頁面,同時爲每一個頁面再建個路由
結構差很少如上。實際項目開發過程當中,一個文件夾應該爲一個功能模塊,模塊內在進行其餘劃分
2,在src目錄下再新建個config文件夾,再接着在內部新建5個配置文件和一個index入口文件 配置文件內容
這步比較重要,首先咱們在不一樣的配置中引入了不一樣的頁面,通用頁面之因此引入到了配置文件內,考慮到實際開發中有可能存在客戶不須要這個功能,方便新加和刪除。入口文件的做用是在以後的開發中咱們只須要引入配置入口文件就能夠了,當切換定製版的時候只須要修改入口文件就能夠實現切換,方便快捷,若是說在須要配置文件的直接引用配置文件,那當咱們切換定製版的時候改起來就是個很耗費精力的一件事了,因此入口文件在這裏主要起個代理做用
3,在主路由文件內引入
4,同時因爲每一個定製版的頁面標題都不同,因此咱們須要給路由加點東西,修改一下。
5,至此,咱們完成了使用一套代碼維護‘多個系統’,若是要切換爲B公司的版本,咱們只須要更換config/index.js
中的引入文件爲B公司的配置。
6,這個樣子每次打包不一樣定製版的時候還須要在config/index.js
中再改一行代碼,這也很麻煩啊,能不能把這步也省略。確定能夠,接下來繼續實現。
7,在項目根目錄下再建個script
文件夾,而後在裏面新建個build.js
文件,而後把打包命令再修改下。
8,再npm install -D shelljs inquirer chalk
,shelljs
是用來執行命令的,inquirer
是用來寫交互式命令行的,chalk
是用來裝扮命令行命令的(好比在命令行輸入個紅色的字)。說一下咱們想要實現的效果,當咱們在運行npm run build
的時候,我但願可讓我來選擇我要部署什麼樣的版本,部署那個環境,我選擇完成以後,它幫我修改個人配置文件修改完成以後再運行vue的打包命令進行打包。
9,安裝完成以後,咱們在build.js
文件寫入如下內容;
const shell = require('shelljs');
const inquirer = require('inquirer');
const chalk = require('chalk');
const fs = require('fs');
const writeFile = (path, content) => {
return new Promise((reslove, reject) => {
fs.writeFile(path,
content,'utf8', () => {
reslove(true);
});
})
}
const release = new Map([
['customizeA定製版', "export * from './customizeA';"],
['customizeB定製版', "export * from './customizeB';"],
['customizeC定製版', "export * from './customizeC';"],
['customizeD定製版', "export * from './customizeD';"],
['customizeE定製版', "export * from './customizeE';"],
])
const env = new Map([
['生產環境', "NODE_ENV = production"],
['預發佈環境', "NODE_ENV = pre-release"],
['測試環境', "NODE_ENV = testing"],
])
const build = async () => {
const res = await inquirer.prompt([
{
type: 'list',
name: 'release',
message: '請選擇你要部署的版本?',
choices: ['customizeA定製版', 'customizeB定製版', 'customizeC定製版', 'customizeD定製版', 'customizeE定製版']
},
{
type: 'list',
name: 'env',
message: '請選擇你要部署的環境?',
choices: ['生產環境', '預發佈環境', '測試環境']
},
]);
await Promise.all([writeFile(`${process.cwd()}/src/config/index.js`, release.get(res.release)),writeFile(`${process.cwd()}/.env`, env.get(res.env))]);
console.log(chalk.green(`您要打包的是${res.env}---${res.release},正在爲您打包......`));
shell.exec('vue-cli-service build');
}
build();
複製代碼
簡單說一下咱們究竟作了什麼,首先當咱們運行npm run build
的時候,實際就是用node去執行/script/build.js
文件去了,而後文件內部先執行build()
函數,而後會讓用戶選擇版本和環境,用戶選擇完成咱們會拿到用戶的選擇結果res
,根據用戶的選擇結果咱們找一下究竟要引入的配置文件和要設置的環境變量,而後把內容寫入到對應的文件中,另外說一下writeFile
是咱們對node寫入文件作的Promis
封裝,最後寫入完成咱們就去執行打包命令了。
OK!完成!看下效果!
這個亞子確實沒問題,使用也很方便!但若是咱們使用Jenkins進行自動化部署,把這個命令給運維,讓運維在Jenkins進行配置上下鍵選擇,回車鍵確認的話,必定要穿好防禦服或者給120提早打個電話,畢竟我第一次興沖沖把這個操做給運維的時候,被運維追了十八條街,差點沒被打死。
10,因此,爲了本身安全,我決定改爲命令直接部署。可是配置多個命令有超級難受,因此可不能夠一個命令就搞定環境與版本選擇,別說還真行,因此開搞。
首先npm install -D
11,在script
文件夾內在新建個cdmBuild.js
文件,在文件內這麼寫
const shell = require('shelljs');
const commander = require('commander');
const chalk = require('chalk');
const fs = require('fs');
const writeFile = (path, content) => {
return new Promise((reslove, reject) => {
fs.writeFile(path,
content,'utf8', () => {
reslove(true);
});
})
}
const Release = new Map([
['customizeA', "export * from './customizeA';"],
['customizeB', "export * from './customizeB';"],
['customizeC', "export * from './customizeC';"],
['customizeD', "export * from './customizeD';"],
['customizeE', "export * from './customizeE';"],
])
const Env = new Map([
['production', "NODE_ENV = production"],
['pre-release', "NODE_ENV = pre-release"],
['testing', "NODE_ENV = testing"],
])
const subcommand = commander.command('build <release> <env>');
subcommand.action(async (release, env) => {
if (!Release.has(release)) {
console.log(chalk.red(`傻子,沒有${release}這個版本`));
return;
}
if (!Env.has(env)) {
console.log(chalk.red(`傻子,沒有${env}這個環境`));
return;
}
await Promise.all([writeFile(`${process.cwd()}/src/config/index.js`, Release.get(release)),writeFile(`${process.cwd()}/.env`, Env.get(env))]);
console.log(chalk.green(`您要打包的是${env}---${release},正在爲您打包......`));
shell.exec('vue-cli-service build');
});
commander.parse(process.argv);
複製代碼
12,在package.json
文件內修改下打包命令
13,以後的打包命令就變成了npm run build:cmd -- build <release> <env>
,release表明版本,env表明環境,若是咱們要打包測試環境customizeD定製版,那麼咱們的命令就是npm run build:cmd -- build customizeD testing
。
14,大功告成,因此這麼長又這麼難記的命令就交給運維去配置了,若是咱們本身用就npm run build
就好。
寫在最後: 本文代碼載體是vue,但解決思路適用於全部相同場景。本文重點在於解決思路不在於代碼實現。
寫在最後: 本篇文章至此就寫完了,由於內容過多因此有些地方不是特別詳細,遇到不明白的多百度多谷歌。同時,解決問題的方式歷來不止一種,本文所述的方案,不過是我在實際開發中所遇到的場景本身的解決方式,若是你在實際開發中遇到了相同的場景可使用個人方式,但請不要中止思考,也許你也會想到更好的解決方案。做爲一名代碼人實際開發中咱們會遇到各類問題,遇到問題的時候,必定多思考,只要你能想到解決方式,剩下的不過是用代碼翻譯思想的過程,加油!
寫在最後: 求給本文起標題,給場景二起標題,我本身起標題的水平實在是太慘不忍睹了。
另外!若是本文對你有幫助也請幫我點個贊,感謝!感謝!感謝!贊!贊!贊!你的一個贊對我很重要
本文所用第三方工具文檔:
【Commander.js】、【shelljs】、【chalk.js】、【inquirer.js】
也許你對個人其餘文章也有興趣: