one
Version:
One is a new React Framework that makes Vite serve both native and web.
184 lines (167 loc) • 7.09 kB
JavaScript
// main dev entry - imports all dev scripts
// this file is served as /@one/dev.js by the devtools plugin
import { createHotContext } from '/@vite/client'
import { injectIntoGlobalHook } from '/@react-refresh'
// route HMR
const routeHot = createHotContext('/__one_route_hmr')
routeHot.on('one:route-update', (data) => {
if (window.__oneRouteCache) {
if (data?.file) {
window.__oneRouteCache.clearFile(data.file)
} else {
window.__oneRouteCache.clear()
}
}
window.dispatchEvent(new CustomEvent('one-hmr-update'))
})
// route load errors. the router catches a failed route import and substitutes an
// empty component so one bad route cannot take down the whole app. in dev that
// resilience hides the cause: a broken import surfaced as a single console line
// while the app rendered nothing, and every provider mounted inside that route
// (sync, auth, theme) silently never started. report it to the dev server for a
// terminal line, and paint it here so the blank screen explains itself.
const routeErrorHot = createHotContext('/__one_route_error')
const ROUTE_ERROR_TAG = 'one-route-error-overlay'
function showRouteErrorOverlay(detail) {
document.querySelector(ROUTE_ERROR_TAG)?.remove()
const host = document.createElement(ROUTE_ERROR_TAG)
host.style.cssText = 'position:fixed;inset:0;z-index:2147483647'
const shadow = host.attachShadow({ mode: 'open' })
shadow.innerHTML = `
<style>
.backdrop {
position: fixed; inset: 0; overflow: auto;
background: rgba(0,0,0,.66); backdrop-filter: blur(2px);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.panel {
margin: 8vh auto; max-width: 900px; width: calc(100% - 48px);
background: #18181b; color: #fafafa; border: 1px solid #f87171;
border-radius: 10px; padding: 22px 24px; box-shadow: 0 24px 60px rgba(0,0,0,.5);
}
h1 { margin: 0 0 4px; font-size: 15px; color: #f87171; letter-spacing: .01em; }
.id { margin: 0 0 14px; font-size: 13px; color: #a1a1aa; word-break: break-all; }
.msg {
margin: 0 0 14px; padding: 12px 14px; background: #0c0c0e; border-radius: 6px;
font-size: 13px; line-height: 1.55; white-space: pre-wrap; word-break: break-word;
}
.why { margin: 0 0 16px; font-size: 12.5px; line-height: 1.6; color: #d4d4d8; }
pre {
margin: 0; padding: 12px 14px; background: #0c0c0e; border-radius: 6px;
font-size: 11.5px; line-height: 1.5; color: #a1a1aa;
white-space: pre-wrap; word-break: break-word; max-height: 34vh; overflow: auto;
}
button {
position: absolute; top: 14px; right: 16px; background: none; border: 0;
color: #a1a1aa; font-size: 20px; line-height: 1; cursor: pointer; padding: 4px 8px;
}
button:hover { color: #fafafa; }
.wrap { position: relative; }
</style>
<div class="backdrop">
<div class="panel wrap">
<button title="dismiss">×</button>
<h1>Route failed to load</h1>
<p class="id"></p>
<p class="msg"></p>
<p class="why">
The router rendered an empty component in its place, so nothing inside this
route mounted — including any providers it wraps. Fix the import above and
this overlay will clear on reload.
</p>
<pre></pre>
</div>
</div>
`
shadow.querySelector('.id').textContent = detail?.id || 'unknown route'
shadow.querySelector('.msg').textContent = detail?.message || 'unknown error'
const stack = shadow.querySelector('pre')
if (detail?.stack) stack.textContent = detail.stack
else stack.remove()
// dismiss clears the stored copy too, or the next reload repaints it
shadow.querySelector('button').addEventListener('click', clearRouteErrorOverlay)
document.documentElement.appendChild(host)
// a failed route makes the client render null where the server rendered the
// real route, so react hits a hydration mismatch and recovers by re-rendering
// from scratch. that recovery calls clearContainerSparingly on the root
// container, which here is <html>, so it strips this overlay out about a
// second after it appears — the reason the error stayed invisible. put it back
// once react is done. bounded and short-lived so it can never fight the app.
let readds = 0
const keepAlive = new MutationObserver(() => {
if (host.isConnected || readds >= 3) return
readds++
document.documentElement.appendChild(host)
})
keepAlive.observe(document.documentElement, { childList: true })
setTimeout(() => keepAlive.disconnect(), 10000)
}
// a failed route makes the client render null where the server rendered the real
// route, so react reports a hydration mismatch and the page full-reloads. that
// reload wipes the overlay within a second of it appearing, and the reloaded page
// resolves the route from cache without failing again, so nothing repaints it.
// hold the error for the tab and repaint after the reload, the way vite replays
// server errors to a reconnecting client. cleared by any successful hot update.
const ROUTE_ERROR_KEY = 'one:last-route-error'
function clearRouteErrorOverlay() {
try {
sessionStorage.removeItem(ROUTE_ERROR_KEY)
} catch {}
document.querySelector(ROUTE_ERROR_TAG)?.remove()
}
function paintRouteError(detail) {
try {
showRouteErrorOverlay(detail)
} catch (err) {
console.error('[one] could not render the route error overlay:', err)
}
}
window.__oneReportRouteLoadError = (detail) => {
routeErrorHot.send('one:route-error', detail)
try {
sessionStorage.setItem(ROUTE_ERROR_KEY, JSON.stringify(detail))
} catch {}
paintRouteError(detail)
}
routeErrorHot.on('vite:afterUpdate', clearRouteErrorOverlay)
try {
const stored = sessionStorage.getItem(ROUTE_ERROR_KEY)
if (stored) {
if (document.body) paintRouteError(JSON.parse(stored))
else {
document.addEventListener(
'DOMContentLoaded',
() => paintRouteError(JSON.parse(stored)),
{ once: true }
)
}
}
} catch {}
// loader HMR
const loaderHot = createHotContext('/__one_loader_hmr')
loaderHot.on('one:loader-data-update', async (data) => {
if (data?.routePaths && window.__oneRefetchLoader) {
const currentPath = window.location.pathname.replace(/\/$/, '') || '/'
for (const routePath of data.routePaths) {
if (routePath === currentPath) {
try {
await window.__oneRefetchLoader(routePath)
} catch (err) {
console.error('[one] Error refetching loader:', err)
}
}
}
}
})
// SSR CSS cleanup - remove SSR CSS on first HMR update so individual styles win
const ssrCssHot = createHotContext('/__clear_ssr_css')
ssrCssHot.on('vite:beforeUpdate', () => {
document.querySelectorAll('[data-ssr-css]').forEach((node) => node.remove())
})
// react refresh
injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
// devtools UI
import './devtools.mjs'
import './source-inspector.mjs'