@@ -10,6 +10,7 @@ import type { BuilderContext } from '@angular-devkit/architect';
10
10
import type { Plugin } from 'esbuild' ;
11
11
import assert from 'node:assert' ;
12
12
import { readFile } from 'node:fs/promises' ;
13
+ import { builtinModules } from 'node:module' ;
13
14
import { basename , join } from 'node:path' ;
14
15
import type { Connect , DepOptimizationConfig , InlineConfig , ViteDevServer } from 'vite' ;
15
16
import { createAngularMemoryPlugin } from '../../tools/vite/angular-memory-plugin' ;
@@ -205,21 +206,31 @@ export async function* serveWithVite(
205
206
}
206
207
207
208
// To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
209
+ let requiresServerRestart = false ;
208
210
if ( result . externalMetadata ) {
209
211
const { implicitBrowser, implicitServer, explicit } = result . externalMetadata ;
212
+ const implicitServerFiltered = ( implicitServer as string [ ] ) . filter (
213
+ ( m ) => removeNodeJsBuiltinModules ( m ) && removeAbsoluteUrls ( m ) ,
214
+ ) ;
215
+ const implicitBrowserFiltered = ( implicitBrowser as string [ ] ) . filter ( removeAbsoluteUrls ) ;
216
+
217
+ if ( browserOptions . ssr && serverOptions . prebundle !== false ) {
218
+ const previousImplicitServer = new Set ( externalMetadata . implicitServer ) ;
219
+ // Restart the server to force SSR dep re-optimization when a dependency has been added.
220
+ // This is a workaround for: https://github.com/vitejs/vite/issues/14896
221
+ requiresServerRestart = implicitServerFiltered . some (
222
+ ( dep ) => ! previousImplicitServer . has ( dep ) ,
223
+ ) ;
224
+ }
225
+
210
226
// Empty Arrays to avoid growing unlimited with every re-build.
211
227
externalMetadata . explicit . length = 0 ;
212
228
externalMetadata . implicitServer . length = 0 ;
213
229
externalMetadata . implicitBrowser . length = 0 ;
214
230
215
231
externalMetadata . explicit . push ( ...explicit ) ;
216
- // Remove any absolute URLs (http://, https://, //) to avoid Vite's prebundling from processing them as files
217
- externalMetadata . implicitServer . push (
218
- ...( implicitServer as string [ ] ) . filter ( ( value ) => ! / ^ (?: h t t p s ? : ) ? \/ \/ / . test ( value ) ) ,
219
- ) ;
220
- externalMetadata . implicitBrowser . push (
221
- ...( implicitBrowser as string [ ] ) . filter ( ( value ) => ! / ^ (?: h t t p s ? : ) ? \/ \/ / . test ( value ) ) ,
222
- ) ;
232
+ externalMetadata . implicitServer . push ( ...implicitServerFiltered ) ;
233
+ externalMetadata . implicitBrowser . push ( ...implicitBrowserFiltered ) ;
223
234
224
235
// The below needs to be sorted as Vite uses these options are part of the hashing invalidation algorithm.
225
236
// See: https://github.com/vitejs/vite/blob/0873bae0cfe0f0718ad2f5743dd34a17e4ab563d/packages/vite/src/node/optimizer/index.ts#L1203-L1239
@@ -234,7 +245,13 @@ export async function* serveWithVite(
234
245
...new Set ( [ ...server . config . server . fs . allow , ...assetFiles . values ( ) ] ) ,
235
246
] ;
236
247
237
- handleUpdate ( normalizePath , generatedFiles , server , serverOptions , context . logger ) ;
248
+ await handleUpdate ( normalizePath , generatedFiles , server , serverOptions , context . logger ) ;
249
+
250
+ if ( requiresServerRestart ) {
251
+ // Restart the server to force SSR dep re-optimization when a dependency has been added.
252
+ // This is a workaround for: https://github.com/vitejs/vite/issues/14896
253
+ await server . restart ( ) ;
254
+ }
238
255
} else {
239
256
const projectName = context . target ?. project ;
240
257
if ( ! projectName ) {
@@ -275,16 +292,10 @@ export async function* serveWithVite(
275
292
server = await createServer ( serverConfiguration ) ;
276
293
await server . listen ( ) ;
277
294
278
- if ( serverConfiguration . ssr ?. optimizeDeps ?. disabled === false ) {
279
- /**
280
- * Vite will only start dependency optimization of SSR modules when the first request comes in.
281
- * In some cases, this causes a long waiting time. To mitigate this, we call `ssrLoadModule` to
282
- * initiate this process before the first request.
283
- *
284
- * NOTE: This will intentionally fail from the unknown module, but currently there is no other way
285
- * to initiate the SSR dep optimizer.
286
- */
287
- void server . ssrLoadModule ( '<deps-caller>' ) . catch ( ( ) => { } ) ;
295
+ if ( browserOptions . ssr && serverOptions . prebundle !== false ) {
296
+ // Warm up the SSR request and begin optimizing dependencies.
297
+ // Without this, Vite will only start optimizing SSR modules when the first request is made.
298
+ void server . warmupRequest ( './main.server.mjs' , { ssr : true } ) ;
288
299
}
289
300
290
301
const urls = server . resolvedUrls ;
@@ -469,11 +480,6 @@ export async function setupServer(
469
480
join ( serverOptions . workspaceRoot , `.angular/vite-root` , serverOptions . buildTarget . project ) ,
470
481
) ;
471
482
472
- const serverExplicitExternal = [
473
- ...( await import ( 'node:module' ) ) . builtinModules ,
474
- ...externalMetadata . explicit ,
475
- ] ;
476
-
477
483
const cacheDir = join ( serverOptions . cacheOptions . path , 'vite' ) ;
478
484
const configuration : InlineConfig = {
479
485
configFile : false ,
@@ -536,23 +542,12 @@ export async function setupServer(
536
542
// Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored.
537
543
noExternal : / .* / ,
538
544
// Exclude any Node.js built in module and provided dependencies (currently build defined externals)
539
- external : serverExplicitExternal ,
545
+ external : externalMetadata . explicit ,
540
546
optimizeDeps : getDepOptimizationConfig ( {
541
- /**
542
- * *********************************************
543
- * NOTE: Temporary disable 'optimizeDeps' for SSR.
544
- * *********************************************
545
- *
546
- * Currently this causes a number of issues.
547
- * - Deps are re-optimized everytime the server is started.
548
- * - Added deps after a rebuild are not optimized.
549
- * - Breaks RxJs (Unless it is added as external). See: https://github.com/angular/angular-cli/issues/26235
550
- */
551
-
552
547
// Only enable with caching since it causes prebundle dependencies to be cached
553
- disabled : true , // serverOptions.prebundle === false,
548
+ disabled : serverOptions . prebundle === false ,
554
549
// Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins)
555
- exclude : serverExplicitExternal ,
550
+ exclude : externalMetadata . explicit ,
556
551
// Include all implict dependencies from the external packages internal option
557
552
include : externalMetadata . implicitServer ,
558
553
ssr : true ,
@@ -678,3 +673,14 @@ function getDepOptimizationConfig({
678
673
} ,
679
674
} ;
680
675
}
676
+
677
+ const nodeJsBuiltinModules = new Set ( builtinModules ) ;
678
+ /** Remove any Node.js builtin modules to avoid Vite's prebundling from processing them as files. */
679
+ function removeNodeJsBuiltinModules ( value : string ) : boolean {
680
+ return ! nodeJsBuiltinModules . has ( value ) ;
681
+ }
682
+
683
+ /** Remove any absolute URLs (http://, https://, //) to avoid Vite's prebundling from processing them as files. */
684
+ function removeAbsoluteUrls ( value : string ) : boolean {
685
+ return ! / ^ (?: h t t p s ? : ) ? \/ \/ / . test ( value ) ;
686
+ }
0 commit comments