-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Resource based vanilla renderer #99
base: main
Are you sure you want to change the base?
Changes from 2 commits
5b13f37
ee4d928
3ecda8a
6a11f91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
export { Cursor } from "./src/cursor.js"; | ||
export { Attr, Element as El, Fragment, Text } from "./src/dom.js"; | ||
export { Attr, Comment,Element as El, Fragment, Text } from "./src/dom.js"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,77 @@ | ||
/** | ||
* TODO: | ||
* - DynamicFragment | ||
* - Namespaces | ||
* - SVG | ||
* - Modifier | ||
* - Portal | ||
* - SSR | ||
* | ||
* Goals: | ||
* - Implement Glimmer compatibility | ||
* - Write compiler Glimmer -> whatever this DSL ends up being | ||
* | ||
* Stretch Goals: | ||
* - other compilers (html``) | ||
*/ | ||
import type { Description, Reactive } from "@starbeam/interfaces"; | ||
import { CachedFormula, DEBUG, type FormulaFn } from "@starbeam/reactive"; | ||
import { RUNTIME } from "@starbeam/runtime"; | ||
import { Resource,type ResourceBlueprint,RUNTIME,use } from "@starbeam/universal"; | ||
|
||
import { Cursor } from "./cursor.js"; | ||
|
||
export function Text( | ||
text: Reactive<string>, | ||
description?: string | Description | ||
): ContentNode { | ||
return ContentNode(({ into }) => { | ||
const node = into.insert(into.document.createTextNode(text.read())); | ||
return Content( | ||
({ into, owner }) => { | ||
const node = into.insert(into.document.createTextNode(text.read())); | ||
|
||
return Resource(({ on }) => { | ||
on.cleanup(() => void node.remove()); | ||
node.textContent = text.read(); | ||
}) | ||
} | ||
|
||
return { | ||
cleanup: () => void node.remove(), | ||
update: () => (node.textContent = text.read()), | ||
}; | ||
}, DEBUG?.Desc("resource", description, "Text")); | ||
, DEBUG?.Desc('resource', description, 'Text')); | ||
} | ||
|
||
export function Comment( | ||
text: Reactive<string>, | ||
description?: string | Description | ||
): ContentNode { | ||
return ContentNode(({ into }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I renamed this to just |
||
return Content(({ into }) => { | ||
const node = into.insert(into.document.createComment(text.read())); | ||
|
||
return { | ||
cleanup: () => void node.remove(), | ||
update: () => (node.textContent = text.read()), | ||
}; | ||
return Resource(({ on }) => { | ||
on.cleanup(() => void node.remove()); | ||
node.textContent = text.read(); | ||
}); | ||
}, DEBUG?.Desc("resource", description, "Comment")); | ||
} | ||
|
||
export function Fragment( | ||
nodes: ContentNode[], | ||
description?: string | Description | ||
): ContentNode { | ||
return ContentNode(({ into, owner }) => { | ||
return Content(({ into, owner }) => { | ||
const start = placeholder(into.document); | ||
into.insert(start); | ||
|
||
const renderedNodes = nodes.map((nodeConstructor) => | ||
nodeConstructor(into).create({ owner }) | ||
const renderedNodes = | ||
nodes.map( | ||
(nodeConstructor) => nodeConstructor(into).create({ owner }) | ||
); | ||
|
||
const end = placeholder(into.document); | ||
into.insert(end); | ||
const range = FragmentRange.create(start, end); | ||
|
||
return { | ||
cleanup: () => void range.clear(), | ||
update: () => void poll(renderedNodes), | ||
}; | ||
return Resource(({ on }) => { | ||
on.cleanup(() => void range.clear()); | ||
poll(renderedNodes); | ||
}) | ||
}, DEBUG?.Desc("resource", description, "Fragment")); | ||
} | ||
|
||
|
@@ -60,7 +80,7 @@ export function Attr<E extends Element>( | |
value: Reactive<string | null | boolean>, | ||
description?: string | Description | ||
): AttrNode<E> { | ||
return ContentNode(({ into }) => { | ||
return Content(({ into }) => { | ||
const current = value.read(); | ||
|
||
if (typeof current === "string") { | ||
|
@@ -69,22 +89,18 @@ export function Attr<E extends Element>( | |
into.setAttribute(name, ""); | ||
} | ||
|
||
return { | ||
cleanup: () => { | ||
return Resource(({ on }) => { | ||
on.cleanup(() => void into.removeAttribute(name)); | ||
const next = value.read(); | ||
|
||
if (typeof next === "string") { | ||
into.setAttribute(name, next); | ||
} else if (next === true) { | ||
into.setAttribute(name, ""); | ||
} else if (next === false) { | ||
into.removeAttribute(name); | ||
}, | ||
update: () => { | ||
const next = value.read(); | ||
|
||
if (typeof next === "string") { | ||
into.setAttribute(name, next); | ||
} else if (next === true) { | ||
into.setAttribute(name, ""); | ||
} else if (next === false) { | ||
into.removeAttribute(name); | ||
} | ||
}, | ||
}; | ||
} | ||
}); | ||
}, DEBUG?.Desc("resource", description, "Attr")); | ||
} | ||
|
||
|
@@ -100,7 +116,7 @@ export function Element<N extends string>( | |
}, | ||
description?: Description | string | ||
): ContentNode { | ||
return ContentNode(({ into, owner }) => { | ||
return Content(({ into, owner }) => { | ||
const element = into.document.createElement(tag); | ||
const elementCursor = Cursor.appendTo(element); | ||
|
||
|
@@ -113,13 +129,16 @@ export function Element<N extends string>( | |
|
||
into.insert(element); | ||
|
||
return { | ||
cleanup: () => void element.remove(), | ||
update: () => { | ||
poll(renderAttributes); | ||
poll(renderBody); | ||
}, | ||
}; | ||
return Resource(({on}, meta) => { | ||
on.cleanup(() => void element.remove()); | ||
|
||
return { | ||
update: () => { | ||
renderAttributes.forEach(a => a.read()); | ||
poll(renderBody); | ||
}, | ||
} | ||
}); | ||
}, DEBUG?.Desc("resource", description, "Element")); | ||
} | ||
|
||
|
@@ -129,12 +148,11 @@ function placeholder(document: Document): Text { | |
return document.createTextNode(""); | ||
} | ||
|
||
type Rendered = FormulaFn<void>; | ||
type Rendered = FormulaFn<unknown>; | ||
|
||
interface OutputConstructor { | ||
create: (options: { owner: object }) => Rendered; | ||
create: (options: { owner: object }) => FormulaFn<unknown>; | ||
} | ||
|
||
type ContentNode = (into: Cursor) => OutputConstructor; | ||
type AttrNode<E extends Element = Element> = (into: E) => OutputConstructor; | ||
|
||
|
@@ -146,21 +164,19 @@ function poll(rendered: Rendered[] | Rendered): void { | |
} | ||
} | ||
|
||
function ContentNode<T extends Cursor | Element>( | ||
create: (options: { into: T; owner: object }) => { | ||
cleanup: () => void; | ||
update: () => void; | ||
}, | ||
type ContentConstructor<T extends Cursor | Element> = (options: { into: T, owner: object }) => ResourceBlueprint; | ||
|
||
function Content<T extends Cursor | Element>( | ||
create: ContentConstructor<T>, | ||
description: Description | undefined | ||
): (into: T) => OutputConstructor { | ||
return (into: T) => { | ||
return { | ||
create({ owner }) { | ||
const { cleanup, update } = create({ into, owner }); | ||
|
||
const formula = CachedFormula(update, description); | ||
const blueprint = create({ into, owner }); | ||
const formula = CachedFormula(() => (use(blueprint, { lifetime: owner, metadata: { owner } })).current, description); | ||
|
||
RUNTIME.onFinalize(owner, cleanup); | ||
RUNTIME.onFinalize(owner, () => void RUNTIME.finalize(formula)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if this is right |
||
|
||
return formula; | ||
}, | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some todos from our convo the other day