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 9 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
25 changes: 15 additions & 10 deletions packages/cli/src/commands/buildHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import path from 'path'

import execa from 'execa'
import { Listr } from 'listr2'
import { rimraf } from 'rimraf'
import terminalLink from 'terminal-link'
import rimraf from 'rimraf'

import { recordTelemetryAttributes } from '@redwoodjs/cli-helpers'
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 { getConfig, getPaths } from '../lib'
import { generatePrismaCommand } from '../lib/generatePrismaClient'

export const handler = async ({
Expand Down Expand Up @@ -105,13 +105,18 @@ export const handler = async ({
title: 'Building Web...',
task: async () => {
if (getConfig().web.bundler === 'vite') {
// @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
1 change: 1 addition & 0 deletions packages/cli/src/commands/devHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const handler = async ({
if (forwardedPortMatches.length) {
webPreferredPort = parseInt(forwardedPortMatches.pop().groups.port)
}

webAvailablePort = await getFreePort(webPreferredPort, [
apiPreferredPort,
apiAvailablePort,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Link, routes } from '@redwoodjs/router'
import { MetaTags } from '@redwoodjs/web'
<% if (paramName !== '') { %>
type ${pascalName}PageProps = {
${paramName}: ${paramType}
Expand All @@ -8,8 +7,6 @@ type ${pascalName}PageProps = {
const ${pascalName}Page = (<%- paramName === '' ? '' : `${propParam}: ${pascalName}PageProps` %>) => {
return (
<>
<MetaTags title="${pascalName}" description="${pascalName} page" />

<h1>${pascalName}Page</h1>
<p>
Find me in <code>${outputPath}</code>
Expand Down
124 changes: 36 additions & 88 deletions packages/cli/src/commands/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,39 @@ function hasExperimentalServerFile() {
return fs.existsSync(serverFilePath)
}

export async function builder(yargs) {
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) => {
const redwoodProjectPaths = getPaths()
const redwoodProjectConfig = getConfig()

const { apiCliOptions, webCliOptions, commonOptions, apiServerHandler } =
await import('@redwoodjs/api-server')

yargs
.usage('usage: $0 <side>')
.command({
command: '$0',
descriptions: 'Run both api and web servers',
handler: streamServerErrorHandler,
builder: (yargs) => yargs.options(commonOptions),
})
.command({
command: 'both',
description: 'Run both api and web servers. Uses the web port and host',
builder: (yargs) =>
yargs.options({
Expand Down Expand Up @@ -77,100 +102,23 @@ export async function builder(yargs) {
})
.command({
command: 'api',
description: 'Start server for serving only the api',
builder: (yargs) =>
yargs.options({
port: {
default: redwoodProjectConfig.api.port,
type: 'number',
alias: 'p',
},
host: {
default: redwoodProjectConfig.api.host,
type: 'string',
},
socket: { type: 'string' },
apiRootPath: {
alias: ['api-root-path', 'rootPath', 'root-path'],
default: '/',
type: 'string',
desc: 'Root path where your api functions are served',
coerce: coerceRootPath,
},
}),
handler: async (argv) => {
recordTelemetryAttributes({
command,
port: argv.port,
host: argv.host,
socket: argv.socket,
apiRootPath: argv.apiRootPath,
})

// Run the experimental server file, if it exists, api side only
if (hasExperimentalServerFile()) {
console.log(
[
separator,
`🧪 ${chalk.green('Experimental Feature')} 🧪`,
separator,
'Using the experimental API server file at api/dist/server.js',
separator,
].join('\n')
)
await execa('yarn', ['node', path.join('dist', 'server.js')], {
cwd: redwoodProjectPaths.api.base,
stdio: 'inherit',
shell: true,
})
return
}

const { apiServerHandler } = await import('./serveHandler.js')
await apiServerHandler(argv)
},
description: 'start server for serving only the api',
handler: apiServerHandler,
builder: (yargs) => yargs.options(apiCliOptions),
})
.command({
command: 'web',
description: 'Start server for serving only the web side',
builder: (yargs) =>
yargs.options({
port: {
default: redwoodProjectConfig.web.port,
type: 'number',
alias: 'p',
},
host: {
default: redwoodProjectConfig.web.host,
type: 'string',
},
socket: { type: 'string' },
apiHost: {
alias: 'api-host',
type: 'string',
desc: 'Forward requests from the apiUrl, defined in redwood.toml to this host',
},
}),
handler: async (argv) => {
recordTelemetryAttributes({
command,
port: argv.port,
host: argv.host,
socket: argv.socket,
apiHost: argv.apiHost,
})

const { webServerHandler } = await import('./serveHandler.js')
await webServerHandler(argv)
},
description: 'start server for serving only the web side',
handler: streamServerErrorHandler,
builder: (yargs) => yargs.options(webCliOptions),
})
.middleware((argv) => {
// Make sure the relevant side has been built, before serving
const positionalArgs = argv._

if (
positionalArgs.includes('web') &&
!fs.existsSync(path.join(redwoodProjectPaths.web.dist), 'index.html')
!fs.existsSync(path.join(getPaths().web.dist), 'index.html')
) {
console.error(
c.error(
Expand All @@ -182,7 +130,7 @@ export async function builder(yargs) {

if (
positionalArgs.includes('api') &&
!fs.existsSync(path.join(redwoodProjectPaths.api.dist))
!fs.existsSync(path.join(getPaths().api.dist))
) {
console.error(
c.error(
Expand All @@ -195,8 +143,8 @@ export async function builder(yargs) {
if (
// serve both
positionalArgs.length === 1 &&
(!fs.existsSync(path.join(redwoodProjectPaths.api.dist)) ||
!fs.existsSync(path.join(redwoodProjectPaths.web.dist), 'index.html'))
(!fs.existsSync(path.join(getPaths().api.dist)) ||
!fs.existsSync(path.join(getPaths().web.dist), 'index.html'))
) {
console.error(
c.error(
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
}
41 changes: 38 additions & 3 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 @@ -60,12 +62,45 @@ if (!RUNTIME_CORE_JS_VERSION) {

export const getCommonPlugins = () => {
return [
['@babel/plugin-proposal-class-properties', { loose: true }],
[
'@babel/plugin-proposal-class-properties',
{ loose: true },
'rw-class-properties',
],
// Note: The private method loose mode configuration setting must be the
// same as @babel/plugin-proposal class-properties.
// (https://babeljs.io/docs/en/babel-plugin-proposal-private-methods#loose)
['@babel/plugin-proposal-private-methods', { loose: true }],
['@babel/plugin-proposal-private-property-in-object', { loose: true }],
[
'@babel/plugin-proposal-private-methods',
{ loose: true },
'rw-private-methods',
],
[
'@babel/plugin-proposal-private-property-in-object',
{ loose: true },
'rw-private-prop-in-object',
],
]
}

// @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',
],
]
}

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
Loading
Loading