UNPKG

@roots/bud-client

Version:

Client scripts for @roots/bud

160 lines (135 loc) 3.94 kB
/* eslint-disable no-console */ /* global __resourceQuery */ /* global __webpack_hash__ */ import * as components from './components/index.js' import {injectEvents} from './events.js' import {makeLogger} from './log.js' import * as clientOptions from './options.js' /** * Initializes bud.js HMR handling */ export const client = async ( queryString: string, webpackHot: __WebpackModuleApi.Hot, ) => { /* Guard: EventSource browser support */ if (typeof window?.EventSource === `undefined`) { console.error( `[bud] hot module reload requires EventSource to work. https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events#Tools`, ) return false } /* Guard: webpackHot api availability */ if (!webpackHot) { console.error( `[bud] hot module reload requires the webpack hot api to be available`, ) return false } /* Set client options from URL params */ const options = clientOptions.setFromParameters(queryString) /* Setup logger */ const logger = makeLogger(options) if (typeof window.bud === `undefined`) { window.bud = { controllers: [], current: {}, hmr: {}, listeners: {}, reload: () => { window.location.reload() }, } } if (!window.bud.current[options.name]) { window.bud.current[options.name] = null } const isStale = (hash?: string) => { if (hash) window.bud.current[options.name] = hash return __webpack_hash__ === window.bud.current[options.name] } /** * Webpack HMR check handler */ const check = async () => { if (webpackHot.status() === `idle`) { await webpackHot.check(false) requestAnimationFrame(async function whenReady() { if (webpackHot.status() === `ready`) { await update() } else { requestAnimationFrame(whenReady) } }) } } /** * Webpack HMR unaccepted module handler */ const onUnacceptedOrDeclined = ( info: __WebpackModuleApi.HotNotifierInfo, ) => { console.warn(`[${options.name}] ${info.type}`, info) options.reload && window.location.reload() } const onAccepted = (info: __WebpackModuleApi.HotNotifierInfo) => { window.bud.controllers.map(controller => controller?.update({action: `sync`, errors: []}), ) } /** * Webpack HMR error handler */ const onErrored = (error: any) => { window.bud.controllers.map(controller => controller?.update({errors: [error]}), ) } /** * Webpack HMR update handler */ const update = async () => { try { await webpackHot.apply({ ignoreDeclined: true, ignoreErrored: true, ignoreUnaccepted: true, onAccepted, onDeclined: onUnacceptedOrDeclined, onErrored, onUnaccepted: onUnacceptedOrDeclined, }) if (!isStale()) await check() } catch (error) { logger.error(error) } } /* Instantiate indicator, overlay */ try { await components.make(options) } catch (error) {} /* Instantiate eventSource */ const events = injectEvents(EventSource).make(options) if (!window.bud.listeners?.[options.name]) { window.bud.listeners[options.name] = async payload => { if (!payload) return if (options.reload && payload.action === `reload`) return window.bud.reload() if (payload.name !== options.name) return window.bud.controllers.map(controller => controller?.update(payload)) if (payload.errors?.length) return if (payload.action === `built` || payload.action === `sync`) { if (isStale(payload.hash)) return if (payload.action === `built`) { logger.log(`built in ${payload.time}ms`) } await check() } } /* * Instantiate HMR event source * and register client listeners */ events.addListener(window.bud.listeners[options.name]) } }