From e8fa858eb659d37fc194ef85a3632b57a72deea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rialland?= Date: Tue, 5 Sep 2023 16:48:08 +0200 Subject: [PATCH] prerender --- ...rerender-worker.js => prerender-worker.ts} | 32 ++- site/build/prerender.ts | 76 ++++---- site/source/components/App.tsx | 182 ++++++++++-------- site/source/components/Provider.tsx | 98 +++++----- site/source/components/RuleLink.tsx | 23 ++- .../conversation/useNavigateQuestions.ts | 21 +- site/source/components/ui/animate.tsx | 6 +- site/source/components/utils/colors.tsx | 37 ++-- site/source/entries/entry-server.tsx | 71 +++++-- site/source/entries/template.html | 3 + site/source/hooks/usePromise.ts | 2 + site/source/pages/Documentation.tsx | 1 + .../assistants/demande-mobilité/EndBlock.tsx | 4 + site/source/store/actions/actions.ts | 3 +- site/tsconfig.json | 7 +- 15 files changed, 347 insertions(+), 219 deletions(-) rename site/build/{prerender-worker.js => prerender-worker.ts} (66%) diff --git a/site/build/prerender-worker.js b/site/build/prerender-worker.ts similarity index 66% rename from site/build/prerender-worker.js rename to site/build/prerender-worker.ts index ab799a908..87cf417b8 100644 --- a/site/build/prerender-worker.js +++ b/site/build/prerender-worker.ts @@ -6,7 +6,7 @@ import { render } from '../dist/ssr/entry-server.js' const dirname = path.dirname(fileURLToPath(import.meta.url)) -const cache = {} +const cache: { [k: string]: string } = {} const htmlBodyStart = '' const htmlBodyEnd = '' @@ -16,15 +16,35 @@ const headTagsEnd = '' const regexHTML = new RegExp(htmlBodyStart + '[\\s\\S]+' + htmlBodyEnd, 'm') const regexHelmet = new RegExp(headTagsStart + '[\\s\\S]+' + headTagsEnd, 'm') -export default async ({ site, url, lang }) => { - // TODO: Add CI test to enforce meta tags on SSR pages - const { html, styleTags, helmet } = render(url, lang) +interface Params { + site: string + url: string + lang: string +} - const template = +// const vite = await createViteServer({ +// server: {}, +// appType: 'mpa', +// }) + +export default async ({ site, url, lang }: Params) => { + const fileTemplate = cache[site] ?? readFileSync(path.join(dirname, `../dist/${site}.html`), 'utf-8') - cache[site] = template + cache[site] ??= fileTemplate + + // const template = await vite.transformIndexHtml(url, fileTemplate) + const template = fileTemplate + + // const { render } = await vite.ssrLoadModule( + // './source/entries/entry-server.tsx' + // ) + + // TODO: Add CI test to enforce meta tags on SSR pages + const { html, styleTags, helmet } = await render(url, lang) + + console.log({ html, styleTags, helmet }) const page = template .replace(regexHTML, html) diff --git a/site/build/prerender.ts b/site/build/prerender.ts index d7c3cccdc..2eebb239f 100644 --- a/site/build/prerender.ts +++ b/site/build/prerender.ts @@ -6,8 +6,12 @@ import Tinypool from 'tinypool' import { absoluteSitePaths } from '../source/sitePaths.js' -const filename = new URL('./prerender-worker.js', import.meta.url).href -const pool = new Tinypool({ filename }) +const filename = new URL('./prerender-worker.ts', import.meta.url).href +const pool = new Tinypool({ + filename, + execArgv: ['--loader', 'ts-node/esm'], + idleTimeout: 2000, +}) const sitePathFr = absoluteSitePaths.fr const sitePathEn = absoluteSitePaths.en @@ -17,33 +21,34 @@ export const pagesToPrerender: { infrance: string[] } = { 'mon-entreprise': [ - '/iframes/pamc', - '/iframes/simulateur-embauche', - '/iframes/simulateur-independant', - sitePathFr.assistants['choix-du-statut'].index, + '/documentation/artiste‑auteur/cotisations/CSG‑CRDS/abattement', + // '/iframes/pamc', + // '/iframes/simulateur-embauche', + // '/iframes/simulateur-independant', + // sitePathFr.assistants['choix-du-statut'].index, sitePathFr.index, - sitePathFr.simulateursEtAssistants, - sitePathFr.simulateurs.index, - sitePathFr.simulateurs.comparaison, - sitePathFr.simulateurs.dividendes, - sitePathFr.simulateurs.eurl, - sitePathFr.simulateurs.indépendant, - sitePathFr.simulateurs.is, - sitePathFr.simulateurs.salarié, - sitePathFr.simulateurs.sasu, - sitePathFr.simulateurs['artiste-auteur'], + // sitePathFr.simulateursEtAssistants, + // sitePathFr.simulateurs.index, + // sitePathFr.simulateurs.comparaison, + // sitePathFr.simulateurs.dividendes, + // sitePathFr.simulateurs.eurl, + // sitePathFr.simulateurs.indépendant, + // sitePathFr.simulateurs.is, + // sitePathFr.simulateurs.salarié, + // sitePathFr.simulateurs.sasu, + // sitePathFr.simulateurs['artiste-auteur'], sitePathFr.simulateurs['auto-entrepreneur'], - sitePathFr.simulateurs['chômage-partiel'], - sitePathFr.simulateurs['coût-création-entreprise'], - sitePathFr.simulateurs['entreprise-individuelle'], - sitePathFr.simulateurs['profession-libérale'].avocat, - sitePathFr.simulateurs['profession-libérale']['chirurgien-dentiste'], - sitePathFr.simulateurs['profession-libérale'].index, + // sitePathFr.simulateurs['chômage-partiel'], + // sitePathFr.simulateurs['coût-création-entreprise'], + // sitePathFr.simulateurs['entreprise-individuelle'], + // sitePathFr.simulateurs['profession-libérale'].avocat, + // sitePathFr.simulateurs['profession-libérale']['chirurgien-dentiste'], + // sitePathFr.simulateurs['profession-libérale'].index, ].map((val) => encodeURI(val)), infrance: [ sitePathEn.index, - sitePathEn.simulateurs.salarié, - '/iframes/simulateur-embauche', + // sitePathEn.simulateurs.salarié, + // '/iframes/simulateur-embauche', ].map((val) => encodeURI(val)), } @@ -51,15 +56,17 @@ const dev = argv.findIndex((val) => val === '--dev') > -1 const redirects = await Promise.all( Object.entries(pagesToPrerender).flatMap(([site, urls]) => - urls.map((url) => - pool - .run({ - site, - url, - lang: site === 'mon-entreprise' ? 'fr' : 'en', - }) - .then((path: string) => { - return ` + urls.map(async (url) => { + const path = await (pool.run({ + site, + url, + lang: site === 'mon-entreprise' ? 'fr' : 'en', + }) as Promise) + + // eslint-disable-next-line no-console + console.log(`preredering ${url} done, adding redirect`) + + return ` [[redirects]] from = ":SITE_${site === 'mon-entreprise' ? 'FR' : 'EN'}${ dev ? decodeURI(url) : url @@ -67,8 +74,7 @@ const redirects = await Promise.all( to = "/${path}" status = 200 ${dev ? ' force = true\n' : ''}` - }) - ) + }) ) ) diff --git a/site/source/components/App.tsx b/site/source/components/App.tsx index 621a52292..8a1ef953f 100644 --- a/site/source/components/App.tsx +++ b/site/source/components/App.tsx @@ -1,6 +1,22 @@ +import { + SuspensePromise, + useAsyncGetRule, + useAsyncParsedRules, + useAsyncShallowCopy, + useLazyPromise, + usePromise, + useWorkerEngine, + WorkerEngine, +} from '@publicodes/worker-react' import { ErrorBoundary } from '@sentry/react' import { FallbackRender } from '@sentry/react/types/errorboundary' -import { ComponentProps, StrictMode, useEffect, useState } from 'react' +import React, { + ComponentProps, + StrictMode, + Suspense, + useEffect, + useState, +} from 'react' import { useTranslation } from 'react-i18next' import { Route, Routes } from 'react-router-dom' import { css, styled } from 'styled-components' @@ -11,13 +27,14 @@ import { Container } from '@/design-system/layout' import { useAxeCoreAnalysis } from '@/hooks/useAxeCoreAnalysis' import { useGetFullURL } from '@/hooks/useGetFullURL' import { useIsEmbedded } from '@/hooks/useIsEmbedded' -import { useLazyPromise, usePromise } from '@/hooks/usePromise' import { useSaveAndRestoreScrollPosition } from '@/hooks/useSaveAndRestoreScrollPosition' import Landing from '@/pages/_landing/Landing' import Page404 from '@/pages/404' import Accessibilité from '@/pages/Accessibilité' import Budget from '@/pages/budget/index' import IntegrationTest from '@/pages/dev/IntegrationTest' +import Documentation from '@/pages/Documentation' +import Iframes from '@/pages/iframes' import Integration from '@/pages/integration/index' import Nouveautés from '@/pages/nouveautés/index' import Offline from '@/pages/Offline' @@ -26,33 +43,26 @@ import Simulateurs from '@/pages/simulateurs' import SimulateursEtAssistants from '@/pages/simulateurs-et-assistants' import Stats from '@/pages/statistiques/LazyStats' import { useSitePaths } from '@/sitePaths' -import { - useAsyncGetRule, - useAsyncParsedRules, - useShallowCopy, - useWorkerEngine, -} from '@/worker/workerEngineClientReact' import Provider, { ProviderProps } from './Provider' import Redirections from './Redirections' type RootProps = { basename: ProviderProps['basename'] - // rulesPreTransform?: (rules: Rules) => Rules } const TestWorkerEngine = () => { const [refresh, setRefresh] = useState(0) const workerEngine = useWorkerEngine() - // const workerEngineCtx = useWorkerEngineContext() const [, trigger] = useLazyPromise( - async () => workerEngine?.asyncSetSituation({ SMIC: '1000€/mois' }), + async () => workerEngine.asyncSetSituation({ SMIC: '1000€/mois' }), [workerEngine], { defaultValue: 'loading...' } ) const date = useAsyncGetRule('date', { defaultValue: 'loading...' }) + const SMIC = useAsyncGetRule('SMIC', { defaultValue: 'loading...' }) const parsedRules = useAsyncParsedRules() @@ -65,12 +75,10 @@ const TestWorkerEngine = () => { const [resultLazySmic, triggerLazySmic] = useLazyPromise( () => workerEngine.asyncEvaluate('SMIC'), [workerEngine], - 'wait 2sec...' + 'wait 3sec...' ) useEffect(() => { - console.log('??? useEffect') - void (async () => { await workerEngine.isWorkerReady setTimeout(() => { @@ -79,65 +87,72 @@ const TestWorkerEngine = () => { })() }, [triggerLazySmic, workerEngine.isWorkerReady]) - const workerEngineCopy = useShallowCopy(workerEngine) - // // const workerEngineCopy = workerEngine - console.log('=========>', workerEngine, workerEngineCopy) + // const workerEngineCopy = useAsyncShallowCopy(workerEngine) + // // // const workerEngineCopy = workerEngine + // console.log('=========>', workerEngine, workerEngineCopy) - const [, triggerCopy] = useLazyPromise(async () => { - // console.log('+++++++++>', workerEngineCopy) + // const [, triggerCopy] = useLazyPromise(async () => { + // console.log('+++++++++>', workerEngineCopy) - await workerEngineCopy?.asyncSetSituation({ - SMIC: '2000€/mois', - }) - }, [workerEngineCopy]) + // await workerEngineCopy?.asyncSetSituation({ + // SMIC: '2000€/mois', + // }) + // }, [workerEngineCopy]) - const dateCopy = useAsyncGetRule('date', { - defaultValue: 'loading...', - // workerEngine: workerEngineCopy, - }) + // const dateCopy = useAsyncGetRule('date', { + // defaultValue: 'loading...', + // workerEngine: workerEngineCopy, + // }) - const parsedRulesCopy = useAsyncParsedRules({ - workerEngine: workerEngineCopy, - }) + // const parsedRulesCopy = useAsyncParsedRules({ + // workerEngine: workerEngineCopy, + // }) - const resultSmicCopy = usePromise( - async () => workerEngineCopy?.asyncEvaluate('SMIC'), - [workerEngineCopy], - 'loading...' - ) + // const resultSmicCopy = usePromise( + // async () => + // !workerEngineCopy + // ? 'still loading...' + // : workerEngineCopy.asyncEvaluate('SMIC'), + // [workerEngineCopy], + // 'loading...' + // ) - const [resultLazySmicCopy, triggerLazySmicCopy] = useLazyPromise( - async () => workerEngineCopy?.asyncEvaluate('SMIC'), - [workerEngineCopy], - 'wait 2sec...' - ) + // const [resultLazySmicCopy, triggerLazySmicCopy] = useLazyPromise( + // async () => + // !workerEngineCopy + // ? 'still loading...' + // : workerEngineCopy.asyncEvaluate('SMIC'), + // [workerEngineCopy], + // 'wait 3sec...' + // ) - useEffect(() => { - // console.log('useEffect') + // useEffect(() => { + // // console.log('useEffect') - void (async () => { - await workerEngine.isWorkerReady - setTimeout(() => { - void triggerLazySmicCopy() - }, 3000) - })() - }, [triggerLazySmicCopy, workerEngine.isWorkerReady]) + // void (async () => { + // await workerEngine.isWorkerReady + // setTimeout(() => { + // void triggerLazySmicCopy() + // }, 3000) + // })() + // }, [triggerLazySmicCopy, workerEngine.isWorkerReady]) - const { asyncSetSituation } = workerEngineCopy ?? {} - usePromise(async () => { - // console.log('**************>', workerEngineCopy, resultSmic) + // const { asyncSetSituation } = workerEngineCopy ?? {} + // usePromise(async () => { + // // console.log('**************>', workerEngineCopy, resultSmic) - if ( - typeof resultSmic !== 'string' && - typeof resultSmic.nodeValue === 'number' - ) { - // console.log('ooooooooooooooooooo', resultSmic) + // if ( + // resultSmic && + // typeof resultSmic !== 'string' && + // typeof resultSmic.nodeValue === 'number' + // ) { + // // console.log('ooooooooooooooooooo', resultSmic) - await asyncSetSituation?.({ - SMIC: resultSmic.nodeValue + '€/mois', - }) - } - }, [asyncSetSituation, resultSmic]) + // await asyncSetSituation?.({ + // SMIC: resultSmic.nodeValue + '€/mois', + // }) + // } + // }, [asyncSetSituation, resultSmic]) return (
@@ -146,12 +161,16 @@ const TestWorkerEngine = () => { Refresh {refresh} - + {/* */}

date title:{' '} {JSON.stringify(typeof date === 'string' ? date : date?.title)}

+

+ SMIC title:{' '} + {JSON.stringify(typeof SMIC === 'string' ? SMIC : SMIC?.title)} +

parsedRules length:{' '} {JSON.stringify(Object.entries(parsedRules ?? {}).length)} @@ -171,7 +190,7 @@ const TestWorkerEngine = () => { )}

-

workerEngineCopy: {JSON.stringify(workerEngineCopy?.engineId)}

+ {/*

workerEngineCopy: {JSON.stringify(workerEngineCopy?.engineId)}

dateCopy title:{' '} @@ -198,7 +217,7 @@ const TestWorkerEngine = () => { ? resultLazySmicCopy : resultLazySmicCopy?.nodeValue )} -

+

*/}
) } @@ -221,13 +240,11 @@ RootProps) { return ( - {/* */} - {/* */} ) } @@ -277,9 +294,21 @@ const Router = () => { exemple d'execution manuel : {JSON.stringify(exampleAsyncValue)} */} {/* */} - } /> + + + {/* */} +
+ +
+
+ + } + /> - {/* } /> */} + } /> } />
@@ -300,6 +329,7 @@ const App = () => { const fullURL = useGetFullURL() useSaveAndRestoreScrollPosition() const isEmbedded = useIsEmbedded() + const workerEngine = useWorkerEngine() if (!import.meta.env.PROD && import.meta.env.VITE_AXE_CORE_ENABLED) { // eslint-disable-next-line react-hooks/rules-of-hooks useAxeCoreAnalysis() @@ -345,15 +375,17 @@ const App = () => { path={relativeSitePaths.simulateursEtAssistants + '/*'} element={} /> - {/* - } - /> */} + + } + /> } diff --git a/site/source/components/Provider.tsx b/site/source/components/Provider.tsx index 2250ff21f..efbede62e 100644 --- a/site/source/components/Provider.tsx +++ b/site/source/components/Provider.tsx @@ -1,3 +1,6 @@ +import NodeWorker from '@eshaz/web-worker' +import { createWorkerEngineClient } from '@publicodes/worker' +import { useWorkerEngine, WorkerEngineProvider } from '@publicodes/worker-react' import { OverlayProvider } from '@react-aria/overlays' import { ErrorBoundary } from '@sentry/react' import i18next from 'i18next' @@ -20,8 +23,6 @@ import { Body, Intro } from '@/design-system/typography/paragraphs' import { EmbededContextProvider } from '@/hooks/useIsEmbedded' import { Actions } from '@/worker/socialWorkerEngine.worker' import SocialeWorkerEngine from '@/worker/socialWorkerEngine.worker?worker' -import { createWorkerEngineClient } from '@/worker/workerEngineClient' -import { WorkerEngineProvider } from '@/worker/workerEngineClientReact' import { Message } from '../design-system' import * as safeLocalStorage from '../storage/safeLocalStorage' @@ -31,40 +32,21 @@ import { createTracker } from './ATInternetTracking/Tracker' import { IframeResizer } from './IframeResizer' import { ServiceWorker } from './ServiceWorker' import { DarkModeProvider } from './utils/DarkModeContext' +import { useSetupSafeSituation } from './utils/EngineContext' -const workerClient = createWorkerEngineClient( - typeof Worker === 'undefined' - ? ({ postMessage: () => {} } as unknown as Worker) - : new SocialeWorkerEngine(), - // () => {}, - // () => - // startTransition(() => { - // setSituationVersion((situationVersion) => { - // // console.log('??? setSituationVersion original') +console.time('start!') - // // situationVersion[engineId] = - // // typeof situationVersion[engineId] !== 'number' - // // ? 0 - // // : situationVersion[engineId]++ +export const worker = import.meta.env.SSR + ? // Node doesn't support web worker :( upvote issue here: https://github.com/nodejs/node/issues/43583 + new NodeWorker( + new URL('../worker/socialWorkerEngine.worker.js', import.meta.url), + { type: 'module' } + ) + : new SocialeWorkerEngine() - // // return situationVersion - // return situationVersion + 1 - // }) - // }), - // - { - initParams: [{ basename: 'mon-entreprise' }], - // onSituationChange: function () { - // console.log('update *****************') - - // startTransition(() => { - // setSituationVersion((situationVersion) => { - // return situationVersion + 1 - // }) - // }) - // }, - } -) +const workerClient = createWorkerEngineClient(worker, { + initParams: [{ basename: 'mon-entreprise' }], +}) type SiteName = 'mon-entreprise' | 'infrance' @@ -75,6 +57,14 @@ export type ProviderProps = { children: ReactNode } +const SituationSynchronize = ({ children }: { children: ReactNode }) => { + const workerEngine = useWorkerEngine() + + useSetupSafeSituation(workerEngine) + + return children +} + export default function Provider({ basename, children, @@ -91,26 +81,28 @@ export default function Provider({ - ( - // eslint-disable-next-line react/jsx-props-no-spreading - - )} - > - {!import.meta.env.SSR && - import.meta.env.MODE === 'production' && - 'serviceWorker' in navigator && } - - - - - - {children} - - - - - + + ( + // eslint-disable-next-line react/jsx-props-no-spreading + + )} + > + {!import.meta.env.SSR && + import.meta.env.MODE === 'production' && + 'serviceWorker' in navigator && } + + + + + + {children} + + + + + + diff --git a/site/source/components/RuleLink.tsx b/site/source/components/RuleLink.tsx index eada78fef..607cb1a6e 100644 --- a/site/source/components/RuleLink.tsx +++ b/site/source/components/RuleLink.tsx @@ -1,10 +1,9 @@ -import { useWorkerEngine } from '@publicodes/worker-react' +import { usePromise, useWorkerEngine } from '@publicodes/worker-react' import { DottedName } from 'modele-social' import { RuleLink as EngineRuleLink } from 'publicodes-react' import React from 'react' import { Link } from '@/design-system/typography/link' -import { usePromise } from '@/hooks/usePromise' import { useSitePaths } from '@/sitePaths' // TODO : quicklink -> en cas de variations ou de somme avec un seul élément actif, faire un lien vers cet élément @@ -15,20 +14,23 @@ export default function RuleLink( documentationPath?: string } & Omit, 'to' | 'children'> ) { - const { dottedName, documentationPath, ...linkProps } = props + const { dottedName, documentationPath, children, ...linkProps } = props const { absoluteSitePaths } = useSitePaths() const [loading, setLoading] = React.useState(true) const [error, setError] = React.useState(false) const workerEngine = useWorkerEngine() - usePromise(() => { + usePromise(async () => { setLoading(true) setError(false) - return workerEngine - .asyncGetRule(dottedName) - .catch(() => setError(true)) - .then(() => setLoading(false)) + try { + const rule = await workerEngine.asyncGetRule(dottedName) + } catch (error) { + setError(true) + } + + setLoading(false) }, [dottedName, workerEngine]) if (loading || error) { @@ -41,9 +43,12 @@ export default function RuleLink( // @ts-ignore linkComponent={Link} engine={workerEngine} + dottedName={dottedName} documentationPath={ documentationPath ?? absoluteSitePaths.documentation.index } - /> + > + {children} + ) } diff --git a/site/source/components/conversation/useNavigateQuestions.ts b/site/source/components/conversation/useNavigateQuestions.ts index 775bc2119..77401f116 100644 --- a/site/source/components/conversation/useNavigateQuestions.ts +++ b/site/source/components/conversation/useNavigateQuestions.ts @@ -1,4 +1,5 @@ -import { useEffect } from 'react' +import { useWorkerEngine, WorkerEngine } from '@publicodes/worker-react' +import { useCallback, useEffect, useRef } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useNextQuestions } from '@/hooks/useNextQuestion' @@ -12,7 +13,6 @@ import { currentQuestionSelector, useMissingVariables, } from '@/store/selectors/simulationSelectors' -import { useWorkerEngine, WorkerEngine } from '@/worker/workerEngineClientReact' export function useNavigateQuestions(workerEngines?: WorkerEngine[]) { const dispatch = useDispatch() @@ -25,22 +25,27 @@ export function useNavigateQuestions(workerEngines?: WorkerEngine[]) { const previousAnswers = useSelector(answeredQuestionsSelector) - const goToPrevious = () => { + const goToPrevious = useCallback(() => { dispatch(updateShouldFocusField(true)) dispatch(goToQuestion(previousAnswers.slice(-1)[0])) - } - const goToNext = () => { + }, [dispatch, previousAnswers]) + + const goToNext = useCallback(() => { dispatch(updateShouldFocusField(true)) if (currentQuestion) { dispatch(stepAction(currentQuestion)) + // dispatch(goToQuestion(nextQuestions[0])) + nextQuestions.length > 1 && dispatch(goToQuestion(nextQuestions[1])) } - } + }, [currentQuestion, dispatch, nextQuestions]) + const init = useRef(false) useEffect(() => { - if (!currentQuestion && nextQuestions[0]) { + if (!init.current && !currentQuestion && nextQuestions.length) { dispatch(goToQuestion(nextQuestions[0])) + init.current = true } - }, [nextQuestions, currentQuestion, dispatch]) + }, [currentQuestion, dispatch, nextQuestions]) return { currentQuestion: currentQuestion ?? nextQuestions[0], diff --git a/site/source/components/ui/animate.tsx b/site/source/components/ui/animate.tsx index 96e286488..14da0a5e2 100644 --- a/site/source/components/ui/animate.tsx +++ b/site/source/components/ui/animate.tsx @@ -122,7 +122,7 @@ export const FadeIn = ({ ) } -export function Appear({ +function AppearAnim({ children, className, unless = false, @@ -158,3 +158,7 @@ export function Appear({ ) } + +export const Appear = (props: Parameters[0]) => + // eslint-disable-next-line react/jsx-props-no-spreading + import.meta.env.SSR ? props.children : diff --git a/site/source/components/utils/colors.tsx b/site/source/components/utils/colors.tsx index e4c84bba3..eb890868d 100644 --- a/site/source/components/utils/colors.tsx +++ b/site/source/components/utils/colors.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react' -import { ThemeProvider, useTheme } from 'styled-components' +import { createGlobalStyle, ThemeProvider, useTheme } from 'styled-components' import { useIsEmbedded } from '@/hooks/useIsEmbedded' import { hexToHSL } from '@/utils/hexToHSL' @@ -42,31 +42,39 @@ const IFRAME_COLOR = iframeColor // the full palette generation that happen here. This is to prevent a UI // flash, cf. #1786. +const GlobalCssVar = createGlobalStyle<{ + $hue: number + $saturation: number +}>` +html { + --${HUE_CSS_VARIABLE_NAME}: ${({ $hue }) => $hue}deg; + --${SATURATION_CSS_VARIABLE_NAME}: ${({ $saturation }) => $saturation}%; +} +` + export function ThemeColorsProvider({ children }: ProviderProps) { const divRef = useRef(null) const [themeColor, setThemeColor] = useState(IFRAME_COLOR) useEffect(() => { - window.addEventListener('message', (evt: MessageEvent) => { - if (evt.data.kind === 'change-theme-color') { - setThemeColor(hexToHSL(evt.data.value)) + window.addEventListener( + 'message', + (evt: MessageEvent<{ kind: string; value: string }>) => { + if (evt.data.kind === 'change-theme-color') { + console.log('change-theme-color', evt.data.value) + setThemeColor(hexToHSL(evt.data.value)) + } } - }) - }, []) - const [hue, saturation] = themeColor - useEffect(() => { - const root = document.querySelector(':root') as HTMLElement | undefined - root?.style.setProperty(`--${HUE_CSS_VARIABLE_NAME}`, `${hue}deg`) - root?.style.setProperty( - `--${SATURATION_CSS_VARIABLE_NAME}`, - `${saturation}%` ) - }, [hue, saturation]) + }, []) + const isEmbeded = useIsEmbedded() const defaultTheme = useTheme() if (!themeColor && !isEmbeded) { return <>{children} } + const [hue, saturation] = themeColor + return ( + {/* This div is only used to set the CSS variables */}
) { + return new Response(stream).text() +} + +const AppFrLazy = lazy(async () => ({ + default: (await import('./entry-fr')).AppFr, +})) + +const AppEnLazy = lazy(async () => ({ + default: (await import('./entry-en')).AppEn, +})) + +// @ts-ignore +global.window = { + // @ts-ignore + location: {}, +} + +interface Result { + html: string + styleTags: string + helmet: FilledContext['helmet'] +} + +export async function render(url: string, lang: 'fr' | 'en'): Promise { + global.window.location.href = url + global.window.location.search = '' + console.log({ url, lang }) + const sheet = new ServerStyleSheet() const helmetContext = {} as FilledContext - const App = lang === 'fr' ? AppFr : AppEn i18next.changeLanguage(lang).catch((err) => // eslint-disable-next-line no-console - console.error(err) + console.error('Error', err) ) const element = ( @@ -22,20 +48,39 @@ export function render(url: string, lang: 'fr' | 'en') { - + [prerender] window: {JSON.stringify(window)} + {lang === 'fr' ? : } ) - // Render to initialize redux store (via useSimulationConfig) - ReactDOMServer.renderToString(element) + console.log('!!! STARTING !!!') - // Render with redux store configured - const html = ReactDOMServer.renderToString(element) + try { + const stream = await ( + ReactDomServer.renderToReadableStream as unknown as typeof renderToReadableStream + )(element, { + onError(error, errorInfo) { + console.error({ error, errorInfo }) + }, + }) - const styleTags = sheet.getStyleTags() + console.log('!!! LOADING !!!') - return { html, styleTags, helmet: helmetContext.helmet } + await stream.allReady + + console.log('!!! DONE !!!') + + const html = await streamToString(stream) + + const styleTags = sheet.getStyleTags() + + return { html, styleTags, helmet: helmetContext.helmet } + } catch (error) { + console.error(error) + + throw error + } } diff --git a/site/source/entries/template.html b/site/source/entries/template.html index accae5352..b6ec2e3d2 100644 --- a/site/source/entries/template.html +++ b/site/source/entries/template.html @@ -43,6 +43,9 @@ + + +