近些年来借着NodeJS
的春风,前端经历了一波大洗牌式得的发展。使得前端开发在效率,质量上有了质的飞跃。能够说NodeJS
已是前端不可欠缺的技能了。可是是事实上大部分的前端对于本地安装的NodeJS
的使用可能仅限于node -v
和npm
了😂。其实NodeJS
做为真正意义上的服务端语言,在咱们开发的时候能够运用NodeJS
强大的模块和众多的npm
包来为咱们本身服务。html
注意:这篇文章基本上不会去将一些很是基础的东西,但愿你们自备ES6+
语法, NodeJS
基础, 简单的Linux
操做等知识。还有这篇文章侧重点不会放在技术的实现细节,主要是提供一些思路和方向。更加深层次的使用,仍是要各位朋友本身去挖掘。并且这篇文章会有点长🎫前端
这个部分我以前在加快Vue项目的开发速度中提到过,不过那个版本写的比较简单(糙),并且基本全都都是经过Node
写的。说白了就是用NodeJS
去代替咱们生成须要复制粘贴的代码。vue
在大型的项目中,尤为是中后台项目在开发一个新的业务模块的时候可能离不开大量的复制粘贴,像中后台项目中可能不少模块都是标准的CURD
模块,包括了列表,新增,详情,编辑
这些页面。那么这就意味着有大量的重复代码存在,每次复制粘贴完以后还有修修改改删删等一大堆麻烦事儿,最重要的是复制粘贴很容易忘记哪一部分就忘记改了,致使项目作的很糙并且也会浪费很多时间。那咱们的需求就是把人工复制粘贴的那部分交给Node
去作,力求时间和质量
获得双重的保障。node
以前在Vue
项目中写过这个模块,那接下来的demo
咱们以一个Vue
项目来作示例,项目地址。webpack
文件结构划分:视图文件,路由文件,Controler
文件该怎么放必定要划分清楚。这个就像是地基,能够根据本身项目的业务去划分,咱们的项目目录是下面这样划分git
vue-base-template │ config // webpack配置config/q其余一些config文件 │ scripts // 帮助脚本文件 ===> 这里存放咱们的项目脚本文件 │ │ template // 模块文件 │ │ build-module.js // build构建脚本 │ │ └───src // 业务逻辑代码 │ │ api // http api 层 │ └── router // 路由文件 │ │ │ modules // 业务路由文件夹 ==> 业务模块路由生成地址 │ │ │ module.js // 制定模块 │ │ store // vuex │ └── views // 视图文件 │ │ │ directory // 抽象模块目录 │ │ │ │ module // 具体模块文件夹 │ │ │ │ │ index.vue // 视图文件 │ │ global.js // 全局模块处理 │ │ main.js // 入口文件
业务模块我基本上是经过抽象模块+具体模块
的方式去划分:程序员
这个划分方式很灵活,主要是根据本身的需求来。github
.vue
这种文件技术准备:web
理论上来说咱们须要用到如下一些npm
模块, 起码要知道这个都是干什么的vuex
流程很简单我画的也很差,凑合看吧。抽空我会从新画的😂
自从有了这个想法以后, 这个脚本到如今已是我第三遍撸了。从第一次的单一简单,到如今的功能完善。我最大的感触就是这个东西开发起来好像没有什么尽头,每一次都能找到不同的需求点,每次都能找到能够优化的部分。针对本身的项目,脚本能够很简单也能够很复杂。很魔性, 对我来讲确定还有第四次第五次的重构。
为了后期的维护,我把全部的脚本相关NodeJS
代码放到了根目录下的scripts
文件夹中
scripts // 帮助脚本文件 ===> 这里存放咱们的项目脚本文件 └───template // template管理文件夹 │ │ index.js // 模块文件处理中心 │ │ api.template.js // api模块文件 │ │ route.template.js // route模块文件 │ │ template.vue // view模块文件 │ build-module.js // 建立脚本入口文件 │ │ | .env.local // 本地配置文件 │ │ │ util.js // 工具文件
下面咱们一个部分一个部分的来说这些文件的做用(<font color="red">大量代码预警</font>)
build-module.js
: 入口文件, 与使用者进行交互的脚本。 经过问答的方式获得咱们须要的三个核心变量目录(抽象模块), 模块(具体模块), 注释
。若是是第一次执行这个脚本, 那么还会有配置相关的问题, 第一次设置完以后接下来的使用将不会再询问,若想修改可本身修改.env.local
文件。这里我不详细描述了, 大部分解释都写在注释里面了。第一部分 配置文件已经输入项处理部分
const inquirer = require('inquirer') const path = require('path') const { Log, FileUtil, LOCAL , ROOTPATH} = require('./util') const { buildVueFile, buildRouteFile, buildApiFile, RouteHelper } = require('./template') const EventEmitter = require('events'); // file options const questions = [ { type: 'input', name: 'folder', message: "请输入所属目录名称(英文,若是检测不到已输入目录将会默认新建,跳过此步骤将在Views文件夹下建立新模块):" }, { type: 'input', name: 'module', message: "请输入模块名称(英文)", // 格式验证 validate: str => ( str !== '' && /^[A-Za-z0-9_-]+$/.test(str)) }, { type: 'input', name: 'comment', message: "请输入模块描述(注释):" }, ] // local configs const configQuestion = [ { type: 'input', name: 'AUTHOR', message: "请输入做者(推荐使用拼音或者英文)", // 格式验证 validate: str => ( str !== '' && /^[\u4E00-\u9FA5A-Za-z]+$/.test(str)), when: () => !Boolean(process.env.AUTHOR) }, { type: 'input', name: 'Email', message: "请输入联系方式(邮箱/电话/钉钉)" } ] // Add config questions if local condfig does not exit if (!LOCAL.hasEnvFile()) { questions.unshift(...configQuestion) } // 获取已经完成的答案 inquirer.prompt(questions).then(answers => { // 1: 日志打印 Log.logger(answers.folder == '' ? '即将为您' : `即将为您在${answers.folder}文件夹下` + `建立${answers.module}模块`) // 2: 配置文件的相关设置 if (!LOCAL.hasEnvFile()) { LOCAL.buildEnvFile({ AUTHOR: answers.AUTHOR, Email: answers.Email }) } // 3: 进入文件和目录建立流程 const { folder, // 目录 module, // 模块 comment // 注释 } = answers buildDirAndFiles(folder, module, comment) }) // 事件处理中心 class RouteEmitter extends EventEmitter {} // 注册事件处理中心 const routeEmitter = new RouteEmitter() routeEmitter.on('success', value => { // 建立成功后正确退出程序 if (value) { process.exit(0) } })
第二部分 实际操做部分
// module-method map // create module methods const generates = new Map([ // views部分 // 2019年6月12日17:39:29 完成 ['view', (folder, module, isNewDir , comment) => { // 目录和文件的生成路径 const folderPath = path.join(ROOTPATH.viewsPath,folder,module) const vuePath = path.join(folderPath, '/index.vue') // vue文件生成 FileUtil.createDirAndFile(vuePath, buildVueFile(module, comment), folderPath) }], // router is not need new folder ['router', (folder, module, isNewDir, comment) => { /** * @des 路由文件和其余的文件生成都不同, 若是是新的目录那么生成新的文件。 * 可是若是module所在的folder 已经存在了那么就对路由文件进行注入。 * @reason 由于咱们当前项目的目录分层结构是按照大模块来划分, 即src下一个文件夹对应一个router/modules中的一个文件夹 * 这样作使得咱们的目录结构和模块划分都更加的清晰。 */ if (isNewDir) { // 若是folder不存在 那么直接使用module命名 folder不存在的状况是直接在src根目录下建立模块 const routerPath = path.join(ROOTPATH.routerPath, `/${folder || module}.js`) FileUtil.createDirAndFile(routerPath, buildRouteFile(folder, module, comment)) } else { // 新建路由helper 进行路由注入 const route = new RouteHelper(folder, module, routeEmitter) route.injectRoute() } }], ['api', (folder, module, isNewDir, comment) => { // inner module will not add new folder // 若是当前的模块已经存在的话那么就在当前模块的文件夹下生成对应的模块js const targetFile = isNewDir ? `/index.js` : `/${module}.js` // 存在上级目录就使用上级目录 不存在上级目录的话就是使用当前模块的名称进行建立 const filePath = path.join(ROOTPATH.apiPath, folder || module) const apiPath = path.join(filePath, targetFile) FileUtil.createDirAndFile(apiPath, buildApiFile(comment), filePath) }] ]) /** * 经过咱们询问的答案来建立文件/文件夹 * @param {*} folder 目录名称 * @param {*} module 模块名称 * @param {*} comment 注释 */ function buildDirAndFiles (folder, module, comment) { let _tempFloder = folder || module // 临时文件夹 若是当前的文件是 let isNewDir // 若是没有这个目录那么就新建这个目录 if (!FileUtil.isPathInDir(_tempFloder, ROOTPATH.viewsPath)) { rootDirPath = path.join(ROOTPATH.viewsPath, _tempFloder) // create dir for path FileUtil.createDir(rootDirPath) Log.success(`已建立${folder ? '目录' : "模块"}${_tempFloder}`) isNewDir = true } else { isNewDir = false } // 循环操做进行 let _arrays = [...generates] _arrays.forEach((el, i) => { if (i < _arrays.length) { el[1](folder, module, isNewDir, comment) } else { Log.success("模块建立成功!") process.exit(1) } }) }
注: 这里我用了一个generates
这个Map
去管理了全部的操做,由于上一个版本是这么写我懒得换了,你也能够用一个二维数组或者是对象去管理, 也省的写条件选择了。
template
: 管理着生成文件使用的模板文件(vue文件,路由文件, api文件
),咱们只看其中的route.template.js
,其余的部分能够参考项目/* * @Author: _author_ * @Email: _email_ * @Date: _date_ * @Description: _comment_ */ export default [ { path: "/_mainPath", component: () => import("@/views/frame/Frame"), redirect: "/_filePath", name: "_mainPath", icon: "", noDropdown: false, children: [ { path: "/_filePath", component: () => import("@/views/_filePath/index"), name: "_module", meta: { keepAlive: false } } ] } ]
在template
中最重要的要属index.js
了, 这个文件主要是包含了模板文件的读取和从新生成出咱们须要的模板字符串, 以及生成咱们须要的特定路由代码。template/index.js
,文件模板的生成主要是经过读取各个模板文件并转化成字符串,并把的指定的字符串用咱们指望的变量去替换, 而后返回新的字符串给生成文件使用。
const fs = require('fs') const path = require('path') const os = require('os') const readline = require('readline') const {Log, DateUtil, StringUtil , LOCAL, ROOTPATH} = require('../util') /** * 替换做者/时间/日期等等通用注释 * @param {*string} content 内容 * @param {*string} comment 注释 * @todo 这个方法还有很大的优化空间 */ const _replaceCommonContent = (content, comment) => { if (content === '') return '' // 注释对应列表 comments = [ [文件中埋下的锚点, 将替换锚点的目标值] ] const comments = [ ['_author_', LOCAL.config.AUTHOR], ['_email_', LOCAL.config.Email], ['_comment_', comment], ['_date_', DateUtil.getCurrentDate()] ] comments.forEach(item => { content = content.replace(item[0], item[1]) }) return content } /** * 生成Vue template文件 * @param {*} moduleName 模块名称 * @returns {*string} */ module.exports.buildVueFile = (moduleName, comment) => { const VueTemplate = fs.readFileSync(path.resolve(__dirname, './template.vue')) const builtTemplate = StringUtil.replaceAll(VueTemplate.toString(), "_module_", moduleName) return _replaceCommonContent(builtTemplate, comment) } /** * @author: etongfu * @description: 生成路由文件 * @param {string} folder 文件夹名称 * @param {string} moduleName 模块名称 * @returns {*string} */ module.exports.buildRouteFile = (folder,moduleName, comment) => { const RouteTemplate = fs.readFileSync(path.resolve(__dirname, './route.template.js')).toString() // 由于路由比较特殊。路由模块须要指定的路径。因此在这里从新生成路由文件所须要的参数。 const _mainPath = folder || moduleName const _filePath = folder == '' ? `${moduleName}` : `${folder}/${moduleName}` // 进行替换 let builtTemplate = StringUtil.replaceAll(RouteTemplate, "_mainPath", _mainPath) // 替换模块主名称 builtTemplate = StringUtil.replaceAll(builtTemplate, "_filePath", _filePath) // 替换具体路由路由名称 builtTemplate = StringUtil.replaceAll(builtTemplate, "_module", moduleName) // 替换模块中的name return _replaceCommonContent(builtTemplate, comment) } /** * @author: etongfu * @description: 生成API文件 * @param {string} comment 注释 * @returns: {*} */ module.exports.buildApiFile = comment => { const ApiTemplate = fs.readFileSync(path.resolve(__dirname, './api.template.js')).toString() return _replaceCommonContent(ApiTemplate, comment) }
路由注入, 当输入的目录已存在的时候就不会新建目录文件, 这个时候就会把新模块的路由注入到已存在的目录的路由文件中,效果以下
这里咱们经过RouteHelper
来完成对已存在的路由文件进行新模块的路由注入操做,主要经过了stream(流),readline(逐行读取)来实现的。
接下来是干货部分 ==> 首先经过参数找到咱们的目标路由文件,而后经过generateRouter()
来拼接生成咱们须要注入的路由。经过injectRoute
方法开始注入路由,在injectRoute
中咱们首先来生成一个名字为_root
临时路径的文件并根据这个路径建立一个writeStream
, 而后根据旧的路由文件地址root
建立一个readStream
并经过readline
读写接口去读取原来的路由文件,用一个数组收集旧的路由每一行的数据。读取完毕以后开始遍历temp
这个数组并找到第一个children
而后把generateRouter()
方法返回的数组插入到这个位置。最后使用拼接完成的temp
遍历逐行写入writeStream
中。最后把原来的root
文件删除,把_root
重命名为root
。一个路由注入的流程就完了。大致的流程就是这样, 关于代码细节不懂得朋友们能够私信我😁。
/** * @author: etongfu * @description: 路由注入器 * @param {string} dirName * @param {string} moduleName * @param {event} event * @returns: {*} */ module.exports.RouteHelper = class { constructor (dirName, moduleName, event) { // the dir path for router file this.dirName = dirName // the path for router file this.moduleName = moduleName // 事件中心 this.event = event // route absolute path this.modulePath = path.join(ROOTPATH.routerPath, `${dirName}.js`) } /** * Generate a router for module * The vue file path is @/name/name/index * The default full url is http:xxxxx/name/name * @param {*} routeName url default is router name * @param {*string} filePath vue file path default is ${this.dirName}/${this.moduleName}/index * @returns {*Array} A string array for write line */ generateRouter (routeName = this.moduleName, filePath = `${this.dirName}/${this.moduleName}/index`) { let temp = [ ` // @Author: ${LOCAL.config.AUTHOR}`, ` // @Date: ${DateUtil.getCurrentDate()}`, ` {`, ` path: "/${this.dirName}/${routeName}",`, ` component: () => import("@/views/${filePath}"),`, ` name: "${routeName}"`, ` },` ] return temp } /** * add router to file */ injectRoute () { try { const root = this.modulePath const _root = path.join(ROOTPATH.routerPath, `_${this.dirName}.js`) // temp file content let temp = [] // file read or write let readStream = fs.createReadStream(root) // temp file let writeStream = fs.createWriteStream(_root) let readInterface = readline.createInterface( { input: readStream // output: writeStream } ) // collect old data in file readInterface.on('line', (line) => { temp.push(line) }) // After read file and we begin write new router to this file readInterface.on('close', async () => { let _index temp.forEach((line, index) => { if (line.indexOf('children') !== -1) { _index = index + 1 } }) temp = temp.slice(0, _index).concat(this.generateRouter(), temp.slice(_index)) // write file temp.forEach((el, index) => { writeStream.write(el + os.EOL) }) writeStream.end('\n') // 流文件读写完毕 writeStream.on('finish', () => { fs.unlinkSync(root) fs.renameSync(_root, root) Log.success(`路由/${this.dirName}/${this.moduleName}注入成功`) //emit 成功事件 this.event.emit('success', true) }) }) } catch (error) { Log.error('路由注入失败') Log.error(error) } } }
关于路由注入这一块我本身这么设计其实并非很满意,有更好的方法还请大佬告知一下。
.env.local
: 配置文件, 这个是第一次使用脚本的时候生成的。没啥特别的,就是记录本地配置项。AUTHOR = etongfu Email = 13583254085@163.com
util.js
: 各类工具方法,包含了date, file, fs, string, Log, ROOTPATH
等等工具方法, 篇幅有限我就贴出来部分代码, 你们能够在项目中查看所有代码const chalk = require('chalk') const path = require('path') const dotenv = require('dotenv') const fs = require('fs') // 本地配置相关 module.exports.LOCAL = class { /** * env path */ static get envPath () { return path.resolve(__dirname, './.env.local') } /** * 配置文件 */ static get config () { // ENV 文件查找优先查找./env.local const ENV = fs.readFileSync(path.resolve(__dirname, './.env.local')) || fs.readFileSync(path.resolve(__dirname, '../.env.development.local')) // 转为config const envConfig = dotenv.parse(ENV) return envConfig } /** * 建立.env配置文件文件 * @param {*} config * @description 建立的env文件会保存在scripts文件夹中 */ static buildEnvFile (config = {AUTHOR: ''}) { if (!fs.existsSync(this.envPath)) { // create a open file fs.openSync(this.envPath, 'w') } let content = '' // 判断配置文件是否合法 if (Object.keys(config).length > 0) { // 拼接内容 for (const key in config) { let temp = `${key} = ${config[key]}\n` content += temp } } // write content to file fs.writeFileSync(this.envPath, content, 'utf8') Log.success(`local env file ${this.envPath} create success`) } /** * 检测env.loacl文件是否存在 */ static hasEnvFile () { return fs.existsSync(path.resolve(__dirname, './.env.local')) || fs.existsSync(path.resolve(__dirname, '../.env.development.local')) } } // 日志帮助文件 class Log { // TODO } module.exports.Log = Log // 字符串Util module.exports.StringUtil = class { // TODO } // 文件操做Util module.exports.FileUtil = class { // TODO /** * If module is Empty then create dir and file * @param {*} filePath .vue/.js 文件路径 * @param {*} content 内容 * @param {*} dirPath 文件夹目录 */ static createDirAndFile (filePath, content, dirPath = '') { try { // create dic if file not exit if (dirPath !== '' && ! fs.existsSync(dirPath)) { // mkdir new dolder fs.mkdirSync(dirPath) Log.success(`created ${dirPath}`) } if (!fs.existsSync(filePath)) { // create a open file fs.openSync(filePath, 'w') Log.success(`created ${filePath}`) } // write content to file fs.writeFileSync(filePath, content, 'utf8') } catch (error) { Log.error(error) } } } // 日期操做Util module.exports.DateUtil = class { // TODO }
在Util
文件中须要注意的部分多是.env
文件的生成和读取这一部分和FileUtil
中createDirAndFile
, 这个是咱们用来生成文件夹和文件的方法,所有使用node
文件系统完成。熟悉了API
以后不会有难度。
Util
文件中有一个ROOTPATH
要注意一下指的是咱们的路由,views, api的根目录配置, 这个配置的话我建议不要写死, 由于若是你的项目有多入口或者是子项目,这些可能都会变。你也能够选择其余的方式进行配置。
// root path const reslove = (file = '.') => path.resolve(__dirname, '../src', file) const ROOTPATH = Object.freeze({ srcPath: reslove(), routerPath: reslove('router/modules'), apiPath: reslove('api'), viewsPath: reslove('views') }) module.exports.ROOTPATH = ROOTPATH
这样的话咱们就能愉快的经过命令行快速的建立模块了, 效果以下
运行
虽然这些事儿复制粘贴也能完成,可是经过机器完成可靠度和可信度都会提高很多。咱们的前端团队目前已经全面使用脚原本建立新模块,而且脚本在不断升级中。亲测在一个大项目中这样一个脚本为团队节约的时间是很是可观的, 建议你们有时间也能够写一写这种脚本为团队或者本身节约下宝贵的时间😁
在开发过程当中,有不少工做都是机械且无趣的。不拿这些东西开刀简直对不起他们
注: 若是团队部署了CI/CD
,这个部分能够直接忽略。
通过个人观察,不少前端程序员并不懂Linux
操做, 有的时候发布测试还须要去找同事帮忙,若是另外一个同事Linux
功底也不是很好的话,那就可能浪费两我的的很大一起时间。今天经过写一个脚本让咱们的全部同事都能独立的发布测试。这个文件一样放在项目的scripts
文件夹下
Web
服务器, 不会的话能够看我以前的一篇文章服务器发布Vue项目指南 须要的技术,都是工具类的包 没什么难度
由于是发布到不一样的服务器, 由于development/stage/production
应该都是不一样的服务器,因此咱们须要一个配置文件来管理服务器。deploy.config.js
module.exports = Object.freeze({ // development development: { SERVER_PATH: "xxx.xxx.xxx.xx", // ssh地址 SSH_USER: "root", // ssh 用户名 SSH_KEY: "xxx", // ssh 密码 / private key文件地址 PATH: '/usr/local' // 操做开始文件夹 能够直接指向配置好的地址 }, // stage stage: { SERVER_PATH: "", SSH_USER: "", SSH_KEY: "", PATH: '' }, // production production: { SERVER_PATH: "", SSH_USER: "", SSH_KEY: "", PATH: '' } })
配置文件配置好了下面开始写脚本, 咱们先肯定下来流程
inquirer
问问题,这是个示例代码, 问题就比较简单了, 在真正使用中包括了发布平台
等等不一样的发布目标。dist
文件,经过zip-local
去操做。很简单node-ssh
链接上服务器SSH
的putFile
方法开始把本地文件上传到服务器。unzip
命令。8: 发布完成🎈
下面是干货代码
第一部分 实际操做部分, 连接SSH
, 压缩文件等等
const fs = require('fs') const path = require('path') const ora = require('ora') const zipper = require('zip-local') const shell = require('shelljs') const chalk = require('chalk') const CONFIG = require('../config/release.confg') let config const inquirer = require('inquirer') const node_ssh = require('node-ssh') let SSH = new node_ssh() // loggs const errorLog = error => console.log(chalk.red(`*********${error}*********`)) const defaultLog = log => console.log(chalk.blue(`*********${log}*********`)) const successLog = log => console.log(chalk.green(`*********${log}*********`)) // 文件夹位置 const distDir = path.resolve(__dirname, '../dist') const distZipPath = path.resolve(__dirname, '../dist.zip') // ********* TODO 打包代码 暂时不用 须要和打包接通以后进行测试 ********* const compileDist = async () => { // 进入本地文件夹 shell.cd(path.resolve(__dirname, '../')) shell.exec(`npm run build`) successLog('编译完成') } // ********* 压缩dist 文件夹 ********* const zipDist = async () => { try { if(fs.existsSync(distZipPath)) { defaultLog('dist.zip已经存在, 即将删除压缩包') fs.unlinkSync(distZipPath) } else { defaultLog('即将开始压缩zip文件') } await zipper.sync.zip(distDir).compress().save(distZipPath); successLog('文件夹压缩成功') } catch (error) { errorLog(error) errorLog('压缩dist文件夹失败') } } // ********* 链接ssh ********* const connectSSh = async () =>{ defaultLog(`尝试链接服务: ${config.SERVER_PATH}`) let spinner = ora('正在链接') spinner.start() try { await SSH.connect({ host: config.SERVER_PATH, username: config.SSH_USER, password: config.SSH_KEY }) spinner.stop() successLog('SSH 链接成功') } catch (error) { errorLog(err) errorLog('SSH 链接失败'); } } // ********* 执行清空线上文件夹指令 ********* const runCommond = async (commond) => { const result = await SSH.exec(commond,[], {cwd: config.PATH}) defaultLog(result) } const commonds = [`ls`, `rm -rf *`] // ********* 执行清空线上文件夹指令 ********* const runBeforeCommand = async () =>{ for (let i = 0; i < commonds.length; i++) { await runCommond(commonds[i]) } } // ********* 经过ssh 上传文件到服务器 ********* const uploadZipBySSH = async () => { // 链接ssh await connectSSh() // 执行前置命令行 await runBeforeCommand() // 上传文件 let spinner = ora('准备上传文件').start() try { await SSH.putFile(distZipPath, config.PATH + '/dist.zip') successLog('完成上传') spinner.text = "完成上传, 开始解压" await runCommond('unzip ./dist.zip') } catch (error) { errorLog(error) errorLog('上传失败') } spinner.stop() }
第二部分 命令行交互和配置校验
// ********* 发布程序 ********* /** * 经过配置文件检查必要部分 * @param {*dev/prod} env * @param {*} config */ const checkByConfig = (env, config = {}) => { const errors = new Map([ ['SERVER_PATH', () => { // 预留其余校验 return config.SERVER_PATH == '' ? false : true }], ['SSH_USER', () => { // 预留其余校验 return config.SSH_USER == '' ? false : true }], ['SSH_KEY', () => { // 预留其余校验 return config.SSH_KEY == '' ? false : true }] ]) if (Object.keys(config).length === 0) { errorLog('配置文件为空, 请检查配置文件') process.exit(0) } else { Object.keys(config).forEach((key) => { let result = errors.get(key) ? errors.get(key)() : true if (!result) { errorLog(`配置文件中配置项${key}设置异常,请检查配置文件`) process.exit(0) } }) } } // ********* 发布程序 ********* const runTask = async () => { // await compileDist() await zipDist() await uploadZipBySSH() successLog('发布完成!') SSH.dispose() // exit process process.exit(1) } // ********* 执行交互 ********* inquirer.prompt([ { type: 'list', message: '请选择发布环境', name: 'env', choices: [ { name: '测试环境', value: 'development' }, { name: 'stage正式环境', value: 'production' }, { name: '正式环境', value: 'production' } ] } ]).then(answers => { config = CONFIG[answers.env] // 检查配置文件 checkByConfig(answers.env, config) runTask() })
效果预览
至此你们就能够愉快的发布代码了, 无痛发布。亲测一次耗时不会超过30s
这个打算专门写一篇文章。由于这篇文章有点长了。。。
这些脚本写的时候可能须要一点时间, 可是一旦完成以后就会为团队在效率和质量上有大幅度的提高,让开发人员更见专一与业务和技术。同时时间成本的节约也是不可忽视的,这是我在团队试验以后得出的结论。 之前开发一个模块前期的复制粘贴准备等等可能须要半个小时还要多点, 如今一个模块前期准备加上一个列表页静态开发10分钟搞定。写了发布脚本以后直接就让每个同事可以独立发布测试环境(正式权限不是每一个人都有),而且耗时极短。这些都是实在的体如今平常开发中了。另外Node
环境都安装了,不用白不用(白嫖😁😁😁), 各位大佬也能够本身发散思惟,能让代码搬的砖就不要本身搬。
原文地址 若是以为有用得话给个⭐吧