From 681807b526531a7b756777ef1ef6a8c27abbb52b Mon Sep 17 00:00:00 2001 From: Edward Faulkner Date: Thu, 7 Mar 2024 13:38:30 -0500 Subject: [PATCH 01/16] Sketching virtual network with loader shimming --- packages/runtime-common/loader.ts | 65 ++++++++++++++-------- packages/runtime-common/virtual-network.ts | 49 ++++++++++++++++ 2 files changed, 92 insertions(+), 22 deletions(-) create mode 100644 packages/runtime-common/virtual-network.ts diff --git a/packages/runtime-common/loader.ts b/packages/runtime-common/loader.ts index 406c1901d4..db0a06f1a6 100644 --- a/packages/runtime-common/loader.ts +++ b/packages/runtime-common/loader.ts @@ -5,12 +5,9 @@ import { trimExecutableExtension, logger } from './index'; import { RealmPaths } from './paths'; import { CardError } from './error'; import flatMap from 'lodash/flatMap'; -import { type RunnerOpts } from './search-index'; import { decodeScopedCSSRequest, isScopedCSSRequest } from 'glimmer-scoped-css'; import jsEscapeString from 'js-string-escape'; -const isFastBoot = typeof (globalThis as any).FastBoot !== 'undefined'; - // this represents a URL that has already been resolved to aid in documenting // when resolution has already been performed export interface ResolvedURL extends URL { @@ -114,6 +111,8 @@ type EvaluatableDep = export type RequestHandler = (req: Request) => Promise; +type Fetch = typeof fetch; + let nonce = 0; export class Loader { nonce = nonce++; // the nonce is a useful debugging tool that let's us compare loaders @@ -136,8 +135,14 @@ export class Loader { private consumptionCache = new WeakMap(); private static loaders = new WeakMap(); + private fetchImplementation: Fetch; + + constructor(fetch: Fetch) { + this.fetchImplementation = fetch; + } + static cloneLoader(loader: Loader): Loader { - let clone = new Loader(); + let clone = new Loader(loader.fetchImplementation); clone.urlHandlers = loader.urlHandlers; clone.urlMappings = loader.urlMappings; for (let [moduleIdentifier, module] of loader.moduleShims) { @@ -508,7 +513,9 @@ export class Loader { return result; } } - return await getNativeFetch()(this.asResolvedRequest(urlOrRequest, init)); + return await this.fetchImplementation( + this.asResolvedRequest(urlOrRequest, init), + ); } catch (err: any) { let url = urlOrRequest instanceof Request @@ -610,9 +617,12 @@ export class Loader { }; this.setModule(moduleIdentifier, module); - let src: string | null | undefined; + let loaded: + | { type: 'source'; source: string } + | { type: 'shimmed'; module: Record }; + try { - src = await this.load(moduleURL); + loaded = await this.load(moduleURL); } catch (exception) { this.setModule(moduleIdentifier, { state: 'broken', @@ -621,6 +631,19 @@ export class Loader { }); throw exception; } + + if (loaded.type === 'shimmed') { + this.setModule(moduleIdentifier, { + state: 'evaluated', + moduleInstance: loaded.module, + consumedModules: new Set(), + }); + module.deferred.fulfill(); + return; + } + + let src: string | null | undefined = loaded.source; + src = transformSync(src, { plugins: [ [ @@ -758,7 +781,12 @@ export class Loader { } } - private async load(moduleURL: ResolvedURL): Promise { + private async load( + moduleURL: ResolvedURL, + ): Promise< + | { type: 'source'; source: string } + | { type: 'shimmed'; module: Record } + > { let response: Response; try { response = await this.fetch(moduleURL); @@ -773,22 +801,15 @@ export class Loader { let error = await CardError.fromFetchResponse(moduleURL.href, response); throw error; } - return await response.text(); - } -} -function getNativeFetch(): typeof fetch { - if (isFastBoot) { - let optsId = (globalThis as any).runnerOptsId; - if (optsId == null) { - throw new Error(`Runner Options Identifier was not set`); + if (Symbol.for('shimmed-module') in response) { + return { + type: 'shimmed', + module: (response as any)[Symbol.for('shimmed-module')], + }; } - let getRunnerOpts = (globalThis as any).getRunnerOpts as ( - optsId: number, - ) => RunnerOpts; - return getRunnerOpts(optsId)._fetch; - } else { - return fetch; + + return { type: 'source', source: await response.text() }; } } diff --git a/packages/runtime-common/virtual-network.ts b/packages/runtime-common/virtual-network.ts new file mode 100644 index 0000000000..70889ba8ee --- /dev/null +++ b/packages/runtime-common/virtual-network.ts @@ -0,0 +1,49 @@ +import type { RunnerOpts } from './search-index'; + +const isFastBoot = typeof (globalThis as any).FastBoot !== 'undefined'; + +function getNativeFetch(): typeof fetch { + if (isFastBoot) { + let optsId = (globalThis as any).runnerOptsId; + if (optsId == null) { + throw new Error(`Runner Options Identifier was not set`); + } + let getRunnerOpts = (globalThis as any).getRunnerOpts as ( + optsId: number, + ) => RunnerOpts; + return getRunnerOpts(optsId)._fetch; + } else { + return fetch.bind(globalThis); + } +} + +export type Handler = (req: Request) => Promise; + +export class VirtualNetwork { + private nativeFetch = getNativeFetch(); + + private handlers: Handler[] = []; + + mount(handler: Handler) { + this.handlers.push(handler); + } + + fetch: typeof fetch = async ( + urlOrRequest: string | URL | Request, + init?: RequestInit, + ) => { + let request = + urlOrRequest instanceof Request + ? urlOrRequest + : new Request(urlOrRequest, init); + + for (let handler of this.handlers) { + let response = await handler(request); + if (response) { + return response; + } + } + + return this.nativeFetch(request, init); + }; +} From 70ce923737e79f5d11e67e247066d1fc520d0e30 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Mon, 11 Mar 2024 13:21:58 +0100 Subject: [PATCH 02/16] Wire up virtual network with the loaders (without shimming handlers for now) --- packages/host/app/services/loader-service.ts | 7 ++++--- packages/realm-server/main.ts | 4 ++-- packages/runtime-common/index.ts | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/host/app/services/loader-service.ts b/packages/host/app/services/loader-service.ts index ac1f531de9..c244c2711e 100644 --- a/packages/host/app/services/loader-service.ts +++ b/packages/host/app/services/loader-service.ts @@ -1,7 +1,7 @@ import Service, { service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; -import { baseRealm } from '@cardstack/runtime-common'; +import { VirtualNetwork, baseRealm } from '@cardstack/runtime-common'; import { Loader } from '@cardstack/runtime-common/loader'; import config from '@cardstack/host/config/environment'; @@ -34,13 +34,14 @@ export default class LoaderService extends Service { } private makeInstance() { + let fetchImplementation = new VirtualNetwork().fetch; if (this.fastboot.isFastBoot) { - let loader = new Loader(); + let loader = new Loader(fetchImplementation); shimExternals(loader); return loader; } - let loader = new Loader(); + let loader = new Loader(fetchImplementation); loader.addURLMapping( new URL(baseRealm.url), new URL(config.resolvedBaseRealmURL), diff --git a/packages/realm-server/main.ts b/packages/realm-server/main.ts index e4dd1ed19a..d81bd6ad17 100644 --- a/packages/realm-server/main.ts +++ b/packages/realm-server/main.ts @@ -1,5 +1,5 @@ import './setup-logger'; // This should be first -import { Realm, logger } from '@cardstack/runtime-common'; +import { Realm, VirtualNetwork, logger } from '@cardstack/runtime-common'; import { Loader } from '@cardstack/runtime-common/loader'; import { NodeAdapter } from './node-realm'; import yargs from 'yargs'; @@ -114,7 +114,7 @@ if ( let log = logger('main'); -let loader = new Loader(); +let loader = new Loader(new VirtualNetwork().fetch); shimExternals(loader); let urlMappings = fromUrls.map((fromUrl, i) => [ diff --git a/packages/runtime-common/index.ts b/packages/runtime-common/index.ts index 7ff331cff4..046ec9360f 100644 --- a/packages/runtime-common/index.ts +++ b/packages/runtime-common/index.ts @@ -54,6 +54,8 @@ export const isNode = export { Realm } from './realm'; export { SupportedMimeType } from './router'; +export { VirtualNetwork } from './virtual-network'; + export type { Kind, RealmAdapter, From efcee9b9a9a729f3a3b2a0799b7a6c2717d17def Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Mon, 11 Mar 2024 17:50:42 +0100 Subject: [PATCH 03/16] Prototyping: shimmed externals via virtual network --- packages/host/app/lib/externals.ts | 65 ++++++++++--------- packages/host/app/services/loader-service.ts | 15 +++-- packages/host/tests/acceptance/basic-test.gts | 7 +- packages/host/tests/helpers/index.gts | 19 ++++-- packages/runtime-common/loader.ts | 5 ++ packages/runtime-common/virtual-network.ts | 23 +++++++ 6 files changed, 90 insertions(+), 44 deletions(-) diff --git a/packages/host/app/lib/externals.ts b/packages/host/app/lib/externals.ts index 2a94ebcce8..4bc5f82743 100644 --- a/packages/host/app/lib/externals.ts +++ b/packages/host/app/lib/externals.ts @@ -30,40 +30,47 @@ import * as boxelUiIcons from '@cardstack/boxel-ui/icons'; import * as runtime from '@cardstack/runtime-common'; import { Loader } from '@cardstack/runtime-common/loader'; +import { VirtualNetwork } from '@cardstack/runtime-common'; +import { debug } from '@ember/debug'; -export function shimExternals(loader: Loader) { - loader.shimModule('@cardstack/runtime-common', runtime); - loader.shimModule('@cardstack/boxel-ui/components', boxelUiComponents); - loader.shimModule('@cardstack/boxel-ui/helpers', boxelUiHelpers); - loader.shimModule('@cardstack/boxel-ui/icons', boxelUiIcons); - loader.shimModule('@glimmer/component', glimmerComponent); - loader.shimModule('@ember/component', emberComponent); - loader.shimModule( +export function shimExternals(virtualNetwork: VirtualNetwork) { + virtualNetwork.shimModule('@cardstack/runtime-common', runtime); + virtualNetwork.shimModule( + '@cardstack/boxel-ui/components', + boxelUiComponents, + ); + virtualNetwork.shimModule('@cardstack/boxel-ui/helpers', boxelUiHelpers); + virtualNetwork.shimModule('@cardstack/boxel-ui/icons', boxelUiIcons); + virtualNetwork.shimModule('@glimmer/component', glimmerComponent); + virtualNetwork.shimModule('@ember/component', emberComponent); + virtualNetwork.shimModule( '@ember/component/template-only', emberComponentTemplateOnly, ); - loader.shimModule('ember-css-url', cssUrl); - loader.shimModule('@ember/template-factory', emberTemplateFactory); - loader.shimModule('@ember/template', emberTemplate); - loader.shimModule('@glimmer/tracking', glimmerTracking); - loader.shimModule('@ember/object', emberObject); - loader.shimModule('@ember/object/internals', emberObjectInternals); - loader.shimModule('@ember/helper', emberHelper); - loader.shimModule('@ember/modifier', emberModifier); - loader.shimModule('ember-resources', emberResources); - loader.shimModule('ember-concurrency', emberConcurrency); - loader.shimModule( + virtualNetwork.shimModule('ember-css-url', cssUrl); + virtualNetwork.shimModule('@ember/template-factory', emberTemplateFactory); + virtualNetwork.shimModule('@ember/template', emberTemplate); + virtualNetwork.shimModule('@glimmer/tracking', glimmerTracking); + virtualNetwork.shimModule('@ember/object', emberObject); + virtualNetwork.shimModule('@ember/object/internals', emberObjectInternals); + virtualNetwork.shimModule('@ember/helper', emberHelper); + virtualNetwork.shimModule('@ember/modifier', emberModifier); + virtualNetwork.shimModule('ember-resources', emberResources); + virtualNetwork.shimModule('ember-concurrency', emberConcurrency); + virtualNetwork.shimModule( 'ember-concurrency/-private/async-arrow-runtime', emberConcurrencyAsyncArrowRuntime, ); - loader.shimModule('ember-modifier', emberModifier2); - loader.shimModule('flat', flat); - loader.shimModule('lodash', lodash); - loader.shimModule('tracked-built-ins', tracked); - loader.shimModule('date-fns', dateFns); - loader.shimModule('@ember/destroyable', emberDestroyable); - loader.shimModule('marked', marked); - loader.shimModule('ethers', ethers); - loader.shimModule('ember-source/types', { default: class {} }); - loader.shimModule('ember-source/types/preview', { default: class {} }); + virtualNetwork.shimModule('ember-modifier', emberModifier2); + virtualNetwork.shimModule('flat', flat); + virtualNetwork.shimModule('lodash', lodash); + virtualNetwork.shimModule('tracked-built-ins', tracked); + virtualNetwork.shimModule('date-fns', dateFns); + virtualNetwork.shimModule('@ember/destroyable', emberDestroyable); + virtualNetwork.shimModule('marked', marked); + virtualNetwork.shimModule('ethers', ethers); + virtualNetwork.shimModule('ember-source/types', { default: class {} }); + virtualNetwork.shimModule('ember-source/types/preview', { + default: class {}, + }); } diff --git a/packages/host/app/services/loader-service.ts b/packages/host/app/services/loader-service.ts index c244c2711e..106de88dbf 100644 --- a/packages/host/app/services/loader-service.ts +++ b/packages/host/app/services/loader-service.ts @@ -5,7 +5,6 @@ import { VirtualNetwork, baseRealm } from '@cardstack/runtime-common'; import { Loader } from '@cardstack/runtime-common/loader'; import config from '@cardstack/host/config/environment'; -import { shimExternals } from '@cardstack/host/lib/externals'; import { type RealmSessionResource, getRealmSession, @@ -13,6 +12,8 @@ import { import MatrixService from '@cardstack/host/services/matrix-service'; import RealmInfoService from '@cardstack/host/services/realm-info-service'; +import { shimExternals } from '../lib/externals'; + export default class LoaderService extends Service { @service declare fastboot: { isFastBoot: boolean }; @service private declare matrixService: MatrixService; @@ -24,30 +25,30 @@ export default class LoaderService extends Service { // which in turn assures the resources will not get torn down. private realmSessions: Map = new Map(); + virtualNetwork = new VirtualNetwork(); + reset() { if (this.loader) { this.loader = Loader.cloneLoader(this.loader); - shimExternals(this.loader); } else { this.loader = this.makeInstance(); } } private makeInstance() { - let fetchImplementation = new VirtualNetwork().fetch; if (this.fastboot.isFastBoot) { - let loader = new Loader(fetchImplementation); - shimExternals(loader); + let loader = new Loader(this.virtualNetwork.fetch); + shimExternals(this.virtualNetwork); return loader; } - let loader = new Loader(fetchImplementation); + let loader = new Loader(this.virtualNetwork.fetch); loader.addURLMapping( new URL(baseRealm.url), new URL(config.resolvedBaseRealmURL), ); loader.prependURLHandlers([(req) => this.fetchWithAuth(req)]); - shimExternals(loader); + shimExternals(this.virtualNetwork); return loader; } diff --git a/packages/host/tests/acceptance/basic-test.gts b/packages/host/tests/acceptance/basic-test.gts index 21f7fe38a5..c90bc42e68 100644 --- a/packages/host/tests/acceptance/basic-test.gts +++ b/packages/host/tests/acceptance/basic-test.gts @@ -28,8 +28,10 @@ module('Acceptance | basic tests', function (hooks) { hooks.beforeEach(async function () { window.localStorage.removeItem('recent-files'); - let loader = (this.owner.lookup('service:loader-service') as LoaderService) - .loader; + let loaderService = this.owner.lookup( + 'service:loader-service', + ) as LoaderService; + let loader = loaderService.loader; let { field, contains, CardDef, Component } = await loader.import< typeof import('https://cardstack.com/base/card-api') >(`${baseRealm.url}card-api`); @@ -94,6 +96,7 @@ module('Acceptance | basic tests', function (hooks) { lastName: 'Abdel-Rahman', }), }, + virtualNetwork: loaderService.virtualNetwork, }); }); diff --git a/packages/host/tests/helpers/index.gts b/packages/host/tests/helpers/index.gts index bd8c91b167..87f44954cf 100644 --- a/packages/host/tests/helpers/index.gts +++ b/packages/host/tests/helpers/index.gts @@ -20,6 +20,7 @@ import { executableExtensions, SupportedMimeType, type TokenClaims, + VirtualNetwork, } from '@cardstack/runtime-common'; import { Loader } from '@cardstack/runtime-common/loader'; @@ -53,6 +54,7 @@ import percySnapshot from './percy-snapshot'; import { renderComponent } from './render-component'; import { WebMessageStream, messageCloseHandler } from './stream'; import visitOperatorMode from './visit-operator-mode'; +import LoaderService from '@cardstack/host/services/loader-service'; export { percySnapshot }; export { visitOperatorMode }; @@ -410,6 +412,7 @@ export async function setupAcceptanceTestRealm({ contents, realmURL, onFetch, + virtualNetwork, }: { loader: Loader; contents: RealmContents; @@ -418,6 +421,7 @@ export async function setupAcceptanceTestRealm({ req: Request; res: Response | null; }>; + virtualNetwork?: VirtualNetwork; }) { return await setupTestRealm({ loader, @@ -425,6 +429,7 @@ export async function setupAcceptanceTestRealm({ realmURL, onFetch, isAcceptanceTest: true, + virtualNetwork, }); } @@ -468,14 +473,17 @@ async function setupTestRealm({ }>; isAcceptanceTest?: boolean; }) { + let owner = (getContext() as TestContext).owner; + + let virtualNetwork = (owner.lookup('service:loader') as LoaderService) + .virtualNetwork; + realmURL = realmURL ?? testRealmURL; + for (const [path, mod] of Object.entries(contents)) { if (path.endsWith('.gts') && typeof mod !== 'string') { - await shimModule( - `${realmURL}${path.replace(/\.gts$/, '')}`, - mod as object, - loader, - ); + let moduleURLString = `${realmURL}${path.replace(/\.gts$/, '')}`; + await shimModule(moduleURLString, mod as object, loader); } } let api = await loader.import(`${baseRealm.url}card-api`); @@ -503,7 +511,6 @@ async function setupTestRealm({ } } let adapter = new TestRealmAdapter(flatFiles, new URL(realmURL)); - let owner = (getContext() as TestContext).owner; if (isAcceptanceTest) { await visit('/acceptance-test-setup'); } else { diff --git a/packages/runtime-common/loader.ts b/packages/runtime-common/loader.ts index db0a06f1a6..5672624242 100644 --- a/packages/runtime-common/loader.ts +++ b/packages/runtime-common/loader.ts @@ -251,6 +251,11 @@ export class Loader { } async import(moduleIdentifier: string): Promise { + if (!isUrlLike(moduleIdentifier)) { + moduleIdentifier = new URL(moduleIdentifier, 'https://shimmed-module/') + .href; + } + let resolvedModule = this.resolve(moduleIdentifier); let resolvedModuleIdentifier = resolvedModule.href; let shimmed = this.moduleShims.get(moduleIdentifier); diff --git a/packages/runtime-common/virtual-network.ts b/packages/runtime-common/virtual-network.ts index 70889ba8ee..3efc132ec4 100644 --- a/packages/runtime-common/virtual-network.ts +++ b/packages/runtime-common/virtual-network.ts @@ -24,6 +24,12 @@ export class VirtualNetwork { private handlers: Handler[] = []; + private shimmedModules = new Map>(); + + shimModule(moduleIdentifier: string, module: Record) { + this.shimmedModules.set(moduleIdentifier, module); + } + mount(handler: Handler) { this.handlers.push(handler); } @@ -37,6 +43,23 @@ export class VirtualNetwork { ? urlOrRequest : new Request(urlOrRequest, init); + // tODO: use constant + if (request.url.startsWith('https://shimmed-module/')) { + let shimmedModule = this.shimmedModules.get( + request.url.replace('https://shimmed-module/', ''), + ); + + if (!shimmedModule) { + throw new Error( + `Shimmed module not found but it should've been: ${request.url}`, + ); + } + + let response = new Response(); + (response as any)[Symbol.for('shimmed-module')] = shimmedModule; + return response; + } + for (let handler of this.handlers) { let response = await handler(request); if (response) { From 983db74fbe524a69879d9b37573fadb95112f084 Mon Sep 17 00:00:00 2001 From: Luke Melia Date: Mon, 11 Mar 2024 13:49:39 -0400 Subject: [PATCH 04/16] Get shimmed externals via virtual network working --- packages/host/tests/helpers/index.gts | 6 --- packages/realm-server/lib/externals.ts | 59 +++++++++++----------- packages/realm-server/main.ts | 6 +-- packages/runtime-common/loader.ts | 11 +++- packages/runtime-common/virtual-network.ts | 5 +- 5 files changed, 44 insertions(+), 43 deletions(-) diff --git a/packages/host/tests/helpers/index.gts b/packages/host/tests/helpers/index.gts index 87f44954cf..ec850463bf 100644 --- a/packages/host/tests/helpers/index.gts +++ b/packages/host/tests/helpers/index.gts @@ -412,7 +412,6 @@ export async function setupAcceptanceTestRealm({ contents, realmURL, onFetch, - virtualNetwork, }: { loader: Loader; contents: RealmContents; @@ -421,7 +420,6 @@ export async function setupAcceptanceTestRealm({ req: Request; res: Response | null; }>; - virtualNetwork?: VirtualNetwork; }) { return await setupTestRealm({ loader, @@ -429,7 +427,6 @@ export async function setupAcceptanceTestRealm({ realmURL, onFetch, isAcceptanceTest: true, - virtualNetwork, }); } @@ -475,9 +472,6 @@ async function setupTestRealm({ }) { let owner = (getContext() as TestContext).owner; - let virtualNetwork = (owner.lookup('service:loader') as LoaderService) - .virtualNetwork; - realmURL = realmURL ?? testRealmURL; for (const [path, mod] of Object.entries(contents)) { diff --git a/packages/realm-server/lib/externals.ts b/packages/realm-server/lib/externals.ts index c2094071e4..a142501132 100644 --- a/packages/realm-server/lib/externals.ts +++ b/packages/realm-server/lib/externals.ts @@ -1,103 +1,102 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -import { Loader } from '@cardstack/runtime-common/loader'; - import * as runtime from '@cardstack/runtime-common'; import * as flat from 'flat'; import * as lodash from 'lodash'; import * as dateFns from 'date-fns'; import * as ethers from 'ethers'; +import { VirtualNetwork } from '@cardstack/runtime-common'; -export function shimExternals(loader: Loader) { - loader.shimModule('@cardstack/runtime-common', runtime); - loader.shimModule('@cardstack/boxel-ui/components', { +export function shimExternals(virtualNetwork: VirtualNetwork) { + virtualNetwork.shimModule('@cardstack/runtime-common', runtime); + virtualNetwork.shimModule('@cardstack/boxel-ui/components', { Button() {}, }); - loader.shimModule('@cardstack/boxel-ui/helpers', { + virtualNetwork.shimModule('@cardstack/boxel-ui/helpers', { cssVar() {}, eq() {}, }); - loader.shimModule('@cardstack/boxel-ui/icons', { + virtualNetwork.shimModule('@cardstack/boxel-ui/icons', { default() {}, }); // import * as glimmerComponent from "@glimmer/component"; - loader.shimModule('@glimmer/component', { + virtualNetwork.shimModule('@glimmer/component', { default: class {}, }); // import * as emberComponent from "ember-source/dist/packages/@ember/component"; - loader.shimModule('@ember/component', { + virtualNetwork.shimModule('@ember/component', { default: class {}, setComponentTemplate() {}, }); // import * as emberComponentTemplateOnly from "ember-source/dist/packages/@ember/component/template-only"; - loader.shimModule('@ember/component/template-only', { default() {} }); + virtualNetwork.shimModule('@ember/component/template-only', { default() {} }); // import * as emberTemplateFactory from "ember-source/dist/packages/@ember/template-factory"; - loader.shimModule('@ember/template-factory', { + virtualNetwork.shimModule('@ember/template-factory', { createTemplateFactory() {}, }); // import * as emberTemplate from "ember-source/dist/packages/@ember/template"; - loader.shimModule('@ember/template', { + virtualNetwork.shimModule('@ember/template', { htmlSafe(html: string) { return html; }, }); // import * as cssUrl from 'ember-css-url'; - loader.shimModule('ember-css-url', { + virtualNetwork.shimModule('ember-css-url', { default: () => {}, }); // import * as glimmerTracking from "@glimmer/tracking"; - loader.shimModule('@glimmer/tracking', { + virtualNetwork.shimModule('@glimmer/tracking', { tracked() {}, }); // import * as emberObject from "ember-source/dist/packages/@ember/object"; - loader.shimModule('@ember/object', { + virtualNetwork.shimModule('@ember/object', { action() {}, get() {}, }); // import * as emberObjectInternals from "ember-source/dist/packages/@ember/object/internals"; - loader.shimModule('@ember/object/internals', { + virtualNetwork.shimModule('@ember/object/internals', { guidFor() {}, }); // import * as emberHelper from "ember-source/dist/packages/@ember/helper"; - loader.shimModule('@ember/helper', { + virtualNetwork.shimModule('@ember/helper', { get() {}, fn() {}, concat() {}, }); // import * as emberModifier from "ember-source/dist/packages/@ember/modifier"; - loader.shimModule('@ember/modifier', { + virtualNetwork.shimModule('@ember/modifier', { on() {}, }); // import * as emberResources from 'ember-resources'; - loader.shimModule('ember-resources', { + virtualNetwork.shimModule('ember-resources', { Resource: class {}, useResource() {}, }); // import * as emberConcurrency from 'ember-concurrency'; - loader.shimModule('ember-concurrency', { + virtualNetwork.shimModule('ember-concurrency', { task() {}, restartableTask() {}, }); // import * as emberConcurrencyAsyncArrowRuntime from 'ember-concurrency/-private/async-arrow-runtime'; - loader.shimModule('ember-concurrency/-private/async-arrow-runtime', { + virtualNetwork.shimModule('ember-concurrency/-private/async-arrow-runtime', { default: () => {}, buildTask: () => {}, }); // import * as emberModifier from 'ember-modifier'; - loader.shimModule('ember-modifier', { + virtualNetwork.shimModule('ember-modifier', { default: class {}, modifier: () => {}, }); - loader.shimModule('flat', flat); + virtualNetwork.shimModule('flat', flat); // import * as tracked from "tracked-built-ins"; - loader.shimModule('tracked-built-ins', { + virtualNetwork.shimModule('tracked-built-ins', { // TODO replace with actual TrackedWeakMap when we add real glimmer // implementations TrackedWeakMap: WeakMap, }); - loader.shimModule('lodash', lodash); - loader.shimModule('date-fns', dateFns); - loader.shimModule('ember-resources', { Resource: class {} }); - loader.shimModule('@ember/destroyable', {}); - loader.shimModule('marked', { marked: () => {} }); - loader.shimModule('ethers', ethers); + virtualNetwork.shimModule('lodash', lodash); + virtualNetwork.shimModule('date-fns', dateFns); + virtualNetwork.shimModule('ember-resources', { Resource: class {} }); + virtualNetwork.shimModule('@ember/destroyable', {}); + virtualNetwork.shimModule('marked', { marked: () => {} }); + virtualNetwork.shimModule('ethers', ethers); } diff --git a/packages/realm-server/main.ts b/packages/realm-server/main.ts index d81bd6ad17..b8b6becf4b 100644 --- a/packages/realm-server/main.ts +++ b/packages/realm-server/main.ts @@ -113,9 +113,9 @@ if ( } let log = logger('main'); - -let loader = new Loader(new VirtualNetwork().fetch); -shimExternals(loader); +let virtualNetwork = new VirtualNetwork(); +let loader = new Loader(virtualNetwork.fetch); +shimExternals(virtualNetwork); let urlMappings = fromUrls.map((fromUrl, i) => [ new URL(String(fromUrl), `http://localhost:${port}`), diff --git a/packages/runtime-common/loader.ts b/packages/runtime-common/loader.ts index 5672624242..22c950ad27 100644 --- a/packages/runtime-common/loader.ts +++ b/packages/runtime-common/loader.ts @@ -8,6 +8,7 @@ import flatMap from 'lodash/flatMap'; import { decodeScopedCSSRequest, isScopedCSSRequest } from 'glimmer-scoped-css'; import jsEscapeString from 'js-string-escape'; +export const SHIMMED_MODULE_FAKE_ORIGIN = 'https://shimmed-module/'; // this represents a URL that has already been resolved to aid in documenting // when resolution has already been performed export interface ResolvedURL extends URL { @@ -252,7 +253,7 @@ export class Loader { async import(moduleIdentifier: string): Promise { if (!isUrlLike(moduleIdentifier)) { - moduleIdentifier = new URL(moduleIdentifier, 'https://shimmed-module/') + moduleIdentifier = new URL(moduleIdentifier, SHIMMED_MODULE_FAKE_ORIGIN) .href; } @@ -681,7 +682,13 @@ export class Loader { moduleURL: this.resolve(depId, new URL(moduleIdentifier)), }; } else { - return { type: 'shim-dep', moduleId: depId }; // for npm imports + return { + type: 'dep', + moduleURL: this.resolve( + new URL(depId, SHIMMED_MODULE_FAKE_ORIGIN).href, + new URL(moduleIdentifier), + ), + }; } }); implementation = impl; diff --git a/packages/runtime-common/virtual-network.ts b/packages/runtime-common/virtual-network.ts index 3efc132ec4..c0a3ba9e4c 100644 --- a/packages/runtime-common/virtual-network.ts +++ b/packages/runtime-common/virtual-network.ts @@ -1,3 +1,4 @@ +import { SHIMMED_MODULE_FAKE_ORIGIN } from './loader'; import type { RunnerOpts } from './search-index'; const isFastBoot = typeof (globalThis as any).FastBoot !== 'undefined'; @@ -44,9 +45,9 @@ export class VirtualNetwork { : new Request(urlOrRequest, init); // tODO: use constant - if (request.url.startsWith('https://shimmed-module/')) { + if (request.url.startsWith(SHIMMED_MODULE_FAKE_ORIGIN)) { let shimmedModule = this.shimmedModules.get( - request.url.replace('https://shimmed-module/', ''), + request.url.replace(SHIMMED_MODULE_FAKE_ORIGIN, ''), ); if (!shimmedModule) { From 815ccf63410bdbbb9a315c786b9c1ef59787b673 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Tue, 12 Mar 2024 09:29:18 +0100 Subject: [PATCH 05/16] Fix host tests --- .../tests/integration/search-index-test.gts | 34 +++++++++---------- packages/runtime-common/loader.ts | 1 + packages/runtime-common/virtual-network.ts | 1 - 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/host/tests/integration/search-index-test.gts b/packages/host/tests/integration/search-index-test.gts index 66bca988b0..c7b4b4c05d 100644 --- a/packages/host/tests/integration/search-index-test.gts +++ b/packages/host/tests/integration/search-index-test.gts @@ -2643,21 +2643,6 @@ module('Integration | search-index', function (hooks) { // Exclude synthetic imports that encapsulate scoped CSS .filter((key) => !key.includes('glimmer-scoped.css')), [ - '@cardstack/boxel-ui/components', - '@cardstack/boxel-ui/helpers', - '@cardstack/boxel-ui/icons', - '@cardstack/runtime-common', - '@ember/component', - '@ember/component/template-only', - '@ember/helper', - '@ember/modifier', - '@ember/object', - '@ember/template-factory', - '@glimmer/component', - '@glimmer/tracking', - 'ember-concurrency', - 'ember-concurrency/-private/async-arrow-runtime', - 'ember-modifier', 'http://localhost:4201/base/card-api', 'http://localhost:4201/base/contains-many-component', 'http://localhost:4201/base/field-component', @@ -2668,8 +2653,23 @@ module('Integration | search-index', function (hooks) { 'http://localhost:4201/base/text-input-validator', 'http://localhost:4201/base/watched-array', 'http://localhost:4202/test/person', - 'lodash', - 'tracked-built-ins', + 'https://shimmed-module/@cardstack/boxel-ui/components', + 'https://shimmed-module/@cardstack/boxel-ui/helpers', + 'https://shimmed-module/@cardstack/boxel-ui/icons', + 'https://shimmed-module/@cardstack/runtime-common', + 'https://shimmed-module/@ember/component', + 'https://shimmed-module/@ember/component/template-only', + 'https://shimmed-module/@ember/helper', + 'https://shimmed-module/@ember/modifier', + 'https://shimmed-module/@ember/object', + 'https://shimmed-module/@ember/template-factory', + 'https://shimmed-module/@glimmer/component', + 'https://shimmed-module/@glimmer/tracking', + 'https://shimmed-module/ember-concurrency', + 'https://shimmed-module/ember-concurrency/-private/async-arrow-runtime', + 'https://shimmed-module/ember-modifier', + 'https://shimmed-module/lodash', + 'https://shimmed-module/tracked-built-ins', ], 'the card references for the instance are correct', ); diff --git a/packages/runtime-common/loader.ts b/packages/runtime-common/loader.ts index 22c950ad27..cfbae87151 100644 --- a/packages/runtime-common/loader.ts +++ b/packages/runtime-common/loader.ts @@ -9,6 +9,7 @@ import { decodeScopedCSSRequest, isScopedCSSRequest } from 'glimmer-scoped-css'; import jsEscapeString from 'js-string-escape'; export const SHIMMED_MODULE_FAKE_ORIGIN = 'https://shimmed-module/'; + // this represents a URL that has already been resolved to aid in documenting // when resolution has already been performed export interface ResolvedURL extends URL { diff --git a/packages/runtime-common/virtual-network.ts b/packages/runtime-common/virtual-network.ts index c0a3ba9e4c..1c9a97f15c 100644 --- a/packages/runtime-common/virtual-network.ts +++ b/packages/runtime-common/virtual-network.ts @@ -44,7 +44,6 @@ export class VirtualNetwork { ? urlOrRequest : new Request(urlOrRequest, init); - // tODO: use constant if (request.url.startsWith(SHIMMED_MODULE_FAKE_ORIGIN)) { let shimmedModule = this.shimmedModules.get( request.url.replace(SHIMMED_MODULE_FAKE_ORIGIN, ''), From 70655310f6fdd9d4e150e37c5e2c18d642eb2fc8 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Tue, 12 Mar 2024 10:12:11 +0100 Subject: [PATCH 06/16] Fix tests after shim externals refactor --- packages/host/app/lib/externals.ts | 2 -- packages/host/tests/acceptance/basic-test.gts | 1 - packages/host/tests/helpers/index.gts | 2 -- .../realm-server/tests/auth-client-test.ts | 6 ++-- packages/realm-server/tests/indexing-test.ts | 11 +++--- packages/realm-server/tests/loader-test.ts | 34 +++++++++--------- .../realm-server/tests/module-syntax-test.ts | 7 ++-- .../realm-server/tests/realm-server-test.ts | 36 ++++++++++++------- 8 files changed, 57 insertions(+), 42 deletions(-) diff --git a/packages/host/app/lib/externals.ts b/packages/host/app/lib/externals.ts index 4bc5f82743..774b6965f6 100644 --- a/packages/host/app/lib/externals.ts +++ b/packages/host/app/lib/externals.ts @@ -29,9 +29,7 @@ import * as boxelUiHelpers from '@cardstack/boxel-ui/helpers'; import * as boxelUiIcons from '@cardstack/boxel-ui/icons'; import * as runtime from '@cardstack/runtime-common'; -import { Loader } from '@cardstack/runtime-common/loader'; import { VirtualNetwork } from '@cardstack/runtime-common'; -import { debug } from '@ember/debug'; export function shimExternals(virtualNetwork: VirtualNetwork) { virtualNetwork.shimModule('@cardstack/runtime-common', runtime); diff --git a/packages/host/tests/acceptance/basic-test.gts b/packages/host/tests/acceptance/basic-test.gts index c90bc42e68..be7b297015 100644 --- a/packages/host/tests/acceptance/basic-test.gts +++ b/packages/host/tests/acceptance/basic-test.gts @@ -96,7 +96,6 @@ module('Acceptance | basic tests', function (hooks) { lastName: 'Abdel-Rahman', }), }, - virtualNetwork: loaderService.virtualNetwork, }); }); diff --git a/packages/host/tests/helpers/index.gts b/packages/host/tests/helpers/index.gts index ec850463bf..f93d2d090d 100644 --- a/packages/host/tests/helpers/index.gts +++ b/packages/host/tests/helpers/index.gts @@ -20,7 +20,6 @@ import { executableExtensions, SupportedMimeType, type TokenClaims, - VirtualNetwork, } from '@cardstack/runtime-common'; import { Loader } from '@cardstack/runtime-common/loader'; @@ -54,7 +53,6 @@ import percySnapshot from './percy-snapshot'; import { renderComponent } from './render-component'; import { WebMessageStream, messageCloseHandler } from './stream'; import visitOperatorMode from './visit-operator-mode'; -import LoaderService from '@cardstack/host/services/loader-service'; export { percySnapshot }; export { visitOperatorMode }; diff --git a/packages/realm-server/tests/auth-client-test.ts b/packages/realm-server/tests/auth-client-test.ts index 3f338d0945..1aeac31159 100644 --- a/packages/realm-server/tests/auth-client-test.ts +++ b/packages/realm-server/tests/auth-client-test.ts @@ -5,7 +5,7 @@ import { RealmAuthClient, type RealmAuthMatrixClientInterface, } from '@cardstack/runtime-common/realm-auth-client'; -import { Loader } from '@cardstack/runtime-common'; +import { Loader, VirtualNetwork } from '@cardstack/runtime-common'; import jwt from 'jsonwebtoken'; function createJWT(expiresIn: string | number) { @@ -33,7 +33,9 @@ module('realm-auth-client', function (assert) { return Promise.resolve(); }, } as RealmAuthMatrixClientInterface; - let loader = new Loader(); + + let virtualNetwork = new VirtualNetwork(); + let loader = new Loader(virtualNetwork.fetch); client = new RealmAuthClient( new URL('http://testrealm.com/'), diff --git a/packages/realm-server/tests/indexing-test.ts b/packages/realm-server/tests/indexing-test.ts index 42cdb2dbd3..22a91b98da 100644 --- a/packages/realm-server/tests/indexing-test.ts +++ b/packages/realm-server/tests/indexing-test.ts @@ -5,6 +5,7 @@ import { baseRealm, LooseSingleCardDocument, Realm, + VirtualNetwork, } from '@cardstack/runtime-common'; import { createRealm, @@ -33,12 +34,14 @@ setGracefulCleanup(); // underlying filesystem in a manner that doesn't leak into other tests (as well // as to test through loader caching) module('indexing', function (hooks) { - let loader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + let loader = new Loader(virtualNetwork.fetch); + loader.addURLMapping( new URL(baseRealm.url), new URL('http://localhost:4201/base/'), ); - shimExternals(loader); + shimExternals(virtualNetwork); setupCardLogs( hooks, @@ -51,12 +54,12 @@ module('indexing', function (hooks) { setupBaseRealmServer(hooks, loader); hooks.beforeEach(async function () { - let testRealmLoader = new Loader(); + let testRealmLoader = new Loader(virtualNetwork.fetch); testRealmLoader.addURLMapping( new URL(baseRealm.url), new URL('http://localhost:4201/base/'), ); - shimExternals(testRealmLoader); + shimExternals(virtualNetwork); dir = dirSync().name; realm = await createRealm(testRealmLoader, dir, { diff --git a/packages/realm-server/tests/loader-test.ts b/packages/realm-server/tests/loader-test.ts index 88b5ae2aa3..b0e0bd9dc2 100644 --- a/packages/realm-server/tests/loader-test.ts +++ b/packages/realm-server/tests/loader-test.ts @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { Loader } from '@cardstack/runtime-common'; +import { Loader, VirtualNetwork } from '@cardstack/runtime-common'; import { dirSync, setGracefulCleanup, DirResult } from 'tmp'; import { createRealm, @@ -22,12 +22,14 @@ module('loader', function (hooks) { let dir: DirResult; let testRealmServer: Server; - let loader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + let loader = new Loader(virtualNetwork.fetch); + loader.addURLMapping( new URL(baseRealm.url), new URL('http://localhost:4201/base/'), ); - shimExternals(loader); + shimExternals(virtualNetwork); setupBaseRealmServer(hooks, loader); @@ -45,7 +47,7 @@ module('loader', function (hooks) { }); test('can dynamically load modules with cycles', async function (assert) { - let loader = new Loader(); + let loader = new Loader(virtualNetwork.fetch); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let module = await loader.import<{ three(): number }>( `${testRealmHref}cycle-two`, @@ -54,7 +56,7 @@ module('loader', function (hooks) { }); test('can resolve multiple import load races against a common dep', async function (assert) { - let loader = new Loader(); + let loader = new Loader(virtualNetwork.fetch); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let a = loader.import<{ a(): string }>(`${testRealmHref}a`); let b = loader.import<{ b(): string }>(`${testRealmHref}b`); @@ -64,7 +66,7 @@ module('loader', function (hooks) { }); test('can resolve a import deadlock', async function (assert) { - let loader = new Loader(); + let loader = new Loader(virtualNetwork.fetch); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let a = loader.import<{ a(): string }>(`${testRealmHref}deadlock/a`); let b = loader.import<{ b(): string }>(`${testRealmHref}deadlock/b`); @@ -76,7 +78,7 @@ module('loader', function (hooks) { }); test('supports import.meta', async function (assert) { - let loader = new Loader(); + let loader = new Loader(virtualNetwork.fetch); let realm = await createRealm( loader, dir.name, @@ -101,7 +103,7 @@ module('loader', function (hooks) { }); test('can determine consumed modules', async function (assert) { - let loader = new Loader(); + let loader = new Loader(virtualNetwork.fetch); await loader.import<{ a(): string }>(`${testRealmHref}a`); assert.deepEqual(await loader.getConsumedModules(`${testRealmHref}a`), [ `${testRealmHref}a`, @@ -111,7 +113,7 @@ module('loader', function (hooks) { }); test('can determine consumed modules when an error is encountered during loading', async function (assert) { - let loader = new Loader(); + let loader = new Loader(virtualNetwork.fetch); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); try { await loader.import<{ d(): string }>(`${testRealmHref}d`); @@ -129,7 +131,7 @@ module('loader', function (hooks) { }); test('can get consumed modules within a cycle', async function (assert) { - let loader = new Loader(); + let loader = new Loader(virtualNetwork.fetch); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); await loader.import<{ three(): number }>(`${testRealmHref}cycle-two`); let modules = await loader.getConsumedModules(`${testRealmHref}cycle-two`); @@ -140,8 +142,8 @@ module('loader', function (hooks) { }); test('supports identify API', async function (assert) { - let loader = new Loader(); - shimExternals(loader); + let loader = new Loader(virtualNetwork.fetch); + shimExternals(virtualNetwork); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let { Person } = await loader.import<{ Person: unknown }>( `${testRealmHref}person`, @@ -158,8 +160,8 @@ module('loader', function (hooks) { }); test('exports cannot be mutated', async function (assert) { - let loader = new Loader(); - shimExternals(loader); + let loader = new Loader(virtualNetwork.fetch); + shimExternals(virtualNetwork); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let module = await loader.import<{ Person: unknown }>( `${testRealmHref}person`, @@ -170,8 +172,8 @@ module('loader', function (hooks) { }); test('can get a loader used to import a specific card', async function (assert) { - let loader = new Loader(); - shimExternals(loader); + let loader = new Loader(virtualNetwork.fetch); + shimExternals(virtualNetwork); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let module = await loader.import(`${testRealmHref}person`); let card = module.Person; diff --git a/packages/realm-server/tests/module-syntax-test.ts b/packages/realm-server/tests/module-syntax-test.ts index eaf033707c..2ae8cb8543 100644 --- a/packages/realm-server/tests/module-syntax-test.ts +++ b/packages/realm-server/tests/module-syntax-test.ts @@ -6,18 +6,21 @@ import { baseRealm, baseCardRef, baseFieldRef, + VirtualNetwork, } from '@cardstack/runtime-common'; import { testRealm, createRealm } from './helpers'; import '@cardstack/runtime-common/helpers/code-equality-assertion'; import { shimExternals } from '../lib/externals'; module('module-syntax', function () { - let loader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + let loader = new Loader(virtualNetwork.fetch); + loader.addURLMapping( new URL(baseRealm.url), new URL('http://localhost:4201/base/'), ); - shimExternals(loader); + shimExternals(virtualNetwork); function addField(src: string, addFieldAtIndex?: number) { let mod = new ModuleSyntax(src, new URL(`${testRealm}dir/person.gts`)); diff --git a/packages/realm-server/tests/realm-server-test.ts b/packages/realm-server/tests/realm-server-test.ts index 507f65d5dc..f87d5bba82 100644 --- a/packages/realm-server/tests/realm-server-test.ts +++ b/packages/realm-server/tests/realm-server-test.ts @@ -26,6 +26,7 @@ import { type LooseSingleCardDocument, Realm, RealmPermissions, + VirtualNetwork, } from '@cardstack/runtime-common'; import { stringify } from 'qs'; import { Query } from '@cardstack/runtime-common/query'; @@ -115,9 +116,11 @@ module('Realm Server', function (hooks) { let request: SuperTest; let dir: DirResult; - let loader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + let loader = new Loader(virtualNetwork.fetch); + loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); - shimExternals(loader); + shimExternals(virtualNetwork); setupCardLogs( hooks, @@ -1712,12 +1715,14 @@ module('Realm Server', function (hooks) { '*': ['read', 'write'], })); - let testRealmServer2Loader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + + let testRealmServer2Loader = new Loader(virtualNetwork.fetch); testRealmServer2Loader.addURLMapping( new URL(baseRealm.url), new URL(localBaseRealm), ); - shimExternals(testRealmServer2Loader); + shimExternals(virtualNetwork); testRealmServer2 = ( await runTestRealmServer( @@ -2025,9 +2030,11 @@ module('Realm Server serving from root', function (hooks) { let dir: DirResult; - let loader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + let loader = new Loader(virtualNetwork.fetch); + loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); - shimExternals(loader); + shimExternals(virtualNetwork); setupCardLogs( hooks, @@ -2040,7 +2047,9 @@ module('Realm Server serving from root', function (hooks) { dir = dirSync(); copySync(join(__dirname, 'cards'), dir.name); - let testRealmServerLoader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + + let testRealmServerLoader = new Loader(virtualNetwork.fetch); testRealmServerLoader.addURLMapping( new URL(baseRealm.url), new URL(localBaseRealm), @@ -2226,9 +2235,10 @@ module('Realm Server serving from a subdirectory', function (hooks) { let dir: DirResult; - let loader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + let loader = new Loader(virtualNetwork.fetch); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); - shimExternals(loader); + shimExternals(virtualNetwork); setupCardLogs( hooks, @@ -2241,7 +2251,7 @@ module('Realm Server serving from a subdirectory', function (hooks) { dir = dirSync(); copySync(join(__dirname, 'cards'), dir.name); - let testRealmServerLoader = new Loader(); + let testRealmServerLoader = new Loader(virtualNetwork.fetch); testRealmServerLoader.addURLMapping( new URL(baseRealm.url), new URL(localBaseRealm), @@ -2293,14 +2303,14 @@ async function setupPermissionedRealm(permissions: RealmPermissions) { let dir = dirSync(); copySync(join(__dirname, 'cards'), dir.name); - - let testRealmServerLoader = new Loader(); + let virtualNetwork = new VirtualNetwork(); + let testRealmServerLoader = new Loader(virtualNetwork.fetch); testRealmServerLoader.addURLMapping( new URL(baseRealm.url), new URL(localBaseRealm), ); - shimExternals(testRealmServerLoader); + shimExternals(virtualNetwork); ({ testRealm, testRealmServer } = await runTestRealmServer( testRealmServerLoader, From f016d5eaacc741eb7057f191fe5f766079627f8c Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Thu, 14 Mar 2024 10:22:16 +0100 Subject: [PATCH 07/16] Implement loader-specific module shimming via virtual network --- .../acceptance/interact-submode-test.gts | 4 + packages/host/tests/helpers/index.gts | 18 +- .../components/card-basics-test.gts | 83 +++--- .../components/card-editor-test.gts | 7 +- .../integration/components/computed-test.gts | 17 +- .../integration/components/preview-test.gts | 4 +- .../components/serialization-test.gts | 265 ++++++++---------- packages/runtime-common/loader.ts | 49 +++- packages/runtime-common/virtual-network.ts | 35 +-- 9 files changed, 217 insertions(+), 265 deletions(-) diff --git a/packages/host/tests/acceptance/interact-submode-test.gts b/packages/host/tests/acceptance/interact-submode-test.gts index 916d3458da..7f1775bbf5 100644 --- a/packages/host/tests/acceptance/interact-submode-test.gts +++ b/packages/host/tests/acceptance/interact-submode-test.gts @@ -313,6 +313,8 @@ module('Acceptance | interact submode tests', function (hooks) { assert.dom('[data-test-search-sheet]').doesNotHaveClass('results'); // Search closed // The card appears on a new stack + await waitFor('[data-test-operator-mode-stack]'); + assert.dom('[data-test-operator-mode-stack]').exists({ count: 1 }); assert .dom( @@ -640,6 +642,7 @@ module('Acceptance | interact submode tests', function (hooks) { assert.dom('[data-test-search-sheet]').doesNotHaveClass('prompt'); // Search closed // There are now 2 stacks + assert.dom('[data-test-operator-mode-stack]').exists({ count: 2 }); assert.dom('[data-test-operator-mode-stack="0"]').includesText('Mango'); // Mango goes on the left stack assert.dom('[data-test-operator-mode-stack="1"]').includesText('Fadhlan'); @@ -668,6 +671,7 @@ module('Acceptance | interact submode tests', function (hooks) { assert.dom('[data-test-search-sheet]').doesNotHaveClass('prompt'); // Search closed // There are now 2 stacks + await waitFor('[data-test-operator-mode-stack="0"]'); assert.dom('[data-test-operator-mode-stack]').exists({ count: 2 }); assert.dom('[data-test-operator-mode-stack="0"]').includesText('Fadhlan'); assert.dom('[data-test-operator-mode-stack="1"]').includesText('Mango'); // Mango gets move onto the right stack diff --git a/packages/host/tests/helpers/index.gts b/packages/host/tests/helpers/index.gts index 66a7696854..014887a827 100644 --- a/packages/host/tests/helpers/index.gts +++ b/packages/host/tests/helpers/index.gts @@ -481,7 +481,7 @@ async function setupTestRealm({ for (const [path, mod] of Object.entries(contents)) { if (path.endsWith('.gts') && typeof mod !== 'string') { let moduleURLString = `${realmURL}${path.replace(/\.gts$/, '')}`; - await shimModule(moduleURLString, mod as object, loader); + loader.shimModule(moduleURLString, mod as object); } } let api = await loader.import(`${baseRealm.url}card-api`); @@ -573,22 +573,6 @@ export async function saveCard(instance: CardDef, id: string, loader: Loader) { return doc; } -export async function shimModule( - moduleURL: string, - module: Record, - loader: Loader, -) { - if (loader) { - loader.shimModule(moduleURL, module); - } - await Promise.all( - Object.keys(module).map(async (name) => { - let m = await loader.import(moduleURL); - m[name]; - }), - ); -} - export function setupCardLogs( hooks: NestedHooks, apiThunk: () => Promise, diff --git a/packages/host/tests/integration/components/card-basics-test.gts b/packages/host/tests/integration/components/card-basics-test.gts index d86f1cef0f..918677608f 100644 --- a/packages/host/tests/integration/components/card-basics-test.gts +++ b/packages/host/tests/integration/components/card-basics-test.gts @@ -34,7 +34,6 @@ import { cleanWhiteSpace, p, testRealmURL, - shimModule, setupCardLogs, saveCard, } from '../../helpers'; @@ -163,7 +162,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Post, Person }); let helloWorld = new Post({ title: 'First Post', @@ -413,7 +412,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Guest, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Guest, Person }); let g1 = new Guest({ name: 'Madeleine', @@ -470,7 +469,7 @@ module('Integration | card-basics', function (hooks) { class Person extends CardDef { @field firstName = contains(StringField); } - await shimModule(`${testRealmURL}test-cards`, { Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person }); // deserialize a card with an ID to mark it as "saved" let card = new Person({ firstName: 'Mango' }); @@ -782,7 +781,7 @@ module('Integration | card-basics', function (hooks) { @field firstName = contains(StringField); @field pet = linksTo(Pet); } - await shimModule(`${testRealmURL}test-cards`, { Person, Pet }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person, Pet }); let mango = new Pet({ firstName: 'Mango', @@ -879,7 +878,7 @@ module('Integration | card-basics', function (hooks) { @field firstName = contains(StringField); @field pets = linksToMany(Pet); } - await shimModule(`${testRealmURL}test-cards`, { Person, Pet }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person, Pet }); let mango = new Pet({ firstName: 'Mango', @@ -967,7 +966,7 @@ module('Integration | card-basics', function (hooks) { // @ts-expect-error Have to purposefully bypass type-checking in order to get into this runtime error state @field pet = linksTo(StringField); } - await shimModule(`${testRealmURL}test-cards`, { Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person }); try { new Person({ firstName: 'Hassan', pet: 'Mango' }); @@ -1006,11 +1005,7 @@ module('Integration | card-basics', function (hooks) { @field firstName = contains(StringField); @field pet = linksTo(Pet); } - await shimModule( - `${testRealmURL}test-cards`, - { Person, Pet, NotAPet }, - loader, - ); + loader.shimModule(`${testRealmURL}test-cards`, { Person, Pet, NotAPet }); let door = new NotAPet({ firstName: 'door' }); try { @@ -1060,7 +1055,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Person, Pet }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person, Pet }); let vanGogh = new Pet({ firstName: 'Van Gogh' }); let mango = new Pet({ firstName: 'Mango', friend: vanGogh }); @@ -1149,7 +1144,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Post, Person }); let helloWorld = new Post({ author: new Person({ @@ -1196,16 +1191,12 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule( - `${testRealmURL}test-cards`, - { - Post, - Person, - TestNumber, - TestString, - }, - loader, - ); + loader.shimModule(`${testRealmURL}test-cards`, { + Post, + Person, + TestNumber, + TestString, + }); let helloWorld = new Post({ author: new Person({ firstName: 'Arthur', number: 10 }), @@ -1234,7 +1225,7 @@ module('Integration | card-basics', function (hooks) { @field title = contains(title); @field author = contains(Person); } - await shimModule(`${testRealmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Post, Person }); let helloWorld = new Post({ title: 'First Post', @@ -1260,7 +1251,7 @@ module('Integration | card-basics', function (hooks) { }, }); } - await shimModule(`${testRealmURL}test-cards`, { Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person }); let helloWorld = new Person({ firstName: 'Arthur', lastName: 'M', @@ -1300,7 +1291,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person }); let helloWorld = new Person({ firstName: 'Arthur', age: 10 }); await renderCard(loader, helloWorld, 'atom'); @@ -1376,7 +1367,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Family, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Family, Person }); let abdelRahmans = new Family({ people: [ @@ -1452,7 +1443,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Family, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Family, Person }); let abdelRahmans = new Family({ people: [ @@ -1498,7 +1489,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Family, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Family, Person }); let mango = new Person({ firstName: 'Mango', }); @@ -1559,16 +1550,12 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule( - `${testRealmURL}test-cards`, - { - Person, - Employee, - Customer, - Group, - }, - loader, - ); + loader.shimModule(`${testRealmURL}test-cards`, { + Person, + Employee, + Customer, + Group, + }); let group = new Group({ people: [ @@ -1708,7 +1695,7 @@ module('Integration | card-basics', function (hooks) { @field firstName = contains(StringField); @field languagesSpoken = containsMany(StringField); } - await shimModule(`${testRealmURL}test-cards`, { Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person }); assert.throws( () => new Person({ languagesSpoken: 'english' }), /Expected array for field value languagesSpoken/, @@ -1735,7 +1722,7 @@ module('Integration | card-basics', function (hooks) { @field title = contains(StringField); @field author = contains(Person); } - await shimModule(`${testRealmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Post, Person }); let helloWorld = new Post({ title: 'My Post', @@ -1777,7 +1764,7 @@ module('Integration | card-basics', function (hooks) { }, }); } - await shimModule(`${testRealmURL}test-cards`, { Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person }); let mango = new Person({ firstName: 'Mango', isCool: true }); let root = await renderCard(loader, mango, 'isolated'); @@ -1797,7 +1784,7 @@ module('Integration | card-basics', function (hooks) { @field isCool = contains(BooleanField); @field isHuman = contains(BooleanField); } - await shimModule(`${testRealmURL}test-cards`, { Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Person }); let mango = new Person({ firstName: 'Mango', isCool: true, @@ -1903,7 +1890,7 @@ module('Integration | card-basics', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Post, Person }); let helloWorld = new Post({ title: 'First Post', @@ -2199,11 +2186,7 @@ module('Integration | card-basics', function (hooks) { @field favoritePlaces = linksToMany(Country); } - await shimModule( - `${testRealmURL}test-cards`, - { Country, ContactCard }, - loader, - ); + loader.shimModule(`${testRealmURL}test-cards`, { Country, ContactCard }); let us = new Country({ countryName: 'United States', flag: 'πŸ‡ΊπŸ‡Έ' }); let canada = new Country({ countryName: 'Canada', flag: 'πŸ‡¨πŸ‡¦' }); diff --git a/packages/host/tests/integration/components/card-editor-test.gts b/packages/host/tests/integration/components/card-editor-test.gts index a93b01adfc..033e68cc3f 100644 --- a/packages/host/tests/integration/components/card-editor-test.gts +++ b/packages/host/tests/integration/components/card-editor-test.gts @@ -24,7 +24,6 @@ import { CardDef } from 'https://cardstack.com/base/card-api'; import { testRealmURL, - shimModule, setupCardLogs, setupLocalIndexing, saveCard, @@ -238,7 +237,7 @@ module('Integration | card-editor', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { TestCard }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { TestCard }); let card = new TestCard({ firstName: 'Mango', lastName: 'Abdel-Rahman' }); await saveCard(card, `${testRealmURL}test-cards/test-card`, loader); @@ -281,7 +280,7 @@ module('Integration | card-editor', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { TestCard }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { TestCard }); let card = new TestCard({ firstName: 'Mango' }); await saveCard(card, `${testRealmURL}test-cards/test-card`, loader); @@ -331,7 +330,7 @@ module('Integration | card-editor', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { TestCard }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { TestCard }); let card = new TestCard({ firstName: 'Mango' }); await saveCard(card, `${testRealmURL}test-cards/test-card`, loader); await renderComponent( diff --git a/packages/host/tests/integration/components/computed-test.gts b/packages/host/tests/integration/components/computed-test.gts index 5e0e847395..2ca3a15499 100644 --- a/packages/host/tests/integration/components/computed-test.gts +++ b/packages/host/tests/integration/components/computed-test.gts @@ -8,12 +8,7 @@ import { Loader } from '@cardstack/runtime-common/loader'; import type LoaderService from '@cardstack/host/services/loader-service'; -import { - cleanWhiteSpace, - testRealmURL, - shimModule, - setupCardLogs, -} from '../../helpers'; +import { cleanWhiteSpace, testRealmURL, setupCardLogs } from '../../helpers'; import { renderCard } from '../../helpers/render-component'; let cardApi: typeof import('https://cardstack.com/base/card-api'); @@ -107,7 +102,7 @@ module('Integration | computeds', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Post, Person }); let firstPost = new Post({ title: 'First Post', @@ -277,7 +272,7 @@ module('Integration | computeds', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Post, Person }); let firstPost = new Post({ title: 'First Post', @@ -412,7 +407,7 @@ module('Integration | computeds', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Family, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Family, Person }); let abdelRahmans = new Family({ people: [ @@ -495,7 +490,7 @@ module('Integration | computeds', function (hooks) { return totalAge; } } - await shimModule(`${testRealmURL}test-cards`, { Family, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Family, Person }); let family = new Family({ people: [ @@ -571,7 +566,7 @@ module('Integration | computeds', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { Location, Person }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { Location, Person }); let person = new Person({ firstName: 'Mango', diff --git a/packages/host/tests/integration/components/preview-test.gts b/packages/host/tests/integration/components/preview-test.gts index 18d3ac9ba7..6063998526 100644 --- a/packages/host/tests/integration/components/preview-test.gts +++ b/packages/host/tests/integration/components/preview-test.gts @@ -12,7 +12,7 @@ import Preview from '@cardstack/host/components/preview'; import type LoaderService from '@cardstack/host/services/loader-service'; -import { testRealmURL, shimModule } from '../../helpers'; +import { testRealmURL } from '../../helpers'; import { renderComponent } from '../../helpers/render-component'; let cardApi: typeof import('https://cardstack.com/base/card-api'); @@ -45,7 +45,7 @@ module('Integration | preview', function (hooks) { }; } - await shimModule(`${testRealmURL}test-cards`, { TestCard }, loader); + loader.shimModule(`${testRealmURL}test-cards`, { TestCard }); let card = new TestCard({ firstName: 'Mango ' }); await renderComponent( class TestDriver extends GlimmerComponent { diff --git a/packages/host/tests/integration/components/serialization-test.gts b/packages/host/tests/integration/components/serialization-test.gts index 9e43968a44..be8a48dff9 100644 --- a/packages/host/tests/integration/components/serialization-test.gts +++ b/packages/host/tests/integration/components/serialization-test.gts @@ -16,13 +16,7 @@ import type LoaderService from '@cardstack/host/services/loader-service'; import { type CardDef as CardDefType } from 'https://cardstack.com/base/card-api'; -import { - p, - cleanWhiteSpace, - shimModule, - setupCardLogs, - saveCard, -} from '../../helpers'; +import { p, cleanWhiteSpace, setupCardLogs, saveCard } from '../../helpers'; import { renderCard } from '../../helpers/render-component'; @@ -83,7 +77,7 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { Post }, loader); + loader.shimModule(`${realmURL}test-cards`, { Post }); // initialize card data as serialized to force us to deserialize instead of using cached data let resource = { @@ -127,7 +121,7 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { Item }, loader); + loader.shimModule(`${realmURL}test-cards`, { Item }); // initialize card data as serialized to force us to deserialize instead of using cached data let resource = { @@ -160,7 +154,8 @@ module('Integration | serialization', function (hooks) { class Person extends CardDef { @field firstName = contains(StringField); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person }); // deserialize a card with an ID to mark it as "saved" let resource = { @@ -216,7 +211,8 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person }); let mango = new Person({ id: `${realmURL}Person/mango`, @@ -248,7 +244,8 @@ module('Integration | serialization', function (hooks) { @field firstName = contains(StringField); @field picture = contains(Base64ImageField); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person }); let mango = new Person({ firstName: 'Mango', @@ -283,7 +280,8 @@ module('Integration | serialization', function (hooks) { class Person extends CardDef { @field firstName = contains(StringField); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person }); let card = new Person({ id: `${realmURL}Person/mango`, @@ -329,7 +327,8 @@ module('Integration | serialization', function (hooks) { class Person extends CardDef { @field firstName = contains(StringField); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person }); // deserialize a card with an ID to mark it as "saved" let resource = { @@ -386,7 +385,8 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { DriverCard }, loader); + + loader.shimModule(`${realmURL}test-cards`, { DriverCard }); let ref = { module: `http://localhost:4202/test/person`, name: 'Person' }; let resource = { @@ -428,7 +428,8 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { DriverCard }, loader); + + loader.shimModule(`${realmURL}test-cards`, { DriverCard }); let ref = { module: `http://localhost:4202/test/person`, name: 'Person' }; let driver = new DriverCard({ ref }); @@ -453,7 +454,8 @@ module('Integration | serialization', function (hooks) { @field created = contains(DateField); @field published = contains(DatetimeField); } - await shimModule(`${realmURL}test-cards`, { Post }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Post }); // initialize card data as deserialized to force us to serialize instead of using cached data let firstPost = new Post({ @@ -489,7 +491,9 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { Post }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Post }); + let resource = { attributes: { title: 'First Post', @@ -552,7 +556,8 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }); let spookyToiletPaper = new Toy({ description: 'Toilet paper ghost: Poooo!', @@ -662,7 +667,7 @@ module('Integration | serialization', function (hooks) { @field firstName = contains(StringField); @field pet = linksTo(Pet); } - await shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -816,7 +821,9 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); + let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -921,7 +928,8 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); let hassan = new Person({ firstName: 'Hassan' }); @@ -984,7 +992,8 @@ module('Integration | serialization', function (hooks) { @field firstName = contains(StringField); @field pet = linksTo(Pet); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); let doc: LooseSingleCardDocument = { data: { @@ -1044,7 +1053,8 @@ module('Integration | serialization', function (hooks) { @field favoriteToy = contains(Toy); @field toys = containsMany(Toy); } - await shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -1162,7 +1172,8 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person }); let mango = new Person({ firstName: 'Mango' }); let hassan = new Person({ firstName: 'Hassan', friend: mango }); @@ -1226,7 +1237,8 @@ module('Integration | serialization', function (hooks) { @field firstName = contains(StringField); @field friend = linksTo(() => Person); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person }); let doc: LooseSingleCardDocument = { data: { @@ -1300,7 +1312,8 @@ module('Integration | serialization', function (hooks) { @field firstName = contains(StringField); @field pet = linksTo(Pet); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); let mango = new Pet({ firstName: 'Mango' }); let hassan = new Person({ firstName: 'Hassan', pet: mango }); @@ -1362,7 +1375,8 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }); let spookyToiletPaper = new Toy({ description: 'Toilet paper ghost: Poooo!', @@ -1491,7 +1505,8 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }); let spookyToiletPaper = new Toy({ description: 'Toilet paper ghost: Poooo!', @@ -1624,7 +1639,8 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }); let spookyToiletPaper = new Toy({ description: 'Toilet paper ghost: Poooo!', @@ -1731,7 +1747,8 @@ module('Integration | serialization', function (hooks) { @field parent = linksTo(() => Person); @field favorite = linksTo(() => Person); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -1806,7 +1823,8 @@ module('Integration | serialization', function (hooks) { @field created = contains(DateField); @field published = contains(DatetimeField); } - await shimModule(`${realmURL}test-cards`, { Post }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Post }); let firstPost = new Post({ created: null, published: null }); let serialized = serializeCard(firstPost, { @@ -1846,7 +1864,8 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { Post, Person }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Post, Person }); let doc = { data: { @@ -1915,7 +1934,7 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Post, Person }); let doc = { data: { @@ -1974,7 +1993,8 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Person, Post }, loader); + + loader.shimModule(`${realmURL}test-cards`, { Person, Post }); let firstPost = new Post({ author: new Person({ @@ -2027,11 +2047,7 @@ module('Integration | serialization', function (hooks) { @field author = contains(Person); } - await shimModule( - `${realmURL}test-cards`, - { Person, Employee, Post }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Person, Employee, Post }); let firstPost = new Post({ author: new Employee({ @@ -2104,7 +2120,7 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { Post, Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Post, Person }); let helloWorld = new Post({ title: 'First Post', @@ -2154,7 +2170,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person }); let mango = new Person({ birthdate: p('2019-10-30') }); let serialized = serializeCard(mango, { includeComputeds: true, @@ -2190,7 +2206,7 @@ module('Integration | serialization', function (hooks) { computeVia: () => '../../person.svg', }); } - await shimModule(`${realmURL}test-cards`, { Pet, Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Pet, Person }); let mango = new Pet({ name: 'Mango' }); let hassan = new Person({ firstName: 'Hassan', pet: mango }); await saveCard(mango, `${realmURL}Pet/mango`, loader); @@ -2286,7 +2302,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Pet, Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Pet, Person }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -2391,7 +2407,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Pet, Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Pet, Person }); let person = new Person({ firstName: 'Burcu' }); let serialized = serializeCard(person, { includeUnrenderedFields: true, @@ -2441,7 +2457,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Pet, Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Pet, Person }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -2493,7 +2509,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Pet, Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Pet, Person }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -2553,7 +2569,7 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { Schedule }, loader); + loader.shimModule(`${realmURL}test-cards`, { Schedule }); let doc = { data: { @@ -2615,11 +2631,7 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule( - `${realmURL}test-cards`, - { Schedule, Appointment }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Schedule, Appointment }); let doc = { data: { @@ -2661,7 +2673,7 @@ module('Integration | serialization', function (hooks) { class Schedule extends CardDef { @field dates = containsMany(DateField); } - await shimModule(`${realmURL}test-cards`, { Schedule }, loader); + loader.shimModule(`${realmURL}test-cards`, { Schedule }); let classSchedule = new Schedule({ dates: [p('2022-4-1'), p('2022-4-4')] }); assert.deepEqual( @@ -2689,11 +2701,7 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule( - `${realmURL}test-cards`, - { Schedule, Appointment }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Schedule, Appointment }); let classSchedule = new Schedule({ appointments: [ @@ -2738,7 +2746,7 @@ module('Integration | serialization', function (hooks) { @field created = contains(DateField); @field published = contains(DatetimeField); } - await shimModule(`${realmURL}test-cards`, { Post }, loader); + loader.shimModule(`${realmURL}test-cards`, { Post }); let firstPost = new Post({ title: 'First Post', @@ -2800,7 +2808,7 @@ module('Integration | serialization', function (hooks) { @field description = contains(StringField, { computeVia: () => 'Post' }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Post, Person, Animal }, loader); + loader.shimModule(`${realmURL}test-cards`, { Post, Person, Animal }); let firstPost = new Post({ title: 'First Post', @@ -2871,11 +2879,7 @@ module('Integration | serialization', function (hooks) { @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule( - `${realmURL}test-cards`, - { Person, Employee, Post }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Person, Employee, Post }); let firstPost = new Post({ title: 'First Post', @@ -2976,11 +2980,7 @@ module('Integration | serialization', function (hooks) { @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule( - `${realmURL}test-cards`, - { Person, Employee, Post, Pet }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Person, Employee, Post, Pet }); let firstPost = new Post({ title: 'First Post', @@ -3108,16 +3108,12 @@ module('Integration | serialization', function (hooks) { @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule( - `${realmURL}test-cards`, - { - Person, - Employee, - Customer, - Group, - }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { + Person, + Employee, + Customer, + Group, + }); let group = new Group({ people: [ @@ -3252,17 +3248,13 @@ module('Integration | serialization', function (hooks) { @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule( - `${realmURL}test-cards`, - { - Person, - Role, - DogWalker, - Employee, - Group, - }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { + Person, + Role, + DogWalker, + Employee, + Group, + }); let group = new Group({ people: [ @@ -3378,7 +3370,7 @@ module('Integration | serialization', function (hooks) { @field firstName = contains(StringField); @field pet = linksTo(Pet); } - await shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }); let doc: LooseSingleCardDocument = { data: { @@ -3472,7 +3464,7 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}person`, { Person }, loader); + loader.shimModule(`${realmURL}person`, { Person }); let doc: LooseSingleCardDocument = { data: { @@ -3545,8 +3537,8 @@ module('Integration | serialization', function (hooks) { @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}person`, { Person }, loader); - await shimModule(`${realmURL}post`, { Post }, loader); + loader.shimModule(`${realmURL}person`, { Person }); + loader.shimModule(`${realmURL}post`, { Post }); let doc: LooseSingleCardDocument = { data: { @@ -3636,9 +3628,9 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}person`, { Person }, loader); - await shimModule(`${realmURL}post`, { Post }, loader); - await shimModule(`${realmURL}blog`, { Blog }, loader); + loader.shimModule(`${realmURL}person`, { Person }); + loader.shimModule(`${realmURL}post`, { Post }); + loader.shimModule(`${realmURL}blog`, { Blog }); let doc: LooseSingleCardDocument = { data: { @@ -3764,11 +3756,12 @@ module('Integration | serialization', function (hooks) { @field editor = contains(Person); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule( - `${realmURL}test-cards`, - { Certificate, Person, Post, Blog }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { + Certificate, + Person, + Post, + Blog, + }); let doc: LooseSingleCardDocument = { data: { @@ -4024,7 +4017,7 @@ module('Integration | serialization', function (hooks) { }); @field thumbnailURL = contains(StringField, { computeVia: () => null }); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person }); let mango = new Person({ birthdate: p('2019-10-30') }); await renderCard(loader, mango, 'isolated'); @@ -4083,7 +4076,7 @@ module('Integration | serialization', function (hooks) { }; } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person }); let mango = new Person({ id: `${realmURL}Person/mango`, @@ -4133,7 +4126,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); let mango = new Pet({ firstName: 'Mango', @@ -4224,7 +4217,7 @@ module('Integration | serialization', function (hooks) { @field firstName = contains(StringField); @field pets = linksToMany(Pet); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -4377,7 +4370,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person, Pet, Toy }); let spookyToiletPaper = new Toy({ description: 'Toilet paper ghost: Poooo!', @@ -4518,7 +4511,7 @@ module('Integration | serialization', function (hooks) { computeVia: () => 'person.svg', }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); let hassan = new Person({ firstName: 'Hassan' }); @@ -4576,7 +4569,7 @@ module('Integration | serialization', function (hooks) { @field firstName = contains(StringField); @field pets = linksToMany(Pet); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); let doc: LooseSingleCardDocument = { data: { @@ -4648,7 +4641,7 @@ module('Integration | serialization', function (hooks) { computeVia: () => 'person.svg', }); } - await shimModule(`${realmURL}test-cards`, { Person, Pet }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person, Pet }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -4785,7 +4778,7 @@ module('Integration | serialization', function (hooks) { computeVia: () => 'person.svg', }); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person }); let mango = new Person({ firstName: 'Mango' }); let vanGogh = new Person({ firstName: 'Van Gogh' }); @@ -4869,7 +4862,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule(`${realmURL}test-cards`, { Person }, loader); + loader.shimModule(`${realmURL}test-cards`, { Person }); let doc: LooseSingleCardDocument = { data: { @@ -4976,11 +4969,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule( - `${realmURL}test-cards`, - { Pet, Friend, Person }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Pet, Friend, Person }); let mango = new Pet({ name: 'Mango' }); let vanGogh = new Pet({ name: 'Van Gogh' }); let hassan = new Friend({ firstName: 'Hassan', pets: [mango, vanGogh] }); @@ -5100,11 +5089,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule( - `${realmURL}test-cards`, - { Pet, Friend, Person }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Pet, Friend, Person }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -5216,11 +5201,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule( - `${realmURL}test-cards`, - { Pet, Friend, Person }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Pet, Friend, Person }); let person = new Person({ firstName: 'Burcu' }); let serialized = serializeCard(person, { includeUnrenderedFields: true, @@ -5272,11 +5253,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule( - `${realmURL}test-cards`, - { Pet, Friend, Person }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Pet, Friend, Person }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -5330,11 +5307,7 @@ module('Integration | serialization', function (hooks) { }, }); } - await shimModule( - `${realmURL}test-cards`, - { Pet, Friend, Person }, - loader, - ); + loader.shimModule(`${realmURL}test-cards`, { Pet, Friend, Person }); let doc: LooseSingleCardDocument = { data: { type: 'card', @@ -5456,7 +5429,7 @@ module('Integration | serialization', function (hooks) { @field notANumber = contains(NumberField); @field infinity = contains(NumberField); } - await shimModule(`${realmURL}test-cards`, { Sample }, loader); + loader.shimModule(`${realmURL}test-cards`, { Sample }); let resource = { attributes: { @@ -5512,7 +5485,7 @@ module('Integration | serialization', function (hooks) { @field someNull = contains(NumberField); } - await shimModule(`${realmURL}test-cards`, { Sample }, loader); + loader.shimModule(`${realmURL}test-cards`, { Sample }); let sample = new Sample({ someNumber: 42, @@ -5554,7 +5527,7 @@ module('Integration | serialization', function (hooks) { @field someDecimal = contains(BigIntegerField); @field someZeroString = contains(BigIntegerField); } - await shimModule(`${realmURL}test-cards`, { Sample }, loader); + loader.shimModule(`${realmURL}test-cards`, { Sample }); let resource = { attributes: { @@ -5602,7 +5575,7 @@ module('Integration | serialization', function (hooks) { @field someNull = contains(BigIntegerField); } - await shimModule(`${realmURL}test-cards`, { Sample }, loader); + loader.shimModule(`${realmURL}test-cards`, { Sample }); let sample = new Sample({ someBigInt: BigInt('9223372036854775808'), @@ -5665,7 +5638,7 @@ module('Integration | serialization', function (hooks) { // }, // }); } - await shimModule(`${realmURL}test-cards`, { Sample }, loader); + loader.shimModule(`${realmURL}test-cards`, { Sample }); let sample = new Sample({ someBigInt: BigInt('1'), @@ -5704,7 +5677,7 @@ module('Integration | serialization', function (hooks) { @field someString = contains(EthereumAddressField); @field someNull = contains(EthereumAddressField); } - await shimModule(`${realmURL}test-cards`, { Sample }, loader); + loader.shimModule(`${realmURL}test-cards`, { Sample }); let resource = { attributes: { @@ -5757,7 +5730,7 @@ module('Integration | serialization', function (hooks) { @field someNull = contains(EthereumAddressField); } - await shimModule(`${realmURL}test-cards`, { Sample }, loader); + loader.shimModule(`${realmURL}test-cards`, { Sample }); let sample = new Sample({ someAddress: '0x00317f9aF5141dC211e9EbcdCE690cf0E98Ef53b', diff --git a/packages/runtime-common/loader.ts b/packages/runtime-common/loader.ts index cfbae87151..46f89903bc 100644 --- a/packages/runtime-common/loader.ts +++ b/packages/runtime-common/loader.ts @@ -8,7 +8,7 @@ import flatMap from 'lodash/flatMap'; import { decodeScopedCSSRequest, isScopedCSSRequest } from 'glimmer-scoped-css'; import jsEscapeString from 'js-string-escape'; -export const SHIMMED_MODULE_FAKE_ORIGIN = 'https://shimmed-module/'; +export const PACKAGES_FAKE_ORIGIN = 'https://packages/'; // this represents a URL that has already been resolved to aid in documenting // when resolution has already been performed @@ -166,13 +166,21 @@ export class Loader { } shimModule(moduleIdentifier: string, module: Record) { - this.moduleShims.set( - moduleIdentifier, - this.createModuleProxy(module, moduleIdentifier), - ); + moduleIdentifier = resolvedPackageName(moduleIdentifier); + let proxiedModule = this.createModuleProxy(module, moduleIdentifier); + + for (let propName of Object.keys(module)) { + // Normal modules always end up in our identity map because the only way for other code to gain access to the module's exports is by getting it through the + // proxy our loader has wrapped around it. But shimmed modules may be used directly by our caller before we've had a chance to put them in the dientity map. + // So this eagerly puts them into the identity map. + proxiedModule[propName]; // Makes sure the shimmed modules get into the identity map. + } + + this.moduleShims.set(moduleIdentifier, proxiedModule); + this.setModule(moduleIdentifier, { state: 'evaluated', - moduleInstance: module, + moduleInstance: proxiedModule, consumedModules: new Set(), }); } @@ -253,17 +261,11 @@ export class Loader { } async import(moduleIdentifier: string): Promise { - if (!isUrlLike(moduleIdentifier)) { - moduleIdentifier = new URL(moduleIdentifier, SHIMMED_MODULE_FAKE_ORIGIN) - .href; - } + moduleIdentifier = resolvedPackageName(moduleIdentifier); let resolvedModule = this.resolve(moduleIdentifier); let resolvedModuleIdentifier = resolvedModule.href; - let shimmed = this.moduleShims.get(moduleIdentifier); - if (shimmed) { - return shimmed as T; - } + await this.advanceToState(resolvedModule, 'evaluated'); let module = this.getModule(resolvedModuleIdentifier); switch (module?.state) { @@ -520,6 +522,16 @@ export class Loader { return result; } } + + let shimmedModule = this.moduleShims.get( + this.asUnresolvedRequest(urlOrRequest, init).url, + ); + if (shimmedModule) { + let response = new Response(); + (response as any)[Symbol.for('shimmed-module')] = shimmedModule; + return response; + } + return await this.fetchImplementation( this.asResolvedRequest(urlOrRequest, init), ); @@ -686,7 +698,7 @@ export class Loader { return { type: 'dep', moduleURL: this.resolve( - new URL(depId, SHIMMED_MODULE_FAKE_ORIGIN).href, + new URL(depId, PACKAGES_FAKE_ORIGIN).href, new URL(moduleIdentifier), ), }; @@ -826,6 +838,13 @@ export class Loader { } } +function resolvedPackageName(moduleIdentifier: string) { + if (!isUrlLike(moduleIdentifier)) { + moduleIdentifier = new URL(moduleIdentifier, PACKAGES_FAKE_ORIGIN).href; + } + return moduleIdentifier; +} + function assertNever(value: never) { throw new Error(`should never happen ${value}`); } diff --git a/packages/runtime-common/virtual-network.ts b/packages/runtime-common/virtual-network.ts index 1c9a97f15c..bdb95f2c36 100644 --- a/packages/runtime-common/virtual-network.ts +++ b/packages/runtime-common/virtual-network.ts @@ -1,4 +1,4 @@ -import { SHIMMED_MODULE_FAKE_ORIGIN } from './loader'; +import { Loader, PACKAGES_FAKE_ORIGIN } from './loader'; import type { RunnerOpts } from './search-index'; const isFastBoot = typeof (globalThis as any).FastBoot !== 'undefined'; @@ -22,13 +22,24 @@ export type Handler = (req: Request) => Promise; export class VirtualNetwork { private nativeFetch = getNativeFetch(); - private handlers: Handler[] = []; - private shimmedModules = new Map>(); + private shimmingLoader = new Loader(() => { + throw new Error('This loader should never call fetch'); + }); + + constructor() { + this.mount(async (request) => { + if (request.url.startsWith(PACKAGES_FAKE_ORIGIN)) { + return this.shimmingLoader.fetch(request); + } + + return null; + }); + } shimModule(moduleIdentifier: string, module: Record) { - this.shimmedModules.set(moduleIdentifier, module); + this.shimmingLoader.shimModule(moduleIdentifier, module); } mount(handler: Handler) { @@ -44,22 +55,6 @@ export class VirtualNetwork { ? urlOrRequest : new Request(urlOrRequest, init); - if (request.url.startsWith(SHIMMED_MODULE_FAKE_ORIGIN)) { - let shimmedModule = this.shimmedModules.get( - request.url.replace(SHIMMED_MODULE_FAKE_ORIGIN, ''), - ); - - if (!shimmedModule) { - throw new Error( - `Shimmed module not found but it should've been: ${request.url}`, - ); - } - - let response = new Response(); - (response as any)[Symbol.for('shimmed-module')] = shimmedModule; - return response; - } - for (let handler of this.handlers) { let response = await handler(request); if (response) { From 10e0e6ecd100d529d39e464c632f35267ec1b8c2 Mon Sep 17 00:00:00 2001 From: Edward Faulkner Date: Thu, 14 Mar 2024 12:39:08 -0400 Subject: [PATCH 08/16] move shared module-scoped state to globals This is a followup to the change that switch to Symbol.for, with the intent of making it not matter which loader you retrive cardApi from. It turns out we also need to make sure you're sharing the same module-scoped WeakMaps, etc. --- packages/base/card-api.gts | 21 +++++++++++++++++---- packages/base/field-component.gts | 6 +++++- packages/base/room.gts | 29 +++++++++++++++++++++-------- packages/base/shared-state.ts | 25 +++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 packages/base/shared-state.ts diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index 6c333ad555..d102a01df9 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -48,6 +48,7 @@ import { type RealmInfo, } from '@cardstack/runtime-common'; import type { ComponentLike } from '@glint/template'; +import { initSharedState } from './shared-state'; export { primitive, isField, type BoxComponent }; export const serialize = Symbol.for('cardstack-serialize'); @@ -170,10 +171,22 @@ function isStaleValue(value: any): value is StaleValue { return false; } } -const deserializedData = new WeakMap>(); -const recomputePromises = new WeakMap>(); -const identityContexts = new WeakMap(); -const subscribers = new WeakMap>(); +const deserializedData = initSharedState( + 'deserializedData', + () => new WeakMap>(), +); +const recomputePromises = initSharedState( + 'recomputePromises', + () => new WeakMap>(), +); +const identityContexts = initSharedState( + 'identityContexts', + () => new WeakMap(), +); +const subscribers = initSharedState( + 'subscribers', + () => new WeakMap>(), +); // our place for notifying Glimmer when a card is ready to re-render (which will // involve rerunning async computed fields) diff --git a/packages/base/field-component.gts b/packages/base/field-component.gts index 9ddff026c7..d4bcef1790 100644 --- a/packages/base/field-component.gts +++ b/packages/base/field-component.gts @@ -16,6 +16,7 @@ import { getField } from '@cardstack/runtime-common'; import type { ComponentLike } from '@glint/template'; import { CardContainer } from '@cardstack/boxel-ui/components'; import Modifier from 'ember-modifier'; +import { initSharedState } from './shared-state'; interface BoxComponentSignature { Args: { Named: { format?: Format } }; @@ -24,7 +25,10 @@ interface BoxComponentSignature { export type BoxComponent = ComponentLike; -const componentCache = new WeakMap, BoxComponent>(); +const componentCache = initSharedState( + 'componentCache', + () => new WeakMap, BoxComponent>(), +); export function getBoxComponent( card: typeof BaseDef, diff --git a/packages/base/room.gts b/packages/base/room.gts index 101d35eb19..181f9bbc48 100644 --- a/packages/base/room.gts +++ b/packages/base/room.gts @@ -21,6 +21,7 @@ import { } from '@cardstack/runtime-common'; //@ts-expect-error cached type not available yet import { cached } from '@glimmer/tracking'; +import { initSharedState } from './shared-state'; // this is so we can have triple equals equivalent room member cards function upsertRoomMember({ @@ -275,14 +276,26 @@ interface RoomState { // in addition to acting as a cache, this also ensures we have // triple equal equivalence for the interior cards of RoomField -const eventCache = new WeakMap>(); -const messageCache = new WeakMap>(); -const roomMemberCache = new WeakMap>(); -const roomStateCache = new WeakMap(); -const fragmentCache = new WeakMap< - RoomField, - Map ->(); +const eventCache = initSharedState( + 'eventCache', + () => new WeakMap>(), +); +const messageCache = initSharedState( + 'messageCache', + () => new WeakMap>(), +); +const roomMemberCache = initSharedState( + 'roomMemberCache', + () => new WeakMap>(), +); +const roomStateCache = initSharedState( + 'roomStateCache', + () => new WeakMap(), +); +const fragmentCache = initSharedState( + 'fragmentCache', + () => new WeakMap>(), +); export class RoomField extends FieldDef { static displayName = 'Room'; diff --git a/packages/base/shared-state.ts b/packages/base/shared-state.ts new file mode 100644 index 0000000000..100dae00b6 --- /dev/null +++ b/packages/base/shared-state.ts @@ -0,0 +1,25 @@ +/* + This module needs to exist so long as we have multiple loaders cooperating in + the same environment. It ensures that any shared global state is really + global. Ultimately we would like to get to the point where the loader itself + is scoped so broadly that there's only one, and module-scoped state is safe to + treat as global. +*/ + +const bucket: Map = (() => { + let g = globalThis as unknown as { + __card_api_shared_state: Map | undefined; + }; + if (!g.__card_api_shared_state) { + g.__card_api_shared_state = new Map(); + } + return g.__card_api_shared_state; +})(); + +export function initSharedState(key: string, fn: () => T): T { + if (bucket.has(key)) { + return bucket.get(key) as T; + } + bucket.set(key, fn()); + return bucket.get(key) as T; +} From 3793c12842dcdf863e9428f771698066d62bf3eb Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 15 Mar 2024 11:12:30 +0100 Subject: [PATCH 09/16] Fixes all tests but one --- .../components/operator-mode-test.gts | 23 +++++++----- .../tests/integration/search-index-test.gts | 35 ++++++++++--------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/host/tests/integration/components/operator-mode-test.gts b/packages/host/tests/integration/components/operator-mode-test.gts index 2fb4f3188c..6f00eff582 100644 --- a/packages/host/tests/integration/components/operator-mode-test.gts +++ b/packages/host/tests/integration/components/operator-mode-test.gts @@ -1234,6 +1234,7 @@ module('Integration | operator-mode', function (hooks) { await waitFor(`[data-test-cards-grid-item]`); await click(`[data-test-cards-grid-item="${testRealmURL}Person/burcu"]`); + await waitFor(`[data-test-stack-card-index="1"]`); assert.dom(`[data-test-stack-card-index="1"]`).exists(); // Opens card on the stack assert .dom(`[data-test-stack-card-index="1"] [data-test-boxel-header-title]`) @@ -1244,7 +1245,7 @@ module('Integration | operator-mode', function (hooks) { }); test('create new card editor opens in the stack at each nesting level', async function (assert) { - assert.expect(11); + assert.expect(10); await setCardInOperatorModeState(`${testRealmURL}grid`); await renderComponent( class TestDriver extends GlimmerComponent { @@ -1352,11 +1353,14 @@ module('Integration | operator-mode', function (hooks) { }); await click('[data-test-stack-card-index="1"] [data-test-edit-button]'); - assert - .dom(`[data-test-stack-card="${packetId}"]`) - .containsText( - 'Everyone knows that Alice ran the show in the Brady household.', - ); + + await waitUntil(() => + document + .querySelector(`[data-test-stack-card="${packetId}"]`) + ?.textContent?.includes( + 'Everyone knows that Alice ran the show in the Brady household.', + ), + ); }); test('can choose a card for a linksTo field that has an existing value', async function (assert) { @@ -1722,6 +1726,7 @@ module('Integration | operator-mode', function (hooks) { await waitFor(`[data-test-stack-card="${testRealmURL}grid"]`); await waitFor(`[data-test-cards-grid-item]`); await click(`[data-test-cards-grid-item="${testRealmURL}Person/fadhlan"]`); + await waitFor(`[data-test-stack-card-index="1"]`); assert.dom(`[data-test-stack-card-index="1"]`).exists(); await waitFor('[data-test-person]'); @@ -1749,6 +1754,7 @@ module('Integration | operator-mode', function (hooks) { await waitFor(`[data-test-cards-grid-item]`); await click(`[data-test-cards-grid-item="${testRealmURL}Person/fadhlan"]`); + await waitFor(`[data-test-stack-card-index="1"]`); assert.dom(`[data-test-stack-card-index="1"]`).exists(); assert .dom( @@ -1775,7 +1781,8 @@ module('Integration | operator-mode', function (hooks) { await waitFor(`[data-test-cards-grid-item]`); await click(`[data-test-cards-grid-item="${testRealmURL}Person/fadhlan"]`); - assert.dom(`[data-test-stack-card-index="1"]`).exists(); + await waitFor(`[data-test-stack-card-index="1"]`); + assert .dom( `[data-test-stack-card="${testRealmURL}Person/fadhlan"] [data-test-boxel-header-title]`, @@ -2352,7 +2359,7 @@ module('Integration | operator-mode', function (hooks) { assert.dom('[data-test-overlay-selected]').doesNotExist(); await click(`[data-test-cards-grid-item="${testRealmURL}Person/fadhlan"]`); - assert.dom(`[data-test-stack-card-index="1"]`).exists(); + await waitFor(`[data-test-stack-card-index="1"]`, { count: 1 }); }); test('displays realm name as header title when hovering realm icon', async function (assert) { diff --git a/packages/host/tests/integration/search-index-test.gts b/packages/host/tests/integration/search-index-test.gts index c7b4b4c05d..dd4ff1df91 100644 --- a/packages/host/tests/integration/search-index-test.gts +++ b/packages/host/tests/integration/search-index-test.gts @@ -2649,27 +2649,28 @@ module('Integration | search-index', function (hooks) { 'http://localhost:4201/base/links-to-editor', 'http://localhost:4201/base/links-to-many-component', 'http://localhost:4201/base/number', + 'http://localhost:4201/base/shared-state', 'http://localhost:4201/base/string', 'http://localhost:4201/base/text-input-validator', 'http://localhost:4201/base/watched-array', 'http://localhost:4202/test/person', - 'https://shimmed-module/@cardstack/boxel-ui/components', - 'https://shimmed-module/@cardstack/boxel-ui/helpers', - 'https://shimmed-module/@cardstack/boxel-ui/icons', - 'https://shimmed-module/@cardstack/runtime-common', - 'https://shimmed-module/@ember/component', - 'https://shimmed-module/@ember/component/template-only', - 'https://shimmed-module/@ember/helper', - 'https://shimmed-module/@ember/modifier', - 'https://shimmed-module/@ember/object', - 'https://shimmed-module/@ember/template-factory', - 'https://shimmed-module/@glimmer/component', - 'https://shimmed-module/@glimmer/tracking', - 'https://shimmed-module/ember-concurrency', - 'https://shimmed-module/ember-concurrency/-private/async-arrow-runtime', - 'https://shimmed-module/ember-modifier', - 'https://shimmed-module/lodash', - 'https://shimmed-module/tracked-built-ins', + 'https://packages/@cardstack/boxel-ui/components', + 'https://packages/@cardstack/boxel-ui/helpers', + 'https://packages/@cardstack/boxel-ui/icons', + 'https://packages/@cardstack/runtime-common', + 'https://packages/@ember/component', + 'https://packages/@ember/component/template-only', + 'https://packages/@ember/helper', + 'https://packages/@ember/modifier', + 'https://packages/@ember/object', + 'https://packages/@ember/template-factory', + 'https://packages/@glimmer/component', + 'https://packages/@glimmer/tracking', + 'https://packages/ember-concurrency', + 'https://packages/ember-concurrency/-private/async-arrow-runtime', + 'https://packages/ember-modifier', + 'https://packages/lodash', + 'https://packages/tracked-built-ins', ], 'the card references for the instance are correct', ); From 29a6c550390e58dbb5325e0856b2e73289462c15 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 15 Mar 2024 15:16:39 +0100 Subject: [PATCH 10/16] Also add card tracking to globals --- packages/base/card-api.gts | 5 ++++- packages/host/app/resources/card-resource.ts | 2 ++ packages/host/tests/acceptance/interact-submode-test.gts | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index d102a01df9..c7c80bfa47 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -190,7 +190,10 @@ const subscribers = initSharedState( // our place for notifying Glimmer when a card is ready to re-render (which will // involve rerunning async computed fields) -const cardTracking = new TrackedWeakMap(); +const cardTracking = initSharedState( + 'cardTracking', + () => new TrackedWeakMap(), +); const isBaseInstance = Symbol.for('isBaseInstance'); diff --git a/packages/host/app/resources/card-resource.ts b/packages/host/app/resources/card-resource.ts index a41915b1e4..af8b838c49 100644 --- a/packages/host/app/resources/card-resource.ts +++ b/packages/host/app/resources/card-resource.ts @@ -225,6 +225,8 @@ export class CardResource extends Resource { return; } + debugger; + if (invalidations.includes(card.id)) { // Do not reload if the event is a result of a request that we made. Otherwise we risk overwriting // the inputs with past values. This can happen if the user makes edits in the time between the auto diff --git a/packages/host/tests/acceptance/interact-submode-test.gts b/packages/host/tests/acceptance/interact-submode-test.gts index d095eeee73..ce982ea8c0 100644 --- a/packages/host/tests/acceptance/interact-submode-test.gts +++ b/packages/host/tests/acceptance/interact-submode-test.gts @@ -1357,6 +1357,7 @@ module('Acceptance | interact submode tests', function (hooks) { ); }, }); + await waitUntil(() => document .querySelector('[data-test-operator-mode-stack="0"] [data-test-person]') From 336c90e70d069c17bf434c134273795abcdb401e Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Fri, 15 Mar 2024 15:58:18 +0100 Subject: [PATCH 11/16] Fix the rest of the tests --- packages/host/app/resources/card-resource.ts | 2 -- .../components/operator-mode-test.gts | 17 ++++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/host/app/resources/card-resource.ts b/packages/host/app/resources/card-resource.ts index af8b838c49..a41915b1e4 100644 --- a/packages/host/app/resources/card-resource.ts +++ b/packages/host/app/resources/card-resource.ts @@ -225,8 +225,6 @@ export class CardResource extends Resource { return; } - debugger; - if (invalidations.includes(card.id)) { // Do not reload if the event is a result of a request that we made. Otherwise we risk overwriting // the inputs with past values. This can happen if the user makes edits in the time between the auto diff --git a/packages/host/tests/integration/components/operator-mode-test.gts b/packages/host/tests/integration/components/operator-mode-test.gts index 6f00eff582..eff6023c99 100644 --- a/packages/host/tests/integration/components/operator-mode-test.gts +++ b/packages/host/tests/integration/components/operator-mode-test.gts @@ -1114,9 +1114,8 @@ module('Integration | operator-mode', function (hooks) { await click(`[data-test-select="${testRealmURL}Person/fadhlan"]`); await click('[data-test-card-catalog-go-button]'); - assert - .dom(`[data-test-stack-card="${testRealmURL}Person/fadhlan"]`) - .isVisible(); + + await waitFor(`[data-test-stack-card="${testRealmURL}Person/fadhlan"]`); }); test('displays cards on cards-grid and includes `catalog-entry` instances', async function (assert) { @@ -1245,7 +1244,7 @@ module('Integration | operator-mode', function (hooks) { }); test('create new card editor opens in the stack at each nesting level', async function (assert) { - assert.expect(10); + assert.expect(9); await setCardInOperatorModeState(`${testRealmURL}grid`); await renderComponent( class TestDriver extends GlimmerComponent { @@ -1324,9 +1323,13 @@ module('Integration | operator-mode', function (hooks) { await click('[data-test-stack-card-index="3"] [data-test-close-button]'); await waitFor('[data-test-stack-card-index="3"]', { count: 0 }); - assert - .dom('[data-test-stack-card-index="2"] [data-test-field="authorBio"]') - .containsText('Alice Enwunder'); + await waitUntil(() => + /Alice\s*Enwunder/.test( + document.querySelector( + '[data-test-stack-card-index="2"] [data-test-field="authorBio"]', + )!.textContent!, + ), + ); await click('[data-test-stack-card-index="2"] [data-test-close-button]'); await waitFor('[data-test-stack-card-index="2"]', { count: 0 }); From e14cc8c62a70f13973201548b7b99ac6eab55bcd Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Thu, 21 Mar 2024 11:25:25 +0100 Subject: [PATCH 12/16] Fix another test, one to go --- .../tests/integration/components/operator-mode-test.gts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/host/tests/integration/components/operator-mode-test.gts b/packages/host/tests/integration/components/operator-mode-test.gts index 35e5a99ec5..8668042630 100644 --- a/packages/host/tests/integration/components/operator-mode-test.gts +++ b/packages/host/tests/integration/components/operator-mode-test.gts @@ -3058,17 +3058,17 @@ module('Integration | operator-mode', function (hooks) { }, ); - let savedCards = new Set(); - this.onSave((url) => savedCards.add(url.href)); + await waitFor(`[data-test-stack-card="${testRealmURL}BlogPost/2"]`); await click('[data-test-edit-button]'); assert.dom('[data-test-add-new]').exists(); await click('[data-test-add-new]'); await waitFor(`[data-test-card-catalog-modal]`); await click(`[data-test-card-catalog-create-new-button]`); - await waitFor('[data-test-edit-button]'); + await waitFor('[data-test-stack-card-index="1"]'); + await click('[data-test-edit-button]'); - await waitFor('[data-test-isolated-author'); + await waitFor('[data-test-isolated-author]'); assert.dom('[data-test-isolated-author]').exists(); }); }); From a0195a9480e54b9ba3030e9a659238d96e4d9846 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Thu, 21 Mar 2024 15:10:42 +0100 Subject: [PATCH 13/16] Fix a situation where creating a linked card is not reflected in the parent card --- .../host/app/components/operator-mode/interact-submode.gts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/host/app/components/operator-mode/interact-submode.gts b/packages/host/app/components/operator-mode/interact-submode.gts index e239d624e6..0912f90705 100644 --- a/packages/host/app/components/operator-mode/interact-submode.gts +++ b/packages/host/app/components/operator-mode/interact-submode.gts @@ -167,7 +167,7 @@ export default class InteractSubmode extends Component { doc, relativeTo, ); - await here.cardService.saveModel(here, newCard); + let newItem = new StackItem({ owner: here, card: newCard, @@ -176,6 +176,7 @@ export default class InteractSubmode extends Component { isLinkedCard: opts?.isLinkedCard, stackIndex, }); + await here.cardService.saveModel(here, newCard); await newItem.ready(); here.addToStack(newItem); return await newItem.request?.promise; From d7dd6402732fc6512168cf89882fe0bd4f8381dd Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Thu, 21 Mar 2024 16:36:10 +0100 Subject: [PATCH 14/16] Add a note about possible race condition --- .../host/app/components/operator-mode/interact-submode.gts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/host/app/components/operator-mode/interact-submode.gts b/packages/host/app/components/operator-mode/interact-submode.gts index 0912f90705..f297a5d3fd 100644 --- a/packages/host/app/components/operator-mode/interact-submode.gts +++ b/packages/host/app/components/operator-mode/interact-submode.gts @@ -176,7 +176,13 @@ export default class InteractSubmode extends Component { isLinkedCard: opts?.isLinkedCard, stackIndex, }); + + // TODO: it is important saveModel happens after newItem because it + // looks like perhaps there is a race condition (or something else) when a + // new linked card is created, and when it is added to the stack and closed + // - the parent card is not updated with the new linked card await here.cardService.saveModel(here, newCard); + await newItem.ready(); here.addToStack(newItem); return await newItem.request?.promise; From d539d564572c7273e17c73c7ea122e5cd0620fe7 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Thu, 21 Mar 2024 17:29:16 +0100 Subject: [PATCH 15/16] Move packages fake origin to virtual network --- packages/host/app/services/loader-service.ts | 4 +- packages/realm-server/main.ts | 2 +- .../realm-server/tests/auth-client-test.ts | 2 +- packages/realm-server/tests/indexing-test.ts | 4 +- packages/realm-server/tests/loader-test.ts | 22 ++-- .../realm-server/tests/module-syntax-test.ts | 2 +- .../realm-server/tests/realm-server-test.ts | 14 +-- packages/runtime-common/loader.ts | 103 ++++-------------- packages/runtime-common/virtual-network.ts | 25 ++++- 9 files changed, 69 insertions(+), 109 deletions(-) diff --git a/packages/host/app/services/loader-service.ts b/packages/host/app/services/loader-service.ts index d3afee4215..edb76b407d 100644 --- a/packages/host/app/services/loader-service.ts +++ b/packages/host/app/services/loader-service.ts @@ -37,12 +37,12 @@ export default class LoaderService extends Service { private makeInstance() { if (this.fastboot.isFastBoot) { - let loader = new Loader(this.virtualNetwork.fetch); + let loader = this.virtualNetwork.createLoader(); shimExternals(this.virtualNetwork); return loader; } - let loader = new Loader(this.virtualNetwork.fetch); + let loader = this.virtualNetwork.createLoader(); loader.addURLMapping( new URL(baseRealm.url), new URL(config.resolvedBaseRealmURL), diff --git a/packages/realm-server/main.ts b/packages/realm-server/main.ts index b8b6becf4b..10508c5cb7 100644 --- a/packages/realm-server/main.ts +++ b/packages/realm-server/main.ts @@ -114,7 +114,7 @@ if ( let log = logger('main'); let virtualNetwork = new VirtualNetwork(); -let loader = new Loader(virtualNetwork.fetch); +let loader = virtualNetwork.createLoader(); shimExternals(virtualNetwork); let urlMappings = fromUrls.map((fromUrl, i) => [ diff --git a/packages/realm-server/tests/auth-client-test.ts b/packages/realm-server/tests/auth-client-test.ts index 1aeac31159..a5367c9678 100644 --- a/packages/realm-server/tests/auth-client-test.ts +++ b/packages/realm-server/tests/auth-client-test.ts @@ -35,7 +35,7 @@ module('realm-auth-client', function (assert) { } as RealmAuthMatrixClientInterface; let virtualNetwork = new VirtualNetwork(); - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); client = new RealmAuthClient( new URL('http://testrealm.com/'), diff --git a/packages/realm-server/tests/indexing-test.ts b/packages/realm-server/tests/indexing-test.ts index 22a91b98da..725d078255 100644 --- a/packages/realm-server/tests/indexing-test.ts +++ b/packages/realm-server/tests/indexing-test.ts @@ -35,7 +35,7 @@ setGracefulCleanup(); // as to test through loader caching) module('indexing', function (hooks) { let virtualNetwork = new VirtualNetwork(); - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping( new URL(baseRealm.url), @@ -54,7 +54,7 @@ module('indexing', function (hooks) { setupBaseRealmServer(hooks, loader); hooks.beforeEach(async function () { - let testRealmLoader = new Loader(virtualNetwork.fetch); + let testRealmLoader = virtualNetwork.createLoader(); testRealmLoader.addURLMapping( new URL(baseRealm.url), new URL('http://localhost:4201/base/'), diff --git a/packages/realm-server/tests/loader-test.ts b/packages/realm-server/tests/loader-test.ts index 741b070b9e..8746746cd1 100644 --- a/packages/realm-server/tests/loader-test.ts +++ b/packages/realm-server/tests/loader-test.ts @@ -23,7 +23,7 @@ module('loader', function (hooks) { let testRealmServer: Server; let virtualNetwork = new VirtualNetwork(); - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping( new URL(baseRealm.url), @@ -47,7 +47,7 @@ module('loader', function (hooks) { }); test('can dynamically load modules with cycles', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let module = await loader.import<{ three(): number }>( `${testRealmHref}cycle-two`, @@ -56,7 +56,7 @@ module('loader', function (hooks) { }); test('can resolve multiple import load races against a common dep', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let a = loader.import<{ a(): string }>(`${testRealmHref}a`); let b = loader.import<{ b(): string }>(`${testRealmHref}b`); @@ -66,7 +66,7 @@ module('loader', function (hooks) { }); test('can resolve a import deadlock', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let a = loader.import<{ a(): string }>(`${testRealmHref}deadlock/a`); let b = loader.import<{ b(): string }>(`${testRealmHref}deadlock/b`); @@ -78,7 +78,7 @@ module('loader', function (hooks) { }); test('supports import.meta', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); let realm = await createRealm( loader, dir.name, @@ -103,7 +103,7 @@ module('loader', function (hooks) { }); test('can determine consumed modules', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); await loader.import<{ a(): string }>(`${testRealmHref}a`); assert.deepEqual(await loader.getConsumedModules(`${testRealmHref}a`), [ `${testRealmHref}a`, @@ -113,7 +113,7 @@ module('loader', function (hooks) { }); test('can determine consumed modules when an error is encountered during loading', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); try { await loader.import<{ d(): string }>(`${testRealmHref}d`); @@ -131,7 +131,7 @@ module('loader', function (hooks) { }); test('can get consumed modules within a cycle', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); await loader.import<{ three(): number }>(`${testRealmHref}cycle-two`); let modules = await loader.getConsumedModules(`${testRealmHref}cycle-two`); @@ -142,7 +142,7 @@ module('loader', function (hooks) { }); test('supports identify API', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); shimExternals(virtualNetwork); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let { Person } = await loader.import<{ Person: unknown }>( @@ -160,7 +160,7 @@ module('loader', function (hooks) { }); test('exports cannot be mutated', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); shimExternals(virtualNetwork); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let module = await loader.import<{ Person: unknown }>( @@ -172,7 +172,7 @@ module('loader', function (hooks) { }); test('can get a loader used to import a specific card', async function (assert) { - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); shimExternals(virtualNetwork); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); let module = await loader.import(`${testRealmHref}person`); diff --git a/packages/realm-server/tests/module-syntax-test.ts b/packages/realm-server/tests/module-syntax-test.ts index 2ae8cb8543..711258b6f2 100644 --- a/packages/realm-server/tests/module-syntax-test.ts +++ b/packages/realm-server/tests/module-syntax-test.ts @@ -14,7 +14,7 @@ import { shimExternals } from '../lib/externals'; module('module-syntax', function () { let virtualNetwork = new VirtualNetwork(); - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping( new URL(baseRealm.url), diff --git a/packages/realm-server/tests/realm-server-test.ts b/packages/realm-server/tests/realm-server-test.ts index f87d5bba82..8d868ae6a2 100644 --- a/packages/realm-server/tests/realm-server-test.ts +++ b/packages/realm-server/tests/realm-server-test.ts @@ -117,7 +117,7 @@ module('Realm Server', function (hooks) { let dir: DirResult; let virtualNetwork = new VirtualNetwork(); - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); shimExternals(virtualNetwork); @@ -1717,7 +1717,7 @@ module('Realm Server', function (hooks) { let virtualNetwork = new VirtualNetwork(); - let testRealmServer2Loader = new Loader(virtualNetwork.fetch); + let testRealmServer2Loader = virtualNetwork.createLoader(); testRealmServer2Loader.addURLMapping( new URL(baseRealm.url), new URL(localBaseRealm), @@ -2031,7 +2031,7 @@ module('Realm Server serving from root', function (hooks) { let dir: DirResult; let virtualNetwork = new VirtualNetwork(); - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); shimExternals(virtualNetwork); @@ -2049,7 +2049,7 @@ module('Realm Server serving from root', function (hooks) { let virtualNetwork = new VirtualNetwork(); - let testRealmServerLoader = new Loader(virtualNetwork.fetch); + let testRealmServerLoader = virtualNetwork.createLoader(); testRealmServerLoader.addURLMapping( new URL(baseRealm.url), new URL(localBaseRealm), @@ -2236,7 +2236,7 @@ module('Realm Server serving from a subdirectory', function (hooks) { let dir: DirResult; let virtualNetwork = new VirtualNetwork(); - let loader = new Loader(virtualNetwork.fetch); + let loader = virtualNetwork.createLoader(); loader.addURLMapping(new URL(baseRealm.url), new URL(localBaseRealm)); shimExternals(virtualNetwork); @@ -2251,7 +2251,7 @@ module('Realm Server serving from a subdirectory', function (hooks) { dir = dirSync(); copySync(join(__dirname, 'cards'), dir.name); - let testRealmServerLoader = new Loader(virtualNetwork.fetch); + let testRealmServerLoader = virtualNetwork.createLoader(); testRealmServerLoader.addURLMapping( new URL(baseRealm.url), new URL(localBaseRealm), @@ -2304,7 +2304,7 @@ async function setupPermissionedRealm(permissions: RealmPermissions) { let dir = dirSync(); copySync(join(__dirname, 'cards'), dir.name); let virtualNetwork = new VirtualNetwork(); - let testRealmServerLoader = new Loader(virtualNetwork.fetch); + let testRealmServerLoader = virtualNetwork.createLoader(); testRealmServerLoader.addURLMapping( new URL(baseRealm.url), new URL(localBaseRealm), diff --git a/packages/runtime-common/loader.ts b/packages/runtime-common/loader.ts index 8e59c206bd..8f341f2435 100644 --- a/packages/runtime-common/loader.ts +++ b/packages/runtime-common/loader.ts @@ -8,8 +8,6 @@ import flatMap from 'lodash/flatMap'; import { decodeScopedCSSRequest, isScopedCSSRequest } from 'glimmer-scoped-css'; import jsEscapeString from 'js-string-escape'; -export const PACKAGES_FAKE_ORIGIN = 'https://packages/'; - // this represents a URL that has already been resolved to aid in documenting // when resolution has already been performed export interface ResolvedURL extends URL { @@ -91,7 +89,6 @@ type EvaluatableModule = type UnregisteredDep = | { type: 'dep'; moduleURL: ResolvedURL } - | { type: 'shim-dep'; moduleId: string } | { type: '__import_meta__' } | { type: 'exports' }; @@ -104,10 +101,6 @@ type EvaluatableDep = type: 'completing-dep'; moduleURL: ResolvedURL; } - | { - type: 'shim-dep'; - moduleId: string; - } | { type: '__import_meta__' } | { type: 'exports' }; @@ -138,13 +131,19 @@ export class Loader { private static loaders = new WeakMap(); private fetchImplementation: Fetch; + private resolveImport: (moduleIdentifier: string) => string; - constructor(fetch: Fetch) { + constructor( + fetch: Fetch, + resolveImport?: (moduleIdentifier: string) => string, + ) { this.fetchImplementation = fetch; + this.resolveImport = + resolveImport ?? ((moduleIdentifier) => moduleIdentifier); } static cloneLoader(loader: Loader): Loader { - let clone = new Loader(loader.fetchImplementation); + let clone = new Loader(loader.fetchImplementation, loader.resolveImport); clone.urlHandlers = loader.urlHandlers; clone.urlMappings = loader.urlMappings; for (let [moduleIdentifier, module] of loader.moduleShims) { @@ -166,7 +165,7 @@ export class Loader { } shimModule(moduleIdentifier: string, module: Record) { - moduleIdentifier = resolvedPackageName(moduleIdentifier); + moduleIdentifier = this.resolveImport(moduleIdentifier); let proxiedModule = this.createModuleProxy(module, moduleIdentifier); for (let propName of Object.keys(module)) { @@ -194,13 +193,9 @@ export class Loader { } consumed.add(moduleIdentifier); - let module: Module | undefined; - if (isUrlLike(moduleIdentifier)) { - let resolvedModuleIdentifier = this.resolve(new URL(moduleIdentifier)); - module = this.getModule(resolvedModuleIdentifier.href); - } else { - module = this.getModule(moduleIdentifier); - } + let resolvedModuleIdentifier = this.resolve(new URL(moduleIdentifier)); + let module = this.getModule(resolvedModuleIdentifier.href); + if (!module || module.state === 'fetching') { // we haven't yet tried importing the module or we are still in the process of importing the module try { @@ -261,7 +256,7 @@ export class Loader { } async import(moduleIdentifier: string): Promise { - moduleIdentifier = resolvedPackageName(moduleIdentifier); + moduleIdentifier = this.resolveImport(moduleIdentifier); let resolvedModule = this.resolve(moduleIdentifier); let resolvedModuleIdentifier = resolvedModule.href; @@ -315,16 +310,7 @@ export class Loader { maybeReadyDeps.push(entry); continue; } - let depModule = this.getModule( - entry.type === 'dep' ? entry.moduleURL.href : entry.moduleId, - ); - if (entry.type === 'shim-dep') { - maybeReadyDeps.push({ - type: 'shim-dep', - moduleId: entry.moduleId, - }); - continue; - } + let depModule = this.getModule(entry.moduleURL.href); if (!isEvaluatable(depModule)) { // we always only await the first dep that actually needs work and // then break back to the top-level state machine, so that we'll @@ -386,18 +372,7 @@ export class Loader { readyDeps.push(entry); continue; } - let depModuleId = - entry.type === 'dep' || entry.type === 'completing-dep' - ? entry.moduleURL.href - : entry.moduleId; - let depModule = this.getModule(depModuleId); - if (entry.type === 'shim-dep') { - readyDeps.push({ - type: 'shim-dep', - moduleId: entry.moduleId, - }); - continue; - } + let depModule = this.getModule(entry.moduleURL.href); if (entry.type === 'dep') { readyDeps.push({ type: 'dep', @@ -636,9 +611,9 @@ export class Loader { } private createModuleProxy(module: any, moduleIdentifier: string) { - let moduleId = isUrlLike(moduleIdentifier) - ? trimExecutableExtension(this.reverseResolution(moduleIdentifier)).href - : moduleIdentifier; + let moduleId = trimExecutableExtension( + this.reverseResolution(moduleIdentifier), + ).href; return new Proxy(module, { get: (target, property, received) => { let value = Reflect.get(target, property, received); @@ -725,16 +700,11 @@ export class Loader { return { type: 'exports' }; } else if (depId === '__import_meta__') { return { type: '__import_meta__' }; - } else if (isUrlLike(depId)) { - return { - type: 'dep', - moduleURL: this.resolve(depId, new URL(moduleIdentifier)), - }; } else { return { type: 'dep', moduleURL: this.resolve( - new URL(depId, PACKAGES_FAKE_ORIGIN).href, + this.resolveImport(depId), new URL(moduleIdentifier), ), }; @@ -779,11 +749,7 @@ export class Loader { ); let consumedModules = new Set( flatMap(module.dependencies, (dep) => - dep.type === 'dep' - ? [dep.moduleURL.href] - : dep.type === 'shim-dep' - ? [dep.moduleId] - : [], + dep.type === 'dep' ? [dep.moduleURL.href] : [], ), ); @@ -811,15 +777,6 @@ export class Loader { } return this.evaluate(entry.moduleURL.href, depModule!); } - case 'shim-dep': { - let shimModule = this.getModule(entry.moduleId); - if (shimModule?.state !== 'evaluated') { - throw new Error( - `bug: shimmed modules should always be in an 'evaluated' state, but ${entry.moduleId} was in '${module.state}' state`, - ); - } - return shimModule.moduleInstance; - } default: throw assertNever(entry); } @@ -874,30 +831,12 @@ export class Loader { } } -function resolvedPackageName(moduleIdentifier: string) { - if (!isUrlLike(moduleIdentifier)) { - moduleIdentifier = new URL(moduleIdentifier, PACKAGES_FAKE_ORIGIN).href; - } - return moduleIdentifier; -} - function assertNever(value: never) { throw new Error(`should never happen ${value}`); } -function isUrlLike(moduleIdentifier: string): boolean { - return ( - moduleIdentifier.startsWith('.') || - moduleIdentifier.startsWith('/') || - moduleIdentifier.startsWith('http://') || - moduleIdentifier.startsWith('https://') - ); -} - function trimModuleIdentifier(moduleIdentifier: string): string { - return isUrlLike(moduleIdentifier) - ? trimExecutableExtension(new URL(moduleIdentifier)).href - : moduleIdentifier; + return trimExecutableExtension(new URL(moduleIdentifier)).href; } type ModuleState = Module['state']; diff --git a/packages/runtime-common/virtual-network.ts b/packages/runtime-common/virtual-network.ts index bdb95f2c36..8b57394d45 100644 --- a/packages/runtime-common/virtual-network.ts +++ b/packages/runtime-common/virtual-network.ts @@ -1,7 +1,8 @@ -import { Loader, PACKAGES_FAKE_ORIGIN } from './loader'; +import { Loader } from './loader'; import type { RunnerOpts } from './search-index'; const isFastBoot = typeof (globalThis as any).FastBoot !== 'undefined'; +const PACKAGES_FAKE_ORIGIN = 'https://packages/'; function getNativeFetch(): typeof fetch { if (isFastBoot) { @@ -24,9 +25,16 @@ export class VirtualNetwork { private nativeFetch = getNativeFetch(); private handlers: Handler[] = []; + private resolveImport = (moduleIdentifier: string) => { + if (!isUrlLike(moduleIdentifier)) { + moduleIdentifier = new URL(moduleIdentifier, PACKAGES_FAKE_ORIGIN).href; + } + return moduleIdentifier; + }; + private shimmingLoader = new Loader(() => { throw new Error('This loader should never call fetch'); - }); + }, this.resolveImport); constructor() { this.mount(async (request) => { @@ -38,6 +46,10 @@ export class VirtualNetwork { }); } + createLoader() { + return new Loader(this.fetch, this.resolveImport); + } + shimModule(moduleIdentifier: string, module: Record) { this.shimmingLoader.shimModule(moduleIdentifier, module); } @@ -65,3 +77,12 @@ export class VirtualNetwork { return this.nativeFetch(request, init); }; } + +function isUrlLike(moduleIdentifier: string): boolean { + return ( + moduleIdentifier.startsWith('.') || + moduleIdentifier.startsWith('/') || + moduleIdentifier.startsWith('http://') || + moduleIdentifier.startsWith('https://') + ); +} From d6739080aacf41f446a6570a36cb7922dd6398c9 Mon Sep 17 00:00:00 2001 From: Matic Jurglic Date: Thu, 21 Mar 2024 19:10:34 +0100 Subject: [PATCH 16/16] Not needed --- packages/realm-server/main.ts | 1 - packages/realm-server/tests/auth-client-test.ts | 2 +- packages/realm-server/tests/indexing-test.ts | 1 - packages/realm-server/tests/module-syntax-test.ts | 1 - packages/realm-server/tests/realm-server-test.ts | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/realm-server/main.ts b/packages/realm-server/main.ts index 10508c5cb7..bc15cfd0af 100644 --- a/packages/realm-server/main.ts +++ b/packages/realm-server/main.ts @@ -1,6 +1,5 @@ import './setup-logger'; // This should be first import { Realm, VirtualNetwork, logger } from '@cardstack/runtime-common'; -import { Loader } from '@cardstack/runtime-common/loader'; import { NodeAdapter } from './node-realm'; import yargs from 'yargs'; import { RealmServer } from './server'; diff --git a/packages/realm-server/tests/auth-client-test.ts b/packages/realm-server/tests/auth-client-test.ts index a5367c9678..adc7e22208 100644 --- a/packages/realm-server/tests/auth-client-test.ts +++ b/packages/realm-server/tests/auth-client-test.ts @@ -5,7 +5,7 @@ import { RealmAuthClient, type RealmAuthMatrixClientInterface, } from '@cardstack/runtime-common/realm-auth-client'; -import { Loader, VirtualNetwork } from '@cardstack/runtime-common'; +import { VirtualNetwork } from '@cardstack/runtime-common'; import jwt from 'jsonwebtoken'; function createJWT(expiresIn: string | number) { diff --git a/packages/realm-server/tests/indexing-test.ts b/packages/realm-server/tests/indexing-test.ts index 725d078255..f748285409 100644 --- a/packages/realm-server/tests/indexing-test.ts +++ b/packages/realm-server/tests/indexing-test.ts @@ -1,7 +1,6 @@ import { module, test } from 'qunit'; import { dirSync, setGracefulCleanup } from 'tmp'; import { - Loader, baseRealm, LooseSingleCardDocument, Realm, diff --git a/packages/realm-server/tests/module-syntax-test.ts b/packages/realm-server/tests/module-syntax-test.ts index 711258b6f2..9bc87cbb73 100644 --- a/packages/realm-server/tests/module-syntax-test.ts +++ b/packages/realm-server/tests/module-syntax-test.ts @@ -2,7 +2,6 @@ import { module, test } from 'qunit'; import { ModuleSyntax } from '@cardstack/runtime-common/module-syntax'; import { dirSync } from 'tmp'; import { - Loader, baseRealm, baseCardRef, baseFieldRef, diff --git a/packages/realm-server/tests/realm-server-test.ts b/packages/realm-server/tests/realm-server-test.ts index 8d868ae6a2..205e9f194e 100644 --- a/packages/realm-server/tests/realm-server-test.ts +++ b/packages/realm-server/tests/realm-server-test.ts @@ -18,7 +18,6 @@ import { } from '@cardstack/runtime-common/etc/test-fixtures'; import { isSingleCardDocument, - Loader, baseRealm, loadCard, Deferred,