Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beginnings of Server Rendering & Streaming #8561

Merged
merged 37 commits into from
Jun 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d74dab7
Pull in changes from experimental render modes branch into Suspense R…
dac09 Jun 7, 2023
66d566c
Merge branch 'main' into feat/kc-dc-server-rendering
dac09 Jun 8, 2023
137f780
Fix bad merge
dac09 Jun 8, 2023
3db95ab
Merge branch 'main' into feat/kc-dc-server-rendering
dac09 Jun 12, 2023
3a9d696
Merge branch 'main' into feat/kc-dc-server-rendering
dac09 Jun 13, 2023
e4bc979
Recursively trigger routeHooks
dac09 Jun 13, 2023
29499b8
Make it build
Tobbe Jun 25, 2023
93603ba
Merge branch 'main' into feat/kc-dc-server-rendering
Tobbe Jun 25, 2023
a177424
extract assetMap
Tobbe Jun 25, 2023
01df1fc
Limit code churn
Tobbe Jun 25, 2023
0268f2f
Limit code churn project.mjs
Tobbe Jun 25, 2023
fbbfdeb
Merge serve.js with latest on main
Tobbe Jun 25, 2023
f29f9dd
babel common.ts: Undo custom plugin names to reduce churn
Tobbe Jun 26, 2023
351e3ba
Merge branch 'main' into feat/kc-dc-server-rendering
Tobbe Jun 26, 2023
12bf302
Resolve getRouteHookBabelPlugins comment
Tobbe Jun 27, 2023
86710c3
Use @TODO for GitHub highlighting to work
Tobbe Jun 27, 2023
94aa4e5
TODO (STREAMING) everywhere
Tobbe Jun 27, 2023
68a50e5
Merge branch 'main' into feat/kc-dc-server-rendering
Tobbe Jun 28, 2023
d48d756
after yarn install
Tobbe Jun 28, 2023
85f1f76
Fix merge conflict
Tobbe Jun 28, 2023
aae431e
Merge branch 'main' into feat/kc-dc-server-rendering
Tobbe Jun 29, 2023
eacfff0
Fix serve.js merge
Tobbe Jun 29, 2023
14a1c63
More serve.js fixes
Tobbe Jun 29, 2023
f2f191d
serve.js keep trying
Tobbe Jun 29, 2023
eac9296
Revert prerender changes to make current CI pass
Tobbe Jun 29, 2023
142cbe6
buildHandler: Wrap in feature flag check
Tobbe Jun 29, 2023
1791dee
serve.js: streamingSsr feature flag
Tobbe Jun 29, 2023
7389399
Work on getting this mergable by getting behavior closer to what's in…
Tobbe Jun 29, 2023
58870fe
Remove serverData
Tobbe Jun 29, 2023
fdf7d64
runFeServer: Add comment about new package
Tobbe Jun 29, 2023
8a3925d
apollo: streamingSsr feature flag
Tobbe Jun 29, 2023
b9a410e
Merge branch 'main' into feat/kc-dc-server-rendering
Tobbe Jun 29, 2023
196132e
Merge branch 'main' into feat/kc-dc-server-rendering
Tobbe Jun 29, 2023
9841fd2
Update paths tests
Tobbe Jun 30, 2023
b606f19
Merge branch 'feat/kc-dc-server-rendering' of https://github.com/redw…
Tobbe Jun 30, 2023
3b56964
No unsupported import assertions
Tobbe Jun 30, 2023
6117d6e
Merge branch 'main' into feat/kc-dc-server-rendering
Tobbe Jun 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions packages/cli/src/commands/buildHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { buildApi } from '@redwoodjs/internal/dist/build/api'
import { loadAndValidateSdls } from '@redwoodjs/internal/dist/validateSchema'
import { detectPrerenderRoutes } from '@redwoodjs/prerender/detection'
import { timedTelemetry } from '@redwoodjs/telemetry'
import { buildFeServer } from '@redwoodjs/vite'

import { getPaths, getConfig } from '../lib'
import { generatePrismaCommand } from '../lib/generatePrismaClient'
Expand Down Expand Up @@ -105,19 +106,28 @@ export const handler = async ({
title: 'Building Web...',
task: async () => {
if (getConfig().web.bundler !== 'webpack') {
// @NOTE: we're using the vite build command here, instead of the
// buildWeb function directly because we want the process.cwd to be
// the web directory, not the root of the project.
// This is important for postcss/tailwind to work correctly
// Having a separate binary lets us contain the change of cwd to that
// process only. If we changed cwd here, or in the buildWeb function,
// it could affect other things that run in parallel while building.
// We don't have any parallel tasks right now, but someone might add
// one in the future as a performance optimization.
await execa(`yarn rw-vite-build --webDir="${rwjsPaths.web.base}"`, {
stdio: verbose ? 'inherit' : 'pipe',
shell: true,
})
if (!getConfig().experimental?.streamingSsr?.enabled) {
// @NOTE: we're using the vite build command here, instead of the
// buildWeb function directly because we want the process.cwd to be
// the web directory, not the root of the project.
// This is important for postcss/tailwind to work correctly
// Having a separate binary lets us contain the change of cwd to that
// process only. If we changed cwd here, or in the buildWeb function,
// it could affect other things that run in parallel while building.
// We don't have any parallel tasks right now, but someone might add
// one in the future as a performance optimization.
await execa(`yarn rw-vite-build --webDir="${rwjsPaths.web.base}"`, {
stdio: verbose ? 'inherit' : 'pipe',
shell: true,
})
} else {
// TODO (STREAMING) we need to contain this in a separate binary
process.chdir(rwjsPaths.web.base)

// TODO (STREAMING) we need to use a binary here, so the the cwd is correct
// Should merge this with the existing rw-vite-build binary
await buildFeServer({ verbose })
}
} else {
await execa(
`yarn cross-env NODE_ENV=production webpack --config ${require.resolve(
Expand Down
26 changes: 26 additions & 0 deletions packages/cli/src/commands/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ function hasExperimentalServerFile() {
return fs.existsSync(serverFilePath)
}

const streamServerErrorHandler = () => {
console.error('⚠️ Experimental Render Mode ~ Cannot serve the web side ⚠️')
console.log('~'.repeat(50))
console.log()
console.log()
console.log('You can run the new frontend server with: `yarn rw-serve-fe`')
console.log('You can run the api server with: yarn rw serve api')
console.log()
console.log()
console.log('~'.repeat(50))

throw new Error(
'You will need to run the FE server and API server separately.'
)
}

export const builder = async (yargs) => {
yargs
.usage('usage: $0 <side>')
Expand All @@ -41,6 +57,11 @@ export const builder = async (yargs) => {
socket: argv.socket,
})

if (getConfig().experimental?.streamingSsr?.enabled) {
streamServerErrorHandler()
return
}

// Run the experimental server file, if it exists, with web side also
if (hasExperimentalServerFile()) {
console.log(
Expand Down Expand Up @@ -145,6 +166,11 @@ export const builder = async (yargs) => {
apiHost: argv.apiHost,
})

if (getConfig().experimental?.streamingSsr?.enabled) {
streamServerErrorHandler()
return
}

const { webServerHandler } = await import('./serveHandler.js')
await webServerHandler(argv)
},
Expand Down
3 changes: 3 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
"redwood": "./dist/bins/redwood.js",
"rw": "./dist/bins/redwood.js",
"rw-api-server-watch": "./dist/bins/rw-api-server-watch.js",
"rw-dev-fe": "./dist/bins/rw-dev-fe.js",
"rw-gen": "./dist/bins/rw-gen.js",
"rw-gen-watch": "./dist/bins/rw-gen-watch.js",
"rw-log-formatter": "./dist/bins/rw-log-formatter.js",
"rw-serve-api": "./dist/bins/rw-serve-api.js",
"rw-serve-fe": "./dist/bins/rw-serve-fe.js",
"rwfw": "./dist/bins/rwfw.js"
},
"files": [
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/bins/rw-dev-fe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env node
import { createRequire } from 'module'

const requireFromRwVite = createRequire(
require.resolve('@redwoodjs/vite/package.json')
)

const bins = requireFromRwVite('./package.json')['bin']

requireFromRwVite(bins['rw-dev-fe'])
10 changes: 10 additions & 0 deletions packages/core/src/bins/rw-serve-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env node
import { createRequire } from 'module'

const requireFromApiServer = createRequire(
require.resolve('@redwoodjs/api-server/package.json')
)

const bins = requireFromApiServer('./package.json')['bin']

requireFromApiServer(bins['rw-serve-api'])
10 changes: 10 additions & 0 deletions packages/core/src/bins/rw-serve-fe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env node
import { createRequire } from 'module'

const requireFromRwVite = createRequire(
require.resolve('@redwoodjs/vite/package.json')
)

const bins = requireFromRwVite('./package.json')['bin']

requireFromRwVite(bins['rw-serve-fe'])
1 change: 1 addition & 0 deletions packages/internal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@graphql-codegen/typescript-resolvers": "3.2.1",
"@redwoodjs/graphql-server": "5.0.0",
"@redwoodjs/project-config": "5.0.0",
"@redwoodjs/router": "5.0.0",
"@sdl-codegen/node": "0.0.10",
"babel-plugin-graphql-tag": "3.3.0",
"babel-plugin-polyfill-corejs3": "0.8.1",
Expand Down
24 changes: 24 additions & 0 deletions packages/internal/src/build/babel/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,27 @@ export const prebuildApiFile = (
})
return result
}

// TODO (STREAMING) I changed the prebuildApiFile function in https://github.com/redwoodjs/redwood/pull/7672/files
// but we had to revert. For this branch temporarily, I'm going to add a new function
// This is used in building routeHooks
export const transformWithBabel = (
srcPath: string,
plugins: TransformOptions['plugins']
) => {
const code = fs.readFileSync(srcPath, 'utf-8')
const defaultOptions = getApiSideDefaultBabelConfig()

const result = transform(code, {
...defaultOptions,
cwd: getPaths().api.base,
filename: srcPath,
// we need inline sourcemaps at this level
// because this file will eventually be fed to esbuild
// when esbuild finds an inline sourcemap, it tries to "combine" it
// so the final sourcemap (the one that esbuild generates) combines both mappings
sourceMaps: 'inline',
plugins,
})
return result
}
24 changes: 24 additions & 0 deletions packages/internal/src/build/babel/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { parseConfigFileTextToJson } from 'typescript'

import { getPaths } from '@redwoodjs/project-config'

import { getWebSideBabelPlugins } from './web'

const pkgJson = require('../../../package.json')

export interface RegisterHookOptions {
Expand Down Expand Up @@ -69,6 +71,28 @@ export const getCommonPlugins = () => {
]
}

// TODO (STREAMING) double check this, think about it more carefully please!
// It's related to yarn workspaces to be or not to be
export const getRouteHookBabelPlugins = () => {
return [
...getWebSideBabelPlugins({
forVite: true,
}),
[
'babel-plugin-module-resolver',
{
alias: {
'api/src': './src',
},
root: [getPaths().api.base],
cwd: 'packagejson',
loglevel: 'silent', // to silence the unnecessary warnings
},
'rwjs-api-module-resolver',
],
]
}

/**
* Finds, reads and parses the [ts|js]config.json file
* @returns The config object
Expand Down
1 change: 1 addition & 0 deletions packages/internal/src/build/babel/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const getWebSideBabelPlugins = (
forJest ? rwjsPaths.web.src : './src',
// adds the paths from [ts|js]config.json to the module resolver
...getPathsFromConfig(tsConfigs.web),
$api: rwjsPaths.api.base,
},
root: [rwjsPaths.web.base],
cwd: 'packagejson',
Expand Down
7 changes: 7 additions & 0 deletions packages/internal/src/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ export const findApiDistFunctions = (cwd: string = getPaths().api.base) => {
})
}

export const findRouteHooksSrc = (cwd: string = getPaths().web.src) => {
return fg.sync('**/*.routeHooks.{js,ts,tsx,jsx}', {
absolute: true,
cwd,
})
}

export const findPrerenderedHtml = (cwd = getPaths().web.dist) =>
fg.sync('**/*.html', { cwd, ignore: ['200.html', '404.html'] })

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ declare module 'src/services/**/*.{js,ts}'
declare module 'src/directives/**/*.{js,ts}'
declare module 'src/graphql/**/*.sdl.{js,ts}'
declare module 'src/subscriptions/**/*.{js,ts}'


declare module 'api/src/services/**/*.{js,ts}'
declare module 'api/src/directives/**/*.{js,ts}'
declare module 'api/src/graphql/**/*.sdl.{js,ts}'
50 changes: 49 additions & 1 deletion packages/internal/src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import path from 'path'

import chalk from 'chalk'

import { getPaths } from '@redwoodjs/project-config'
import { getPaths, getRouteHookForPage } from '@redwoodjs/project-config'
import { getRouteRegexAndParams } from '@redwoodjs/router'

// Circular dependency when trying to use the standard import
const { getProject } = require('@redwoodjs/structure/dist/index')
Expand Down Expand Up @@ -63,3 +66,48 @@ export function warningForDuplicateRoutes() {
}
return message.trimEnd()
}

export interface RouteSpec {
name: string
path: string
hasParams: boolean
id: string
isNotFound: boolean
filePath: string | undefined
relativeFilePath: string | undefined
routeHooks: string | undefined | null
matchRegexString: string | null
redirect: { to: string; permanent: boolean } | null
renderMode: 'stream' | 'html'
}

export const getProjectRoutes = (): RouteSpec[] => {
const rwProject = getProject(getPaths().base)
const routes = rwProject.getRouter().routes

return routes.map((route: any) => {
const { matchRegexString, routeParams } = route.isNotFound
? { matchRegexString: null, routeParams: null }
: getRouteRegexAndParams(route.path)

return {
name: route.isNotFound ? 'NotFoundPage' : route.name,
path: route.isNotFound ? 'notfound' : route.path,
hasParams: route.hasParameters,
id: route.id,
isNotFound: route.isNotFound,
filePath: route.page?.filePath,
relativeFilePath: route.page?.filePath
? path.relative(getPaths().web.src, route.page?.filePath)
: undefined,
routeHooks: getRouteHookForPage(route.page?.filePath),
renderMode: route.renderMode,
matchRegexString: matchRegexString,
paramNames: routeParams,
// TODO (STREAMING) deal with permanent/temp later
redirect: route.redirect
? { to: route.redirect, permanent: false }
: null,
}
})
}
1 change: 1 addition & 0 deletions packages/internal/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"references": [
{ "path": "../graphql-server" }, // ODD, but we do this so we dont have to have internal as a runtime dependency
{ "path": "../project-config" },
{ "path": "../router" },
]
}
Loading
Loading