@@ -34,7 +34,7 @@ import {
3434 getFullQueryParams ,
3535 hasDefaultSerialize ,
3636} from '@ember/routing/route' ;
37- import { getRouteManager } from '@ember/-internals/routing/route-managers/utils' ;
37+ import { getRouteManager , RouteStateBucket } from '@ember/-internals/routing/route-managers/utils' ;
3838import type {
3939 InternalRouteInfo ,
4040 ModelFor ,
@@ -51,6 +51,7 @@ import type { QueryParams } from 'route-recognizer';
5151import type { AnyFn , MethodNamesOf , OmitFirst } from '@ember/-internals/utility-types' ;
5252import type { Template } from '@glimmer/interfaces' ;
5353import type ApplicationInstance from '@ember/application/instance' ;
54+ import { RouteManager } from '@ember/-internals/routing/route-managers/route-manager' ;
5455
5556/**
5657@module @ember /routing/router
@@ -312,71 +313,112 @@ class EmberRouter extends EmberObject.extend(Evented) implements Evented {
312313 this . _routerService = routerService ;
313314 }
314315
315- _initRouterJs ( ) : void {
316- let location = get ( this , 'location' ) as EmberLocation ;
317- let router = this ;
318- const owner = getOwner ( this ) ;
319- assert ( 'Router is unexpectedly missing an owner' , owner ) ;
320- let seen = Object . create ( null ) ;
316+ // Both routes and manager instances are keyed first by owner so that engine routes
317+ // (which share local names like 'application' with the main app) are isolated.
318+ #routes = new WeakMap < object , Map < string , RouteStateBucket > > ( ) ;
319+ #routeManagerInstances = new WeakMap < object , WeakMap < Function , RouteManager < RouteStateBucket > > > ( ) ;
321320
322- class PrivateRouter extends Router < Route > {
323- getRoute ( name : string ) : Route {
324- let routeName = name ;
325- let routeOwner = owner ;
326- let engineInfo = router . _engineInfoByRoute [ routeName ] ;
321+ getRoute ( name : string , engineOwner ?: Owner ) {
322+ if ( name === 'undefined' ) {
323+ return ;
324+ }
327325
328- if ( engineInfo ) {
329- let engineInstance = router . _getEngineInstance ( engineInfo ) ;
326+ assert ( 'Name should not start with "route:"' , ! name . startsWith ( 'route:' ) ) ;
330327
331- routeOwner = engineInstance ;
332- routeName = engineInfo . localFullName ;
333- }
328+ // Resolve the owner and local route name.
329+ // If engineOwner is provided (from the container bypass for an engine container),
330+ // use it directly — the name is already the local route name within that engine.
331+ // Otherwise, check _engineInfoByRoute to resolve engine routes by fully-qualified name.
332+ let routeName = name ;
333+ let routeOwner : Owner ;
334334
335- let fullRouteName = `route:${ routeName } ` as const ;
336-
337- assert ( 'Route is unexpectedly missing an owner' , routeOwner ) ;
335+ if ( engineOwner ) {
336+ routeOwner = engineOwner ;
337+ } else {
338+ routeOwner = getOwner ( this ) ! ;
339+ assert ( 'BUG: Missing owner' , routeOwner ) ;
338340
339- let route = routeOwner . lookup ( fullRouteName ) as Route | undefined ;
341+ const engineInfo = this . _engineInfoByRoute [ routeName ] ;
342+ if ( engineInfo ) {
343+ routeOwner = this . _getEngineInstance ( engineInfo ) ;
344+ routeName = engineInfo . localFullName ;
345+ }
346+ }
340347
341- if ( seen [ name ] ) {
342- assert ( 'seen routes should exist' , route ) ;
343- return route ;
344- }
348+ // Per-owner route cache.
349+ let ownerRoutes = this . #routes. get ( routeOwner ) ;
350+ if ( ! ownerRoutes ) {
351+ ownerRoutes = new Map ( ) ;
352+ this . #routes. set ( routeOwner , ownerRoutes ) ;
353+ }
345354
346- seen [ name ] = true ;
355+ if ( ! ownerRoutes . has ( routeName ) ) {
356+ let fullRouteName = `route:${ routeName } ` as const ;
357+ let factoryManager = routeOwner . factoryFor ( fullRouteName ) ;
347358
348- if ( ! route ) {
349- // SAFETY: this is configured in `commonSetupRegistry` in the
350- // `@ember/application/lib` package.
351- let DefaultRoute : any = routeOwner . factoryFor ( 'route:basic' ) ! . class ;
352- routeOwner . register ( fullRouteName , class extends DefaultRoute { } ) ;
353- route = routeOwner . lookup ( fullRouteName ) as Route ;
359+ // Auto-generate a default route if none is registered.
360+ if ( ! factoryManager ) {
361+ let DefaultRoute : any = routeOwner . factoryFor ( 'route:basic' ) ! . class ;
362+ routeOwner . register ( fullRouteName , class extends DefaultRoute { } ) ;
363+ factoryManager = routeOwner . factoryFor ( fullRouteName ) ;
354364
355- if ( DEBUG ) {
356- if ( router . namespace . LOG_ACTIVE_GENERATION ) {
357- info ( `generated -> ${ fullRouteName } ` , { fullName : fullRouteName } ) ;
358- }
365+ if ( DEBUG ) {
366+ if ( this . namespace . LOG_ACTIVE_GENERATION ) {
367+ info ( `generated -> ${ fullRouteName } ` , { fullName : fullRouteName } ) ;
359368 }
360369 }
370+ }
361371
362- // Look up the route manager and create a bucket.
363- // This activates the manager-driven code paths in router_js.
364- let manager = getRouteManager ( route . constructor ) ;
365- assert ( `Route ${ fullRouteName } does not have a manager` , manager ) ;
372+ assert ( 'BUG: Missing factory for route' , factoryManager ) ;
373+ let RouteClass = factoryManager . class ;
366374
367- let bucket = manager . createRoute ( route , { name : routeName } ) ;
368- route . manager = manager ;
375+ // Engine routes must not define a custom serialize method.
376+ const isEngineRoute = routeOwner !== getOwner ( this ) ;
377+ if ( isEngineRoute && ! hasDefaultSerialize ( RouteClass . prototype as any ) ) {
378+ throw new Error (
379+ 'Defining a custom serialize method on an Engine route is not supported.'
380+ ) ;
381+ }
369382
370- assert ( 'Route manager should return a bucket' , bucket ) ;
371- route . bucket = bucket ;
383+ // Look up the manager factory function via prototype walk on the class.
384+ let managerFactory = getRouteManager ( RouteClass ) ;
385+ assert ( 'Route manager needs to be defined' , managerFactory !== undefined ) ;
386+
387+ // Instantiate the manager once per owner. Keyed first by owner so engine routes
388+ // get their own manager instances, then by factory function so all subclasses
389+ // sharing the same factory share one manager instance within that owner.
390+ let ownerManagerInstances = this . #routeManagerInstances. get ( routeOwner ) ;
391+ if ( ! ownerManagerInstances ) {
392+ ownerManagerInstances = new WeakMap ( ) ;
393+ this . #routeManagerInstances. set ( routeOwner , ownerManagerInstances ) ;
394+ }
395+ if ( ! ownerManagerInstances . has ( managerFactory ) ) {
396+ ownerManagerInstances . set ( managerFactory , managerFactory ( routeOwner ) ) ;
397+ }
398+ let routeManagerInstance = ownerManagerInstances . get ( managerFactory ) ! ;
372399
373- if ( engineInfo && ! hasDefaultSerialize ( route ) ) {
374- throw new Error (
375- 'Defining a custom serialize method on an Engine route is not supported.'
376- ) ;
377- }
400+ // Pass the concrete class to createRoute — the manager is responsible for instantiation.
401+ let routeStateBucket = routeManagerInstance . createRoute ( RouteClass , { name : routeName } ) ;
402+
403+ assert ( 'Failed to create Route instance' , routeStateBucket . instance ) ;
404+ ( routeStateBucket . instance as any ) . bucket = routeStateBucket ;
405+ ( routeStateBucket . instance as any ) . manager = routeManagerInstance ;
406+ ownerRoutes . set ( routeName , routeStateBucket as RouteStateBucket ) ;
407+ }
378408
379- return route ;
409+ return ownerRoutes . get ( routeName ) ! . instance ;
410+ }
411+
412+ _initRouterJs ( ) : void {
413+ let location = get ( this , 'location' ) as EmberLocation ;
414+ let router = this ;
415+ const owner = getOwner ( this ) ;
416+ assert ( 'Router is unexpectedly missing an owner' , owner ) ;
417+ class PrivateRouter extends Router < Route > {
418+ getRoute ( name : string ) : Route {
419+ // All route instantiation and caching is owned by EmberRouter.getRoute,
420+ // which resolves engine vs main-app owner internally.
421+ return router . getRoute ( name ) as Route ;
380422 }
381423
382424 getSerializer ( name : string ) {
0 commit comments