-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathindex.ts
286 lines (267 loc) · 10.3 KB
/
index.ts
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
import { existsSync } from 'fs'
import path from 'path'
import react from '@vitejs/plugin-react'
import type { ConfigEnv, UserConfig, PluginOption } from 'vite'
import { normalizePath } from 'vite'
import EnvironmentPlugin from 'vite-plugin-environment'
import { getWebSideDefaultBabelConfig } from '@redwoodjs/internal/dist/build/babel/web'
import { getConfig, getPaths } from '@redwoodjs/project-config'
import { handleJsAsJsx } from './vite-plugin-jsx-loader'
/**
* Pre-configured vite plugin, with required config for Redwood apps.
*/
export default function redwoodPluginVite(): PluginOption[] {
const rwPaths = getPaths()
const rwConfig = getConfig()
const clientEntryPath = rwPaths.web.entryClient
if (!clientEntryPath) {
throw new Error(
'Vite client entry point not found. Please check that your project has an entry.client.{jsx,tsx} file in the web/src directory.'
)
}
const relativeEntryPath = path.relative(rwPaths.web.src, clientEntryPath)
return [
{
name: 'redwood-plugin-vite',
// ---------- Bundle injection ----------
// Used by Vite during dev, to inject the entrypoint.
transformIndexHtml: {
order: 'pre',
handler: (html: string) => {
// So we inject the entrypoint with the correct extension .tsx vs .jsx
// And then inject the entry
if (existsSync(clientEntryPath)) {
return html.replace(
'</head>',
// @NOTE the slash in front, for windows compatibility and for pages in subdirectories
`<script type="module" src="/${relativeEntryPath}"></script>
</head>`
)
} else {
return html
}
},
},
// Used by rollup during build to inject the entrypoint
// but note index.html does not come through as an id during dev
transform: (code: string, id: string) => {
if (
existsSync(clientEntryPath) &&
normalizePath(id) === normalizePath(rwPaths.web.html)
) {
return {
code: code.replace(
'</head>',
`<script type="module" src="/${relativeEntryPath}"></script>
</head>`
),
map: null,
}
} else {
return {
code,
map: null, // Returning null here preserves the original sourcemap
}
}
},
// ---------- End Bundle injection ----------
config: (options: UserConfig, env: ConfigEnv): UserConfig => {
return {
root: rwPaths.web.src,
// Disabling for now, let babel handle this for consistency
// resolve: {
// alias: [
// {
// find: 'src',
// replacement: redwoodPaths.web.src,
// },
// ],
// },
envPrefix: 'REDWOOD_ENV_',
publicDir: path.join(rwPaths.web.base, 'public'),
define: {
RWJS_ENV: {
// @NOTE we're avoiding process.env here, unlike webpack
RWJS_API_GRAPHQL_URL:
rwConfig.web.apiGraphQLUrl ?? rwConfig.web.apiUrl + '/graphql',
RWJS_API_URL: rwConfig.web.apiUrl,
__REDWOOD__APP_TITLE:
rwConfig.web.title || path.basename(rwPaths.base),
RWJS_EXP_STREAMING_SSR:
rwConfig.experimental.streamingSsr &&
rwConfig.experimental.streamingSsr.enabled,
RWJS_EXP_RSC: rwConfig.experimental?.rsc?.enabled,
},
RWJS_DEBUG_ENV: {
RWJS_SRC_ROOT: rwPaths.web.src,
REDWOOD_ENV_EDITOR: JSON.stringify(
process.env.REDWOOD_ENV_EDITOR
),
},
// Vite can automatically expose environment variables, but we
// disable that in `buildFeServer.ts` by setting `envFile: false`
// because we want to use our own logic for loading .env,
// .env.defaults, etc
// The two object spreads below will expose all environment
// variables listed in redwood.toml and all environment variables
// prefixed with REDWOOD_ENV_
...Object.fromEntries(
rwConfig.web.includeEnvironmentVariables.flatMap((envName) => [
[
`import.meta.env.${envName}`,
JSON.stringify(process.env[envName]),
],
[
`process.env.${envName}`,
JSON.stringify(process.env[envName]),
],
])
),
...Object.entries(process.env).reduce<Record<string, any>>(
(acc, [key, value]) => {
if (key.startsWith('REDWOOD_ENV_')) {
acc[`import.meta.env.${key}`] = JSON.stringify(value)
acc[`process.env.${key}`] = JSON.stringify(value)
}
return acc
},
{}
),
},
css: {
// @NOTE config path is relative to where vite.config.js is if you use relative path
// postcss: './config/',
postcss: rwPaths.web.config,
},
server: {
open: rwConfig.browser.open,
port: rwConfig.web.port,
host: rwConfig.web.host,
proxy: {
[rwConfig.web.apiUrl]: {
target: `http://${rwConfig.api.host}:${rwConfig.api.port}`,
changeOrigin: true,
// Remove the `.redwood/functions` part, but leave the `/graphql`
rewrite: (path) => path.replace(rwConfig.web.apiUrl, ''),
configure: (proxy) => {
// @MARK: this is a hack to prevent showing confusing proxy errors on startup
// because Vite launches so much faster than the API server.
let waitingForApiServer = true
// Wait for 2.5s, then restore regular proxy error logging
setTimeout(() => {
waitingForApiServer = false
}, 2500)
proxy.on('error', (err, _req, res) => {
if (
waitingForApiServer &&
err.message.includes('ECONNREFUSED')
) {
err.stack =
'⌛ API Server launching, please refresh your page...'
}
const msg = {
errors: [
{
message:
'The RedwoodJS API server is not available or is currently reloading. Please refresh.',
},
],
}
res.writeHead(203, {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
})
res.write(JSON.stringify(msg))
res.end()
})
},
},
},
},
build: {
outDir: options.build?.outDir || rwPaths.web.dist,
emptyOutDir: true,
manifest: !env.ssrBuild ? 'build-manifest.json' : undefined,
sourcemap: !env.ssrBuild && rwConfig.web.sourceMap, // Note that this can be boolean or 'inline'
},
legacy: {
buildSsrCjsExternalHeuristics: rwConfig.experimental?.rsc?.enabled
? false
: env.ssrBuild,
},
optimizeDeps: {
esbuildOptions: {
// @MARK this is because JS projects in Redwood don't have .jsx extensions
loader: {
'.js': 'jsx',
},
// Node.js global to browser globalThis
// @MARK unsure why we need this, but required for DevFatalErrorPage atleast
define: {
global: 'globalThis',
},
},
},
}
},
},
// Loading Environment Variables, to process.env in the browser
// This maintains compatibility with Webpack. We can choose to switch to import.meta.env at a later stage
EnvironmentPlugin('all', { prefix: 'REDWOOD_ENV_', loadEnvFiles: false }),
EnvironmentPlugin(
Object.fromEntries(
rwConfig.web.includeEnvironmentVariables.map((envName) => [
envName,
JSON.stringify(process.env[envName]),
])
),
{
loadEnvFiles: false, // to prevent vite from loading .env files
}
),
// -----------------
handleJsAsJsx(),
react({
babel: {
...getWebSideDefaultBabelConfig({
forVite: true,
}),
},
}),
{
name: 'redwood-plugin-vite-html-env',
// Vite can support replacing environment variables in index.html but
// there are currently two issues with that:
// 1. It requires the environment variables to be exposed on
// `import.meta.env`, but we expose them on `process.env` in Redwood.
// 2. There's an open issue on Vite where it adds extra quotes around
// the replaced values, which breaks trying to use environment
// variables in src attributes for example.
// Until those issues are resolved, we'll do the replacement ourselves
// instead using transformIndexHtml. Doing it this was was also the
// recommended way until Vite added built-in support for it.
//
// Extra quotes issue: https://github.com/vitejs/vite/issues/13424
// transformIndexHtml being the recommended way:
// https://github.com/vitejs/vite/issues/3105#issuecomment-1059975023
transformIndexHtml: {
// Setting order: 'pre' so that it runs before the built-in
// html env replacement.
order: 'pre',
handler: (html: string) => {
let newHtml = html
rwConfig.web.includeEnvironmentVariables.map((envName) => {
newHtml = newHtml.replaceAll(
`%${envName}%`,
process.env[envName] || ''
)
})
Object.entries(process.env).forEach(([envName, value]) => {
newHtml = newHtml.replaceAll(`%${envName}%`, value || '')
})
return newHtml
},
},
},
]
}