1
导读
近期一次需求开发涉及到了java Veloctiy,由于当前vue项目无法在本地编译运行velocity,且开发成本过高,如果需要调试则要发布后才能看到效果。之前有类似的案例,遂尝试继续采用这种方式解决,但是通过比对认为该方案迁移到本项目中的成本较高,且需要改动大量配置文件。
基于以上,我们便尝试将veloctiy本地工程化,尝试跟vue和webpack的结合,实现热更等开发常用需求。
本文把velocity工程化的心路历程记录下来,主要给大家描述我们在解决问题过程中的一些感悟,同时文中也详细介绍了具体方案。
2
背景
本文提到的“Velocity”指的是Java Velocity,后面全部以“velocity”代称。
本文不对Velocity基础概念做详细介绍,默认阅读本文的读者具备velocity基础相关知识。如果确有不清楚的地方,可依据参考文献提供的文档或搜索引擎进一步了解。
3
现状
行业现状:随着前端技术的发展,页面的开发逐步形成了前后端分离的开发模式,形成了以vue、react等主流前端框架为主的MVVM模式。
回首再面对这些基于Velocity的旧系统,无论是后端还是前端人员维护,都会存在诸多问题:
1. RD不熟悉前端开发模式,需要花大量时间学习前端js和框架。
2. Velocity渲染依赖Java环境,FE需要花费大量精力学习Maven工程、环境配置,且前端MVC框架版本老,开发效率低。
内部业务现状:原有项目涉及面较广,基于Velocity开发的情况分散在不同业务系统中,随着业务需求的迭代,维护成本越来越高。解决方案一般是针对高频迭代的业务模块进行前后端分离开发,但这样会带来新的问题:
1. 使用新项目重构原有项目成本高
2. 覆盖面不广
3. 周期长
4. 重构本身不能为业务带来更高的价值
如下图,我们在本地开发velocity模版页面时,目前基本处于盲写阶段,不能即时查看,造成开发时间大量浪费在部署和联调中。
基于以上问题,我们团队在维护的时候探索一种新的方式,旨在提升开发效率,解决本地编译运行velocity模版。
4
实现
以我们团队维护的本地服务大类页为例。
该项目基于vue,近期一次需求迭代中,需要把部分页面内容前置到服务端交由java预渲染,然而本地开发目前不支持模拟java 环境运行velocity模版。因此需把velocity和vue进行整合,降低开发成本,让FE无痛开发,不论是编译还是打包上线都跟普通vue项目无差异。
基于此目标,需要实现一个velocity mvc mvvm的混合架构,该架构需具备两个能力:
1. 把velocity模版页面进行本地渲染,然后跟vue的index.html模版页面进行整合
2.让vue把dom挂载到整合后的模版页面中
现在思考第一个问题,如何实现上面的架构?回顾刚才我们描述的架构需要具备的能力,可以分为两段:
第一段是velocity mvc,上线后由服务端执行
第二段是mvvm,上线后由客户端执行
问题进一步明确,基于以上两段,我们需要做的事可以分为3步:
1. 本地实现一套velocity模版渲染引擎,实现velocity页面的渲染和mock数据填充
2. 把渲染好的velocity模版整合到vue的模版文件index.html中
3. 把整合了velocity页面的vue模版文件index.html交给vue进行dom挂载
好了,到此,我们不再停留在理论层面,而是需要考虑怎么实现以上三步。由于后面步骤依赖前面步骤的产物,所以我们按顺序实现。
现在思考第二个问题,velocity渲染引擎应该具备什么能力?
根据第一步的描述,该引擎需要在前端本地编译运行velocity模版页面,同时支持mock数据的渲染。
总结一下就是两点:1 渲染velocity 2 支持mock数据
目前本地渲染velocity的主流方式是在本地运行一套java服务,但是当前需求开发伙伴对java不太了解,并且一些配置项对我们来说较为复杂,这与我们在现状中描述的问题不谋而合,这条路不适合我们。换个思路,java能跑服务,js不能吗?答案很明显,可以,是nodejs。虽然对java不熟悉,但是nodejs我们熟啊。
通过调研,发现nodejs有类似java velocity的模版引擎,能运行velocity模版,同时能支持mock数据。接下来尝试把该引擎接入项目中。
下面是接入引擎时尝试的两种方式:
方法一:在vue执行入口文件index.js前启动node服务,然后通过在node服务中使用引擎提供的编译和运行方法把velocity模版页面加载好供后续使用。
方法二:把该引擎写在webpack的loader中,随着webpack的执行逻辑编译velocity。
如果大家对vue本地运行时各个页面加载的顺序有了解,应该能看出来方法一存在问题,我们逐个实现,看看问题在哪里。
方法一具体实现:以下代码为当时在本地调试时临时编写,仅用于帮助大家理解思路。不具有参考性。核心代码会加注释。
step1: 改造vue入口文件index.js
// setup中启动node服务,编译velocity并生成一个html文件到指定文件夹下
import './setup';
// entry是原有vue的index.js入口文件内容
import './entry';
step2: 实现setup.js
import Axios from 'axios';
// 该方法向本地启动的node服务发送请求,告诉node服务需要重新编译模版
(() => {
new Promise((resolve, reject) => {
Axios.get('initdom')
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
});
})();
step3: 实现node服务
// node服务在本地手动启动
// 这里需要在全局安装velocityjs包,用于后续命令执行
const http = require('http');
const fs = require('fs');
const { exec } = require('child_process');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
if (req.url === '/initdom') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
// 该命令用于执行velocity模版页面,并且生成html文件到执行目录下
exec('velocity ./index.vm', { encoding: 'utf-8' }, (error, stdout, stderr) => {
if (error) {
console.error(error);
}
if (stderr) {
console.error(stderr);
}
const isExist = fs.existsSync('模版文件地址');
if (isExist) {
console.log('file is exist');
fs.rmSync('生成html文件地址');
}
console.log('file is rewriting');
fs.writeFileSync('生成html文件地址', stdout);
console.log('file is rewrited');
// html文件生成成功后,通知前端,执行后续逻辑
res.end('模版编译成功');
});
} else {
console.log('url is not correct!!!');
}
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
step4: 本地启动node服务和vue项目
// 启动node服务
node node.js
// 启动vue
npm run dev
至此,方法一整个流程实现,项目启动后确实也符合我们的预期,但是前文提到的问题在哪里?大家知道,我们平时本地开发中很重要的一个需求是热更新。当我改动mock数据后发现,生成的html文件中的mock数据确实变动了,但是我需要刷新两次浏览器才能在页面中体现出来,也就是说热更生效了,但又没完全生效。
问题在于vue读取html文件的时机早于html文件生成的时机,那么能把html文件生成的逻辑再前置吗?基于方法一很明显不行,而且方法一需要本地启动node服务,通过请求接口的方式实现服务启动,这很不优雅,因此考虑换个方式。
我们知道webpack提供了自定义loader的机制,允许我们创建loader预处理指定的文件。
vue cli支持通过webpack chain添加新的loader。
回顾方法一遇到的问题,尝试用webpack loader实现。
方法二具体实现:
下图为整体流程图:
实现方法二之前我们再思考一个问题,随着项目逐步迭代,可能会对velocity模版文件进行拆分,同时一些外部文件也需要注入到最后的html文件中,而且后续还会逐步把代码进行升级。好消息是velocity提供了#parse和#include能力,配合script和api接口我们能够实现velocity vue的单文件组件化模式。
下图为文件基本结构:
基于这个结构,我们搭建一套前端沙盒环境,通过在本地json文件中保存mock服务端的数据,然后开发velocity-loader引擎来解析velocity模版,嵌入到webpack loader中,实时解析文件并注入mock数据到模版中,解决方法一中遇到的热更问题。如下图。
下面贴上方法二实现的相关代码。
step1: 配置webpack chain
config.module.rule('velocity')
// 只处理.vm结尾的文件
.test(/.vm$/)
.exclude.add(/node_modules/).end()
.use('html-loader')
.loader('html-loader')
.end()
.use('velocity-loader')
.loader(path.resolve(__dirname, './v-loader.js'))
.options({
basePath: path.join(__dirname, 'src'),
});
step2: 实现velocity-loader引擎
这里只写部分核心代码,其他的代码为其他配置项,跟本文关系不大。
module.exports = function (content) {
const callback = this.async();
const filePath = this.resourcePath;
const fileDirPath = path.dirname(filePath);
watcher = this.addDependency;
// mock文件名称这里写死,用于描述,实际可进行配置
const mockPath = path.join(fileDirPath, 'mock.js');
let mock = {};
// 判断mock文件是否存在,如果存在则进行监听,实现热更
if (fs.existsSync(mockPath)) {
watcher(mockPath);
delete require.cache[mockPath];
// eslint-disable-next-line global-require
mock = require(mockPath);
}
// 使用compile解析velocity模版
content = new Compile(parse(content), {
escape: false,
}).render(mock, macros(filePath, {}, mock));
// 返回html内容给webpack,进行下一步处理
callback(null, content);
};
至此,我们解决了本地编译运行velocity模版的问题,也实现了mock数据和热更,提升了开发效率和开发体验,维护成本并没有增加。
5
总结
其实到现在关于本地编译运行velocity已经有很多方案且大都成熟,只是我们要么直接使用了现有的方案,要么没遇到这种情况,写这篇文章的目的也是想把自己在解决问题时的思路描述出来,做个总结。
参考文献
[1] velocity官网:https://velocity.apache.org/
[2] velocityjs官网: https://github.com/shepherdwind/velocity.js
[3] webpack 官网:https://www.webpackjs.com/loaders/
[4] vue官网:https://cli.vuejs.org/zh/guide/webpack.html
作者简介
雷鸣生:LBG-FE
来源:微信公众号:58技术
出处:https://mp.weixin.qq.com/s/LqBf6H-6o88tMfl59IRPbw
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved