Skip to content

Commit

Permalink
implement a SSR plugin system to decouple vite-pages ssr from antd ss…
Browse files Browse the repository at this point in the history
…r steps
  • Loading branch information
csr632 committed Dec 13, 2022
1 parent 735266f commit a54d61d
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 45 deletions.
19 changes: 13 additions & 6 deletions packages/react-pages/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// this module exists so that users or themes can:
// this module exists so that users or themes can import utils from it:
// import { useStaticData } from "vite-plugin-react-pages/client"

// This module can be imported by theme, which may be optimized by vite
// so this module must be optimizable too.
// So this module can't import vite-pages core.
// Otherwise vite will try to optimize vite-pages core during dev.
// This module can be imported by theme. So:
// - This module can't import vite-pages core. Otherwise vite will try to optimize vite-pages core during dev (when theme been optimized or built).
// - This module can be duplicated (due to dep optimization, or built step of themes) and been executed multiple times

import type { UseStaticData, UseAllPagesOutlines } from './clientTypes'
import type {
UseStaticData,
UseAllPagesOutlines,
SSRPlugin,
} from './clientTypes'

// access globalThis['__vite_pages_use_static_data'] lazily
export const useStaticData: UseStaticData = (...params: any[]) => {
Expand All @@ -22,3 +25,7 @@ export const useAllPagesOutlines: UseAllPagesOutlines = (...params) => {
}

export type { Theme } from './clientTypes'

export function useSSRPlugin(ssrPlugin: SSRPlugin) {
;(globalThis as any)['__vite_pages_useSSRPlugin'](ssrPlugin)
}
8 changes: 8 additions & 0 deletions packages/react-pages/clientTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,11 @@ export interface TsInterfacePropertyInfo {
}

export type UseAllPagesOutlines = (timeout: number) => any

export type SSRPlugin = {
id: string
prepare: (app: React.ReactNode) => {
app?: React.ReactNode
extractStyle?: () => string
}
}
34 changes: 34 additions & 0 deletions packages/react-pages/src/client/SSRPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { createContext, useContext } from 'react'
import ReactDOM from 'react-dom/server'

import { isSSR } from './utils'
import type { SSRPlugin } from '../../clientTypes'
export type { SSRPlugin } from '../../clientTypes'

export const SSRPluginContext = createContext<SSRPluginMap>(new Map())

export type SSRPluginMap = Map<string, SSRPlugin>

/**
* render React tree on the server to get the server plugins that are declared by themes or users
*/
export function collectSSRPlugins(app: React.ReactNode): SSRPlugin[] {
const map: SSRPluginMap = new Map()
ReactDOM.renderToString(
<SSRPluginContext.Provider value={map}>{app}</SSRPluginContext.Provider>
)
return [...map.values()]
}

/**
* Users or themes use this function to declare SSR plugin.
* This function is exported from vite-plugin-react-pages/client
*/
export function useSSRPlugin(plugin: SSRPlugin) {
if (isSSR) {
const ctxVal = useContext(SSRPluginContext)
ctxVal?.set(plugin.id, plugin)
}
}

;(globalThis as any)['__vite_pages_useSSRPlugin'] = useSSRPlugin
45 changes: 38 additions & 7 deletions packages/react-pages/src/client/entries/ssg-server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,48 @@ import ssrData from '/@react-pages/ssrData'
import App from '../App'
import { dataCacheCtx } from '../ctx'
import type { PagesLoaded } from '../../../clientTypes'
import type { SSRPlugin } from '../SSRPlugin'
import { collectSSRPlugins as collect } from '../SSRPlugin'

export { ssrData }

// put all page data in cache, so that we don't need to load it in ssr
const dataCache: PagesLoaded = ssrData

export function renderToString(url: string) {
let ssrApp = (
export function renderToString(
url: string,
{ applySSRPlugins }: { applySSRPlugins?: SSRPlugin[] } = {}
) {
let ssrApp: React.ReactNode = <SSRApp url={url} />

const extractStyleArr: (() => string)[] = []
applySSRPlugins?.reverse().forEach((ssrPlugin) => {
const { app, extractStyle } = ssrPlugin.prepare(ssrApp)
if (extractStyle) extractStyleArr.push(extractStyle)
if (app) ssrApp = app
})

const contentText = ReactDOM.renderToString(ssrApp)

const styles = extractStyleArr
.map((extractStyle) => {
return extractStyle()
})
.filter(Boolean)
const styleText = styles.join('\n')

return {
contentText,
styleText,
}
}

export function collectSSRPlugins(url: string): SSRPlugin[] {
return collect(<SSRApp url={url} />)
}

function SSRApp({ url }: { url: string }) {
return (
<StaticRouter
basename={import.meta.env.BASE_URL?.replace(/\/$/, '')}
location={url}
Expand All @@ -27,9 +63,4 @@ export function renderToString(url: string) {
</dataCacheCtx.Provider>
</StaticRouter>
)
const contentText = ReactDOM.renderToString(ssrApp)
const styleText = ''
return { contentText, styleText }
}

export { ssrData }
5 changes: 3 additions & 2 deletions packages/react-pages/src/client/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useLayoutEffect, useEffect } from 'react'

export const isSSR = import.meta.env.SSR

// fix warning of useLayoutEffect during ssr
// https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
export const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect
export const useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect
7 changes: 6 additions & 1 deletion packages/react-pages/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ if (root) {
;(async () => {
if (!command || command === 'ssr') {
// user can pass in vite config like --outDir or --configFile
const viteConfig = await resolveConfig(argv, 'build')
const viteConfig = await resolveConfig(
argv,
'build',
'production',
'production'
)
const thisPlugin = viteConfig.plugins.find((plugin) => {
return plugin.name === 'vite-plugin-react-pages'
})
Expand Down
9 changes: 7 additions & 2 deletions packages/react-pages/src/node/static-site-generation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export async function ssrBuild(

console.log('\n\nrendering html...')

const { renderToString, ssrData } = await import(
const { renderToString, collectSSRPlugins, ssrData } = await import(
pathToFileURL(path.join(ssrOutDir, 'ssg-server.mjs')).toString()
)

Expand Down Expand Up @@ -120,6 +120,9 @@ export async function ssrBuild(
)
}

// render any page, and collect SSRPlugin from it
const ssrPlugins = collectSSRPlugins('/internal-404-page')

await Promise.all(
pagePaths.map(async (pagePath) => {
// currently not support pages with path params
Expand Down Expand Up @@ -165,7 +168,9 @@ export async function ssrBuild(
return

function renderHTML(pagePath: string) {
const { contentText, styleText } = renderToString(pagePath)
const { contentText, styleText } = renderToString(pagePath, {
applySSRPlugins: ssrPlugins,
})
const ssrInfo = {
routePath: pagePath,
}
Expand Down
15 changes: 2 additions & 13 deletions packages/theme-doc/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ export default {
format: 'esm',
sourcemap: true,
},
{
dir: 'dist-cjs',
format: 'cjs',
sourcemap: true,
},
],
external: [
'react',
Expand All @@ -31,6 +26,7 @@ export default {
resolve({
// prevent bundling unexpected deps
// resolveOnly: ['antd', /^antd\/.*$/, '@babel/runtime'],
// resolveOnly: ['none!'],
extensions,
}),
commonjs(),
Expand Down Expand Up @@ -73,14 +69,7 @@ export default {
chunk.imports.push('./index.css')
chunk.importedBindings['./index.css'] = []
const s = new MagicString(code)
if (options.format === 'cjs') {
if (code.startsWith(`'use strict';`)) {
s.remove(0, `'use strict';`.length)
}
s.prepend(`'use strict';\nrequire('./index.css');\n`)
} else {
s.prepend(`import './index.css';\n`)
}
s.prepend(`import './index.css';\n`)
const map = s.generateMap({ hires: true })
return {
code: s.toString(),
Expand Down
2 changes: 2 additions & 0 deletions packages/theme-doc/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AnchorLink from './components/AnchorLink'
import type { ThemeConfig, ThemeContextValue } from './ThemeConfig.doc'
import { normalizeI18nConfig, useIsomorphicLayoutEffect } from './utils'
import { getPageGroups, matchPagePathLocalePrefix } from './analyzeStaticData'
import { useAntdSSRPlugin } from './useAntdSSRPlugin'

export function createTheme(
originalThemeConfig: ThemeConfig
Expand All @@ -24,6 +25,7 @@ export function createTheme(
}

const ThemeComp = (props: ThemeProps) => {
useAntdSSRPlugin()
const { loadState, loadedData } = props
const staticData = useStaticData()

Expand Down
12 changes: 0 additions & 12 deletions packages/theme-doc/src/prepareSSR.tsx

This file was deleted.

20 changes: 20 additions & 0 deletions packages/theme-doc/src/useAntdSSRPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { useSSRPlugin } from 'vite-plugin-react-pages/client'
// https://ant.design/docs/react/customize-theme#server-side-render-ssr
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs'
import { isSSR } from './utils'

export function useAntdSSRPlugin() {
if (isSSR) {
useSSRPlugin({
id: 'vite-pages-theme-doc-antd-ssr',
prepare(app) {
const cache = createCache()
return {
app: <StyleProvider cache={cache}>{app}</StyleProvider>,
extractStyle: () => extractStyle(cache),
}
},
})
}
}
2 changes: 1 addition & 1 deletion packages/theme-doc/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function commonjsExportsInterop<T>(commonjsExports: T) {

export const Anchor_Scroll_Offset = 72

export const isSSR = typeof window === 'undefined'
export const isSSR = import.meta.env.SSR

// fix warning of useLayoutEffect during ssr
// https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
Expand Down
2 changes: 1 addition & 1 deletion packages/theme-doc/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"exclude": ["./lib/**/*"],
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"module": "ESNext",
"rootDir": "./src",
"outDir": "./lib"
}
Expand Down

0 comments on commit a54d61d

Please sign in to comment.