说明: 本文代码实现以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】
也许你对个人其余文章也有兴趣: