Skip to content

Commit

Permalink
feat: worked on cleaner code using various caching levels
Browse files Browse the repository at this point in the history
  • Loading branch information
swernerx committed Feb 3, 2023
1 parent 00cbcf2 commit 614533e
Showing 1 changed file with 87 additions and 8 deletions.
95 changes: 87 additions & 8 deletions src/loader.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { lazy, Suspense } from "react";
import type { ActionFunction, LoaderFunction, RouteObject } from 'react-router-dom'
import { ComponentType, lazy, Suspense } from "react"
import type { ActionFunction, LoaderFunction, RouteObject } from "react-router-dom"

export type ElementFactory = () => JSX.Element

/**
* Defines the exports used from the actual route modules
*/
export type ReactRouterRouteModule = { default: ElementFactory; loader?: LoaderFunction; action?: ActionFunction; RouteError?: ElementFactory }
export type ReactRouterRouteModule = {
default: ElementFactory
loader?: LoaderFunction
action?: ActionFunction
RouteError?: ElementFactory
}

/**
* The import map retrieved from the Vite bundler maps paths to these modules.
Expand All @@ -16,20 +21,94 @@ export type ReactRouterImportMap = Record<string, () => Promise<ReactRouterRoute
/**
* Filter for relevant fields from official RouteObject
*/
export type ReactRouterBaseRouteObject = Pick<RouteObject, "element" | "errorElement" | "loader" | "action">
export type ReactRouterBaseRouteObject = Pick<
RouteObject,
"element" | "errorElement" | "loader" | "action"
>

const loaderCache: Record<string, Promise<ReactRouterRouteModule>> = {}
const moduleCache: Record<string, ReactRouterRouteModule> = {}

type ModuleMember = "default" | "loader" | "action" | "RouteError"

function getMember<T>(fileName: string, memberName: ModuleMember, fallbackName?: ModuleMember): T {
const module = moduleCache[fileName]
if (memberName in module) {
return module[memberName] as T
} else if (fallbackName && fallbackName in module) {
console.log(`Fallback ${memberName} => ${fallbackName}`)
return module[fallbackName] as T
} else {
throw new Error(`Could not resolve: ${memberName} from ${fileName}`)
}
}

/**
* A wrapper around the `import` statements to allow access resolved promises without `await`
* for synchronous access possibilities to module members.
*
* @param imports Wrapper ar
* @param fileName
* @param memberName
* @param fallbackName
* @returns
*/
async function getModuleMember<T>(
imports: ReactRouterImportMap,
fileName: string,
memberName: ModuleMember,
fallbackName?: ModuleMember
) {
let moduleLoader: Promise<ReactRouterRouteModule>
if (fileName in loaderCache) {
moduleLoader = loaderCache[fileName]
} else {
console.log(`Caching ${fileName}...`)
moduleLoader = loaderCache[fileName] = imports[fileName]()
}

const module = await moduleLoader
moduleCache[fileName] = module

return getMember<T>(fileName, memberName, fallbackName)
}

// function getModuleMemberSync(
// fileName: string,
// memberName: ModuleMember = "default",
// fallbackName?: ModuleMember
// ) {
// const module = moduleCache[fileName]
// if (module) {
// return getMember(fileName, memberName, fallbackName)
// }
// }

export function modulesToLazyRouteObjects(imports: ReactRouterImportMap, root: string) {
const routeConfig: Record<string, ReactRouterBaseRouteObject> = {}
for (const fileName in imports) {
const LazyElement = lazy(imports[fileName])
const LazyErrorElement = lazy(() => imports[fileName]().then((module) => ({ default: module.RouteError ?? module.default })))
const LazyElement = lazy(async () => {
return { default: await getModuleMember<ComponentType>(imports, fileName, "default") }
})

const LazyErrorElement = lazy(async () => {
return {
default: await getModuleMember<ComponentType>(imports, fileName, "RouteError", "default")
}
})

const route: ReactRouterBaseRouteObject = {
element: <Suspense fallback={null} children={<LazyElement />} />,
errorElement: <Suspense fallback={null} children={<LazyErrorElement />} />,
loader: async (args) => {
const callback = await imports[fileName]().then((module) => module.loader)
return callback ? callback(args) : null
let callback
try {
callback = await getModuleMember<LoaderFunction>(imports, fileName, "loader")
} catch (ex) {
return null
}

return callback(args)
},
action: async (args) => {
const callback = await imports[fileName]().then((module) => module.action)
Expand Down

0 comments on commit 614533e

Please sign in to comment.