fix: React Refresh has trouble inferring router exports

This commit is contained in:
cha0s 2024-02-18 23:10:28 -06:00
parent b9dfea12d5
commit 7113d4db53
2 changed files with 34 additions and 7 deletions

View File

@ -4,6 +4,8 @@ import {
RouterProvider, RouterProvider,
} from 'react-router-dom'; } from 'react-router-dom';
import {performReactRefresh} from 'react-refresh/runtime';
export const hooks = { export const hooks = {
'@flecks/core.hmr.hook': (hook, fleck, flecks) => { '@flecks/core.hmr.hook': (hook, fleck, flecks) => {
if ('@flecks/react/router.routes' === hook) { if ('@flecks/react/router.routes' === hook) {
@ -48,17 +50,23 @@ export const mixin = (Flecks) => class FlecksWithReactRouterClient extends Fleck
constructor(runtime) { constructor(runtime) {
super(runtime); super(runtime);
const flecks = this; const flecks = this;
let debounceRefresh;
this.reactRouter = { this.reactRouter = {
invalidate() { invalidate() {
const {root} = flecks.get('@flecks/react/router'); if (debounceRefresh) {
return;
}
// Sorry. // Sorry.
setTimeout(() => { debounceRefresh = setTimeout(() => {
const {root} = flecks.get('@flecks/react/router');
Promise.resolve(flecks.invokeFleck('@flecks/react/router.routes', root)) Promise.resolve(flecks.invokeFleck('@flecks/react/router.routes', root))
.then((routes) => { .then((routes) => {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
this.router._internalSetRoutes(routes); this.router._internalSetRoutes(routes);
performReactRefresh();
debounceRefresh = undefined;
}); });
}, 20); }, 10);
}, },
router: undefined, router: undefined,
}; };

View File

@ -1,5 +1,7 @@
import {resolve} from 'path'; import {resolve} from 'path';
import {register} from 'react-refresh/runtime';
function filePathToSegmentPath(path) { function filePathToSegmentPath(path) {
let localPath = path; let localPath = path;
let suffix = ''; let suffix = '';
@ -34,16 +36,24 @@ export async function createRoutesFromFiletree({importer, paths, resolver}) {
// Build initial router object. // Build initial router object.
Object.entries(moduleMap) Object.entries(moduleMap)
.map(([, paths]) => ( .map(([, paths]) => (
// Second-longest: path without extension.
paths paths
.sort((l, r) => (l.length < r.length ? -1 : 1)) .sort((l, r) => (l.length < r.length ? -1 : 1))
.slice(0, -1)
.pop() .pop()
)) ))
.sort((l, r) => (l < r ? -1 : 1)) .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; let walk = children;
const parts = ['/', ...resolve('/', path).split('/').slice(1)]; const parts = ['/', ...resolve('/', trimmed).split('/').slice(1)];
const segments = parts.map(filePathToSegmentPath); const segments = parts.map(filePathToSegmentPath);
for (let i = 0; i < segments.length; ++i) { for (let i = 0; i < segments.length; ++i) {
const segment = segments[i]; const segment = segments[i];
@ -90,6 +100,10 @@ export async function createRoutesFromFiletree({importer, paths, resolver}) {
await Promise.all( await Promise.all(
elements.map(async ([children, node, path]) => { elements.map(async ([children, node, path]) => {
const route = await importer(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; let {hoist} = route;
if (hoist > 0) { if (hoist > 0) {
const parts = node.path.split('/'); const parts = node.path.split('/');
@ -108,6 +122,11 @@ export async function createRoutesFromFiletree({importer, paths, resolver}) {
case 'children': case 'children':
// ??? // ???
return; return;
case 'default':
if (!route.Component) {
node.Component = value;
}
return;
case 'hoist': case 'hoist':
return; return;
case 'index': case 'index':