Skip to content

Commit

Permalink
Merge pull request #2292 from NDLANO/refactor/render-in-react
Browse files Browse the repository at this point in the history
refactor: render the entire document with react.
  • Loading branch information
Jonas-C authored Jan 10, 2025
2 parents 0832bf2 + 57341a4 commit 22dfe3a
Show file tree
Hide file tree
Showing 24 changed files with 401 additions and 340 deletions.
1 change: 0 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ RUN yarn install --immutable

# Copy necessary source files for server and client build
COPY babel.config.cjs tsconfig.json vite.config.ts panda.config.ts eslint.config.mjs postcss.config.cjs $APP_PATH/
COPY iframe-article.html iframe-embed.html index.html lti.html error.html $APP_PATH/
COPY scripts $APP_PATH/scripts

COPY src $APP_PATH/src
Expand Down
27 changes: 0 additions & 27 deletions error.html

This file was deleted.

27 changes: 0 additions & 27 deletions iframe-article.html

This file was deleted.

27 changes: 0 additions & 27 deletions iframe-embed.html

This file was deleted.

27 changes: 0 additions & 27 deletions index.html

This file was deleted.

27 changes: 0 additions & 27 deletions lti.html

This file was deleted.

106 changes: 106 additions & 0 deletions src/Document.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Copyright (c) 2025-present, NDLA.
*
* This source code is licensed under the GPLv3 license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import { ReactNode } from "react";
import type { ManifestChunk } from "vite";
import config from "./config";

interface Props {
language: string;
children?: ReactNode;
chunks?: ManifestChunk[];
devEntrypoint: string;
}

const getUniqueCss = (chunks: ManifestChunk[]) => {
const uniq = chunks.reduce((acc, curr) => {
curr.css?.forEach((css) => acc.add(css));
return acc;
}, new Set<string>());
return Array.from(uniq);
};

export const Document = ({ language, children, chunks = [], devEntrypoint }: Props) => {
const faviconEnvironment = config.ndlaEnvironment === "dev" ? "test" : config.ndlaEnvironment;
const locale = language === "nb" || language === "nn" ? "no" : language;

const [entryPoint, ...importedChunks] = chunks;
const css = getUniqueCss(chunks);

return (
<html lang={locale}>
<head>
<link rel="icon" type="image/png" sizes="32x32" href={`/static/favicon-${faviconEnvironment}-32x32.png`} />
<link rel="icon" type="image/png" sizes="16x16" href={`/static/favicon-${faviconEnvironment}-16x16.png`} />
<link
rel="apple-touch-icon"
type="image/png"
sizes="180x180"
href={`/static/apple-touch-icon-${faviconEnvironment}.png`}
/>
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1 viewport-fit=cover" />
<link href="https://api.fontshare.com/v2/css?f[]=satoshi@1&display=swap" rel="stylesheet" />
</head>
<body>
<script
type="text/javascript"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
window._mtm = window._mtm || [];
window.originalLocation = {
originalLocation:
document.location.protocol +
"//" +
document.location.hostname +
document.location.pathname +
document.location.search,
};
window.dataLayer.push(window.originalLocation);
`,
}}
></script>
{config.runtimeType === "development" && (
<>
<script
dangerouslySetInnerHTML={{
__html: `
import RefreshRuntime from "/@react-refresh";
RefreshRuntime.injectIntoGlobalHook(window);
window.$RefreshReg$ = () => {};
window.$RefreshSig$ = () => (type) => type;
window.__vite_plugin_react_preamble_installed__ = true;
`,
}}
type="module"
/>
<script src="/@vite/client" type="module" />
<script type="module" src={`/${devEntrypoint}`}></script>
</>
)}
<script
// We're hydrating the entire document. Our config differentiates between server and client, so it's necessary to suppress any hydration warnings here. TODO: Find a better workaround for this
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: config.isClient ? "" : `window.DATA = "$WINDOW_DATA"`,
}}
></script>
{!!entryPoint && <script type="module" src={`/${entryPoint.file}`}></script>}
{css.map((file) => (
<link rel="stylesheet" href={`/${file}`} key={file} />
))}
{importedChunks.map((chunk) => (
<link rel="modulepreload" href={`/${chunk.file}`} key={chunk.file}></link>
))}
<div id="root">{children}</div>
</body>
</html>
);
};
32 changes: 20 additions & 12 deletions src/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import App from "./App";
import ResponseContext from "./components/ResponseContext";
import { VersionHashProvider } from "./components/VersionHashContext";
import { STORED_LANGUAGE_COOKIE_KEY } from "./constants";
import { Document } from "./Document";
import { entryPoints } from "./entrypoints";
import {
getLangAttributeValue,
getLocaleInfoFromPath,
Expand All @@ -49,7 +51,7 @@ declare global {
}

const {
DATA: { config, serverPath, serverResponse },
DATA: { config, serverPath, serverResponse, chunks },
} = window;

initSentry(config);
Expand Down Expand Up @@ -151,7 +153,7 @@ const LanguageWrapper = ({ basename }: { basename?: string }) => {
);
};

const renderOrHydrate = (container: HTMLElement, children: ReactNode) => {
const renderOrHydrate = (container: Element | Document, children: ReactNode) => {
if (config.disableSSR) {
const root = createRoot(container);
root.render(children);
Expand All @@ -161,14 +163,20 @@ const renderOrHydrate = (container: HTMLElement, children: ReactNode) => {
};

renderOrHydrate(
document.getElementById("root")!,
<I18nextProvider i18n={i18n}>
<ApolloProvider client={client}>
<ResponseContext.Provider value={{ status: serverResponse }}>
<VersionHashProvider value={versionHash}>
<LanguageWrapper basename={basename} />
</VersionHashProvider>
</ResponseContext.Provider>
</ApolloProvider>
</I18nextProvider>,
document,
<Document
chunks={chunks}
language={isValidLocale(storedLanguage) ? storedLanguage : config.defaultLocale}
devEntrypoint={entryPoints.default}
>
<I18nextProvider i18n={i18n}>
<ApolloProvider client={client}>
<ResponseContext.Provider value={{ status: serverResponse }}>
<VersionHashProvider value={versionHash}>
<LanguageWrapper basename={basename} />
</VersionHashProvider>
</ResponseContext.Provider>
</ApolloProvider>
</I18nextProvider>
</Document>,
);
26 changes: 15 additions & 11 deletions src/containers/ErrorPage/ErrorEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import { getCookie, setCookie } from "@ndla/util";
import ErrorPage from "./ErrorPage";
import Scripts from "../../components/Scripts/Scripts";
import { STORED_LANGUAGE_COOKIE_KEY } from "../../constants";
import { Document } from "../../Document";
import { entryPoints } from "../../entrypoints";
import { getLocaleInfoFromPath, initializeI18n } from "../../i18n";
import { initSentry } from "../../util/sentry";

const { config, serverPath } = window.DATA;
const { config, serverPath, chunks } = window.DATA;

initSentry(config);

Expand All @@ -39,7 +41,7 @@ const storedLanguage = getCookie(STORED_LANGUAGE_COOKIE_KEY, document.cookie)!;

const i18n = initializeI18n(i18nInstance, storedLanguage);

const renderOrHydrate = (container: HTMLElement, children: ReactNode) => {
const renderOrHydrate = (container: Document | Element, children: ReactNode) => {
if (config.disableSSR) {
const root = createRoot(container);
root.render(children);
Expand All @@ -49,13 +51,15 @@ const renderOrHydrate = (container: HTMLElement, children: ReactNode) => {
};

renderOrHydrate(
document.getElementById("root")!,
<I18nextProvider i18n={i18n}>
<BrowserRouter>
<MissingRouterContext.Provider value={true}>
<Scripts />
<ErrorPage />
</MissingRouterContext.Provider>
</BrowserRouter>
</I18nextProvider>,
document,
<Document language={storedLanguage} chunks={chunks} devEntrypoint={entryPoints.error}>
<I18nextProvider i18n={i18n}>
<BrowserRouter>
<MissingRouterContext.Provider value={true}>
<Scripts />
<ErrorPage />
</MissingRouterContext.Provider>
</BrowserRouter>
</I18nextProvider>
</Document>,
);
17 changes: 17 additions & 0 deletions src/entrypoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2025-present, NDLA.
*
* This source code is licensed under the GPLv3 license found in the
* LICENSE file in the root directory of this source tree.
*
*/

export type EntryPointType = "default" | "lti" | "iframeArticle" | "iframeEmbed" | "error";

export const entryPoints: Record<EntryPointType, string> = {
default: "src/client.tsx",
lti: "src/lti/index.tsx",
iframeArticle: "src/iframe/index.tsx",
iframeEmbed: "src/iframe/embedIframeIndex.tsx",
error: "src/containers/ErrorPage/ErrorEntry.tsx",
} as const;
Loading

0 comments on commit 22dfe3a

Please sign in to comment.