diff --git a/packages/react/src/router/client.js b/packages/react/src/router/client.js index 095ea97..bd7e2a9 100644 --- a/packages/react/src/router/client.js +++ b/packages/react/src/router/client.js @@ -4,6 +4,8 @@ import { RouterProvider, } from 'react-router-dom'; +import {performReactRefresh} from 'react-refresh/runtime'; + export const hooks = { '@flecks/core.hmr.hook': (hook, fleck, flecks) => { if ('@flecks/react/router.routes' === hook) { @@ -48,17 +50,23 @@ export const mixin = (Flecks) => class FlecksWithReactRouterClient extends Fleck constructor(runtime) { super(runtime); const flecks = this; + let debounceRefresh; this.reactRouter = { invalidate() { - const {root} = flecks.get('@flecks/react/router'); + if (debounceRefresh) { + return; + } // Sorry. - setTimeout(() => { + debounceRefresh = setTimeout(() => { + const {root} = flecks.get('@flecks/react/router'); Promise.resolve(flecks.invokeFleck('@flecks/react/router.routes', root)) .then((routes) => { // eslint-disable-next-line no-underscore-dangle this.router._internalSetRoutes(routes); + performReactRefresh(); + debounceRefresh = undefined; }); - }, 20); + }, 10); }, router: undefined, }; diff --git a/packages/react/src/router/filetree-router.js b/packages/react/src/router/filetree-router.js index 9919691..a711b4d 100644 --- a/packages/react/src/router/filetree-router.js +++ b/packages/react/src/router/filetree-router.js @@ -1,5 +1,7 @@ import {resolve} from 'path'; +import {register} from 'react-refresh/runtime'; + function filePathToSegmentPath(path) { let localPath = path; let suffix = ''; @@ -34,16 +36,24 @@ export async function createRoutesFromFiletree({importer, paths, resolver}) { // Build initial router object. Object.entries(moduleMap) .map(([, paths]) => ( - // Second-longest: path without extension. paths .sort((l, r) => (l.length < r.length ? -1 : 1)) - .slice(0, -1) .pop() )) .sort((l, r) => (l < r ? -1 : 1)) - .forEach((path) => { + .map((path) => { + let trimmed = path; + // @todo dynamic + const extensions = ['.jsx', '.js', '.tsx', '.ts', '.mdx']; + const extension = extensions.find((ext) => path.endsWith(ext)); + if (extension) { + trimmed = path.slice(0, -extension.length); + } + return [path, trimmed]; + }) + .forEach(([path, trimmed]) => { let walk = children; - const parts = ['/', ...resolve('/', path).split('/').slice(1)]; + const parts = ['/', ...resolve('/', trimmed).split('/').slice(1)]; const segments = parts.map(filePathToSegmentPath); for (let i = 0; i < segments.length; ++i) { const segment = segments[i]; @@ -90,6 +100,10 @@ export async function createRoutesFromFiletree({importer, paths, resolver}) { await Promise.all( elements.map(async ([children, node, path]) => { const route = await importer(path); + // React Refresh has trouble inferring component types when modules are structured as React + // Router prefers (e.g. `const export index = true` not being a component export). We'll give + // it a helping hand. + register(route, `${path} %exports%`); let {hoist} = route; if (hoist > 0) { const parts = node.path.split('/'); @@ -108,6 +122,11 @@ export async function createRoutesFromFiletree({importer, paths, resolver}) { case 'children': // ??? return; + case 'default': + if (!route.Component) { + node.Component = value; + } + return; case 'hoist': return; case 'index':