-
Notifications
You must be signed in to change notification settings - Fork 9
Add common-html package #116
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
Add common-html package #116
Conversation
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.
this appears unused by the rest (which i think is good, since we might use a different implementation in other contexts).
hence this question might be unrelated: sink here calls the callback with the initial state, but i don't think updates did. which is the proper semantics assumed by your code?
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.
Q1 This is just a simple reactive value container without scheduling that I added to the package for test mocking purposes. It’s not intended for use outside of tests.
Q2 Regarding behavior, it’s typical for reactive data containers like cells and signals to call the subscriber once immediately with the current value. This ensures that the subscriber doesn’t have to time its subscription correctly to avoid missing the first value.
Our signals implementation did this, and I would recommend our propagators implementation do the same.
Note that event streams (e.g. observables) don’t have this “call immediately” behavior because they don’t have state. They are just streams of events over time.
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.
Ah, then this is a difference with updates: That one doesn't send values, it accepts Sendable<void>.
That's actually useful for driving the scheduling, which when ultimately invoking the function will sample the value. So we might want both on cells, for different purposes.
| }; | ||
|
|
||
| /** Generates a random number with 8 digits */ | ||
| const randomSalt = () => Math.random().toFixed(9).slice(2); |
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.
Is this for security reasons or just to reduce the chance of accidental collisions?
Since the context is created with and associated with the template, is there really something disallowed code pull off by guessing the numbers?
In any case, the templates's hash can't be inserted into the template, so that would be a safe choice.
(Mostly trying to understand)
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.
My rationale here basically boils down to trying to reduce the possibility of failure.
Before we were using code to construct markup, and could enforce invariants through that code so that it was impossible to construct broken markup. Now that we're parsing a string (or rather allowing the browser to parse it), we have to be a bit clever. We walk over the resulting markup, and so can guarantee it is correct on the way out. However, the template holes are one place where it might be possible to break the render by faking a hole placeholder.
The random salt prevents collision (this is also what Lit does under the hood). I'm not sure if it is a hard security requirement to do better than this. If it is, I like the idea of using the hash, as you mentioned.
...and update browser test harness assert to match names of node assert for consistency.
This PR adds a new
htmlfunction for tagged template literal strings.TODO
htmltemplate literal tag functionattr="",.prop="", and@event=""Sendablevalues as event handlerscommon-uicommon-frpOverview
This template tag looks a lot like
lit-html, but it comes with a few new tricks:.sink<T>((value: T) => void): Cancelmethod is treated as a reactive valuesetSanitizerTemplate objects are plain JavaScript objects and can be serialized and persisted.
How to use it
Set attributes,
.properties, or@events, just like with Lit:Set reactive attributes and
.properties. Any type with a.sink<T>((value: T) => void): Cancelmethod will work.You can easily create template utility functions:
Templates can be nested:
Nested templates can be reactive:
How it works
Under the hood, the implementation is very similar to
lit-html, but with a few alterations to support reactivity and deep sanitization.htmljust gathers template strings array and substitutions array into a template object. The object returned fromhtmlis a plain frozen JavaScript object of{ template, context }:The template object is a plain JavaScript object, and should be safe to serialize and persist (assuming the values in the context array are themselves serializable).
No actual work happens until you
render():At this point, render takes the template and renders it to DOM using the following approach:
#hole12345678-0#.contentis cloned and thefirstElementChildreturned from the DocumentFragmentattr="#hole12345678-0#", properties are placeholder attributes with a leading dot, like.prop="#hole12345678-0#", events are placeholder attributes with a leading at, like@event="#hole12345678-0#".render()it, and insert the DOM node.sink()method), we bind updates so that the child will be replaced any time the reactive value changes.Template grammar and render behavior
Template objects
Template objects are constructed via the
htmltagged template literal and consist of:Render behavior
templateis an array of strings, where the breaks between array items represent "holes" in the template that will be replaced with template substitutionscontextis an array of values to be placed in holes.template.length - 1. However, the renderer is robust to missing or extra template context values.contextvalues are typeunknown(any), and are disambiguated at runtime.on, the attribute is rejected and a warning is logged..propertieson, the attribute is rejected and a warning is logged.@eventsTemplate string parsing
We rely on the browser to parse template strings via the
<template>tag. Once the template part array is flattened to a string with placeholder values, the formal parsing behavior for template strings can be considered to be the same as the HTML spec for parsing documents, with some minor variations on top:on(event attributes are not allowed).hidden=${true}., but otherwise follow normal attribute name grammaron(event properties are not allowed)@click=${handle}@, but otherwise follow normal attribute name grammar