/* eslint-env browser */ import { createProxy, hasFatalError } from './proxy.js' const logPrefix = '[HMR:Svelte]' // eslint-disable-next-line no-console const log = (...args) => console.log(logPrefix, ...args) const domReload = () => { // eslint-disable-next-line no-undef const win = typeof window !== 'undefined' && window if (win && win.location && win.location.reload) { log('Reload') win.location.reload() } else { log('Full reload required') } } const replaceCss = (previousId, newId) => { if (typeof document === 'undefined') return false if (!previousId) return false if (!newId) return false // svelte-xxx-style => svelte-xxx const previousClass = previousId.slice(0, -6) const newClass = newId.slice(0, -6) // eslint-disable-next-line no-undef document.querySelectorAll('.' + previousClass).forEach(el => { el.classList.remove(previousClass) el.classList.add(newClass) }) return true } const removeStylesheet = cssId => { if (cssId == null) return if (typeof document === 'undefined') return // eslint-disable-next-line no-undef const el = document.getElementById(cssId) if (el) el.remove() return } const defaultArgs = { reload: domReload, } export const makeApplyHmr = transformArgs => args => { const allArgs = transformArgs({ ...defaultArgs, ...args }) return applyHmr(allArgs) } let needsReload = false function applyHmr(args) { const { id, cssId, nonCssHash, reload = domReload, // normalized hot API (must conform to rollup-plugin-hot) hot, hotOptions, Component, acceptable, // some types of components are impossible to HMR correctly preserveLocalState, ProxyAdapter, emitCss, } = args const existing = hot.data && hot.data.record const canAccept = acceptable && (!existing || existing.current.canAccept) const r = existing || createProxy({ Adapter: ProxyAdapter, id, Component, hotOptions, canAccept, preserveLocalState, }) const cssOnly = hotOptions.injectCss && existing && nonCssHash && existing.current.nonCssHash === nonCssHash r.update({ Component, hotOptions, canAccept, nonCssHash, cssId, previousCssId: r.current.cssId, cssOnly, preserveLocalState, }) hot.dispose(data => { // handle previous fatal errors if (needsReload || hasFatalError()) { if (hotOptions && hotOptions.noReload) { log('Full reload required') } else { reload() } } // 2020-09-21 Snowpack master doesn't pass data as arg to dispose handler data = data || hot.data data.record = r if (!emitCss && cssId && r.current.cssId !== cssId) { if (hotOptions.cssEjectDelay) { setTimeout(() => removeStylesheet(cssId), hotOptions.cssEjectDelay) } else { removeStylesheet(cssId) } } }) if (canAccept) { hot.accept(async arg => { const { bubbled } = arg || {} // NOTE Snowpack registers accept handlers only once, so we can NOT rely // on the surrounding scope variables -- they're not the last version! const { cssId: newCssId, previousCssId } = r.current const cssChanged = newCssId !== previousCssId // ensure old style sheet has been removed by now if (!emitCss && cssChanged) removeStylesheet(previousCssId) // guard: css only change if ( // NOTE bubbled is provided only by rollup-plugin-hot, and we // can't safely assume a CSS only change without it... this means we // can't support CSS only injection with Nollup or Webpack currently bubbled === false && // WARNING check false, not falsy! r.current.cssOnly && (!cssChanged || replaceCss(previousCssId, newCssId)) ) { return } const success = await r.reload() if (hasFatalError() || (!success && !hotOptions.optimistic)) { needsReload = true } }) } // well, endgame... we won't be able to render next updates, even successful, // if we don't have proxies in svelte's tree // // since we won't return the proxy and the app will expect a svelte component, // it's gonna crash... so it's best to report the real cause // // full reload required // const proxyOk = r && r.proxy if (!proxyOk) { throw new Error(`Failed to create HMR proxy for Svelte component ${id}`) } return r.proxy }