prerender
parent
d33d7db5ed
commit
e8fa858eb6
|
@ -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 = '<!--app-html:start-->'
|
||||
const htmlBodyEnd = '<!--app-html:end-->'
|
||||
|
@ -16,15 +16,35 @@ const headTagsEnd = '<!--app-helmet-tags:end-->'
|
|||
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)
|
|
@ -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<string>)
|
||||
|
||||
// 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' : ''}`
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<div>
|
||||
|
@ -146,12 +161,16 @@ const TestWorkerEngine = () => {
|
|||
Refresh {refresh}
|
||||
</button>
|
||||
<button onClick={() => void trigger()}>trigger</button>
|
||||
<button onClick={() => void triggerCopy()}>trigger copy</button>
|
||||
{/* <button onClick={() => void triggerCopy()}>trigger copy</button> */}
|
||||
|
||||
<p>
|
||||
date title:{' '}
|
||||
{JSON.stringify(typeof date === 'string' ? date : date?.title)}
|
||||
</p>
|
||||
<p>
|
||||
SMIC title:{' '}
|
||||
{JSON.stringify(typeof SMIC === 'string' ? SMIC : SMIC?.title)}
|
||||
</p>
|
||||
<p>
|
||||
parsedRules length:{' '}
|
||||
{JSON.stringify(Object.entries(parsedRules ?? {}).length)}
|
||||
|
@ -171,7 +190,7 @@ const TestWorkerEngine = () => {
|
|||
)}
|
||||
</p>
|
||||
|
||||
<p>workerEngineCopy: {JSON.stringify(workerEngineCopy?.engineId)}</p>
|
||||
{/* <p>workerEngineCopy: {JSON.stringify(workerEngineCopy?.engineId)}</p>
|
||||
|
||||
<p>
|
||||
dateCopy title:{' '}
|
||||
|
@ -198,7 +217,7 @@ const TestWorkerEngine = () => {
|
|||
? resultLazySmicCopy
|
||||
: resultLazySmicCopy?.nodeValue
|
||||
)}
|
||||
</p>
|
||||
</p> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -221,13 +240,11 @@ RootProps) {
|
|||
|
||||
return (
|
||||
<StrictMode>
|
||||
{/* <EngineProvider value={engine}> */}
|
||||
<Provider basename={basename}>
|
||||
<Redirections>
|
||||
<Router />
|
||||
</Redirections>
|
||||
</Provider>
|
||||
{/* </EngineProvider> */}
|
||||
</StrictMode>
|
||||
)
|
||||
}
|
||||
|
@ -277,9 +294,21 @@ const Router = () => {
|
|||
exemple d'execution manuel : {JSON.stringify(exampleAsyncValue)} */}
|
||||
{/* */}
|
||||
<Routes>
|
||||
<Route path="test-worker" element={<TestWorkerEngine />} />
|
||||
<Route
|
||||
path="test-worker"
|
||||
element={
|
||||
<>
|
||||
<SuspensePromise isSSR={import.meta.env.SSR}>
|
||||
{/* <TestWorkerEngine /> */}
|
||||
<div>
|
||||
<TestWorkerEngine />
|
||||
</div>
|
||||
</SuspensePromise>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* <Route path="/iframes/*" element={<Iframes />} /> */}
|
||||
<Route path="/iframes/*" element={<Iframes />} />
|
||||
<Route path="*" element={<App />} />
|
||||
</Routes>
|
||||
</>
|
||||
|
@ -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={<SimulateursEtAssistants />}
|
||||
/>
|
||||
{/* <Route
|
||||
path={relativeSitePaths.documentation.index + '/*'}
|
||||
element={
|
||||
<Route
|
||||
path={relativeSitePaths.documentation.index + '/*'}
|
||||
element={
|
||||
<SuspensePromise isSSR={import.meta.env.SSR}>
|
||||
<Documentation
|
||||
documentationPath={documentationPath}
|
||||
engine={engine}
|
||||
engine={workerEngine}
|
||||
/>
|
||||
}
|
||||
/> */}
|
||||
</SuspensePromise>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={relativeSitePaths.développeur.index + '/*'}
|
||||
element={<Integration />}
|
||||
|
|
|
@ -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<Actions>(
|
||||
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<Actions>(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({
|
|||
<ReduxProvider store={store}>
|
||||
<BrowserRouterProvider basename={basename}>
|
||||
<WorkerEngineProvider workerClient={workerClient}>
|
||||
<ErrorBoundary
|
||||
fallback={(errorData) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<ErrorFallback {...errorData} />
|
||||
)}
|
||||
>
|
||||
{!import.meta.env.SSR &&
|
||||
import.meta.env.MODE === 'production' &&
|
||||
'serviceWorker' in navigator && <ServiceWorker />}
|
||||
<IframeResizer />
|
||||
<OverlayProvider>
|
||||
<ThemeColorsProvider>
|
||||
<DisableAnimationOnPrintProvider>
|
||||
<SiteNameContext.Provider value={basename}>
|
||||
{children}
|
||||
</SiteNameContext.Provider>
|
||||
</DisableAnimationOnPrintProvider>
|
||||
</ThemeColorsProvider>
|
||||
</OverlayProvider>
|
||||
</ErrorBoundary>
|
||||
<SituationSynchronize>
|
||||
<ErrorBoundary
|
||||
fallback={(errorData) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<ErrorFallback {...errorData} />
|
||||
)}
|
||||
>
|
||||
{!import.meta.env.SSR &&
|
||||
import.meta.env.MODE === 'production' &&
|
||||
'serviceWorker' in navigator && <ServiceWorker />}
|
||||
<IframeResizer />
|
||||
<OverlayProvider>
|
||||
<ThemeColorsProvider>
|
||||
<DisableAnimationOnPrintProvider>
|
||||
<SiteNameContext.Provider value={basename}>
|
||||
{children}
|
||||
</SiteNameContext.Provider>
|
||||
</DisableAnimationOnPrintProvider>
|
||||
</ThemeColorsProvider>
|
||||
</OverlayProvider>
|
||||
</ErrorBoundary>
|
||||
</SituationSynchronize>
|
||||
</WorkerEngineProvider>
|
||||
</BrowserRouterProvider>
|
||||
</ReduxProvider>
|
||||
|
|
|
@ -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<React.ComponentProps<typeof Link>, '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}
|
||||
</EngineRuleLink>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -122,7 +122,7 @@ export const FadeIn = ({
|
|||
)
|
||||
}
|
||||
|
||||
export function Appear({
|
||||
function AppearAnim({
|
||||
children,
|
||||
className,
|
||||
unless = false,
|
||||
|
@ -158,3 +158,7 @@ export function Appear({
|
|||
</animated.div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Appear = (props: Parameters<typeof AppearAnim>[0]) =>
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
import.meta.env.SSR ? props.children : <AppearAnim {...props} />
|
||||
|
|
|
@ -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<HTMLDivElement>(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 (
|
||||
<ThemeProvider
|
||||
theme={{
|
||||
|
@ -77,6 +85,7 @@ export function ThemeColorsProvider({ children }: ProviderProps) {
|
|||
},
|
||||
}}
|
||||
>
|
||||
<GlobalCssVar $hue={hue} $saturation={saturation} />
|
||||
{/* This div is only used to set the CSS variables */}
|
||||
<div
|
||||
ref={divRef}
|
||||
|
|
|
@ -1,20 +1,46 @@
|
|||
import { SSRProvider } from '@react-aria/ssr'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import { lazy } from 'react'
|
||||
import ReactDomServer, { type renderToReadableStream } from 'react-dom/server'
|
||||
import { FilledContext, HelmetProvider } from 'react-helmet-async'
|
||||
import { StaticRouter } from 'react-router-dom/server'
|
||||
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
|
||||
|
||||
import i18next from '../locales/i18n'
|
||||
import { AppEn } from './entry-en'
|
||||
import { AppFr } from './entry-fr'
|
||||
|
||||
export function render(url: string, lang: 'fr' | 'en') {
|
||||
function streamToString(stream: ReadableStream<Uint8Array>) {
|
||||
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<Result> {
|
||||
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') {
|
|||
<SSRProvider>
|
||||
<StyleSheetManager sheet={sheet.instance}>
|
||||
<StaticRouter location={url}>
|
||||
<App />
|
||||
[prerender] window: {JSON.stringify(window)}
|
||||
{lang === 'fr' ? <AppFrLazy /> : <AppEnLazy />}
|
||||
</StaticRouter>
|
||||
</StyleSheetManager>
|
||||
</SSRProvider>
|
||||
</HelmetProvider>
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,9 @@
|
|||
|
||||
<meta name="theme-color" content="#2975d1" />
|
||||
|
||||
<!--app-helmet-tags:start-->
|
||||
<!--app-helmet-tags:end-->
|
||||
|
||||
<style>
|
||||
/* CSS Loader */
|
||||
#loading {
|
||||
|
|
|
@ -3,6 +3,7 @@ import { DependencyList, useCallback, useEffect, useState } from 'react'
|
|||
/**
|
||||
* Execute an asynchronous function and return its result (Return default value if the promise is not finished).
|
||||
* The function is executed each time the dependencies change.
|
||||
* @deprecated use `import { usePromise } from '@publicodes/worker-react'`
|
||||
*/
|
||||
export const usePromise = <T, Default = undefined>(
|
||||
promise: () => Promise<T>,
|
||||
|
@ -47,6 +48,7 @@ const tuple = <T extends unknown[]>(args: [...T]): T => args
|
|||
/**
|
||||
* Execute an asynchronous function and return its result (Return default value if the promise is not finished).
|
||||
* Use this hook if you want to fire the promise manually.
|
||||
* @deprecated use `import { useLazyPromise } from '@publicodes/worker-react'`
|
||||
*/
|
||||
export const useLazyPromise = <
|
||||
T,
|
||||
|
|
|
@ -146,6 +146,7 @@ function DocumentationPageBody({
|
|||
return (
|
||||
<StyledDocumentation>
|
||||
<RulePage
|
||||
isSSR={import.meta.env.SSR}
|
||||
language={i18n.language as 'fr' | 'en'}
|
||||
rulePath={params['*'] ?? ''}
|
||||
engine={engine}
|
||||
|
|
|
@ -260,6 +260,10 @@ const LazyBlobProvider = lazy<typeof BlobProvider>(
|
|||
|
||||
// From https://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript/4819886#4819886
|
||||
function isOnTouchDevice() {
|
||||
if (import.meta.env.SSR) {
|
||||
return false
|
||||
}
|
||||
|
||||
const prefixes = ' -webkit- -moz- -o- -ms- '.split(' ')
|
||||
const mq = function (query: string) {
|
||||
return window.matchMedia(query).matches
|
||||
|
|
|
@ -39,12 +39,11 @@ export const goToQuestion = (question: DottedName) =>
|
|||
step: question,
|
||||
}) as const
|
||||
|
||||
export const stepAction = (step: DottedName, source?: string) =>
|
||||
export const stepAction = (step: DottedName) =>
|
||||
({
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
step,
|
||||
source,
|
||||
}) as const
|
||||
|
||||
export const setSimulationConfig = (
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM", "WebWorker"],
|
||||
"baseUrl": "source",
|
||||
"moduleResolution": "node",
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "Node",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
@ -38,6 +38,7 @@
|
|||
"vite-iframe-script.config.ts",
|
||||
"build/vite-build-simulation-data.config.ts",
|
||||
"build/prerender.ts",
|
||||
"build/prerender-worker.ts",
|
||||
"vite-pwa-options.ts"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue