@@ -10,6 +10,7 @@ import type { BuilderContext } from '@angular-devkit/architect';
1010import type { Plugin } from 'esbuild' ;
1111import assert from 'node:assert' ;
1212import { readFile } from 'node:fs/promises' ;
13+ import { builtinModules } from 'node:module' ;
1314import { basename , join } from 'node:path' ;
1415import type { Connect , DepOptimizationConfig , InlineConfig , ViteDevServer } from 'vite' ;
1516import { createAngularMemoryPlugin } from '../../tools/vite/angular-memory-plugin' ;
@@ -205,21 +206,31 @@ export async function* serveWithVite(
205206 }
206207
207208 // To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
209+ let requiresServerRestart = false ;
208210 if ( result . externalMetadata ) {
209211 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+
210226 // Empty Arrays to avoid growing unlimited with every re-build.
211227 externalMetadata . explicit . length = 0 ;
212228 externalMetadata . implicitServer . length = 0 ;
213229 externalMetadata . implicitBrowser . length = 0 ;
214230
215231 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 ) ;
223234
224235 // The below needs to be sorted as Vite uses these options are part of the hashing invalidation algorithm.
225236 // 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(
234245 ...new Set ( [ ...server . config . server . fs . allow , ...assetFiles . values ( ) ] ) ,
235246 ] ;
236247
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+ }
238255 } else {
239256 const projectName = context . target ?. project ;
240257 if ( ! projectName ) {
@@ -275,16 +292,10 @@ export async function* serveWithVite(
275292 server = await createServer ( serverConfiguration ) ;
276293 await server . listen ( ) ;
277294
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 } ) ;
288299 }
289300
290301 const urls = server . resolvedUrls ;
@@ -469,11 +480,6 @@ export async function setupServer(
469480 join ( serverOptions . workspaceRoot , `.angular/vite-root` , serverOptions . buildTarget . project ) ,
470481 ) ;
471482
472- const serverExplicitExternal = [
473- ...( await import ( 'node:module' ) ) . builtinModules ,
474- ...externalMetadata . explicit ,
475- ] ;
476-
477483 const cacheDir = join ( serverOptions . cacheOptions . path , 'vite' ) ;
478484 const configuration : InlineConfig = {
479485 configFile : false ,
@@ -536,23 +542,12 @@ export async function setupServer(
536542 // Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored.
537543 noExternal : / .* / ,
538544 // Exclude any Node.js built in module and provided dependencies (currently build defined externals)
539- external : serverExplicitExternal ,
545+ external : externalMetadata . explicit ,
540546 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-
552547 // Only enable with caching since it causes prebundle dependencies to be cached
553- disabled : true , // serverOptions.prebundle === false,
548+ disabled : serverOptions . prebundle === false ,
554549 // Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins)
555- exclude : serverExplicitExternal ,
550+ exclude : externalMetadata . explicit ,
556551 // Include all implict dependencies from the external packages internal option
557552 include : externalMetadata . implicitServer ,
558553 ssr : true ,
@@ -678,3 +673,14 @@ function getDepOptimizationConfig({
678673 } ,
679674 } ;
680675}
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