当前位置: 首页 > 科技观察

多项目集成下的项目脚手架配置方案

时间:2023-03-17 23:40:52 科技观察

......一、背景随着项目的复杂化和功能的增加,一个项目下可能会有多个项目。这时候如果我们单独打开项目进行开发,项目代码就会冗余。后期的维护成本也很高,而且代码的冗余会导致静态资源包加载和执行的时间变长,直接影响性能和体验。为了解决这个问题,我们需要实现多项目分模块打包,项目之间共享组件和依赖,运行和打包时互不干扰。2.应用场景以后台管理系统为例。我们还有运营管理制度、业务管理制度、设备管理制度和一些内部管理制度。这些系统的菜单管理、权限管理、用户管理都是一样的。业务模块较多,业务组件和UI组件必须遵循公司规范。在这种情况下,您可以使用存储库来管理这些系统。所有设计文档、源代码和文件都放在一个insiderepo中。3.技术方案本文基于vue-cli3,核心是vue.config.js文件。vue-cli2的实现方法类似,核心是webpack.config.js文件,这里不再赘述。1.功能项目区分命令项目配置路由模块管理项目生成脚本2.技术要点process.argv[1]:获取命令行参数cross-env[2]:设置环境fs-extra[3]:命令行生成项目chalk[4]:命令行美化Inquirer[5]:命令行交互node-progress[6]:加载进度条3.思路我们知道在package.json中有项目启动和打包的命令。我们可以从这里开始。我们的思路大致是这样的:通过命令行输入项目名对指定项目进行打包,处理命令行参数;配置公共文件和项目配置文件;设置当前运行/打包项目(project.js);打包项目所需的模块和Resources;npmrundevprojectA#在开发环境中运行projectA项目npmrunbuild:devprojectA#在开发环境中打包projectA项目npmrunbuildprojectA#打包projectA项目4.目录结构.├──README.md├───babel.config.js├──config#配置项│├──build.js#打包配置文件│├──copy.js#项目生成文件│├──dev.js#开发配置文件│├──project.js#获取项目信息│└──projectConfig.js#项目配置文件(与常用脚手架配置项相同)├──package.json#项目依赖├──postcss.config.js#postcss配置文件├──project#项目信息配置│├──index.js│├──module#公共路由模块│└──projects#公共项目信息├──public│└──index.html├──src│├──assets#公共资源文件││└──logo.png│├──components#公共组件││├──404.vue││└──main.vue│└──projects#project目录(独立路由状态管理接口请求)│├──projectA│├──projectB│└──projectC├──temp#项目模板文件(可根据项目需求定制)│├──App.vue│├──components│├──main.js│├──page││└──home.vue│├──router.js│└──store.js├──vue.config.js#核心配置文件└──yarn.lock13个目录,26个文件好了,我们的视图目录结构大概就是上面的样子看起来,我们期望的是像打包一个完整的工程一样,把这个A工程打包到src目录下,那么这部分怎么实现呢?5.流程图6项目配置1)修改package.json配置这里不得不提到cross-env模块,我们之前在生产、沙盒、测试、开发环境中也用到了。npmi--save-devcross-envcode:"scripts":{"serve":"vue-cli-serviceserve","build":"vue-cli-servicebuild","lint":"vue-cli-servicelint","dev":"cross-envNODE_ENV=developmentnodeconfig/dev.js","test":"cross-envNODE_ENV=testnodeconfig/dev.js","pre":"交叉-envNODE_ENV=预览节点配置/dev.js","prd":"cross-envNODE_ENV=productionnodeconfig/dev.js","build:dev":"cross-envNODE_ENV=developmentnodeconfig/build.js","build:test":"cross-envNODE_ENV=testnodeconfig/build.js","build:pre":"cross-envNODE_ENV=previewnodeconfig/build.js","build:prd":"cross-envNODE_ENV=productionnodeconfig/build.js","copy":"nodeconfig/copy.js"}2)编写项目代码本版本为简单demo,配置好运行命令并打包后命令我们在项目中编写业务代码。路径:src/projects/projectA/App.vue......路径:src/projects/projectB/App.vue......3)配置项目信息在项目根目录下新建一个config文件夹,并在里面新建一个projectsConfig.js写:constprojectName=require("./project");constconfig={//$ProjectAprojectA:{pages:{index:{entry:"src/projects/projectA/main.js",模板:"public/index.html",文件名:"index.html",title:"projectA"},},devServer:{port:7777,//端口地址}},//$projectBprojectB:{页面:{索引:{条目:“src/projects/projectB/main.js”,模板:“public/index.html”,文件名:“index.html”,标题:“projectB”},},devServer:{port:8888,//端口地址}},//$projectCprojectC:{pages:{index:{entry:"src/projects/projectC/main.js",template:"public/index.html",filename:"index.html",title:"projectC"},},devServer:{port:9999,//端口地址}},};constconfigObj=config[projectName.name];//$这里导出的是当前运行项目的配置module.exports=configObj;4)运行时配置开始之前,先说一下process.argv,它返回一个数组,里面包含了第一次启动Node.js进程时传入的命令行参数。第一个元素是process.execPath,第二个元素是到的路径正在执行的JavaScript文件,其余元素将是任何其他命令行参数。constfse=require("fs-extra");constchalk=require('chalk');letprojectName=process.argv[2];//$获取命令行项目名if(!projectName)throw(chalk`{red.bold.bgWhite------项目不存在,请检查配置------}`);console.log(chalk.red.bold(`Running---${projectName}project`),`${process.env.NODE_ENV}environment`,)fse.writeFileSync('./config/project.js',`exports.name='${projectName}'`)letexec=require('child_process').execSync;exec('npmrunserve',{stdio:'inherit'});提示:命令行参数在一个固定格式的npmrundevprojectA,如果缺少项目名,会提示项目不存在。5)这里打包的时候配置比较简单,按照当前项目名打包即可constprojectName=process.argv[2]constfse=require("fs-extra");fse.writeFileSync('./config/project.js',`exports.name='${projectName}'`)conststr='npmrunbuild'constexec=require('child_process').execSync;exec(str,{stdio:'inherit'});6)配置VueCLI通过process.argv获取当前命令行的项目名,判断该命令行的项目名是否在项目列表中,如果没有则给出异常提示;设置当前运行项目的脚手架信息;终端命令提示符;constpath=require('path')constconf=require('./config/projectConfig');//$当前项目constchalk=require('chalk');//$终端颜色设置插件constProgressBarPlugin=require('progress-bar-webpack-plugin');//$进度条插件constPROJECTNAME=require('./config/project.js').name;if(!conf)throw(chalk`{black.bold.bgWhite------项目不存在,请检查配置777-----}`);constassetsDir=''functiongetAssetPath(assetsDir,filePath){返回assetsDir?path.posix.join(assetsDir,filePath):filePath}module.exports={pages:conf.pages,//$当前项目页面outputDir:"dist/"+projectName+"/",//$设置输出目录命名资产目录:'static',//$添加静态文件夹lintOnSave:process.env.NODE_ENV!=='production',//$开发环境是否使用eslint-loader来lint代码productionSourceMap:false,//$是否需要生产环境的源地图?devServer:conf.devServer,//$取决于项目是否需要配置configureWebpack:{plugins:[newProgressBarPlugin({width:50,//默认20,进度格的个数是每个代表进度,如果是20,那么一格就是5//格式:'build[:bar]:percent(:elapsedseconds)',格式:chalk.blue.bold("build")+chalk.yellow('[:bar]')+chalk.green.bold(':percent')+'(:elapsedseconds)',//stream:process.stderr,//默认stderr,输出流//complete:"~",//默认"=",completioncharacterclear:false,//默认true,完成时清除栏的选项//renderThrottle:"",//默认16,更新之间的最短时间(以毫秒为单位)callback(){//进度条完成时调用的可选函数console.log(chalk.red.bold("---->>>>Compiled<<<<----"))}}),]},//$进行更详细的内部webpack配置粒度修改chainWebpack:config=>{//$修复HMRconfig.resolve.symlinks(true);//$制定环境包js路径constfilename=getAssetPath(assetsDir,`static/js/[name].js`)config.mode('production').devtool(false).output.filename(filename).chunkFilename(文件名)config.performance.set('hints',false)},css:{extract:false//$是否将组件中的CSS提取到单独的CSS文件中(而不是将内联代码动态注入JavaScript)loaderOptions:{sass:{implementation:require('sass'),fiber:require('fibers')}}}}配置终端插件的效果图:7)运行效果写到这里我们就搭建一个完整的vue小工程,我们运行一下看看效果:npmrundevprojectA如图:8)打包效果npmrunbuild:projectAcddist/projectAlive-server--port=9999live-server是一个带有实时加载功能的小型服务器,在项目中使用live-server作为实时服务器查看开发好的网页或项目效果七、自动生成模板Item1)Flowchart2)思路安排本文涉及脚手架中与命令行交互的知识点。有兴趣的可以复制文末的demo进行练习;这里主要是复制新建的模板,在流程节点执行复制命令上次输入的项目名如果本地已经存在则提示是否需要删除或覆盖,根据实际业务场景处理,这里就不过多讨论了;示例代码中涉及的模板代码存放在项目根目录下,也可以放在src目录下,不强制;3)执行命令npmruncopy4)样例代码fs-extra:添加原生fs模块中没有的文件系统方法,并为fs方法添加promise支持;fse.pathExists:判断当前复制的项目是否存在;fse.copy:复制模板文件到指定目录;constfse=require("fs-extra");constchalk=require("chalk");constpath=require("path");constinquirer=require("inquirer");inquirer.prompt([{type:"input",name:"projectName",message:"请输入要生成的项目名称",},]).then((answers)=>{createProject(answers.projectName);});//$复制项目模板constcreateProject=(projectName)=>{constcurrentTemp=path.join(`./src/projects/${projectName}`);//$判断要复制的item当前是否存在fse.pathExists(currentTemp,(err,exists)=>{console.log(err,exists);//$=>null,false//$是否替换这个根据用户的选择项目或者删除这个项目if(exists){//$这里也可以覆盖原来的项目或者donginquirer.prompt([{type:"input",name:"projectName",message:"The项目已经存在,请重新输入项目名称",},]).then((answers)=>{createProject(answers.projectName);});//throwchalk`{red.bold.bgWhite>>>${projectName}<<{//if(err)returnconsole.error(err)if(err)throwchalk`{red.bold.bgWhite------${projectName}项目复制失败${错误}------}`;console.log(chalk.red.bold(`--->>>${projectName}项目复制成功`));});}});};8个优缺点优点:方便统一管理项目;在项目之间共享组件和依赖关系;运行和打包时互不干扰;支持同时运行多个项目;公共模块一次提交可以解决所有子项目的问题;缺点:执行复制模板命令后生成的项目需要在config/projectConfig.js文件中手动配置项目信息;随着项目的增多,路由文件的提交每次提交代码都需要进行CodeReview,否则不熟悉项目的同学很可能会把Conflictingmodules删掉;随着程序规模的增大,代码量的增加,文档数量的增加,整个repo会越来越大;4.思考有兴趣的童鞋可以考虑以下两个问题:有公有路由应该怎么处理?这个项目下如何处理状态管理和接口管理?五、小结通过以上分析,我们应该对同一项目下多项目配置打包的大致流程有了基本的了解,以上方案只是其中一种实现方式。写这篇文章的目的是为大家提供一个在以后项目需要定制的时候,可以通过改变脚手架的配置来实现。演示:[https://github.com/licairen/multi_project_demo](