本文中的一切功能原则上都可以通过webpack-dev-server配合hot-module-replacement-plugin来实现,本文使用express只是一种解决方案。
刚刚接触cocos开发,发现了几个问题,没找到很好的官方解决方案,所以自己试了试,也算是间接解决了,于是分享出来。
要实现的功能:
1、cocos项目在本地可以做像vue cli devserver 一样的反向代理操作,自定义接口别名,并且解决跨域。
2、在满足上面的需求下,再实现本地实时预览。
其实要实现本地实时预览很简单,因为cocos提供了预览功能,但是如果满足反向代理功能再实时预览的话就不太容易实现了,至少我没找到比较好的方法,于是我有了个想法。
我的思路大概如下
[正文]
express实现反向代理
app.js
const express = require('express');
const child_process = require("child_process");
const proxyMiddleWare = require("http-proxy-middleware");
let port = 8080
// 代理配置 添加代理配置在里面按照格式写就行
const proxy = [
{
router: '/test', // 相当于devServer里的 path
target: 'http://192.168.0.123:9999', // 相当于devServer里的target
changeOrigin: true, // 相当于devServer里的changeOrigin
}
]
const app = express();
// cocos build后的目录作为express的静态目录
app.use('/local-realtime',express.static("./build/web-mobile"));
// 设置反向代理
for (let i in proxy) {
const {target, changeOrigin} = proxy[i]
app.use(proxy[i].router, proxyMiddleWare.createProxyMiddleware({
target,
changeOrigin
}))
}
app.listen(port, () => {
console.log(`\n\n项目在 http://localhost:${port}/local-realtime 地址启动。${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()} \n\n`)
});
相关依赖 express、http-proxy-middleware 请自行npm安装 child_process是Node标准库。
写完后,我们安装一下nodemon
npm install nodemon
接着配置一下启动命令
package.json -> script部分
"scripts": {
"dev": "node ./node_modules/nodemon/bin/nodemon.js --watch build/web-mobile/index.html app.js"
},
意思是通过nodemon启动 app.js并监听 cocos build目录中index.html的变化。
nodemon来监听index.html文件的变化并动态重启app.js是为了构建后能实时看到变化做的准备。
创建cocos构建模板
可直接复制(文末链接)的build-templates目录到自己的项目目录中。
实现页面实时变化 - 自动打开页面
其实我个人觉得自动打开页面的功能就足够了,没必要做到vue-cli那样的实时刷新,因为vue-cli的实时刷新在一些情况下是保持页面状态局部刷新的,是为了调试单个组件更方便。
但cocos项目只要能在每次构建完保证不用手动再次打开页面就行了,美中不足是 每次打开新页面不会关闭上一次的页面,只能手动关闭。目前这种方法我没找到自动关闭上一次页面的解决方案,不嫌麻烦的可以直接参考本方法。
app.js 的 app.listen部分修改为下方代码
app.listen(port, () => {
console.log(`\n\n项目在 http://localhost:${port}/local-realtime 地址启动。${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()} \n\n`)
// 自动打开浏览器
switch (process.platform) {
case 'wind32':
cmd = 'start';
break;
case 'linux':
cmd = 'xdg-open';
break;
case 'darwin':
cmd = 'open';
break;
}
child_process.exec(cmd + ' ' + `http://localhost:${port}/local-realtime`);
});
这样每次修改完就能自动打开浏览器了。
每次修改完自动打开浏览器, 可以理解为间接的打开了一个新的窗口展示本次最新修改,相当于页面实时变化了。 本文的基础部分看到这里就可以结束了,接下来的部分时高级一点的实现方法。
实时页面变化 - 絮叨部分
我查询过vue-cli对于修改后实时刷新页面的解决方案,它使用了 hot-module-replacement-plugin 这个插件,在构建时注入了一段代码来和 webpack-dev-server进行了websocket通讯,
保证页面实时刷新的。
同理,我们只要让页面和express 建立http通讯或者websocket通讯通知页面刷新就可以了。
实时页面变化 - 建立http协议实现。
app.js 添加一个接口 代码如下:
const h = new Date().getTime()
app.get('/heartBeat',((req, res) => {
res.json({
time: h.toString()
})
}))
这个接口可以理解为: 服务器每次重启,/heartBeat 接口都会返回一个固定的字符串,我们用时间戳去代表这个字符串。
在index.html页面中,我们用setInterval 去轮询这个接口,只要返回的字符串和上次不一样了,那就代表服务器重启了,就需要刷新页面了,否则页面不动。
build-templates/web-mobile/index.html 中加入下面代码:
<script>
let __heartBead = ''
// 如果当前的路径是local-realtime 轮询生效
if(location.pathname === '/local-realtime/'){
setInterval(() => {
fetch("http://localhost:8080/heartBeat/")
.then(response => response.json())
.then(res => {
// 如果 __heartBead 为空 那就把返回结果赋值
if(__heartBead === ''){
__heartBead = res.time
}else{
// 不为空,就判断是否相等,否则刷新页面
if(__heartBead !== res.time){
location.reload()
}
}
})
},1000)
}
</script>
我判断了local-realtime这个路径的原因是为了防止线上环境也去轮询,这样的话只要线上环境的路径不是local-realtime 这段代码就不会被启动
如果实现页面实时刷新,那就要将app.js里自动打开浏览器的代码删掉,否则页面即重新打开 又自动刷新,等于脱了裤衩放p了。
这种方法的缺点是 network里会一直刷检测接口的记录,影响前后端联调。
但是我们接下来要说的websocket也一样, 个人更倾向websocket的解决方案
实时页面变化 - 建立websocket实现。
build-templates/web-mobile/index.html 中加入下面代码:
<script>
let __heartBeat = ''
if(location.pathname === '/local-realtime/'){
// 创建websocket请求
let ws = new WebSocket(`ws://${location.host}/ws`)
// 发送初始数据,开始轮询
ws.onopen = function()
{
ws.send("start");
};
// 接收到服务器返回数据后 延迟3秒处理,如果实时处理的话,浏览器会特别卡,如果需要实时处理,可以考虑使用webworker来实现。
ws.onmessage = function (evt)
{
setTimeout(() => {
if(__heartBeat === ''){
__heartBeat = JSON.parse(evt.data).time;
}else{
if(__heartBeat !== JSON.parse(evt.data).time){
location.reload()
}
}
ws.send("start")
},3000)
};
// websocket关闭时只有两个原因 1、手动关闭,2、服务重启, 这时刷新页面即可
ws.onclose = function (){
if(__heartBeat){
location.reload()
}
}
}
</script>
app.js 添加websocket部分
const expressWs = require('express-ws');
const app = express();
expressWs(app)
const h = new Date().getTime()
app.ws('/ws',(ws,req) => {
ws.on('message', function (msg) {
ws.send(JSON.stringify({
msg: '通讯正常',
time: h
}))
})
})
这样就实现了websocket实时刷新页面的功能。
完整代码链接如下:
https://github.com/gch116620/cocos-node-build-template