@@ -14,23 +14,78 @@ import {
14
14
} from '@angular-devkit/architect' ;
15
15
import { json } from '@angular-devkit/core' ;
16
16
import * as fs from 'fs' ;
17
+ import { readFile } from 'node:fs/promises' ;
17
18
import ora from 'ora' ;
18
19
import * as path from 'path' ;
19
20
import Piscina from 'piscina' ;
20
21
import { normalizeOptimization } from '../../utils' ;
21
22
import { maxWorkers } from '../../utils/environment-options' ;
22
23
import { assertIsError } from '../../utils/error' ;
23
24
import { augmentAppWithServiceWorker } from '../../utils/service-worker' ;
25
+ import { getIndexOutputFile } from '../../utils/webpack-browser-config' ;
24
26
import { BrowserBuilderOutput } from '../browser' ;
25
27
import { Schema as BrowserBuilderOptions } from '../browser/schema' ;
26
28
import { ServerBuilderOutput } from '../server' ;
27
29
import type { RenderOptions , RenderResult } from './render-worker' ;
30
+ import { RoutesExtractorWorkerData } from './routes-extractor-worker' ;
28
31
import { Schema } from './schema' ;
29
- import { getIndexOutputFile , getRoutes } from './utils' ;
30
32
31
33
type PrerenderBuilderOptions = Schema & json . JsonObject ;
32
34
type PrerenderBuilderOutput = BuilderOutput ;
33
35
36
+ class RoutesSet extends Set < string > {
37
+ override add ( value : string ) : this {
38
+ return super . add ( value . charAt ( 0 ) === '/' ? value . slice ( 1 ) : value ) ;
39
+ }
40
+ }
41
+
42
+ async function getRoutes (
43
+ indexFile : string ,
44
+ outputPath : string ,
45
+ serverBundlePath : string ,
46
+ options : PrerenderBuilderOptions ,
47
+ workspaceRoot : string ,
48
+ ) : Promise < string [ ] > {
49
+ const { routes : extraRoutes = [ ] , routesFile, discoverRoutes } = options ;
50
+ const routes = new RoutesSet ( extraRoutes ) ;
51
+
52
+ if ( routesFile ) {
53
+ const routesFromFile = ( await readFile ( path . join ( workspaceRoot , routesFile ) , 'utf8' ) ) . split (
54
+ / \r ? \n / ,
55
+ ) ;
56
+ for ( const route of routesFromFile ) {
57
+ routes . add ( route ) ;
58
+ }
59
+ }
60
+
61
+ if ( discoverRoutes ) {
62
+ const renderWorker = new Piscina ( {
63
+ filename : require . resolve ( './routes-extractor-worker' ) ,
64
+ maxThreads : 1 ,
65
+ workerData : {
66
+ indexFile,
67
+ outputPath,
68
+ serverBundlePath,
69
+ zonePackage : require . resolve ( 'zone.js' , { paths : [ workspaceRoot ] } ) ,
70
+ } as RoutesExtractorWorkerData ,
71
+ } ) ;
72
+
73
+ const extractedRoutes : string [ ] = await renderWorker
74
+ . run ( { } )
75
+ . finally ( ( ) => void renderWorker . destroy ( ) ) ;
76
+
77
+ for ( const route of extractedRoutes ) {
78
+ routes . add ( route ) ;
79
+ }
80
+ }
81
+
82
+ if ( routes . size === 0 ) {
83
+ throw new Error ( 'Could not find any routes to prerender.' ) ;
84
+ }
85
+
86
+ return [ ...routes ] ;
87
+ }
88
+
34
89
/**
35
90
* Schedules the server and browser builds and returns their results if both builds are successful.
36
91
*/
@@ -80,7 +135,7 @@ async function _scheduleBuilds(
80
135
* <route>/index.html for each output path in the browser result.
81
136
*/
82
137
async function _renderUniversal (
83
- routes : string [ ] ,
138
+ options : PrerenderBuilderOptions ,
84
139
context : BuilderContext ,
85
140
browserResult : BrowserBuilderOutput ,
86
141
serverResult : ServerBuilderOutput ,
@@ -98,7 +153,7 @@ async function _renderUniversal(
98
153
) ;
99
154
100
155
// Users can specify a different base html file e.g. "src/home.html"
101
- const indexFile = getIndexOutputFile ( browserOptions ) ;
156
+ const indexFile = getIndexOutputFile ( browserOptions . index ) ;
102
157
const { styles : normalizedStylesOptimization } = normalizeOptimization (
103
158
browserOptions . optimization ,
104
159
) ;
@@ -112,15 +167,26 @@ async function _renderUniversal(
112
167
workerData : { zonePackage } ,
113
168
} ) ;
114
169
170
+ let routes : string [ ] | undefined ;
171
+
115
172
try {
116
173
// We need to render the routes for each locale from the browser output.
117
174
for ( const { path : outputPath } of browserResult . outputs ) {
118
175
const localeDirectory = path . relative ( browserResult . baseOutputPath , outputPath ) ;
119
176
const serverBundlePath = path . join ( baseOutputPath , localeDirectory , 'main.js' ) ;
177
+
120
178
if ( ! fs . existsSync ( serverBundlePath ) ) {
121
179
throw new Error ( `Could not find the main bundle: ${ serverBundlePath } ` ) ;
122
180
}
123
181
182
+ routes ??= await getRoutes (
183
+ indexFile ,
184
+ outputPath ,
185
+ serverBundlePath ,
186
+ options ,
187
+ context . workspaceRoot ,
188
+ ) ;
189
+
124
190
const spinner = ora ( `Prerendering ${ routes . length } route(s) to ${ outputPath } ...` ) . start ( ) ;
125
191
126
192
try {
@@ -197,21 +263,14 @@ export async function execute(
197
263
const browserOptions = ( await context . getTargetOptions (
198
264
browserTarget ,
199
265
) ) as unknown as BrowserBuilderOptions ;
200
- const tsConfigPath =
201
- typeof browserOptions . tsConfig === 'string' ? browserOptions . tsConfig : undefined ;
202
-
203
- const routes = await getRoutes ( options , tsConfigPath , context ) ;
204
- if ( ! routes . length ) {
205
- throw new Error ( `Could not find any routes to prerender.` ) ;
206
- }
207
-
208
266
const result = await _scheduleBuilds ( options , context ) ;
209
267
const { success, error, browserResult, serverResult } = result ;
268
+
210
269
if ( ! success || ! browserResult || ! serverResult ) {
211
270
return { success, error } as BuilderOutput ;
212
271
}
213
272
214
- return _renderUniversal ( routes , context , browserResult , serverResult , browserOptions ) ;
273
+ return _renderUniversal ( options , context , browserResult , serverResult , browserOptions ) ;
215
274
}
216
275
217
276
export default createBuilder ( execute ) ;
0 commit comments