背景: 一个路径包含了PC和手机端两套代码,两端代码是独立的vue-cli项目,要分别打包后放入同一个生产目录publish中。其中PC端打包输出的代码放在publish根目录,手机端放在publish/phone目录中。
资源结构:
publish //生产目录
src //PC端源码目录
--src //PC端资源
----phone //手机源码目录
缺陷: 每次修改代码都需要打包两次,打包完后还需要手动复制进生产目录。
优化: 将两个打包过程一体化,做到输入一个指令就能将PC和手机代码都打包并放进相应的生产目录。
实现:
第一步:在PC端源码根目录添加打包指令 “build-all”。
要做到打包PC端代码的同时,还要去手机端代码目录进行打包,这应该无法用一句字符串做到,所以将详细操作写进js文件里,输入指令后通过node 执行js文件达到目的。
第二步:创建build-all.js,实现打包逻辑
这里梳理一下要做的事情:
1、打包前清空原publish目录,防止旧文件残留。
2、要在PC端源码根目录启动命令,执行js代码,执行npm run build打包PC端代码,并能继续进入到手机端目录,执行npm run build打包手机端代码
3、PC端打包好后,将输出代码(dist目录)复制到publish目录,phone打包好后,从phone下的dist移动到publish/phone。
实现第一点: 首先是想到了node的文件系统,参考网上用递归写了个删除函数
function deleteFile(pathNamedir) {
// 读取文件夹内容
const files = fs.readdirSync(pathNamedir);
files.forEach(file => {
const dirName = path.resolve(pathNamedir, file),
stats = fs.lstatSync(dirName); // 返回值可以判断文件类型
// 如果是文件夹递归调用函数
if (stats.isDirectory()) {
deleteFile(dirName);
} else {
fs.unlinkSync(dirName);
}
});
// 清空文件夹内容之后之后删除文件夹
// fs.rmdirSync(pathNamedir);
}
参考资源:全新体验,用node.js命令行方式清空并删除文件夹
实现第二点::由于对nodejs不熟,卡在nodejs怎么去执行npm指令这块,而且还要能去其他目录执行。网上一顿冲浪,找到child_process.exec(),它能创建一个shell,然后在shell里执行命令,关键还可以定义执行路径。没错这就是我想要的。
代码大概是这样:(两个打包程序同时进行)
const child_process = require("child_process");
child_process.exec("npm run build", function(error) {
if (error) {
console.log("PC端编译失败!!!", error);
}
});
child_process.exec("npm run build", { cwd: "./src/phone" }, function(error) {
if (error) {
console.log("phone端编译失败!!!", error);
}
});
实现第三点: 能打包完成了,还要能复制进生产目录,可以直接设置vue.config.js的 outputdir,把两者的输出目录改为相应的生产目录就行,但需要注意把打包时自动清理输出目录关闭掉,因为手机输出目录是PC输出目录的子目录,他俩无法保证谁先打包完,所以不能在打包时删除,但可提前删除。
但这里我还是想试着通过复制的方式去实现,首先想到的还是fs,但由于太麻烦了,找到了一个shelljs库,可以直接通过shell脚本操作文件。
一个shell.cp()方法即可实现资源复制;另外之前的删除也不用这么一大堆了,shelljs一句话就能搞定。
//安装shelljs
//删除publish目录后新建publish和phone空文件夹
shell.rm("-rf", "../publish");
shell.mkdir("-p", "../publish/phone");
//移动PC端文件至publish目录,(注意会将整个文件夹包括最外层一起复制过去,外层是dist文件夹,不需要所以遍历去复制了)
shell.ls(distPc).forEach(file => {
shell.cp("-r", distPc + file, publishPc);
});
完整的实现代码(简易版):
const shell = require("shelljs"); //用于操作文件
const child_process = require("child_process");
//PC和手机端dist输出路径和生产路径(相对于当前文件执行目录)
const distPc = "dist/",
distPhone = "src/phone/dist/",
publishPc = "../publish/",
publishPhone = "../publish/phone/";
var compiledPC = false,
compiledPHONE = false;
function startCompile() {
console.log("正在编译PC端文件和phone端文件......");
// 衍生一个 shell 并在 shell 中执行 command
child_process.exec("npm run build", function(error) {
if (error) {
console.log("PC端编译失败!!!", error);
} else {
console.log("PC端编译完成");
//移动PC端文件至publish目录
shell.ls(distPc).forEach(file => {
shell.cp("-r", distPc + file, publishPc);
});
compiledPC = true;
}
});
child_process.exec("npm run build", { cwd: "./src/phone" }, function(error) {
if (error) {
console.log("PHONE端编译失败!!!", error);
} else {
console.log("PHONE端编译完成");
//移动PHONE端文件至publish/phone目录
shell.ls(distPhone).forEach(file => {
shell.cp("-r", distPhone + file, publishPhone);
});
compiledPHONE = true;
}
});
}
(function() {
console.log("重置publish目录");
shell.rm("-rf", "../publish");
shell.mkdir("-p", "../publish/phone");
startCompile();
var timer = setInterval(() => {
if (compiledPC && compiledPHONE) {
clearInterval(timer);
timer = null;
console.log("编译完成!!!");
}
}, 1000);
})();
//TODO :还有缺陷,要结束进程啥的
完整的实现代码(使用了Promise和差错重编):
const shell = require("shelljs"); //用于操作文件
const child_process = require("child_process");
//PC和手机端dist输出路径和生产路径(相对于当前文件执行目录)
const distPc = "dist/",
distPhone = "src/phone/dist/",
publishPc = "../publish/",
publishPhone = "../publish/phone/";
var reBuildTimes = 1;
var startCompile = compileLog => {
console.log(compileLog);
let promisePC = new Promise((resolve, reject) => {
// 衍生一个 shell 并在 shell 中执行 command
child_process.exec("npm run build", error => {
if (error) {
reject({ type: "PC", error });
} else {
//移动PC端文件至publish目录
shell.ls(distPc).forEach(file => {
shell.cp("-r", distPc + file, publishPc);
});
resolve();
}
});
});
let promisePHONE = new Promise((resolve, reject) => {
child_process.exec("npm run build", { cwd: "./src/phone" }, error => {
if (error) {
reject({ type: "PHONE", error });
} else {
//移动PHONE端文件至publish/phone目录
shell.ls(distPhone).forEach(file => {
shell.cp("-r", distPhone + file, publishPhone);
});
resolve();
}
});
});
Promise.all([promisePC, promisePHONE])
.then(() => {
console.log("编译完成!!!");
})
.catch(error => {
//重新编译reBuildTimes次
if (reBuildTimes-- > 0) {
startCompile(`${error.type}端编译出错,正在重新编译......`);
} else {
console.log(
error.error + "\n" + error.type + "端编译出错,编译失败!!!"
);
process.exit(0);
}
});
};
(function() {
console.log("重置publish目录");
shell.rm("-rf", "../publish");
shell.mkdir("-p", "../publish/phone");
startCompile("正在编译PC端文件和PHONE端文件......");
})();
编译效果:
参考资源:
shelljs官方
shelljs中文
使用ShellJS提升你的开发效率(一)
nodeJs写一个移动文件的脚本
Stream文件流模块以及pipe()进行大文件复制
移动文件讨论
vue-cli不自动清理输出目录