feat: support preload script hot reload

This commit is contained in:
草鞋没号 2021-10-25 20:45:37 +08:00
parent d05338ca1b
commit a4ca740c99
7 changed files with 130 additions and 40 deletions

View File

@ -2,7 +2,9 @@ import { watch, rollup, OutputOptions } from 'rollup'
import minimist from 'minimist' import minimist from 'minimist'
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora from 'ora'
import WebSocket from 'ws'
import options from './rollup.config' import options from './rollup.config'
import { createWsServer, formatWsSendData } from './ws'
const argv = minimist(process.argv.slice(2)) const argv = minimist(process.argv.slice(2))
const opt = options({ proc: 'preload', env: argv.env }) const opt = options({ proc: 'preload', env: argv.env })
@ -12,13 +14,17 @@ const spinner = ora(`${TAG} Electron preload build...`)
; (async () => { ; (async () => {
if (argv.watch) { if (argv.watch) {
const watcher = watch(opt) const watcher = watch(opt)
const wssObj = createWsServer({ TAG })
watcher.on('change', filename => { watcher.on('change', filename => {
const log = chalk.yellow(`change -- ${filename}`) const log = chalk.yellow(`change -- ${filename}`)
console.log(TAG, log) console.log(TAG, log)
/** // Hot reload renderer process !!!
* @todo Hot reload render process !!! if (wssObj.instance?.readyState === WebSocket.OPEN) {
*/ console.log(TAG, 'Hot reload renderer process')
wssObj.instance.send(formatWsSendData({ cmd: 'reload', data: Date.now() }))
}
}) })
} else { } else {
spinner.start() spinner.start()

43
script/ws.ts Normal file
View File

@ -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)
}

View File

@ -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)

View File

@ -1,5 +1,21 @@
import './communication' import fs from 'fs'
import { domReady } from './utils/dom' import { contextBridge, ipcRenderer } from 'electron'
import { loading } from './loading' import { domReady, injectWsCode } from './utils'
import { useLoading } from './loading'
domReady().then(loading) 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)

View File

@ -1,4 +1,3 @@
import { contextBridge } from 'electron'
function loadingBootstrap() { function loadingBootstrap() {
const loadingStyle = document.createElement('style') const loadingStyle = document.createElement('style')
@ -102,17 +101,18 @@ function loadingBootstrap() {
} }
/** 闪屏 loading */ /** 闪屏 loading */
export function loading() { export function useLoading() {
let _isCallRemoveLoading = false let _isCallRemoveLoading = false
const { removeLoading, appendLoading } = loadingBootstrap(); const { appendLoading, removeLoading } = loadingBootstrap();
contextBridge.exposeInMainWorld('removeLoading', () => {
_isCallRemoveLoading = true
removeLoading()
})
// 5 秒超时自动关闭 // 5 秒超时自动关闭
setTimeout(() => !_isCallRemoveLoading && removeLoading(), 4999) setTimeout(() => !_isCallRemoveLoading && removeLoading(), 4999)
appendLoading() return {
appendLoading,
removeLoading() {
_isCallRemoveLoading = true
removeLoading()
},
}
} }

49
src/preload/utils.ts Normal file
View File

@ -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)
}
}
}

View File

@ -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)
}
})
}
})
}