diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts index f42cb8e7b..bd6eb354e 100644 --- a/packages/router/src/index.ts +++ b/packages/router/src/index.ts @@ -10,8 +10,6 @@ interface GlobalConfig { '@embroider/core'?: { active: boolean }; } -type EngineInfoByRoute = Record; - let Router: typeof EmberRouter; interface GetRoute { @@ -20,25 +18,40 @@ interface GetRoute { } interface Internals { + _engineInfoByRoute: Record; _routerMicrolib: { getRoute: GetRoute; }; } +interface EmbroiderBundle { + names: string[]; + loaded?: true; + load: () => Promise; +} + if (macroCondition(getGlobalConfig()['@embroider/core']?.active ?? false)) { const waiter = buildWaiter('@embroider/router:lazy-route-waiter'); function embroiderBundles(): { - _embroiderEngineBundles_?: { names: string[]; loaded?: true; load: () => Promise }[]; - _embroiderRouteBundles_?: { names: string[]; loaded?: true; load: () => Promise }[]; + _embroiderEngineBundles_?: EmbroiderBundle[]; + _embroiderRouteBundles_?: EmbroiderBundle[]; } { return window as ReturnType; } class EmbroiderRouter extends EmberRouter { - private lazyBundle(routeName: string) { - let engineInfoByRoute = (this as unknown as { _engineInfoByRoute: EngineInfoByRoute })._engineInfoByRoute; + private seenByRoute = new Set(); + private lazyRoute(this: this & Internals, routeName: string): EmbroiderBundle | undefined { + let bundles = embroiderBundles(); + if (bundles._embroiderRouteBundles_) { + return bundles._embroiderRouteBundles_.find(bundle => bundle.names.indexOf(routeName) !== -1); + } + return undefined; + } + + private lazyEngine(this: this & Internals, routeName: string): EmbroiderBundle | undefined { // Here we map engine names to route names. We need to do this because // engines can be specified with "as" such as: // @@ -48,28 +61,42 @@ if (macroCondition(getGlobalConfig()['@embroider/core']?.active ?? // router is dynamic and the string could be defined as anything. Luckly, this._engineInfoByRoute contains // mappings from routeName to the engines "original name" (which we know at build time). let bundles = embroiderBundles(); - let engine = engineInfoByRoute[routeName]; + let engine = this._engineInfoByRoute[routeName]; if (engine && bundles._embroiderEngineBundles_) { let engineName = engine.name; return bundles._embroiderEngineBundles_.find(bundle => bundle.names.indexOf(engineName) !== -1); } + return undefined; + } - if (bundles._embroiderRouteBundles_) { - return bundles._embroiderRouteBundles_.find(bundle => bundle.names.indexOf(routeName) !== -1); - } - - return false; + private isEngine(this: this & Internals, name: string): boolean { + return Boolean(this._engineInfoByRoute[name]); } // This is necessary in order to prevent the premature loading of lazy routes // when we are merely trying to render a link-to that points at them. // Unfortunately the stock query parameter behavior pulls on routes just to // check what their previous QP values were. - _getQPMeta(handlerInfo: { name: string }, ...rest: unknown[]) { - let bundle = this.lazyBundle(handlerInfo.name); + _getQPMeta(this: this & Internals, handlerInfo: { name: string }, ...rest: unknown[]) { + let bundle = this.lazyRoute(handlerInfo.name); if (bundle && !bundle.loaded) { + // unloaded split routes + return undefined; + } + + if (this.isEngine(handlerInfo.name) && !this.seenByRoute.has(handlerInfo.name)) { + // unvisited engines, whether loaded or not, because the same bundle + // could by mounted multiple places and engines expect to only run the + // super._getQPMeta after they've been visited. return undefined; } + + bundle = this.lazyEngine(handlerInfo.name); + if (bundle && !bundle.loaded) { + // unloaded lazy engines + return undefined; + } + // @ts-expect-error extending private method return super._getQPMeta(handlerInfo, ...rest); } @@ -86,9 +113,10 @@ if (macroCondition(getGlobalConfig()['@embroider/core']?.active ?? return isSetup; } - private _handlerResolver(original: (name: string) => unknown) { + private _handlerResolver(this: this & Internals, original: (name: string) => unknown) { let handler = ((name: string) => { - const bundle = this.lazyBundle(name); + const bundle = this.lazyRoute(name) ?? this.lazyEngine(name); + this.seenByRoute.add(name); if (!bundle || bundle.loaded) { return original(name); }