Skip to content

Commit

Permalink
Merge pull request #93 from cardstack/dynamic-module-loader
Browse files Browse the repository at this point in the history
Dynamic module loader
  • Loading branch information
ef4 authored Aug 4, 2022
2 parents 1be2628 + 244b090 commit 8fd91d3
Show file tree
Hide file tree
Showing 17 changed files with 480 additions and 46 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn --prefer-offline
- name: Base realm server
run: yarn start:base &
- name: Start realm servers
run: yarn start:test-realms &
working-directory: realm-server
- name: realm server test suite
run: yarn test
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ The tests are available at `http://localhost:4200/tests`

### Realm Server
To run the `realm-server/` workspace tests start:
1. `yarn start:base` in the `realm-server/` workspace to serve the base realm (alternatively you can use `yarn start:test-realms` which also serves the base realm)
1. `yarn start:test-realms` in the `realm-server/` to serve _both_ the base realm and the realm that serves the test cards for node.

Run `yarn test` in the `realm-server/` workspace to run the realm tests
3 changes: 3 additions & 0 deletions host/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import './lib/glint-embroider-workaround';
* runtime-common/index.js file.
*/

// TODO eventually we should replace this with
// import "@cardstack/runtime-common/external-globals";
// when our common external-globals can support glimmer
(window as any).RUNTIME_SPIKE_EXTERNALS = new Map();
import * as glimmerComponent from '@glimmer/component';
(window as any).RUNTIME_SPIKE_EXTERNALS.set(
Expand Down
4 changes: 2 additions & 2 deletions host/tests/cards/person.gts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { contains, field, Component, Card } from 'https://cardstack.com/base/card-api';
import StringCard from 'https://cardstack.com/base/string';
import { contains, field, Component, Card } from "https://cardstack.com/base/card-api";
import StringCard from "https://cardstack.com/base/string";

export class Person extends Card {
@field firstName = contains(StringCard);
Expand Down
4 changes: 2 additions & 2 deletions host/tests/cards/post.gts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { contains, field, Component, Card } from 'https://cardstack.com/base/card-api';
import StringCard from 'https://cardstack.com/base/string';
import { contains, field, Component, Card } from "https://cardstack.com/base/card-api";
import StringCard from "https://cardstack.com/base/string";
import { Person } from "./person";

export class Post extends Card {
Expand Down
2 changes: 1 addition & 1 deletion realm-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
"test": "qunit --require ts-node/register tests/index.ts",
"start": "ts-node --transpileOnly main",
"start:base": "ts-node --transpileOnly main --port=4201 --path='../base' --url='https://cardstack.com/base/' --baseRealmURL='http://localhost:4201/base/'",
"start:test-realms": "ts-node --transpileOnly main --port=4201 --path='../base' --url='https://cardstack.com/base/' --path='../host/tests/cards' --url='http://test-realm/test/' --baseRealmURL='http://localhost:4201/base/'"
"start:test-realms": "ts-node --transpileOnly main --port=4201 --path='../base' --url='https://cardstack.com/base/' --path='../host/tests/cards' --url='http://test-realm/test/' --path='./tests/cards' --url='http://test-realm/node-test/' --baseRealmURL='http://localhost:4201/base/'"
}
}
5 changes: 4 additions & 1 deletion realm-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { resolve } from "path";
import { webStreamToText } from "@cardstack/runtime-common/stream";
import { LocalPath, RealmPaths } from "@cardstack/runtime-common/paths";
import { Readable } from "stream";
import "@cardstack/runtime-common/externals-global";

const externalsPath = "/externals/";

Expand Down Expand Up @@ -137,7 +138,9 @@ function handleExternals(req: IncomingMessage, res: ServerResponse): void {
return;
}

let src = [`const m = window.RUNTIME_SPIKE_EXTERNALS.get('${moduleName}');`];
let src = [
`const m = globalThis.RUNTIME_SPIKE_EXTERNALS.get('${moduleName}');`,
];

for (let name of names) {
if (name === "default") {
Expand Down
5 changes: 5 additions & 0 deletions realm-server/tests/cards/cycle-one.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { two } from "./cycle-two";

export function one() {
return two() - 1;
}
9 changes: 9 additions & 0 deletions realm-server/tests/cards/cycle-two.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { one } from "./cycle-one";

export function two() {
return 2;
}

export function three() {
return one() * 3;
}
4 changes: 2 additions & 2 deletions realm-server/tests/cards/person.gts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { contains, field, Component, Card } from 'https://cardstack.com/base/card-api';
import StringCard from 'https://cardstack.com/base/string';
import { contains, field, Component, Card } from "https://cardstack.com/base/card-api";
import StringCard from "https://cardstack.com/base/string";

export class Person extends Card {
@field firstName = contains(StringCard);
Expand Down
67 changes: 66 additions & 1 deletion realm-server/tests/realm-server-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
cardSrc,
compiledCard,
} from "@cardstack/runtime-common/etc/test-fixtures";
import { CardRef, isCardDocument } from "@cardstack/runtime-common";
import { CardRef, isCardDocument, Realm } from "@cardstack/runtime-common";
import { stringify } from "qs";
import { NodeRealm } from "../node-realm";

setGracefulCleanup();
const testRealmURL = new URL("http://127.0.0.1:4444/");
const testRealmHref = testRealmURL.href;
const testRealm2Href = "http://localhost:4201/node-test/";

module("Realm Server", function (hooks) {
let server: Server;
Expand Down Expand Up @@ -282,6 +284,22 @@ module("Realm Server", function (hooks) {
kind: "file",
},
},
"cycle-one.js": {
links: {
related: "http://127.0.0.1:4444/cycle-one.js",
},
meta: {
kind: "file",
},
},
"cycle-two.js": {
links: {
related: "http://127.0.0.1:4444/cycle-two.js",
},
meta: {
kind: "file",
},
},
"person-1.json": {
links: {
related: `${testRealmHref}person-1.json`,
Expand Down Expand Up @@ -413,4 +431,51 @@ module("Realm Server", function (hooks) {
"card ID is correct"
);
});

test("can dynamically load a card from own realm", async function (assert) {
let nodeRealm = new NodeRealm(dir.name);
let realm = new Realm(
"http://test-realm/",
nodeRealm,
"http://localhost:4201/base/"
);
await realm.ready;

let module = await realm.load<Record<string, any>>("./person");
let Person = module["Person"];
let person = Person.fromSerialized({ firstName: "Mango" });
assert.strictEqual(person.firstName, "Mango", "card data is correct");
});

test("can dynamically load a card from a different realm", async function (assert) {
let nodeRealm = new NodeRealm(dir.name);
let realm = new Realm(
"http://test-realm/",
nodeRealm,
"http://localhost:4201/base/"
);
await realm.ready;

let module = await realm.load<Record<string, any>>(
`${testRealm2Href}person`
);
let Person = module["Person"];
let person = Person.fromSerialized({ firstName: "Mango" });
assert.strictEqual(person.firstName, "Mango", "card data is correct");
});

test("can dynamically modules with cycles", async function (assert) {
let nodeRealm = new NodeRealm(dir.name);
let realm = new Realm(
"http://test-realm/",
nodeRealm,
"http://localhost:4201/base/"
);
await realm.ready;

let module = await realm.load<{ three(): number }>(
`${testRealm2Href}cycle-two`
);
assert.strictEqual(module.three(), 3);
});
});
8 changes: 4 additions & 4 deletions runtime-common/etc/test-fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const cardSrc = `
import { contains, field, Component, Card } from 'https://cardstack.com/base/card-api';
import StringCard from 'https://cardstack.com/base/string';
import { contains, field, Component, Card } from "https://cardstack.com/base/card-api";
import StringCard from "https://cardstack.com/base/string";
export class Person extends Card {
@field firstName = contains(StringCard);
Expand All @@ -25,8 +25,8 @@ function _applyDecoratedDescriptor(target, property, decorators, descriptor, con
function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and runs after the decorators transform.'); }
import { contains, field, Component, Card } from 'https://cardstack.com/base/card-api';
import StringCard from 'https://cardstack.com/base/string';
import { contains, field, Component, Card } from "https://cardstack.com/base/card-api";
import StringCard from "https://cardstack.com/base/string";
export let Person = (_class = (_class2 = class Person extends Card {
constructor(...args) {
super(...args);
Expand Down
59 changes: 59 additions & 0 deletions runtime-common/externals-global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* The following modules are made available to cards as external modules.
* This is paired with the worker/src/externals.ts file which is responsible
* for compiling the external module stubs into the cards, which consumes the
* modules in the globalThis.RUNTIME_SPIKE_EXTERNALS Map. Any changes to the
* globalThis.RUNTIME_SPIKE_EXTERNALS Map should also be reflected in the in the
* runtime-common/index.js file.
*/

(globalThis as any).RUNTIME_SPIKE_EXTERNALS = new Map();
// import * as glimmerComponent from "@glimmer/component";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("@glimmer/component", {
default: class {},
});
// import * as emberComponent from "ember-source/dist/packages/@ember/component";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("@ember/component", {
default: class {},
setComponentTemplate() {},
});
// import * as emberComponentTemplateOnly from "ember-source/dist/packages/@ember/component/template-only";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set(
"@ember/component/template-only",
{ default() {} }
);
// import * as emberTemplateFactory from "ember-source/dist/packages/@ember/template-factory";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("@ember/template-factory", {
createTemplateFactory() {},
});
// import * as glimmerTracking from "@glimmer/tracking";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("@glimmer/tracking", {
tracked() {},
});
// import * as emberObject from "ember-source/dist/packages/@ember/object";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("@ember/object", {
action() {},
get() {},
});
// import * as emberHelper from "ember-source/dist/packages/@ember/helper";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("@ember/helper", {
get() {},
fn() {},
});
// import * as emberModifier from "ember-source/dist/packages/@ember/modifier";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("@ember/modifier", {
on() {},
});
// import * as emberDestroyable from "ember-source/dist/packages/@ember/destroyable";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("@ember/destroyable", {
registerDestructor() {},
});
// import * as tracked from "tracked-built-ins";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("tracked-built-ins", {
// TODO replace with actual TrackedWeakMap when we add real glimmer
// implementations
TrackedWeakMap: WeakMap,
});
import * as lodash from "lodash";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("lodash", lodash);
import * as dateFns from "date-fns";
(globalThis as any).RUNTIME_SPIKE_EXTERNALS.set("date-fns", dateFns);
Loading

0 comments on commit 8fd91d3

Please sign in to comment.