Skip to content

Commit

Permalink
feat(ssr): Collect CSS links during dev (#9382)
Browse files Browse the repository at this point in the history
  • Loading branch information
dac09 authored Nov 9, 2023
1 parent bdf885a commit 1e42b43
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 15 deletions.
30 changes: 23 additions & 7 deletions packages/vite/src/devFeServer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { createServerAdapter } from '@whatwg-node/server'
import express from 'express'
import type { ViteDevServer } from 'vite'
import { createServer as createViteServer } from 'vite'

import type { RouteSpec } from '@redwoodjs/internal/dist/routes'
import { getProjectRoutes } from '@redwoodjs/internal/dist/routes'
import type { Paths } from '@redwoodjs/project-config'
import { getConfig, getPaths } from '@redwoodjs/project-config'

import { collectCssPaths, componentsModules } from './streaming/collectCss'
import { createReactStreamingHandler } from './streaming/createReactStreamingHandler'
import { registerFwGlobals } from './streaming/registerGlobals'
import { ensureProcessDirWeb } from './utils'
Expand Down Expand Up @@ -55,18 +59,12 @@ async function createServer() {

const routes = getProjectRoutes()

// TODO (STREAMING) CSS is handled by Vite in dev mode, we don't need to
// worry about it in dev but..... it causes a flash of unstyled content.
// For now I'm just injecting index css here
// Look at collectStyles in packages/vite/src/fully-react/find-styles.ts
const FIXME_HardcodedIndexCss = ['index.css']

for (const route of routes) {
const routeHandler = await createReactStreamingHandler(
{
route,
clientEntryPath: rwPaths.web.entryClient as string,
cssLinks: FIXME_HardcodedIndexCss,
getStylesheetLinks: () => getCssLinks(rwPaths, route, vite),
},
vite
)
Expand Down Expand Up @@ -100,3 +98,21 @@ process.stdin.on('data', async (data) => {
})
}
})

/**
* This function is used to collect the CSS links for a given route.
*
* Passed as a getter to the createReactStreamingHandler function, because
* at the time of creating the handler, the ViteDevServer hasn't analysed the module graph yet
*/
function getCssLinks(rwPaths: Paths, route: RouteSpec, vite: ViteDevServer) {
const appAndRouteModules = componentsModules(
[rwPaths.web.app, route.filePath].filter(Boolean) as string[],
vite
)

const collectedCss = collectCssPaths(appAndRouteModules)

const cssLinks = Array.from(collectedCss)
return cssLinks
}
4 changes: 2 additions & 2 deletions packages/vite/src/runFeServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,14 @@ export async function runFeServer() {
})
)

const collectedCss = indexEntry.css || []
const getStylesheetLinks = () => indexEntry.css || []
const clientEntry = '/' + indexEntry.file

for (const route of Object.values(routeManifest)) {
const routeHandler = await createReactStreamingHandler({
route,
clientEntryPath: clientEntry,
cssLinks: collectedCss,
getStylesheetLinks,
})

// if it is a 404, register it at the end somehow.
Expand Down
40 changes: 40 additions & 0 deletions packages/vite/src/streaming/collectCss.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { ViteDevServer, ModuleNode } from 'vite'

/**
* Collect SSR CSS for Vite
*/
export const componentsModules = (
components: string[],
vite: ViteDevServer
) => {
const matchedModules: Set<ModuleNode> = new Set()
components.forEach((component) => {
const modules = vite.moduleGraph.getModulesByFile(component)
modules?.forEach((mod) => {
matchedModules.add(mod)
})
})
return matchedModules
}

export const collectCssPaths = (
mods: Set<ModuleNode>,
cssLinks = new Set<string>(),
checkedComponents = new Set()
) => {
for (const mod of mods) {
if (
mod.file?.endsWith('.scss') ||
mod.file?.endsWith('.css') ||
mod.file?.endsWith('.less') // technically less is not supported oob by vite
) {
cssLinks.add(mod.url)
}
if (mod.importedModules.size > 0 && !checkedComponents.has(mod.id)) {
checkedComponents.add(mod.id)
collectCssPaths(mod.importedModules, cssLinks, checkedComponents)
}
}

return cssLinks
}
14 changes: 10 additions & 4 deletions packages/vite/src/streaming/createReactStreamingHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@ import { getAppRouteHook, getPaths } from '@redwoodjs/project-config'
import { matchPath } from '@redwoodjs/router'
import type { TagDescriptor } from '@redwoodjs/web'

// import { stripQueryStringAndHashFromPath } from '../utils'

import { reactRenderToStreamResponse } from './streamHelpers'
import { loadAndRunRouteHooks } from './triggerRouteHooks'

interface CreateReactStreamingHandlerOptions {
route: RWRouteManifestItem
clientEntryPath: string
cssLinks: string[]
getStylesheetLinks: () => string[]
}

const checkUaForSeoCrawler = isbot.spawn()
checkUaForSeoCrawler.exclude(['chrome-lighthouse'])

export const createReactStreamingHandler = async (
{ route, clientEntryPath, cssLinks }: CreateReactStreamingHandlerOptions,
{
route,
clientEntryPath,
getStylesheetLinks,
}: CreateReactStreamingHandlerOptions,
viteDevServer?: ViteDevServer
) => {
const { redirect, routeHooks, bundle } = route
Expand Down Expand Up @@ -103,6 +105,10 @@ export const createReactStreamingHandler = async (
req.headers.get('user-agent') || ''
)

// Using a function to get the CSS links because we need to wait for the
// vite dev server to analyze the module graph
const cssLinks = getStylesheetLinks()

const reactResponse = await reactRenderToStreamResponse(
{
ServerEntry,
Expand Down
14 changes: 12 additions & 2 deletions packages/web/src/components/htmlTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@ const extractFromAssetMap = (key: 'css' | 'meta') => {
return null
}

function addSlashIfNeeded(path: string): string {
if (path.startsWith('http') || path.startsWith('/')) {
return path
} else {
return '/' + path
}
}

/** CSS is a specialised metatag */
export const Css = ({ css }: { css: string[] }) => {
const cssLinks = css || extractFromAssetMap('css') || []
const cssLinks = (css || extractFromAssetMap('css') || []).map(
addSlashIfNeeded
)

return (
<>
{cssLinks.map((cssLink, index) => {
return (
<link rel="stylesheet" key={`css-${index}`} href={`/${cssLink}`} />
<link rel="stylesheet" key={`css-${index}`} href={`${cssLink}`} />
)
})}
</>
Expand Down

0 comments on commit 1e42b43

Please sign in to comment.