Skip to content

Commit

Permalink
Some long-needed renaming and consolidation (#90)Co-authored-by: Null…
Browse files Browse the repository at this point in the history
…VoxPopuli <[email protected]>

This commit holistic renames, consolidates, and cleans up internal
concepts.

* Rename "internal" concepts to Tag and Tagged
* Simplify and improve the notion of subscriptions

- @starbeam/runtime
- @starbeam/tags
- @starbeam/reactive
- @starbeam/resource
- @starbeam/service

Previously, the implementation of tags lived in `TIMELINE`, which has a
whole bunch of other unrelated stuff. So the abstraction went directly
from the interfaces defined in `@starbeam/interfaces` to the full-fledged
timeline definition in `@starbeam/timeline`.

This created an awkward situation where tags ought to have been real
objects (so they can share utility code), but we had utility function
instead that worked with the interfaces to avoid dragging all of
TIMELINE into the definition of tags.

This commit separates tags into their own package which clearly defines
the semantics of tags and provides concrete implementations for them.

This simplifies the code considerably, and also more clearly
communicates what tags are for.

The third attempt to reimplement resource on top of the new primitives worked
beautifully.

It supports `ResourceList`, but where the previous implementation worked
by implicitly adopting resources across runs, the new implementation of
`ResourceList` manages the lifetimes of its child resources explicitly.

The code is nearly as compact, in part because the new resource
implementation is more honest about separating entire-resource state
from per-run state.

The new resource primitive adds support for resource metadata (state
that lives for the entire lifetime of the resource and can be used for
cross-run state).

---
Co-authored-by: NullVoxPopuli <[email protected]>
  • Loading branch information
wycats committed Jun 13, 2023
1 parent 54f643d commit 50399e9
Show file tree
Hide file tree
Showing 51 changed files with 2,852 additions and 1,178 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.repo.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"extends": ["plugin:@starbeam/tight"],
"files": ["vitest.config.ts", "rollup.config.mjs"],
"parserOptions": {
"project": ["tsconfig.json"]
"project": ["tsconfig.root.json"]
}
}
],
Expand Down
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"vite.config.ts": "rollup.config.*, .env.*"
},

"prettier.prettierPath": "./node_modules/prettier",

// rewrap provides a quick keyboard shortcut (alt-q) to reformat comments to
// a specific column width. It also automatically rewraps comments when you
// type.
Expand Down
2 changes: 1 addition & 1 deletion @types/ansicolor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
"test:types": "tsc -b"
},
"devDependencies": {
"@types/node": "18.16.1"
"@types/node": "18.16.18"
}
}
2 changes: 1 addition & 1 deletion demos/react-jsnation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@vitest/ui": "*",
"vite": "4.3.3"
"vite": "4.3.9"
}
}
2 changes: 1 addition & 1 deletion demos/react-jsnation/src/components/DateFormatter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export default function (props: { locale: string }): JSX.Element {
</label>
</form>

<p className="output">{date.current.formatted}</p>
<p className="output">{date.current?.formatted}</p>
</>
);
});
Expand Down
3 changes: 3 additions & 0 deletions demos/react-lite-query/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Axios } from "axios";

import useQuery from "./lib/use-query.js";

type FIXME = never;
type DevtoolsOptions = FIXME;

export default function App(): JSX.Element {
return <Queried />;
}
Expand Down
2 changes: 1 addition & 1 deletion demos/react-store/src/components/DateFormatter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function (props: { locale: string }): JSX.Element {
</label>
</form>

<p className="output">{date.current.formatted}</p>
<p className="output">{date.current?.formatted}</p>
</>
);
};
Expand Down
2 changes: 1 addition & 1 deletion demos/react/src/components/formatter/DateFormatter-v2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default function DateFormatterStarbeam(): JSX.Element {
</>
</h3>

<p className="output">{date.current.formatted}</p>
<p className="output">{date.current?.formatted}</p>
</>
);
});
Expand Down
2 changes: 1 addition & 1 deletion demos/react/src/components/formatter/DateFormatter-v3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default function DateFormatterStarbeam(): JSX.Element {
</h3>

{selectBox}
<p className="output">{date.current.formatted}</p>
<p className="output">{date.current?.formatted}</p>
</>
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function DateFormatterStarbeam(props: {
</select>
</label>
</form>
<p className="output">{date.formatted}</p>
<p className="output">{date?.formatted}</p>
</>
);
}
Expand Down
46 changes: 23 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,44 +103,44 @@
"@starbeam/core-utils": "workspace:^"
},
"devDependencies": {
"@babel/eslint-parser": "^7.21.3",
"@babel/plugin-proposal-decorators": "^7.21.0",
"@babel/eslint-parser": "^7.22.5",
"@babel/plugin-proposal-decorators": "^7.22.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.21.4",
"@babel/preset-env": "^7.21.4",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.4",
"@babel/runtime": "^7.21.0",
"@babel/plugin-transform-runtime": "^7.22.5",
"@babel/preset-env": "^7.22.5",
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@babel/runtime": "^7.22.5",
"@changesets/changelog-git": "^0.1.14",
"@changesets/cli": "^2.26.1",
"@changesets/config": "^2.3.0",
"@starbeam/eslint-plugin": "workspace:^",
"@starbeam-dev/build-support": "workspace:*",
"@types/eslint": "^8.37.0",
"@types/node": "18.15.11",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"@vitest/ui": "^0.30.1",
"eslint": "^8.38.0",
"@types/eslint": "^8.40.2",
"@types/node": "18.16.18",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11",
"@vitest/ui": "^0.32.0",
"eslint": "^8.42.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.8.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsonc": "^2.7.0",
"eslint-plugin-jsonc": "^2.9.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"esno": "^0.16.3",
"fast-glob": "^3.2.12",
"happy-dom": "^9.8.1",
"jsdom": "^21.1.1",
"prettier": "^2.8.7",
"rollup": "^3.20.6",
"tslib": "^2.5.0",
"turbo": "^1.9.3",
"typescript": "^5.0.4",
"vite": "4.2.2",
"vitest": "0.30.1"
"happy-dom": "^9.20.3",
"jsdom": "^21.1.2",
"prettier": "^2.8.8",
"rollup": "^3.25.1",
"tslib": "^2.5.3",
"turbo": "^1.10.3",
"typescript": "^5.1.3",
"vite": "4.3.9",
"vitest": "0.32.0"
},
"license": "MIT",
"nodemonConfig": {
Expand Down
2 changes: 2 additions & 0 deletions packages/preact/preact-utils/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ function createHook<
const [originalFn, mangled] = getOriginal(originalOptions, hookName);

originalOptions[mangled] = ((...args: HookParams<K>) => {
console.log(hookName, mangled);

const handler = AugmentHandler.create(
hookName,
originalFn && (() => originalFn(...args))
Expand Down
2 changes: 2 additions & 0 deletions packages/preact/preact/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const install = Plugin((on) => {
component.context[STARBEAM] = component;
}

console.log("willRender", componentName(component.fn));

CONTEXT.app = getRoot(component);

ComponentFrame.start(
Expand Down
77 changes: 77 additions & 0 deletions packages/preact/preact/tests/create.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// @vitest-environment jsdom

import { install, setup } from "@starbeam/preact";
import { Cell } from "@starbeam/universal";
import { html, rendering } from "@starbeam-workspace/preact-testing-utils";
import { describe } from "@starbeam-workspace/test-utils";
import { options } from "preact";
import { beforeAll } from "vitest";

let nextId = 0;

describe("create", () => {
beforeAll(() => {
install(options);
});

rendering.test(
"baseline",
function App({ name }: { name: string }) {
return html`<div>hello ${name}</div>`;
},
(render) =>
render
.expect(({ name }) => html`<div>hello ${name}</div>`)
.render({ name: "world" })
);

rendering.test(
"reactive values render",
function App() {
const { cell } = setup(ReactiveObject);

return html`<p>${cell.current}</p>`;
},
(render) =>
render
.expect(({ count }: { count: number }) => html`<p>${count}</p>`)
.render({ count: 0 })
);

rendering.test(
"reactive values update",
function App() {
const { cell, increment } = setup(ReactiveObject);

return html`<p>${cell}</p>
<button onClick=${increment}>++</button>`;
},
(render) =>
render
.expect(
({ count }: { count: number }) =>
html`<p>${count}</p>
<button>++</button>`
)
.render({ count: 0 })
.update(
{ count: 1 },
{ before: async (prev) => prev.find("button").fire.click() }
)
);
});

const INITIAL_COUNT = 0;
const INCREMENT = 1;

function ReactiveObject(): { cell: Cell<number>; increment: () => void } {
const cell = Cell(INITIAL_COUNT, {
description: `ReactiveObject #${++nextId}`,
});

function increment(): void {
cell.set(cell.current + INCREMENT);
}

return { cell, increment };
}
1 change: 1 addition & 0 deletions packages/preact/preact/tests/support/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type ComponentChildren,
createElement,
Fragment,
type FunctionComponent,
h,
type VNode,
} from "preact";
Expand Down
63 changes: 38 additions & 25 deletions packages/react/react/src/modifiers/element.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { browser } from "@domtree/flavors";
import type { Description, Reactive, Tagged } from "@starbeam/interfaces";
import type { Description, HasTag, Reactive, Tagged } from "@starbeam/interfaces";
import { DEBUG, Formula } from "@starbeam/reactive";
import type { IntoResourceBlueprint, Resource } from "@starbeam/resource";
import * as resource from "@starbeam/resource";
import { CONTEXT, render, RUNTIME, type Unsubscribe } from "@starbeam/runtime";
import { service } from "@starbeam/service";
import { Cell } from "@starbeam/universal";

import { missingApp, ReactApp } from "../app.js";
import { type ElementRef, type ReactElementRef, ref } from "./ref.js";
import { missingApp, ReactApp } from "../app.js";

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyRecord = Record<PropertyKey, any>;
Expand Down Expand Up @@ -192,8 +192,9 @@ export class ReactiveElement {
element.#lifecycle = Lifecycle.create(element.#description);
}

static subscribe(element: ReactiveElement, reactive: Tagged): void {
element.#render(reactive);
static subscribe(element: ReactiveElement, reactive: HasTag): void {
const subscription = RUNTIME.subscribe(reactive, element.notify);
element.on.cleanup(subscription);
}

#lifecycle: Lifecycle;
Expand Down Expand Up @@ -233,8 +234,8 @@ export class ReactiveElement {
service = <T>(
blueprint: IntoResourceBlueprint<T>,
description?: string | Description | undefined
): Resource<T> => {
const desc = DEBUG?.Desc("service", description, "UseSetup.service");
): Reactive<T> => {
const desc = DEBUG?.Desc("service", description);
const context = this.#context;

if (context === null) {
Expand All @@ -246,25 +247,28 @@ export class ReactiveElement {
return service(blueprint, { description: desc });
};

readonly use = <T>(
factory: IntoResourceBlueprint<T>,
options?: { initial?: T }
): Reactive<T | undefined> => {
return internalUseResource(
this,
{
notify: this.notify,
render: (reactive) => this.on.cleanup(render(reactive, this.notify)),
on: {
layout: (callback) => {
if (!callback) return;
return this.on.layout(() => void callback(factory));
},
cleanup: this.on.cleanup,
},
},
options?.initial
);
use = <T, Initial extends undefined>(
factory: IntoResourceBlueprint<T, Initial>,
options?: { initial?: T; description: string | Description | undefined }
): Reactive<T | Initial> => {
const desc = DEBUG?.Desc("resource", options?.description);
const resource = MountedResource.create(options?.initial, desc);

RUNTIME.link(this, resource);

const create = (): void => {
resource.create((owner) => Factory.resource(factory, owner));

this.notify();
};

const unsubscribe = RUNTIME.subscribe(resource, this.notify);

RUNTIME.onFinalize(resource, unsubscribe);

this.on.layout(create);

return resource as Reactive<T | Initial>;
};

refs<R extends RefsTypes>(refs: R): RefsRecordFor<R> {
Expand All @@ -274,6 +278,15 @@ export class ReactiveElement {
return record;
}
}
interface ResourceHost<T> {
readonly notify: () => void;
readonly on: {
layout: (callback: (value: T) => void) => Unsubscribe | void;
cleanup: (callback: Unsubscribe) => Unsubscribe | void;
};
}



interface ResourceHost<T> {
readonly notify: () => void;
Expand Down
Loading

0 comments on commit 50399e9

Please sign in to comment.