diff --git a/script/build-preload.ts b/script/build-preload.ts index fadf0dc..ebf9971 100644 --- a/script/build-preload.ts +++ b/script/build-preload.ts @@ -2,7 +2,9 @@ import { watch, rollup, OutputOptions } from 'rollup' import minimist from 'minimist' import chalk from 'chalk' import ora from 'ora' +import WebSocket from 'ws' import options from './rollup.config' +import { createWsServer, formatWsSendData } from './ws' const argv = minimist(process.argv.slice(2)) const opt = options({ proc: 'preload', env: argv.env }) @@ -12,13 +14,17 @@ const spinner = ora(`${TAG} Electron preload build...`) ; (async () => { if (argv.watch) { const watcher = watch(opt) + const wssObj = createWsServer({ TAG }) + watcher.on('change', filename => { const log = chalk.yellow(`change -- ${filename}`) console.log(TAG, log) - /** - * @todo Hot reload render process !!! - */ + // Hot reload renderer process !!! + if (wssObj.instance?.readyState === WebSocket.OPEN) { + console.log(TAG, 'Hot reload renderer process') + wssObj.instance.send(formatWsSendData({ cmd: 'reload', data: Date.now() })) + } }) } else { spinner.start() diff --git a/script/ws.ts b/script/ws.ts new file mode 100644 index 0000000..520a81d --- /dev/null +++ b/script/ws.ts @@ -0,0 +1,43 @@ +/** + * Hot reload from preload script during development + */ +import WebSocket from 'ws' +import chalk from 'chalk' +import pkg from '../package.json' + +export interface CreateWsServerOptions { + TAG: string +} + +export function createWsServer(options: CreateWsServerOptions) { + const { TAG } = options + const port = pkg.env.PORT_WS + const host = '127.0.0.1' + const wss = new WebSocket.Server({ host, port }) + const wssObj: { wss: WebSocket.Server; instance: WebSocket | null } = { wss, instance: null } + + console.log(TAG, 'Wss run at - ' + chalk.yellow(`ws://${host}:${port}`)) + + wss.on('connection', ws => { + console.log(TAG, chalk.yellow(`wss.on('connection')`)) + + wssObj.instance = ws + ws.on('message', message => { + console.log(TAG, `ws.on('message'):`, message.toString()) + }) + + ws.send(formatWsSendData({ cmd: 'message', data: 'connected.' })) + }) + + wss.on('close', () => { + console.log(TAG, chalk.gray(`wss.on('close')`)) + + wssObj.instance = null + }) + + return wssObj +} + +export function formatWsSendData(json: { cmd: string, data?: any }) { + return JSON.stringify(json) +} diff --git a/src/preload/communication.ts b/src/preload/communication.ts deleted file mode 100644 index 149f35b..0000000 --- a/src/preload/communication.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Renderer and Main bridge - */ -import fs from 'fs' -import { contextBridge, ipcRenderer } from 'electron' - -contextBridge.exposeInMainWorld('fs', fs) - -contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer) diff --git a/src/preload/index.ts b/src/preload/index.ts index 30915cf..05d9c6a 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,5 +1,21 @@ -import './communication' -import { domReady } from './utils/dom' -import { loading } from './loading' +import fs from 'fs' +import { contextBridge, ipcRenderer } from 'electron' +import { domReady, injectWsCode } from './utils' +import { useLoading } from './loading' -domReady().then(loading) \ No newline at end of file +const isDev = process.env.NODE_ENV === 'development' +const { removeLoading, appendLoading } = useLoading() + +domReady().then(() => { + appendLoading() + isDev && injectWsCode({ + host: '127.0.0.1', + port: process.env.PORT_WS as string, // process.env.npm_package_env_PORT_WS + }) +}) + + +// --------- Expose some API to Renderer process. --------- +contextBridge.exposeInMainWorld('fs', fs) +contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer) +contextBridge.exposeInMainWorld('removeLoading', removeLoading) diff --git a/src/preload/loading.ts b/src/preload/loading.ts index e68da3e..4f4dc62 100644 --- a/src/preload/loading.ts +++ b/src/preload/loading.ts @@ -1,4 +1,3 @@ -import { contextBridge } from 'electron' function loadingBootstrap() { const loadingStyle = document.createElement('style') @@ -102,17 +101,18 @@ function loadingBootstrap() { } /** 闪屏 loading */ -export function loading() { +export function useLoading() { let _isCallRemoveLoading = false - const { removeLoading, appendLoading } = loadingBootstrap(); - - contextBridge.exposeInMainWorld('removeLoading', () => { - _isCallRemoveLoading = true - removeLoading() - }) + const { appendLoading, removeLoading } = loadingBootstrap(); // 5 秒超时自动关闭 setTimeout(() => !_isCallRemoveLoading && removeLoading(), 4999) - appendLoading() + return { + appendLoading, + removeLoading() { + _isCallRemoveLoading = true + removeLoading() + }, + } } diff --git a/src/preload/utils.ts b/src/preload/utils.ts new file mode 100644 index 0000000..5405abf --- /dev/null +++ b/src/preload/utils.ts @@ -0,0 +1,49 @@ + +/** docoment ready */ +export function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { + return new Promise(resolve => { + if (condition.includes(document.readyState)) { + resolve(true) + } else { + document.addEventListener('readystatechange', () => { + if (condition.includes(document.readyState)) { + resolve(true) + } + }) + } + }) +} + +/** Inject ws related code */ +export function injectWsCode(options: { + host: string + port: string | number +}) { + const oScript = document.createElement('script') + oScript.id = 'ws-preload-hot-reload' + + oScript.innerHTML = ` +${__ws_hot_reload_for_preload.toString()} +${__ws_hot_reload_for_preload.name}(${JSON.stringify(options)}) +` + + document.body.appendChild(oScript) +} + +function __ws_hot_reload_for_preload(options: { host: string; port: string | number }) { + const ws = new WebSocket(`ws://${options.host}:${options.port}`) + ws.onmessage = function (ev) { + try { + console.log('[preload] ws.onmessage:', ev.data) + + const data = JSON.parse(ev.data) // { "cmd": "string", data: "string|number" } + + if (data.cmd === 'reload') { + setTimeout(() => window.location.reload(), 999) + } + } catch (error) { + console.warn(`ws.onmessage should be accept "JSON.string" formatted string.`) + console.error(error) + } + } +} diff --git a/src/preload/utils/dom.ts b/src/preload/utils/dom.ts deleted file mode 100644 index 6c8e0c7..0000000 --- a/src/preload/utils/dom.ts +++ /dev/null @@ -1,15 +0,0 @@ - -/** docoment 加载完成 */ -export function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { - return new Promise(resolve => { - if (condition.includes(document.readyState)) { - resolve(true) - } else { - document.addEventListener('readystatechange', () => { - if (condition.includes(document.readyState)) { - resolve(true) - } - }) - } - }) -}