-
Notifications
You must be signed in to change notification settings - Fork 999
Expand file tree
/
Copy pathdevFeServer.ts
More file actions
171 lines (138 loc) · 5.37 KB
/
devFeServer.ts
File metadata and controls
171 lines (138 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import path from 'path'
import express from 'express'
import { renderToPipeableStream } from 'react-dom/server'
import { createServer as createViteServer } from 'vite'
import { getProjectRoutes } from '@redwoodjs/internal/dist/routes'
import { getAppRouteHook, getConfig, getPaths } from '@redwoodjs/project-config'
import { matchPath } from '@redwoodjs/router'
import type { TagDescriptor } from '@redwoodjs/web'
import { loadAndRunRouteHooks } from './triggerRouteHooks'
import { stripQueryStringAndHashFromPath } from './utils'
// These values are defined in the vite.config.ts
globalThis.RWJS_ENV = {}
// @MARK, @TODO Just so it doesn't error out. Not sure how to handle this.
globalThis.__REDWOOD__PRERENDER_PAGES = {}
async function createServer() {
const app = express()
const rwPaths = getPaths()
// @MARK: Vite is still experimental, and opt-in
if (!rwPaths.web.viteConfig) {
throw new Error(
'Vite config not found. You need to setup your project with Vite using `yarn rw setup vite`'
)
}
// Create Vite server in middleware mode and configure the app type as
// 'custom', disabling Vite's own HTML serving logic so parent server
// can take control
const vite = await createViteServer({
configFile: rwPaths.web.viteConfig,
server: { middlewareMode: true },
logLevel: 'info',
clearScreen: false,
appType: 'custom',
})
// use vite's connect instance as middleware
// if you use your own express router (express.Router()), you should use router.use
app.use(vite.middlewares)
app.use('*', async (req, res, next) => {
const currentPathName = stripQueryStringAndHashFromPath(req.originalUrl)
try {
const routes = getProjectRoutes()
// Do a simple match with regex, don't bother parsing params yet
const currentRoute = routes.find((route) => {
if (!route.matchRegexString) {
// This is the 404/NotFoundPage case
return false
}
const matches = [
...currentPathName.matchAll(new RegExp(route.matchRegexString, 'g')),
]
return matches.length > 0
})
let serverData = {}
let metaTags: TagDescriptor[] = []
if (currentRoute?.redirect) {
return res.redirect(currentRoute.redirect.to)
}
if (currentRoute) {
const parsedParams = currentRoute.hasParams
? matchPath(currentRoute.path, currentPathName).params
: undefined
const routeHookOutput = await loadAndRunRouteHooks({
paths: [getAppRouteHook(), currentRoute.routeHooks],
reqMeta: {
req,
parsedParams,
},
viteDevServer: vite, // because its dev
})
serverData = routeHookOutput.serverData
metaTags = routeHookOutput.meta
}
if (!currentRoute) {
// @TODO do something
}
if (!rwPaths.web.entryServer || !rwPaths.web.entryClient) {
throw new Error(
'Vite entry points not found. Please check that your project has an entry-client.{jsx,tsx} and entry-server.{jsx,tsx} file in the web/src directory.'
)
}
// 3. Load the server entry. vite.ssrLoadModule automatically transforms
// your ESM source code to be usable in Node.js! There is no bundling
// required, and provides efficient invalidation similar to HMR.
const { serverEntry } = await vite.ssrLoadModule(rwPaths.web.entryServer)
// Serialize route context so it can be passed to the client entry
const serializedRouteContext = JSON.stringify(serverData)
// @TODO 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
const FIXME_HardcodedIndexCss = ['index.css']
const assetMap = JSON.stringify({
css: FIXME_HardcodedIndexCss,
meta: metaTags,
})
const bootstrapModules = [
path.join(__dirname, '../inject', 'reactRefresh.js'),
]
const pageWithJs = currentRoute?.renderMode !== 'html'
if (pageWithJs) {
bootstrapModules.push(rwPaths.web.entryClient)
}
const { pipe } = renderToPipeableStream(
serverEntry({
url: currentPathName,
routeContext: serverData,
css: FIXME_HardcodedIndexCss,
meta: metaTags,
}),
{
bootstrapScriptContent: pageWithJs
? `window.__loadServerData = function() { return ${serializedRouteContext} }; window.__assetMap = function() { return ${assetMap} }`
: undefined,
bootstrapModules,
onShellReady() {
res.setHeader('content-type', 'text/html; charset=utf-8')
pipe(res)
},
}
)
} catch (e) {
// send back a SPA page
// res.status(200).set({ 'Content-Type': 'text/html' }).end(template)
// If an error is caught, let Vite fix the stack trace so it maps back to
// your actual source code.
vite.ssrFixStacktrace(e as any)
next(e)
}
})
const port = getConfig().web.port
console.log(`Started server on http://localhost:${port}`)
return await app.listen(port)
}
let devApp = createServer()
process.stdin.on('data', async (data) => {
const str = data.toString().trim().toLowerCase()
if (str === 'rs' || str === 'restart') {
;(await devApp).close()
devApp = createServer()
}
})