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 14 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
20 changes: 13 additions & 7 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,13 +106,18 @@ 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
// 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
await execa(`yarn rw-vite-build`, {
Tobbe marked this conversation as resolved.
Show resolved Hide resolved
stdio: verbose ? 'inherit' : 'pipe',
shell: true,
cwd: rwjsPaths.web.base, // <-- important for postcss/tailwind
// @WARN DO NOT MERGE TEMPORARY HACK
// @WARN DO NOT MERGE TEMPORARY HACK
// @WARN DO NOT MERGE TEMPORARY HACK
// @WARN DO NOT MERGE TEMPORARY HACK
// @WARN DO NOT MERGE TEMPORARY HACK
// @WARN DO NOT MERGE TEMPORARY HACK
// @WARN DO NOT MERGE TEMPORARY HACK
process.chdir(rwjsPaths.web.base)

// @TODO: we need to use a binary here, so the the cwd is correct
await buildFeServer({
verbose,
})
} else {
await execa(
Expand Down
47 changes: 45 additions & 2 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 async function builder(yargs) {
const redwoodProjectPaths = getPaths()
const redwoodProjectConfig = getConfig()
Expand All @@ -26,6 +42,34 @@ export async function builder(yargs) {
.usage('usage: $0 <side>')
.command({
command: '$0',
descriptions: 'Run both api and web servers. Uses the web port and host',
handler: (argv) => {
recordTelemetryAttributes({
command,
port: argv.port,
host: argv.host,
socket: argv.socket,
apiHost: argv.apiHost,
})

streamServerErrorHandler()
},
builder: (yargs) =>
yargs.options({
port: {
default: redwoodProjectConfig.web.port,
type: 'number',
alias: 'p',
},
host: {
default: redwoodProjectConfig.web.host,
type: 'string',
},
socket: { type: 'string' },
}),
})
.command({
command: 'both',
description: 'Run both api and web servers. Uses the web port and host',
builder: (yargs) =>
yargs.options({
Expand Down Expand Up @@ -160,8 +204,7 @@ export async function builder(yargs) {
apiHost: argv.apiHost,
})

const { webServerHandler } = await import('./serveHandler.js')
await webServerHandler(argv)
streamServerErrorHandler()
},
})
.middleware((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 @@ -17,9 +17,12 @@
"rimraf": "./dist/bins/rimraf.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: 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
}
23 changes: 23 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,27 @@ export const getCommonPlugins = () => {
]
}

// @TODO double check this, think about it more carefully please!
Tobbe marked this conversation as resolved.
Show resolved Hide resolved
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 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" },
]
}
38 changes: 38 additions & 0 deletions packages/project-config/src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@ export interface WebPaths {
webpack: string
viteConfig: string | null // because vite is opt-in only
entryClient: string | null
entryServer: string | null
postcss: string
storybookConfig: string
storybookPreviewConfig: string
storybookManagerConfig: string
dist: string
distServer: string
distRouteHooks: string
routeManifest: string
types: string
}

Expand Down Expand Up @@ -101,13 +105,17 @@ const PATH_WEB_DIR_CONFIG = 'web/config'
const PATH_WEB_DIR_CONFIG_WEBPACK = 'web/config/webpack.config.js'
const PATH_WEB_DIR_CONFIG_VITE = 'web/vite.config' // .js,.ts
const PATH_WEB_DIR_ENTRY_CLIENT = 'web/src/entry.client' // .jsx,.tsx
const PATH_WEB_DIR_ENTRY_SERVER = 'web/src/entry-server' // .jsx,.tsx

const PATH_WEB_DIR_CONFIG_POSTCSS = 'web/config/postcss.config.js'
const PATH_WEB_DIR_CONFIG_STORYBOOK_CONFIG = 'web/config/storybook.config.js'
const PATH_WEB_DIR_CONFIG_STORYBOOK_PREVIEW = 'web/config/storybook.preview.js'
const PATH_WEB_DIR_CONFIG_STORYBOOK_MANAGER = 'web/config/storybook.manager.js'

const PATH_WEB_DIR_DIST = 'web/dist'
const PATH_WEB_DIR_DIST_SERVER = 'web/dist/server'
const PATH_WEB_DIR_DIST_SERVER_ROUTEHOOKS = 'web/dist/server/routeHooks'
const PATH_WEB_DIR_ROUTE_MANIFEST = 'web/dist/server/route-manifest.json'

/**
* The Redwood config file is used as an anchor for the base directory of a project.
Expand Down Expand Up @@ -212,8 +220,12 @@ export const getPaths = (BASE_DIR: string = getBaseDir()): Paths => {
PATH_WEB_DIR_CONFIG_STORYBOOK_MANAGER
),
dist: path.join(BASE_DIR, PATH_WEB_DIR_DIST),
distServer: path.join(BASE_DIR, PATH_WEB_DIR_DIST_SERVER),
distRouteHooks: path.join(BASE_DIR, PATH_WEB_DIR_DIST_SERVER_ROUTEHOOKS),
routeManifest: path.join(BASE_DIR, PATH_WEB_DIR_ROUTE_MANIFEST),
types: path.join(BASE_DIR, 'web/types'),
entryClient: resolveFile(path.join(BASE_DIR, PATH_WEB_DIR_ENTRY_CLIENT)), // new vite/stream entry point for client
entryServer: resolveFile(path.join(BASE_DIR, PATH_WEB_DIR_ENTRY_SERVER)),
},
}

Expand All @@ -224,6 +236,32 @@ export const getPaths = (BASE_DIR: string = getBaseDir()): Paths => {
return paths
}

/**
* Returns the route hook for the supplied page path.
* Note that the page name doesn't have to match
*
* @param pagePath
* @returns string
*/
export const getRouteHookForPage = (pagePath: string | undefined | null) => {
if (!pagePath) {
return null
}

// We just use fg, so if they make typos in the routeHook file name,
// it's all good, we'll still find it
return fg
.sync('*.routeHooks.{js,ts,tsx,jsx}', {
absolute: true,
cwd: path.dirname(pagePath), // the page's folder
})
.at(0)
}

export const getAppRouteHook = () => {
return resolveFile(path.join(getPaths().web.src, 'App.routeHooks'))
}

/**
* Process the pages directory and return information useful for automated imports.
*
Expand Down
Loading