当前位置: 首页 > 后端技术 > Node.js

NodeJS大放异彩发烧:封装hook

时间:2023-04-03 12:28:59 Node.js

让NodeJS在项目系列文章中大放异彩,没看过这篇的朋友建议先看一下,不然直奔这个可能有点吃力。前面写的是我最近离职了,闲着也闲着。才想起之前还有一些Node相关的文章没有写过。正好有时间,今天继续。现在的前端开发,通过Node可以实现高度定制化,为我们的项目打造一站式服务。因为一站式不仅仅是开发阶段,我们还要处理打包之后的事情。这篇文章会讲一些打包后有用的hooks。同样的文章也被用来吸引玉石。文章涉及一些简单的部分,深层次的操作需要小伙伴们自己去发现。首先,让我们看一下总体效果。准备准备一个项目,Vue/React/Angular都有。这里我们使用vue-base-template作为项目模板修改打包命令,因为我使用的是Vue-cli3.0版本的脚手架。它打包的命令是通过vue-cli-service构建命令执行的。如果npm直接运行,我们无法添加hooks,所以我们写一个Node脚本来执行打包命令,并在scripts文件夹下新建build.js。该脚本用于组织我们所有与包装相关的东西。build.js的职责其实很简单:整理打包参数,打包完成后运行打包命令检测并运行自定义的hooks,有需求的时候再写函数。这对每个人来说都是一个小案例。让我们实现这三个要求。打包参数这个参数就是我们在命令行输入的参数,也就是package.json脚本中写的参数,例如"scripts":{"build":"nodescripts/build.js--modeproduction"}这里我们暂时不对参数做特殊处理,直接拿出来打包命令。如果你需要复杂的参数,我建议你使用commander.js。我的项目还没有涉及到复杂的封装参数。所以直接用process.argv取出即可。如果对process的具体使用感兴趣可以自己看文档constscriptArgv=process.argv.slice(2)//deletenodescripts/build.jsconstargs=scriptArgv.join('')//组装转换为正常格式以运行命令。对于Node,这很简单。通过child_process可以完美实现。我比较懒,直接用tasksfile库。也可以使用原生Node来写,但是要注意异步问题。const{sh}=require('tasksfile')//同步执行打包命令sh(`vue-cli-servicebuild${args}`,{silent:false})打包完成,执行Hooks。这是执行方法。下面是build.js中的全部代码。constora=require('ora')const{sh}=require('tasksfile')const{Notify}=require('./util')constbuiltHooks=require('./build-hooks')constscriptArgv=process.argv.slice(2)constargs=scriptArgv.join('')constspinner=ora(`buildingfor${process.env.NODE_ENV}...\n`)spinner.start()//realpackcommandsh(`vue-cli-servicebuild${args}`,{silent:false})//buildsuccessspinner.succeed("打包完成")//notifyNotify.showNotify("打包完成","下一步即将进行takeplace")//delay2ssetTimeout(()=>{//runhooksbuiltHooks()},2000)自定义hooks还有很多篇幅,大部分是为了提高我们的效率,我自己写几个我认为它更常用。发布到服务器本地预览生成Zip文件备份Zip文件到本地执行完build.js的前两步之后,hooks就会运行。因为hooks脚本的目的就是尽可能的为开发者节省时间。所以它被设计为一个非强制选项。首先新建文件build-hooks.js。第一步是设计hooks选项,使用我们的老朋友inquirerconstbuiltHooks=()=>{inquirer.prompt([{type:'list',message:`检测生产环境打包完成,请选择下一步`,name:'next',choices:[{name:'Exitscript',value:0},{name:'Publishtoserver',value:1},{name:'LocalPreview',value:2},{name:'生成Zip文件',value:3},{name:'备份Zip文件到本地',value:4}]}]).then(answers=>{afterHooks.get(answers.next)()})}第二步,设计不同选项对应的行为constafterHooks=newMap([[0,()=>{Log.logger('退出程序')process.exit(0)}],[1,()=>{Log.logger('即将发布?')require('./deploy')}],[2,()=>{Log.logger('开始本地预览?')require('./server')}],[3,async()=>{Log.logger('开始压缩zip文件?')awaitFileUtil.zipDir()}],[4,async()=>{Log.logger('开始备份Zip文件到本地?')awaitBackup.doBackup()}]])是不是很简单,简洁易懂哈哈哈我们来看看具体的操作实现细节,发布到服务器?这里就不说了,大家可以直接看这里。本地预览?这是一个简单的问题。无非就是在本地搭建一个轻量级的web服务器。使用第三方库实现它甚至更容易。代码如下server.jsconsthttp=require('http')constfs=require('fs')constpath=require('path')const{Log}=require('./util')consthttpPort=8088constfilePath=path.resolve(__dirname,'../dist/index.html')constopen=require('open')//创建当前的httpserverhttp.createServer((req,res)=>{Log.logger(req.url)try{constcontent=fs.readFileSync(filePath,'utf-8')//处理资源if(req.url.indexOf('static')!==-1||req.url.indexOf('vendor')!==-1||req.url=="/favicon.ico"){constdata=fs.readFileSync(path.resolve(__dirname,`../dist${req.url}`))返回res.end(data)}//index.htmlres.setHeader('Content-Type','text/html;charset=utf-8')res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'})res.end(content)}catch(error){Log.error('我们无法打开“index.htm”文件。')}}).listen(httpPort,async()=>{constlocation=`http://localhost:${httpPort}`Log.success(`Serverlisteningon:${location},Openinthebrowserafter3seconds`)//打开默认浏览器setTimeout(async()=>{//自动打开默认浏览器awaitopen(location)},3000)})效果如下:压缩zip文件?这有什么好说的?就是通过Node压缩dist文件夹。这里我使用[zip-local]()来实现需求(也可以用Node来实现,不过我比较懒哈哈哈)代码如下/***压缩文件夹*@param{*}dir到的文件夹被压缩默认为ROOTPATH.distDir*@param{*}zipedPath压缩后zip存储路径*/staticasynczipDir(dir=ROOTPATH.distDir,zipedPath=ROOTPATH.distZipPath){try{if(fs.existsSync(zipedPath)){Log.logger('zip已经存在,压缩包即将删除')fs.unlinkSync(zipedPath)}else{Log.logger('即将压缩zip文件')}awaitzipper.sync.zip(dir).compress().save(zipedPath);Log.success('文件夹压缩成功')}catch(error){Log.error(error)Log.error('dist文件夹压缩失败')}}备份Zip文件到本地?压缩+备份(就是复制一个文件到指定的文件夹)我是经过深思熟虑后决定加上备份的,反正。你没有备份吗?此选项将在备份文件目录中生成一个以当前日期命名的新压缩文件。如果熟悉Node的文件系统,这对大家来说就很简单了。backup.js代码如下:constpath=require('path')const{FileUtil,StringUtil,DateUtil,ROOTPATH,Log,Notify}=require('./util')classBackup{/***@author:etongfu*@description:清除所有备份*/staticclearBackups(){}/***@author:etongfu*@description:执行备份*@param{type}{*}*@returns:{*}*/staticasyncdoBackup(){try{//文件名为当前日期,精确到秒letdate=StringUtil.trim(DateUtil.getCurrentDate("YYYY-MM-DDhh:mm:ss"),1)date=StringUtil.replaceAll(date,"-","")date=StringUtil.replaceAll(date,":","")lettargetPath=path.resolve(__dirname,`../backups/${date}.backup.zip`)if(FileUtil.fileExist(targetPath)){returnLog.warning(`${targetPath}已经存在,备份已被放弃`)}//ZipFileawaitFileUtil.zipDir(ROOTPATH.distDir,targetPath)Log.success(`本地备份完成,文件:${targetPath}`)Notify.showNotify("本地备份",`本次备份完成,文件地址:${targetPath}`)}catch(error){Log.error(`Backup文件Failure:${error}`)}}}module.exports=Backup效果如下:综上所述,hooks完全不需要耦合包装,可以拆开使用,但这是对下个版本哈哈哈?写完这些脚本,使用了一段时间后,真心觉得可以节省很多时间。消除一些繁琐的手动操作。推荐大家试试,毕竟定制化程度极高,有利于团队。示例代码原文地址如果觉得有用请给个?