[ ] JSX factory (because it can be converted into html tag string by certain libs)
[ ] render JSX <tag name={bind`prop`}> as <tag name=value :: infer:name=prop> only if instance.prop changed else just render it as <tag name=value :: name:=prop>
[ ] render JSX text {bind`prop`} as text <span :: infer:text=prop>value</span> only if instance.prop changed else just render it as text <span :: text:=prop>value</span>
[ ] @href(import.meta.url) decorator
[ ] bind(...ESClasses) tag string
[ ] [global] access to static member
[ ] auto wrap JSX <Component> into <render-scope> if it use ES class binding or CSS style scoping
import { href } from "w/prerender"
@href(import.meta.url)
export default class Money {
static sum = 0
/** @type {string} */
#currency
get currency() { return this.#currency }
set currency(value) {
this.#currency = `$${this.value}`
}
accessor count = 0
increment() {
this.currency = ++this.count
Money.sum += sum
}
}
import { href } from "w/prerender"
import USD from "./usd.js"
@href(import.meta.url)
export default class Money extends USD {
/** @type {string} */
set currency(value) {
super.currency = `¥${this.count}`
}
async toUSD() {
this.currency = await fetch(`https://trade.xyz/v1/convert/from=jpy&to=usd&price=${this.count}`)
}
}
export { default as USD } from "../usd.js"
export { default as JPY } from "../jpy.js"
import { bind, global } from "w/prerender"
import { USD, JPY } from "./modules/money.js"
// automatic <render-scope>
async function Item({ id }) {
const dollar = <link href={USD}/>
const yen = <link href={JPY}/>
// const money = bind() // bind all module default classes as long as <link href/> has been called inside this function. However, all <link> followed by bind() must always be on top
const item = await fetch(`https://api.mystore.com/v1/inventory?item=${id}`)
const money = bind(dollar, yen) // <link> doesn't need to be on top
dollar.count = yen.count = item.price;
dollar[global].sum = item.total * item.price;
// run script every time static sum has changed
<script>console.log({money`^sum`})</script>
return <label name="dollar">
price {money`count->toUSD currency`}
<button on:click={money`increment`}>increment</button>
</label>
}
stream html on deadline
class DeadlineController extends AbortController {
abort() { // cancel both streaming and ongoing fetch
super.abort()
this.cancel()
}
cancel() {/*cancel streaming*/}
}
export default new DeadlineController()
import { render } from "w/prerender"
import deadline from "w/prerender/deadline"
const IPAddress = async () => {
const resp = await fetch("//ipfy.com", { signal: deadline.signal })
const addr = await resp.text()
return <span>Your IP is {addr}</span>
}
Page.href = "user/dashboard"
function Page() {/*use <IPAddress />*/}
// edge/serverless function
export default(request) {
if (no_AllowStreamingHeader_in(request))
deadline.cancel()
if (has_DontShowIpQuery_in(request))
deadline.abort()
return render(request, [<Page/>], {
limit: { await: .1*s, response: 5*kb }, // each stream are buffered and has a limit
deadline: 200*ms, timeout: 1*s,
}) // if render not completed in 200ms (cause by async-await)
// then switch to stream mode for 1s
// and send the remaining html via SSE
}
timing boundary
Sometimes you want to control what to render when static deadline or stream timeout expired.
\<Deadline> boundary
<Deadline fallback={
<>
can't get IP address
probably service timeout
</>
}>
<IPAddress/>
</Deadline>
which can be simplified as
<IPAddress
fallback:deadline={<>
can't get IP address
probably service timeout
</>}
}/>
import { IPAddress } from "../ipaddr.jsx"
function Page() {
return <div class="dashboard">
<slot name="ip">Loading…</slot>
<IPAddress slot="ip"/>
…
<div>
}
The trick is simple: if function.href.endsWith(".js") then import(href) on the client, get the function name from Comment string, and finally render/build that function.
\<Static> boundary
Sometimes you want to keep it on the server and render it as static html
function will scope/isolate the DOM by wrapping the returned html in <render-scope>
=> (arrow function) doesn't scope/isolate the DOM
EXCEPT function *generator
(doesn't matter if it async or not)
Generator functions are special because it's dynamic Component. Each yield and return doesn't wrap the value in <render-scope>. To scope/isolate the DOM, you must return a function.
<tag name={bind`prop`}>
as<tag name=value :: infer:name=prop>
only if instance.prop changed else just render it as<tag name=value :: name:=prop>
text {bind`prop`}
astext <span :: infer:text=prop>value</span>
only if instance.prop changed else just render it astext <span :: text:=prop>value</span>
@href(import.meta.url)
decoratorbind(...ESClasses)
tag string[global]
access to static member<Component>
into<render-scope>
if it use ES class binding or CSS style scopingstream html on deadline
timing boundary
Sometimes you want to control what to render when static deadline or stream timeout expired.
\<Deadline> boundary
which can be simplified as
\<Timeout> boundary
which can be simplified as
Island
Nothing special here
auto Island
But it can be special
The trick is simple: if
function.href.endsWith(".js")
thenimport(href)
on the client, get the function name fromComment
string, and finally render/build that function.\<Static> boundary
Sometimes you want to keep it on the server and render it as static html
You can use timing boundary to fallback into client rendering
which can be simplified as
or
function
vs=>
function
will scope/isolate the DOM by wrapping the returned html in<render-scope>
=>
(arrow function) doesn't scope/isolate the DOMEXCEPT function *generator (doesn't matter if it async or not)
Generator functions are special because it's dynamic Component. Each yield and return doesn't wrap the value in
<render-scope>
. To scope/isolate the DOM, you must return a function.use(Class)
vs<link href={Class}/>