|
一、背景随着项目的复杂和功能的增加,一个工程下可能存在多个项目,这个时候我们单独开项目去开发的话项目代码会冗余,项目后期的维护成本也很高,而代码的冗余会造成静态资源包加载时间变长、执行时间也会变长,进而很直接的影响性能和体验。为了解决此问题我们需要实现多项目的分模块打包,且项目之间共享组件和依赖,运行、打包时互不干扰。二、应用场景以一个后台管理系统为例,我们同时有运营管理系统、商家管理系统、设备管理系统,还有一些内部的管理系统,这几个系统的菜单管理、权限管理、用户管理等相同业务模块较多,业务组件以及UI组件都要遵循公司的规范,这种情况下就可以用一个repo来管理这些系统,所有的设计文档、源代码、文件都放在一个repo里面。三、技术方案本文基于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);打包项目所需的模块和资源;npm run dev projectA # 运行开发环境下的projectA项目npm run build:dev projectA # 打包开发环境下的projectA项目npm run build projectA # 打包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 # 项目目录(独立的路由 状态管理 接口请求)│ ├── projectA│ ├── projectB│ └── projectC├── temp # 项目模板文件(可根据项目需求定制)│ ├── App.vue│ ├── components│ ├── main.js│ ├── page│ │ └── Home.vue│ ├── router.js│ └── store.js├── vue.config.js # 核心配置文件└── yarn.lock13 directories, 26 files好了,我们的视图目录结构大概就是上面的样子,我们期望的是打包src目录下这个A项目就像打包一个完整的项目一样。那么如何实现这部分呢?5.流程图6项目配置1)修改package.json配置这里就不得不提到cross-env这个模块,我们之前在生产、沙箱、测试、开发环境时也会用到这个命令。npmi--save-devcross-env代码:"scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "dev": "cross-env NODE_ENV=development node config/dev.js", "test": "cross-env NODE_ENV=test node config/dev.js", "pre": "cross-env NODE_ENV=preview node config/dev.js", "prd": "cross-env NODE_ENV=production node config/dev.js", "build:dev": "cross-env NODE_ENV=development node config/build.js", "build:test": "cross-env NODE_ENV=test node config/build.js", "build:pre": "cross-env NODE_ENV=preview node config/build.js", "build:prd": "cross-env NODE_ENV=production node config/build.js", "copy": "node config/copy.js"}2)编写项目代码此版本为简易demo,配置完运行命令和打包命令我们就可以编写项目中的业务代码了。路径:src/projects/projectA/App.vue 路径:src/projects/projectB/App.vue 3)配置项目信息在项目根目录建立config文件夹,在其中新建projectsConfig.js写入:const projectName = require("./project");const config = { // $ 项目A projectA: { pages: { index: { entry: "src/projects/projectA/main.js", template: "public/index.html", filename: "index.html", title: "projectA" }, }, devServer: { port: 7777, // 端口地址 } }, // $ 项目B projectB: { pages: { index: { entry: "src/projects/projectB/main.js", template: "public/index.html", filename: "index.html", title: "projectB" }, }, devServer: { port: 8888, // 端口地址 } }, // $ 项目C projectC: { pages: { index: { entry: "src/projects/projectC/main.js", template: "public/index.html", filename: "index.html", title: "projectC" }, }, devServer: { port: 9999, // 端口地址 } },};const configObj = config[projectName.name];// $ 这里导出的是当前运行项目的配置module.exports = configObj;4)运行时配置开始前先讲下process.argv它返回的是一个数组,其中包含启动Node.js进程时传入的命令行参数。第一个元素将是process.execPath,第二个元素将是正在执行的JavaScript文件的路径,其余元素将是任何其他命令行参数。const fse = require("fs-extra");const chalk = require('chalk');let projectName = process.argv[2]; // $ 获取命令行项目名称if(!projectName) throw(chalk`{red.bold.bgWhite ------项目不存在,请检查配置------}`);console.log(chalk.red.bold(`正在运行---${projectName}项目`), `${process.env.NODE_ENV} 环境`, )fse.writeFileSync('./config/project.js', `exports.name = '${projectName}'`)let exec = require('child_process').execSync;exec('npm run serve', {stdio: 'inherit'});Tips:命令行参数是固定格式npmrundevprojectA,少了项目名称会提示项目不存在。5)打包时配置这里就比较简单了,根据当前项目名称进行打包即可const projectName = process.argv[2]const fse = require("fs-extra");fse.writeFileSync('./config/project.js', `exports.name = '${projectName}'`)const str = 'npm run build'const exec = require('child_process').execSync;exec(str, {stdio: 'inherit'});6)配置VueCLI通过process.argv获取当前命令行的项目名称,判断命令行的项目名称是否在项目列表里,如果没有给出异常提示;设置当前运行项目的脚手架信息;终端命令提示;const path = require('path')const conf = require('./config/projectConfig'); // $ 当前项目const chalk = require('chalk'); // $ 终端颜色设置插件const rogressBarPlugin = require('progress-bar-webpack-plugin'); // $ 进度条插件const ROJECTNAME = require('./config/project.js').name;if(!conf) throw(chalk`{black.bold.bgWhite ------项目不存在,请检查配置 777------}`);const assetsDir = ''function getAssetPath (assetsDir, filePath) { return assetsDir ? path.posix.join(assetsDir, filePath) : filePath}module.exports = { pages: conf.pages, // $ 当前项目页面 outputDir: "dist/" + projectName + "/", // $ 设置输出目录名 assetsDir: 'static', // $ 增加static文件夹 lintOnSave: process.env.NODE_ENV !== 'production', // $ 是否在开发环境下通过 eslint-loader 在每次保存时 lint 代码 productionSourceMap: false, // $ 是否需要生产环境的 source map devServer: conf.devServer, // $ 看项目需求 可配可不配 configureWebpack: { plugins: [ new rogressBarPlugin({ width: 50, // 默认20,进度格子数量即每个代表进度数,如果是20,那么一格就是5。 // format: 'build [:bar] :percent (:elapsed seconds)', format: chalk.blue.bold("build") + chalk.yellow('[:bar] ') + chalk.green.bold(':percent') + ' (:elapsed秒)', // stream: process.stderr, // 默认stderr,输出流 // complete: "~", // 默认“=”,完成字符 clear: false, // 默认true,完成时清除栏的选项 // renderThrottle: "", // 默认16,更新之间的最短时间(以毫秒为单位) callback() { // 进度条完成时调用的可选函数 console.log(chalk.red.bold("---->>>>编译完成<<<<----")) } }), ] }, // $ 对内部的 webpack 配置进行更细粒度的修改 chainWebpack: config => { // $ 修复HMR config.resolve.symlinks(true); // $ 制定环境打包js路径 const filename = getAssetPath( assetsDir, `static/js/[name].js` ) config.mode('production').devtool(false).output.filename(filename).chunkFilename(filename) config.performance.set('hints', false) }, css: { extract: false // $ 是否将组件中的 CSS 提取至一个独立的 CSS 文件中 (而不是动态注入到 JavaScript 中的 inline 代码) loaderOptions: { sass: { implementation: require('sass'), fiber: require('fibers') } } }}配置终端插件的效果图:7)运行效果写到这里我们就建立一个完整的小vue项目了,我们运行看看效果:npm run dev projectA如图:8)打包效果npm run build:projectAcd dist/projectAlive-server --port=9999live-server是一个具有实时加载功能的小型服务器,在项目中用live-server作为一个实时服务器查看开发的网页或项目效果7.自动化生成模板项目1)流程图2)思路整理本文涉及到脚手架里边与命令行交互的知识点,感兴趣的可以拷贝文末demo去练习下;这里主要是针对新建的模板做拷贝处理,流程节点中执行拷贝命令后输入的项目名称提示在本地已存在是否需要删除或者覆盖,根据实际业务场景做处理,这里不做过多探讨;示例代码涉及到的模板代码存放在工程根目录,也可以放在src目录下,不做强制要求;3)执行命令npm run copy4)示例代码fs-extra:添加了未包含在原生fs模块中的文件系统方法,并向fs方法添加了promise支持;fse.pathExists:判断当前要拷贝的项目是否存在;fse.copy:拷贝模板文件到指定目录;const fse = require("fs-extra");const chalk = require("chalk");const path = require("path");const inquirer = require("inquirer");inquirer .prompt([ { type: "input", name: "projectName", message: "请输入要生成的项目名称", }, ]) .then((answers) => { createProject(answers.projectName); });// $ 拷贝项目模板const createProject = (projectName) => { const currentTemp = path.join(`./src/projects/${projectName}`); // $ 判断当前要拷贝的项目是否存在 fse.pathExists(currentTemp, (err, exists) => { console.log(err, exists); // $ => null, false // $ 根据用户选择是否替换本项目或者删除本项目 if (exists) { // $ 这里也可以覆盖原项目或者dong inquirer .prompt([ { type: "input", name: "projectName", message: "项目已存在,请重新输入项目名称", }, ]) .then((answers) => { createProject(answers.projectName); }); // throw chalk`{red.bold.bgWhite >>> ${projectName} <<< 项目已经存在}`; } else { // $ 拷贝模板文件到指定目录 fse.copy("./temp", path.join(`./src/projects/${projectName}`), (err) => { // if (err) return console.error(err) if (err) throw chalk`{red.bold.bgWhite ------${projectName}项目拷贝失败 ${err}------}`; console.log(chalk.red.bold(`--->>>${projectName}项目拷贝成功`)); }); } });};8优缺点优点:方便统一管理项目;项目之间共享组件和依赖;运行、打包时互不干扰;支持同时运行多个项目;对于公共模块一次提交可以解决所有子项目的问题;缺点:执行拷贝模板命令后生成的项目需要在config/projectConfig.js文件中手动配置项目信息;随着项目的增加路由文件的提交在每次代码的时候都需要进行CodeReview,不然的话不熟悉项目的同学很可能会在解决冲突的过程中把冲突的模块删除;随着程序规模的不断增加,代码量的增加,文档的增加,整个repo会变得越来越大;四、思考有兴趣的童鞋可以考虑以下两个问题:项目中有公共路由我们应该如何处理?状态管理和接口管理在这个工程下如何处理?五、总结通过以上的分析,我们应该对同一工程下多项目配置化打包的大概流程有基本的了解,而上边的方案也只是其中的一种实现方式。写本文的目的主要是给大家提供一种思路,以后在遇到工程需要定制化的时候就可以通过更改脚手架的配置来实现。Demo:[https://github.com/licairen/multi_project_demo](参考资料[1]process.argv:http://nodejs.cn/api/process/process_argv.html[2]cross-env:https://www.jianshu.com/p/ecf1a4130add[3]fs-extra:https://github.com/jprichardson/node-fs-extra[4]chalk:https://github.com/chalk/chalk[5]inquirer:https://github.com/SBoudrias/Inquirer.js[6]node-progress:https://github.com/tj/node-progress#options
|
|