-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: code split & dynamic imports #188
Changes from 13 commits
2d76e28
0b5e91c
6af1cc5
4150a91
c858eb0
1cd5ade
d119363
4965f57
9447215
f33286b
8b2875d
20bad73
ac41343
2998fb7
8a5e599
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,8 +8,8 @@ export const ConfigContext = createContext({ | |
setConfigExpanded: (value: boolean) => {} | ||
}) | ||
|
||
export const ConfigProvider = ({ children, expanded = isLoadedInIframe }: { children: JSX.Element[] | JSX.Element, expanded?: boolean }): JSX.Element => { | ||
const [isConfigExpanded, setConfigExpanded] = useState(expanded) | ||
export const ConfigProvider = ({ children }: { children: JSX.Element[] | JSX.Element, expanded?: boolean }): JSX.Element => { | ||
const [isConfigExpanded, setConfigExpanded] = useState(isConfigPage(window.location.hash)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could be updated to loaded dynamically but I don't think it would give us much for the effort |
||
const isExplicitlyLoadedConfigPage = isConfigPage(window.location.hash) | ||
|
||
const setConfigExpandedWrapped = (value: boolean): void => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import React, { useCallback, useEffect } from 'react' | ||
|
||
export interface Route { | ||
default?: boolean | ||
path?: string | ||
shouldRender?(): Promise<boolean> | ||
component: React.LazyExoticComponent<(...args: any[]) => React.JSX.Element | null> | ||
} | ||
|
||
export const RouteContext = React.createContext<{ | ||
// routes: Route[] | ||
currentRoute: Route | undefined | ||
gotoPage(route?: string): void | ||
}>({ currentRoute: undefined, gotoPage: () => {} }) | ||
|
||
export const RouterProvider = ({ children, routes }: { children: React.ReactNode, routes: Route[] }): JSX.Element => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. very basic hash-router implementation. I looked around for some and most were 200kb and unmaintained, or very large. All required some intimate knowledge or specific finagling for things we needed. lmk if there's something I missed. |
||
const [currentRoute, setCurrentRoute] = React.useState<Route | undefined>(undefined) | ||
/** | ||
* The default route is the first route in the list of routes, | ||
* or the first one with the `default` property set to `true` | ||
*/ | ||
const defaultRoute = routes.find(route => route.default) ?? routes[0] | ||
|
||
const findRouteByPath = useCallback((path: string): Route | undefined => { | ||
const result = routes.find(route => route.path === path) | ||
return result | ||
}, [routes]) | ||
|
||
const findRouteByRenderFn = useCallback(async (): Promise<Route | undefined> => { | ||
const validRoutes: Route[] = [] | ||
for (const route of routes) { | ||
if (route.shouldRender == null) { | ||
continue | ||
} | ||
const renderFuncResult = await route.shouldRender() | ||
|
||
if (renderFuncResult) { | ||
validRoutes.push(route) | ||
} | ||
} | ||
return validRoutes[0] ?? undefined | ||
}, [routes]) | ||
|
||
const setDerivedRoute = useCallback(async (hash: string): Promise<void> => { | ||
setCurrentRoute(findRouteByPath(hash) ?? await findRouteByRenderFn() ?? defaultRoute) | ||
}, [findRouteByPath, findRouteByRenderFn, defaultRoute]) | ||
|
||
const onHashChange = useCallback((event: HashChangeEvent) => { | ||
const newUrl = new URL(event.newURL) | ||
void setDerivedRoute(newUrl.hash) | ||
}, [setDerivedRoute]) | ||
|
||
const onPopState = useCallback((event: PopStateEvent) => { | ||
void setDerivedRoute(window.location.hash) | ||
}, [setDerivedRoute]) | ||
|
||
useEffect(() => { | ||
void setDerivedRoute(window.location.hash) | ||
}, [setDerivedRoute]) | ||
|
||
useEffect(() => { | ||
window.addEventListener('popstate', onPopState, false) | ||
window.addEventListener('hashchange', onHashChange, false) | ||
|
||
return () => { | ||
window.removeEventListener('popstate', onPopState, false) | ||
window.removeEventListener('hashchange', onHashChange, false) | ||
} | ||
}, [onPopState, onHashChange]) | ||
|
||
return ( | ||
<RouteContext.Provider | ||
value={{ | ||
currentRoute, | ||
gotoPage: (page?: string) => { | ||
if (page == null) { | ||
// clear out the hash | ||
window.history.pushState('', document.title, `${window.location.pathname}${window.location.search}`) | ||
void setDerivedRoute('') | ||
} else { | ||
window.location.hash = `#${page}` | ||
} | ||
} | ||
}} | ||
> | ||
{children} | ||
</RouteContext.Provider> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export async function shouldRenderRedirectPage (): Promise<boolean> { | ||
const { isConfigPage } = await import('../lib/is-config-page.js') | ||
const { isPathOrSubdomainRequest } = await import('./path-or-subdomain.js') | ||
const isRequestToViewConfigPage = isConfigPage(window.location.hash) | ||
const shouldRequestBeHandledByServiceWorker = isPathOrSubdomainRequest(window.location) && !isRequestToViewConfigPage | ||
const isTopLevelWindow = window.self === window.top | ||
const isRequestToViewConfigPageAndTopLevelWindow = isRequestToViewConfigPage && isTopLevelWindow | ||
const result = shouldRequestBeHandledByServiceWorker && !isRequestToViewConfigPageAndTopLevelWindow | ||
return result | ||
} | ||
|
||
export async function shouldRenderConfigPage (): Promise<boolean> { | ||
const { isConfigPage } = await import('../lib/is-config-page.js') | ||
|
||
const isRequestToViewConfigPage = isConfigPage(window.location.hash) | ||
return isRequestToViewConfigPage | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
import React, { useState, useEffect } from 'react' | ||
import CidRenderer from './components/CidRenderer.jsx' | ||
import Form from './components/Form.jsx' | ||
import Header from './components/Header.jsx' | ||
import { LOCAL_STORAGE_KEYS } from './lib/local-storage.js' | ||
import Form from '../components/Form.jsx' | ||
import Header from '../components/Header.jsx' | ||
Comment on lines
+2
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: rename these to snake-case, |
||
import CidRenderer from '../components/input-validator.jsx' | ||
import { LOCAL_STORAGE_KEYS } from '../lib/local-storage.js' | ||
|
||
export default function (): JSX.Element { | ||
const [requestPath, setRequestPath] = useState(localStorage.getItem(LOCAL_STORAGE_KEYS.forms.requestPath) ?? '') | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could avoid any potential "not prod ready" issues with preact's
Suspense
by doing a simple useEffect wrapper of the async operation