@@ -5,172 +5,17 @@ import colors from "picocolors";
5
5
6
6
import {
7
7
type ReactRouterPluginContext ,
8
- type ServerBundleBuildConfig ,
8
+ type EnvironmentName ,
9
+ type EnvironmentBuildContext ,
10
+ type EnvironmentOptionsResolvers ,
9
11
resolveViteConfig ,
10
12
extractPluginContext ,
11
- getServerBuildDirectory ,
13
+ getBuildManifest ,
14
+ getEnvironmentOptionsResolvers ,
12
15
} from "./plugin" ;
13
- import {
14
- type BuildManifest ,
15
- type ServerBundlesBuildManifest ,
16
- configRouteToBranchRoute ,
17
- } from "../config/config" ;
18
- import type { RouteManifestEntry , RouteManifest } from "../config/routes" ;
19
16
import invariant from "../invariant" ;
20
17
import { preloadVite , getVite } from "./vite" ;
21
18
22
- function getAddressableRoutes ( routes : RouteManifest ) : RouteManifestEntry [ ] {
23
- let nonAddressableIds = new Set < string > ( ) ;
24
-
25
- for ( let id in routes ) {
26
- let route = routes [ id ] ;
27
-
28
- // We omit the parent route of index routes since the index route takes ownership of its parent's path
29
- if ( route . index ) {
30
- invariant (
31
- route . parentId ,
32
- `Expected index route "${ route . id } " to have "parentId" set`
33
- ) ;
34
- nonAddressableIds . add ( route . parentId ) ;
35
- }
36
-
37
- // We omit pathless routes since they can only be addressed via descendant routes
38
- if ( typeof route . path !== "string" && ! route . index ) {
39
- nonAddressableIds . add ( id ) ;
40
- }
41
- }
42
-
43
- return Object . values ( routes ) . filter (
44
- ( route ) => ! nonAddressableIds . has ( route . id )
45
- ) ;
46
- }
47
-
48
- function getRouteBranch ( routes : RouteManifest , routeId : string ) {
49
- let branch : RouteManifestEntry [ ] = [ ] ;
50
- let currentRouteId : string | undefined = routeId ;
51
-
52
- while ( currentRouteId ) {
53
- let route : RouteManifestEntry = routes [ currentRouteId ] ;
54
- invariant ( route , `Missing route for ${ currentRouteId } ` ) ;
55
- branch . push ( route ) ;
56
- currentRouteId = route . parentId ;
57
- }
58
-
59
- return branch . reverse ( ) ;
60
- }
61
-
62
- type ReactRouterClientBuildArgs = {
63
- ssr : false ;
64
- serverBundleBuildConfig ?: never ;
65
- } ;
66
-
67
- type ReactRouterServerBuildArgs = {
68
- ssr : true ;
69
- serverBundleBuildConfig ?: ServerBundleBuildConfig ;
70
- } ;
71
-
72
- type ReactRouterBuildArgs =
73
- | ReactRouterClientBuildArgs
74
- | ReactRouterServerBuildArgs ;
75
-
76
- async function getServerBuilds ( ctx : ReactRouterPluginContext ) : Promise < {
77
- serverBuilds : ReactRouterServerBuildArgs [ ] ;
78
- buildManifest : BuildManifest ;
79
- } > {
80
- let { rootDirectory } = ctx ;
81
- const { routes, serverBuildFile, serverBundles, appDirectory } =
82
- ctx . reactRouterConfig ;
83
- let serverBuildDirectory = getServerBuildDirectory ( ctx ) ;
84
- if ( ! serverBundles ) {
85
- return {
86
- serverBuilds : [ { ssr : true } ] ,
87
- buildManifest : { routes } ,
88
- } ;
89
- }
90
-
91
- let { normalizePath } = await import ( "vite" ) ;
92
-
93
- let resolvedAppDirectory = path . resolve ( rootDirectory , appDirectory ) ;
94
- let rootRelativeRoutes = Object . fromEntries (
95
- Object . entries ( routes ) . map ( ( [ id , route ] ) => {
96
- let filePath = path . join ( resolvedAppDirectory , route . file ) ;
97
- let rootRelativeFilePath = normalizePath (
98
- path . relative ( rootDirectory , filePath )
99
- ) ;
100
- return [ id , { ...route , file : rootRelativeFilePath } ] ;
101
- } )
102
- ) ;
103
-
104
- let buildManifest : ServerBundlesBuildManifest = {
105
- serverBundles : { } ,
106
- routeIdToServerBundleId : { } ,
107
- routes : rootRelativeRoutes ,
108
- } ;
109
-
110
- let serverBundleBuildConfigById = new Map < string , ServerBundleBuildConfig > ( ) ;
111
-
112
- await Promise . all (
113
- getAddressableRoutes ( routes ) . map ( async ( route ) => {
114
- let branch = getRouteBranch ( routes , route . id ) ;
115
- let serverBundleId = await serverBundles ( {
116
- branch : branch . map ( ( route ) =>
117
- configRouteToBranchRoute ( {
118
- ...route ,
119
- // Ensure absolute paths are passed to the serverBundles function
120
- file : path . join ( resolvedAppDirectory , route . file ) ,
121
- } )
122
- ) ,
123
- } ) ;
124
- if ( typeof serverBundleId !== "string" ) {
125
- throw new Error ( `The "serverBundles" function must return a string` ) ;
126
- }
127
- if ( ! / ^ [ a - z A - Z 0 - 9 - _ ] + $ / . test ( serverBundleId ) ) {
128
- throw new Error (
129
- `The "serverBundles" function must only return strings containing alphanumeric characters, hyphens and underscores.`
130
- ) ;
131
- }
132
- buildManifest . routeIdToServerBundleId [ route . id ] = serverBundleId ;
133
-
134
- let relativeServerBundleDirectory = path . relative (
135
- rootDirectory ,
136
- path . join ( serverBuildDirectory , serverBundleId )
137
- ) ;
138
- let serverBuildConfig = serverBundleBuildConfigById . get ( serverBundleId ) ;
139
- if ( ! serverBuildConfig ) {
140
- buildManifest . serverBundles [ serverBundleId ] = {
141
- id : serverBundleId ,
142
- file : normalizePath (
143
- path . join ( relativeServerBundleDirectory , serverBuildFile )
144
- ) ,
145
- } ;
146
- serverBuildConfig = {
147
- routes : { } ,
148
- serverBundleId,
149
- } ;
150
- serverBundleBuildConfigById . set ( serverBundleId , serverBuildConfig ) ;
151
- }
152
- for ( let route of branch ) {
153
- serverBuildConfig . routes [ route . id ] = route ;
154
- }
155
- } )
156
- ) ;
157
-
158
- let serverBuilds = Array . from ( serverBundleBuildConfigById . values ( ) ) . map (
159
- ( serverBundleBuildConfig ) : ReactRouterServerBuildArgs => {
160
- let serverBuild : ReactRouterServerBuildArgs = {
161
- ssr : true ,
162
- serverBundleBuildConfig,
163
- } ;
164
- return serverBuild ;
165
- }
166
- ) ;
167
-
168
- return {
169
- serverBuilds,
170
- buildManifest,
171
- } ;
172
- }
173
-
174
19
async function cleanBuildDirectory (
175
20
viteConfig : Vite . ResolvedConfig ,
176
21
ctx : ReactRouterPluginContext
@@ -187,22 +32,23 @@ async function cleanBuildDirectory(
187
32
}
188
33
189
34
function getViteManifestPaths (
190
- ctx : ReactRouterPluginContext ,
191
- serverBuilds : Array < ReactRouterServerBuildArgs >
35
+ environmentOptionsResolvers : EnvironmentOptionsResolvers
192
36
) {
193
- let buildRelative = ( pathname : string ) =>
194
- path . resolve ( ctx . reactRouterConfig . buildDirectory , pathname ) ;
195
-
196
- let viteManifestPaths : Array < string > = [
197
- "client/.vite/manifest.json" ,
198
- ...serverBuilds . map ( ( { serverBundleBuildConfig } ) => {
199
- let serverBundleId = serverBundleBuildConfig ?. serverBundleId ;
200
- let serverBundlePath = serverBundleId ? serverBundleId + "/" : "" ;
201
- return `server/${ serverBundlePath } .vite/manifest.json` ;
202
- } ) ,
203
- ] . map ( ( srcPath ) => buildRelative ( srcPath ) ) ;
204
-
205
- return viteManifestPaths ;
37
+ return Object . entries ( environmentOptionsResolvers ) . map (
38
+ ( [ environmentName , resolveOptions ] ) => {
39
+ invariant (
40
+ resolveOptions ,
41
+ `Expected build environment options resolver for ${ environmentName } `
42
+ ) ;
43
+ let options = resolveOptions ( {
44
+ viteCommand : "build" ,
45
+ viteUserConfig : { } ,
46
+ } ) ;
47
+ let outDir = options . build . outDir ;
48
+ invariant ( outDir , `Expected build.outDir for ${ environmentName } ` ) ;
49
+ return path . join ( outDir , ".vite/manifest.json" ) ;
50
+ }
51
+ ) ;
206
52
}
207
53
208
54
export interface ViteBuildOptions {
@@ -253,10 +99,23 @@ export async function build(
253
99
254
100
let vite = getVite ( ) ;
255
101
256
- async function viteBuild ( {
257
- ssr,
258
- serverBundleBuildConfig,
259
- } : ReactRouterBuildArgs ) {
102
+ async function viteBuild (
103
+ environmentOptionsResolvers : EnvironmentOptionsResolvers ,
104
+ environmentName : EnvironmentName
105
+ ) {
106
+ let ssr = environmentName !== "client" ;
107
+
108
+ let resolveOptions = environmentOptionsResolvers [ environmentName ] ;
109
+ invariant (
110
+ resolveOptions ,
111
+ `Missing environment options resolver for ${ environmentName } `
112
+ ) ;
113
+
114
+ let environmentBuildContext : EnvironmentBuildContext = {
115
+ name : environmentName ,
116
+ resolveOptions,
117
+ } ;
118
+
260
119
await vite . build ( {
261
120
root,
262
121
mode,
@@ -271,22 +130,34 @@ export async function build(
271
130
optimizeDeps : { force } ,
272
131
clearScreen,
273
132
logLevel,
274
- ...( serverBundleBuildConfig
275
- ? { __reactRouterServerBundleBuildConfig : serverBundleBuildConfig }
276
- : { } ) ,
133
+ ...{ __reactRouterEnvironmentBuildContext : environmentBuildContext } ,
277
134
} ) ;
278
135
}
279
136
280
137
await cleanBuildDirectory ( viteConfig , ctx ) ;
281
138
139
+ let buildManifest = await getBuildManifest ( ctx ) ;
140
+ let environmentOptionsResolvers = await getEnvironmentOptionsResolvers (
141
+ ctx ,
142
+ buildManifest
143
+ ) ;
144
+
282
145
// Run the Vite client build first
283
- await viteBuild ( { ssr : false } ) ;
146
+ await viteBuild ( environmentOptionsResolvers , "client" ) ;
284
147
285
148
// Then run Vite SSR builds in parallel
286
- let { serverBuilds, buildManifest } = await getServerBuilds ( ctx ) ;
287
- await Promise . all ( serverBuilds . map ( viteBuild ) ) ;
149
+ let serverEnvironmentNames = (
150
+ Object . keys (
151
+ environmentOptionsResolvers
152
+ ) as ( keyof typeof environmentOptionsResolvers ) [ ]
153
+ ) . filter ( ( environmentName ) => environmentName !== "client" ) ;
154
+ await Promise . all (
155
+ serverEnvironmentNames . map ( ( environmentName ) =>
156
+ viteBuild ( environmentOptionsResolvers , environmentName )
157
+ )
158
+ ) ;
288
159
289
- let viteManifestPaths = getViteManifestPaths ( ctx , serverBuilds ) ;
160
+ let viteManifestPaths = getViteManifestPaths ( environmentOptionsResolvers ) ;
290
161
await Promise . all (
291
162
viteManifestPaths . map ( async ( viteManifestPath ) => {
292
163
let manifestExists = await fse . pathExists ( viteManifestPath ) ;
0 commit comments