UNPKG

vite-plugin-react-pages

Version:

<p> <a href="https://www.npmjs.com/package/vite-plugin-react-pages" target="_blank" rel="noopener"><img src="https://img.shields.io/npm/v/vite-plugin-react-pages.svg" alt="npm package" /></a> </p>

226 lines (199 loc) 6.87 kB
import { useMemo, useEffect, useState } from 'react' import { dequal } from 'dequal' import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { atomFamily } from 'jotai/utils' import type { PageLoaded, UseStaticData, Theme, UseAllPagesOutlines, } from '../../clientTypes' export let useTheme: () => Theme export let usePagePaths: () => string[] export let usePageModule: (path: string) => Promise<PageModule> | undefined export let useStaticData: UseStaticData export let useAllPagesOutlines: UseAllPagesOutlines interface PageModule { ['default']: PageLoaded } type SetAtom<Args extends any[], Result> = (...args: Args) => Result import initialPages from '/@react-pages/pages' import initialTheme from '/@react-pages/theme' // TODO: simplify this // there is no easy way to handle the hmr of module such as `/@react-pages/pages/page1` so stop trying it // https://github.com/vitejs/vite-plugin-react-pages/pull/19#discussion_r604251258 const initialPagePaths = Object.keys(initialPages) // This HMR code assumes that our Jotai atoms are always managed // by the same Provider. It also mutates during render, which is // generally discouraged, but in this case it's okay. if (import.meta.hot) { let setTheme: SetAtom<[{ Theme: Theme }], void> | undefined import.meta.hot!.accept('/@react-pages/theme', (module) => { // console.log('@@hot update /@react-pages/theme', module) if (!module) { console.error('unexpected hot module', module) return } setTheme?.({ Theme: module.default }) }) const themeAtom = atom({ Theme: initialTheme }) useTheme = () => { const [{ Theme }, set] = useAtom(themeAtom) setTheme = set return Theme } let setPages: SetAtom<any, void> | undefined import.meta.hot!.accept('/@react-pages/pages', (module) => { // console.log('@@hot update /@react-pages/pages', module) if (!module) { console.error('unexpected hot module', module) return } setPages?.(module.default) }) // let setAllPagesOutlines: SetAtom<any, void> | undefined // import.meta.hot!.accept('/@react-pages/allPagesOutlines', (module) => { // // console.log('@@hot update /@react-pages/allPagesOutlines', module) // if (!module) { // console.error('unexpected hot module', module) // return // } // setAllPagesOutlines?.(module) // }) const pagesAtom = atom(initialPages) const pagePathsAtom = atom(initialPagePaths.sort()) const staticDataAtom = atom(toStaticData(initialPages)) const allPagesOutlinesAtom = atom(initialPages) const setPagesAtom = atom(null, (get, set, newPages: any) => { let newStaticData: Record<string, any> | undefined const pages = get(pagesAtom) for (const path in newPages) { const newPage = newPages[path] const page = pages[path] // Avoid changing the identity of `page.staticData` unless // a change is detected. This prevents unnecessary renders // of components that depend on `useStaticData(path)` call. if (page && dequal(page.staticData, newPage.staticData)) { newPage.staticData = page.staticData } else { newStaticData ??= {} newStaticData[path] = newPage.staticData } } // detect deleted pages for (const path in pages) { if (!newPages[path]) { newStaticData ??= {} newStaticData[path] = undefined } } // Update the `pagesAtom` every time, since no hook uses it directly. set(pagesAtom, newPages) // Avoid re-rendering `useStaticData()` callers if no data changed. if (newStaticData) { newStaticData = { ...get(staticDataAtom), ...newStaticData, } // filter out deleted paths newStaticData = Object.fromEntries( Object.entries(newStaticData).filter(([k, v]) => v !== undefined) ) set(staticDataAtom, newStaticData) } // Avoid re-rendering `usePagePaths()` callers if no paths were added/deleted. const newPagePaths = Object.keys(newPages).sort() if (!dequal(get(pagePathsAtom), newPagePaths)) { set(pagePathsAtom, newPagePaths) } }) const dataAtoms = atomFamily((path: string) => atom((get) => { const pages = get(pagesAtom) return pages[path] }) ) const staticDataAtoms = atomFamily((path: string) => atom((get) => { const pages = get(pagesAtom) const page = pages[path] return page?.staticData }) ) usePagePaths = () => { setPages = useSetAtom(setPagesAtom) return useAtomValue(pagePathsAtom) } usePageModule = (pagePath) => { const data = useAtomValue(dataAtoms(pagePath)) return useMemo(() => data?.data(), [data]) } useStaticData = (pagePath?: string, selector?: Function) => { const staticData = pagePath ? staticDataAtoms(pagePath) : staticDataAtom if (selector) { const selection = useMemo( () => atom((get) => selector(get(staticData))), [staticData] ) return useAtomValue(selection) } return useAtomValue(staticData) } useAllPagesOutlines = (timeout: number) => { const [data, set] = useAtom(allPagesOutlinesAtom) // setAllPagesOutlines = set useEffect(() => { setTimeout(() => { import('/@react-pages/allPagesOutlines').then((mod) => { set(mod) }) }, timeout) }, []) return data } } // Static mode else { useTheme = () => initialTheme usePagePaths = () => initialPagePaths usePageModule = (path) => { const page = initialPages[path] return useMemo(() => page?.data(), [page]) } useStaticData = (path?: string, selector?: Function) => { if (path) { const page = initialPages[path] const staticData = page?.staticData || {} return selector ? selector(staticData) : staticData } return toStaticData(initialPages) } useAllPagesOutlines = (timeout: number) => { const [data, set] = useState<any>() useEffect(() => { setTimeout(() => { import('/@react-pages/allPagesOutlines').then((mod) => { set(mod) }) }, timeout) }, []) return data } } function toStaticData(pages: Record<string, any>) { const staticData: Record<string, any> = {} for (const path in pages) { staticData[path] = pages[path].staticData } return staticData } if ((globalThis as any)['__vite_pages_use_static_data']) { throw new Error( `[vite-pages] global hooks (.e.g useStaticData) already exists on window. It means there are multiple vite-pages runtime in this page. Please report this to vite-pages.` ) } else { // make them available in vite-plugin-react-pages/client ;(globalThis as any)['__vite_pages_use_static_data'] = useStaticData ;(globalThis as any)['__vite_pages_use_all_pages_outlines'] = useAllPagesOutlines }