@oeyoews/tiddlywiki-plugin-dev
Version:
[](https://github.com/tiddly-gittly)
132 lines (127 loc) • 4.05 kB
text/typescript
import fs from 'fs';
import path from 'path';
import tw from 'tiddlywiki';
import chokidar from 'chokidar';
import { Server } from 'tw5-typed';
import { getPort } from 'get-port-please';
import { WebSocketServer, WebSocket } from 'ws';
import { rebuild } from './packup';
import { tiddlywiki } from './utils';
// WebSocket with TiddlyWiki on broswer
const runServer = async () => {
const port = await getPort({ port: 8081 });
const server = new WebSocketServer({ port });
const refreshHeartBeat = (ws: any) => {
ws.isAlive = true;
if (ws.heartBeatInterval) {
clearInterval(ws.heartBeatInterval);
}
// eslint-disable-next-line consistent-return
ws.heartBeatInterval = setInterval(() => {
if (ws.isAlive === false) {
clearInterval(ws.heartBeatInterval);
delete ws.heartBeatInterval;
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
}, 5_000);
};
server.on('connection', ws => {
refreshHeartBeat(ws);
ws.ping();
ws.on('pong', () => refreshHeartBeat(ws));
ws.on('close', () => {
if ((ws as any).heartBeatInterval) {
clearInterval((ws as any).heartBeatInterval);
}
});
});
server.on('close', () => {
server.clients.forEach(ws => {
if ((ws as any).heartBeatInterval) {
clearInterval((ws as any).heartBeatInterval);
}
ws.send('bye');
});
});
return { server, port };
};
// Run refresh server
export const runDev = async (
wiki: string,
src: string,
excludeFilter?: string,
) => {
const { server, port } = await runServer();
const devWebListnerScript = fs
.readFileSync(path.resolve(__dirname, 'src/devweb-listener.js'), 'utf-8')
.replace('$$$$port$$$$', `${port}`);
// Watch source files change
const $tw1 = tiddlywiki([], wiki);
let twServer: Server;
const watcher = chokidar.watch(src, {
ignoreInitial: true,
followSymlinks: true,
ignored: $tw1.boot.excludeRegExp,
awaitWriteFinish: {
stabilityThreshold: 1000,
pollInterval: 100,
},
});
let updateFiles: string[] | undefined;
const refresh = async (path: string) => {
// 因为 build 是异步的,这里给 refresh 加一个资源锁,否则会出现奇怪的问题
if (updateFiles !== undefined) {
updateFiles.push(path);
return;
} else {
updateFiles = [path];
}
while (updateFiles?.length) {
let resolve: (value: void | PromiseLike<void>) => void;
const wait = new Promise<void>(_resolve => (resolve = _resolve));
const tmp = updateFiles;
updateFiles = [];
$tw1.wiki.deleteTiddler('$:/Modern.TiddlyDev/devWebsocket/listener');
const plugins = await rebuild($tw1, src, tmp, true, excludeFilter);
const $tw = tw.TiddlyWiki();
$tw.preloadTiddler({
title: '$:/Modern.TiddlyDev/devWebsocket/listener',
text: devWebListnerScript,
type: 'application/javascript',
'module-type': 'startup',
});
$tw.preloadTiddlerArray(plugins);
$tw.hooks.addHook(
'th-server-command-post-start',
// eslint-disable-next-line @typescript-eslint/no-loop-func
(_listenCommand, newTwServer) => {
newTwServer.on('listening', () => resolve());
twServer = newTwServer;
},
);
const serve = async () => {
const port = await getPort({ port: 8080 });
$tw.boot.argv = [wiki, '--listen', `port=${port}`];
$tw.boot.boot();
};
if (twServer) {
twServer.on('close', serve);
twServer.close();
} else {
serve();
}
await wait;
}
server.clients.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('refresh');
}
});
updateFiles = undefined;
};
watcher.on('ready', refresh);
watcher.on('add', refresh);
watcher.on('change', refresh);
};