Bladeren bron

render with SSR

Yuki Takei 3 jaren geleden
bovenliggende
commit
a19ca0794a

+ 6 - 6
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -7,6 +7,7 @@ import dynamic from 'next/dynamic';
 import { useHackmdDraftUpdatedEffect } from '~/client/services/side-effects/hackmd-draft-updated';
 import { useHackmdDraftUpdatedEffect } from '~/client/services/side-effects/hackmd-draft-updated';
 import { useHashChangedEffect } from '~/client/services/side-effects/hash-changed';
 import { useHashChangedEffect } from '~/client/services/side-effects/hash-changed';
 import { usePageUpdatedEffect } from '~/client/services/side-effects/page-updated';
 import { usePageUpdatedEffect } from '~/client/services/side-effects/page-updated';
+import type { RendererConfig } from '~/interfaces/services/renderer';
 import { useIsEditable } from '~/stores/context';
 import { useIsEditable } from '~/stores/context';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 
 
@@ -18,6 +19,8 @@ import type { PageSideContentsProps } from '../PageSideContents';
 import { UserInfo } from '../User/UserInfo';
 import { UserInfo } from '../User/UserInfo';
 import type { UsersHomePageFooterProps } from '../UsersHomePageFooter';
 import type { UsersHomePageFooterProps } from '../UsersHomePageFooter';
 
 
+import { Page2 } from './Page2';
+
 const { isUsersHomePage } = pagePathUtils;
 const { isUsersHomePage } = pagePathUtils;
 
 
 
 
@@ -41,6 +44,7 @@ const IdenticalPathPage = (): JSX.Element => {
 
 
 
 
 type Props = {
 type Props = {
+  rendererConfig: RendererConfig,
   pagePath: string,
   pagePath: string,
   page?: IPagePopulatedToShowRevision,
   page?: IPagePopulatedToShowRevision,
   isIdenticalPathPage?: boolean,
   isIdenticalPathPage?: boolean,
@@ -51,17 +55,13 @@ type Props = {
 
 
 const View = (props: Props): JSX.Element => {
 const View = (props: Props): JSX.Element => {
   const {
   const {
+    rendererConfig,
     pagePath, page,
     pagePath, page,
     isIdenticalPathPage, isNotFound, isForbidden, isNotCreatable,
     isIdenticalPathPage, isNotFound, isForbidden, isNotCreatable,
   } = props;
   } = props;
 
 
   const pageId = page?._id;
   const pageId = page?._id;
 
 
-  const Page = dynamic(() => import('./Page').then(mod => mod.Page), {
-    ssr: false,
-    loading: () => <span>loading...</span>,
-  });
-
   const specialContents = useMemo(() => {
   const specialContents = useMemo(() => {
     if (isIdenticalPathPage) {
     if (isIdenticalPathPage) {
       return <IdenticalPathPage />;
       return <IdenticalPathPage />;
@@ -110,7 +110,7 @@ const View = (props: Props): JSX.Element => {
       { specialContents == null && (
       { specialContents == null && (
         <>
         <>
           { isUsersHomePagePath && <UserInfo author={page?.creator} /> }
           { isUsersHomePagePath && <UserInfo author={page?.creator} /> }
-          <Page />
+          <Page2 rendererConfig={rendererConfig} pagePath={pagePath} markdownForSSR={page?.revision.body} />
         </>
         </>
       ) }
       ) }
 
 

+ 1 - 1
packages/app/src/components/Page/Page.tsx

@@ -2,7 +2,7 @@ import React, {
   useCallback, useEffect, useRef,
   useCallback, useEffect, useRef,
 } from 'react';
 } from 'react';
 
 
-import { IPagePopulatedToShowRevision, pagePathUtils } from '@growi/core';
+import { pagePathUtils } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { HtmlElementNode } from 'rehype-toc';
 import { HtmlElementNode } from 'rehype-toc';
 
 

+ 26 - 0
packages/app/src/components/Page/Page2.tsx

@@ -0,0 +1,26 @@
+import dynamic from 'next/dynamic';
+
+import type { RendererConfig } from '~/interfaces/services/renderer';
+import { generateSSRViewOptions } from '~/services/renderer/renderer';
+
+import RevisionRenderer from './RevisionRenderer';
+
+
+type Props = {
+  rendererConfig: RendererConfig,
+  pagePath: string,
+  markdownForSSR?: string,
+}
+
+export const Page2 = (props: Props): JSX.Element => {
+  const { rendererConfig, pagePath, markdownForSSR: markdown } = props;
+
+  const rendererOptions = generateSSRViewOptions(rendererConfig, pagePath);
+
+  const Page = dynamic(() => import('./Page').then(mod => mod.Page), {
+    ssr: false,
+    loading: () => <RevisionRenderer rendererOptions={rendererOptions} markdown={markdown ?? ''} />,
+  });
+
+  return <Page />;
+};

+ 3 - 1
packages/app/src/pages/[[...path]].page.tsx

@@ -249,6 +249,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
 
 
   const pageId = pageWithMeta?.data._id;
   const pageId = pageWithMeta?.data._id;
   const pagePath = pageWithMeta?.data.path ?? props.currentPathname;
   const pagePath = pageWithMeta?.data.path ?? props.currentPathname;
+  const revisionBody = pageWithMeta?.data.revision?.body;
 
 
   useCurrentPageId(pageId ?? null);
   useCurrentPageId(pageId ?? null);
   useRevisionIdHackmdSynced(pageWithMeta?.data.revisionHackmdSynced);
   useRevisionIdHackmdSynced(pageWithMeta?.data.revisionHackmdSynced);
@@ -259,7 +260,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
 
 
   useSWRxCurrentPage(pageWithMeta?.data ?? null); // store initial data
   useSWRxCurrentPage(pageWithMeta?.data ?? null); // store initial data
 
 
-  useEditingMarkdown(pageWithMeta?.data.revision?.body);
+  useEditingMarkdown(revisionBody);
 
 
   const { data: grantData } = useSWRxIsGrantNormalized(pageId);
   const { data: grantData } = useSWRxIsGrantNormalized(pageId);
   const { mutate: mutateSelectedGrant } = useSelectedGrant();
   const { mutate: mutateSelectedGrant } = useSelectedGrant();
@@ -310,6 +311,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
 
 
         <DisplaySwitcher
         <DisplaySwitcher
+          rendererConfig={props.rendererConfig}
           pagePath={pagePath}
           pagePath={pagePath}
           page={pageWithMeta?.data}
           page={pageWithMeta?.data}
           isIdenticalPathPage={props.isIdenticalPathPage}
           isIdenticalPathPage={props.isIdenticalPathPage}

+ 52 - 0
packages/app/src/services/renderer/renderer.tsx

@@ -283,6 +283,58 @@ export const generateSimpleViewOptions = (
   return options;
   return options;
 };
 };
 
 
+export const generateSSRViewOptions = (
+    config: RendererConfig,
+    pagePath: string,
+): RendererOptions => {
+  const options = generateCommonOptions(pagePath);
+
+  const { remarkPlugins, rehypePlugins, components } = options;
+
+  // add remark plugins
+  remarkPlugins.push(
+    math,
+    xsvToTable.remarkPlugin,
+    lsxGrowiPlugin.remarkPlugin,
+    table.remarkPlugin,
+  );
+
+  const isEnabledLinebreaks = config.isEnabledLinebreaks;
+
+  if (isEnabledLinebreaks) {
+    remarkPlugins.push(breaks);
+  }
+
+  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+    injectCustomSanitizeOption(config);
+  }
+
+  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      lsxGrowiPlugin.sanitizeOption,
+    )]
+    : () => {};
+
+  // add rehype plugins
+  rehypePlugins.push(
+    [lsxGrowiPlugin.rehypePlugin, { pagePath }],
+    rehypeSanitizePlugin,
+    katex,
+  );
+
+  // add components
+  if (components != null) {
+    components.lsx = LsxImmutable;
+    components.table = Table;
+  }
+
+  if (config.isEnabledXssPrevention) {
+    verifySanitizePlugin(options, false);
+  }
+  return options;
+};
+
 export const generatePreviewOptions = (config: RendererConfig, pagePath: string): RendererOptions => {
 export const generatePreviewOptions = (config: RendererConfig, pagePath: string): RendererOptions => {
   const options = generateCommonOptions(pagePath);
   const options = generateCommonOptions(pagePath);