版本号:
vue/cli:4.5.12
node:v14.15.3
npm:6.14.9
需求
- 代码多环境打包很常见,直接说说多项目打包:
- 多项目打包就是一套代码,只有logo,部分样式,标题等不同。
- 如果分好几套代码,后期改需求总不能每套代码都改一遍,所以就需要多项目打包。
(当然,如果后期项目差异过大时,还是强烈建议把项目区分开来)
多环境打包实现
- 在
package.json
的scripts
下push
以下代码
"dev": "vue-cli-service build --mode dev",
"pre": "vue-cli-service build --mode pre",
"prod": "vue-cli-service build --mode prod"
- 在项目根目录创建以下三个文件。注意:
.env.
之后的名称与 --mode 之后的要一致
.env.dev --开发/测试环境
.env.pre --预发布环境
.env.prod --生产/线上环境
-
以
.env.dev
举例
这里需要注意以下几点- 不允许有注释
NODE_ENV
相当于自定义打包的环境变量- 接口地址使用
'//xxx.xxx.com/'
这样的好处就是
1. 会根据项目打开是https或者http进行自动切换
2. 不能用逗号,句号,分号
等。例如NODE_ENV = 'development';
3. 不能写成'//xxx.xxx.com'
,否则拼接api时不会出现https://xxx.xxx.comtest/testA
情况
4. 不能写成'"//xxx.xxx.com/"'
NODE_ENV = 'development'
VUE_APP_TITLE = 'dev'
VUE_APP_BASE_API = '//xxx.xxx.com/'
.env.pre
NODE_ENV = 'preview'
VUE_APP_TITLE = 'pre'
VUE_APP_BASE_API = '//xxx.xxx.com/'
.env.prod
NODE_ENV = 'production'
VUE_APP_TITLE = 'prod'
VUE_APP_BASE_API = '//xxx.xxx.com/'
- 在
request.js
中进行设置即可
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
})
当然其他地方如果需要根据不同环境写逻辑的话也可以配置后使用process.env.xxx
。
如果只用多环境到此就可以结束了。
需要使用多项目的继续冲!!!
多环境打包实现
- 在
package.json
的scripts
下push
以下代码
"switch": "node build/switch.js",
"current": "node build/current.js",
- 在项目根目录创建以下文件。
|-----build 打包配置
| |-current.js 运行 npm run current。作用:查询当前项目。
| |-project.js 全部项目的配置文件。
| |-switch.js 运行 npm run switch 调用的文件。例如:npm run switch projectA 。作用:切换项目。
| |-temp.js 当前项目的临时文件,使用module.exports导出主要供配置文件使用。
- 先从
project.js
文件开始
module.exports = {
projectA: {
name: "projectA", // 项目名称
title: "项目A", // 项目标题
version:"1.0.0", // 版本号
// 不同环境的接口地址
dev: {
baseUrl: "//xxx.com/"
},
pre: {
baseUrl: "//xxx.com/"
},
prod: {
baseUrl: "//xxx.com/"
},
targetUrl: "https://xxxx.com",// 本地启动时服务器的接口地址
isShowLoginLogo: false,// 是否展示登录LOGO
},
projectB: {
name: "projectB",
title: "项目B",
version:"1.0.0",
dev: {
baseUrl: "//xxx.com/"
},
pre: {
baseUrl: "//xxx.com/"
},
prod: {
baseUrl: "//xxx.com/"
},
targetUrl: "https://xxxx.com",
isShowLoginLogo: true,
}
}
- 敲黑板哒哒哒,重点来了:
switch.js
var fs = require('fs');
var path = require('path');
var project = require('./project');
/**
* fDelete 删除文件
* @param {String} src 需要删除的文件夹地址
* @returns {Null}
*/
var fDelete = function(src) {
//同步读取当前目录
let paths = fs.readdirSync(src);
// 是否删除成功
var isDeleteSuc = false;
var pathsLength = paths.length;
paths.forEach(function(path, index) {
var _src = src + '/' + path;
// unlinkSync同步删除 fs.unlinkSync(path);
// unlink 异步删除 fs.unlink (path,callback);
fs.unlink(_src, error => {
if (error) return console.log("删除文件失败,原因是" + error.message);
isDeleteSuc = true;
if (index === pathsLength - 1) {
console.log("img delete success!");
fCheckDirectory("./src/assets/images/" + projectName, "./src/assets/images/project",
fCopyFolder, true);
fs.readdir("./src/assets/images/project", (err, files) => {
if (error) return console.log("读取文件./src/assets/images/project失败,原因是" +
error.message);
console.log("images updata success!");
});
fCheckDirectory("./src/assets/images/" + projectName + "/favicon.ico",
"./public/favicon.ico", fCopy, false);
};
})
});
};
/**
* fCopy 复制文件
* @param {String} src 需要被复制的文件地址
* @param {String} dst 需要复制到的文件地址
* @returns {Null}
*/
var fCopy = function(src, dst) {
let readable = fs.createReadStream(src); //创建读取流
let writable = fs.createWriteStream(dst); //创建写入流
readable.pipe(writable);
writable.on('finish', function() {
console.log('favicon.ico updata success!');
});
}
/**
* fCopyFolder 复制文件夹下所有内容
* @param {String} src 需要被复制的文件夹地址
* @param {String} dst 需要复制到的文件夹地址
* @returns {Null}
*/
var fCopyFolder = function(src, dst) {
let paths = fs.readdirSync(src); //同步读取当前目录
paths.forEach(function(path, index) {
var _src = src + '/' + path;
var _dst = dst + '/' + path;
fs.stat(_src, function(err, stats) { //stats 该对象 包含文件属性
if (err) throw err;
if (stats.isFile()) { //如果是个文件则拷贝
let readable = fs.createReadStream(_src); //创建读取流
let writable = fs.createWriteStream(_dst); //创建写入流
readable.pipe(writable);
} else if (stats.isDirectory()) { //是目录则 递归
fCheckDirectory(_src, _dst, fCopyFolder, true);
}
});
});
}
/**
* fCheckDirectory 检查目录
* @param {String} src 需要被复制的文件夹地址
* @param {String} dst 需要复制到的文件夹地址
* @param {Function} callback 回调参数
* @param {Boolean} isMkdir 是否创建目录
* @returns {Null}
*/
var fCheckDirectory = function(src, dst, callback, isMkdir) {
// fs.access 判断文件的状态; fs.constants.F_OK 除了判断文件是否存在(默认模式),还可以用来判断文件的权限。
fs.access(dst, fs.constants.F_OK, (err) => {
if (err) {
if (isMkdir) {
// mkdir创建目录
fs.mkdirSync(dst);
}
callback(src, dst);
} else {
callback(src, dst);
}
});
};
// 截取npm run switch projectA
// aCommand为数组
var aCommand = process.argv.splice(2);
// 默认切换项目名为projectA---console.log(aCommand.length, aCommand[0])
var projectName = aCommand[0] ? aCommand[0] : "projectA";
console.log("当前运行项目:" + projectName);
// console.log(projectName)
// console.log(project[projectName]);
// 当前项目 字符串
var oCurrentProject = project[projectName];
var sCurrentProject = null;
if (oCurrentProject) {
sCurrentProject = JSON.stringify(oCurrentProject)
} else {
return new TypeError("-----项目不存在,请检查-----");
}
var jsExport = 'export default ' + sCurrentProject;
var jsModuleExports = 'module.exports = ' + sCurrentProject;
fs.writeFile(path.join(__dirname, '../src/utils/config.js'), jsExport, function(err) {
if (err) throw err;
console.log("config.js updata success!");
});
fs.writeFile(path.join(__dirname, './temp.js'), jsModuleExports, function(err) {
if (err) throw err;
console.log("temp.js updata success!");
});
// 修改html的title
fs.readFile(path.join(__dirname, '../public/index.html'), "utf8", function(err, data) {
if (err) throw err;
console.log("index.html read success!");
var html = "";
html = data.replace(/<title>([\S\s\t]*?)<\/title>/g, "<title>" + oCurrentProject.title + "</title>")
fs.writeFile(path.join(__dirname, '../public/index.html'), html, function(err) {
if (err) throw err;
console.log("index.html updata success!");
});
});
fDelete("./src/assets/images/project");
- 准备区分项目所属图片
将有差异的图片存放到和项目名相同的文件里
switch.js
会帮我们把项目对应的文件夹里的所有内容存入到project
所以我们在每个项目对应的文件夹中都需要存入名字相同但内容不同的图片
比如logo,背景页面等等
因此页面引用时,只需要引入project
文件夹的内容
这里需要注意一点:
projectA_001.png
在两个项目都有,但是为什么是projectA_001.png
而不是img_003.png
呢?
因为这是projectA
项目独有的文件,其他项目如果没有对应的图片。
那么就把该图片放到其他项目中作为区分。
- 这个时候我们可以使用命令
npm run switch projectA
进行项目切换
你会发现生成的src/utils/config.js
和build/temp.js
内容基本一致只是导出方式不同
这牵扯到 nodejs中module.exports和exports的区别 以后有机会讲讲
你暂时可以这么理解:
build/temp.js
module.exports
主要用于node加载的js文件
。例如vue.config.js
和build
文件夹下的js文件
src/utils/config.js
export
主要用于项目中的js文件
。例如main.js
和src/utils
文件夹下的js文件
current.js
安装npm install --save-dev colors
我的版本号为:"colors": "^1.4.0"
var colors = require('colors');
colors.setTheme({
silly: 'rainbow',
input: 'grey',
verbose: 'cyan',
prompt: 'red',
info: 'green',
data: 'blue',
help: 'cyan',
warn: 'yellow',
debug: 'magenta',
error: 'red'
});
var temp = require('./temp');
var project = require('./project');
var current = "no init!";
if (temp && temp.name) {
current = temp.name
}
console.log("===================================================")
console.log("当前项目:" + current)
console.log("===================================================")
for (var key in project) {
if (key == current) {
console.log("* " + key.info)
} else {
console.log(" " + key.debug)
}
}
用colors
就是想给代码增添一些色彩~就像生活一样
这个时候使用 npm run current
查看当前项目
8. 最后再修改一下需要用到动态文件的地方
vue.config.js
const temp = require('./build/temp');
// 时间戳保证不会版本重复
const Timestamp = new Date().getTime();
const { version,targetUrl } = temp;
module.exports = {
devServer: {
https: true, // 允许https接口代理
proxy: {
'/': {
target: targetUrl, //服务器的接口地址 // 重写路径
changeOrigin: true, //是否是跨域请求
ws: false, //开启WebSocket
secure: true, // 如果是https接口,需要配置这个参数
pathRewrite: {
'^/': ''
}
}
}
},
css: {
extract: process.env.NODE_ENV === 'production' ? {
ignoreOrder: true,
} : false,
},
configureWebpack: {
output: { // 输出重构 打包编译后的 文件名称 【模块名称.版本号.时间戳】
filename: `js/[name].${version}.${Timestamp}.js`,
chunkFilename: `js/[name].${version}.${Timestamp}.js`
},
performance: {
hints: 'warning',
//入口起点的最大体积 整数类型(以字节为单位)
maxEntrypointSize: 50000000,
//生成文件的最大体积 整数类型(以字节为单位 300k)
maxAssetSize: 30000000,
//只给出 js 文件的性能提示
assetFilter: function(assetFilename) {
return assetFilename.endsWith('.js');
}
}
}
}
最后一步注意了昂!
request.js
import uConfig from '@/utils/config';
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_TITLE && uConfig[process.env.VUE_APP_TITLE] ? uConfig[process.env.VUE_APP_TITLE].baseUrl : uConfig.dev.baseUrl,
// 超时
timeout: 10000
})
这样写是因为非本地项目是根据项目对应的环境切换baseURL
而本地启动项目的时候,没有VUE_APP_TITLE
,所以需要直接使用项目dev
环境的baseUrl
完结撒花~~✿✿ヽ(°▽°)ノ✿
如有不正确的地方,欢迎留言讨论!