Skip to main content

FOREIGN_JS

Constant FOREIGN_JS 

Source
pub const FOREIGN_JS: &str = "// The lifecycle of a `foreign \u{2026} gives view` \u{2014} spec \u{a7}14E.1, \u{a7}14E.3.\n//\n// **Its own module, and that is a size decision rather than a tidiness\n// one.** A DOM-owning foreign is the one construct here that a program\n// can go its whole life without writing, and \u{a7}16.3.1 promises a bundle\n// ships nothing it does not use. Left in `dom.js` these bytes were\n// downloaded by every page ever served, including one with no FFI in it,\n// which is a fixed cost paid for an optional feature. `Bundle::runtime`\n// already computes a transitive import closure \u{2014} `rpc.js`, `store.js` and\n// `wire.js` are linked only when the split finds a crossing or a durable\n// key \u{2014} so this is that existing mechanism applied once more, and not a\n// new exemption from the size gate. `zdc-bench` charges this file to the\n// programs that link it and to no others, and a test pins both halves of\n// that: a null program must not reach it, and a program with a `gives\n// view` foreign must.\n//\n// Nothing here touches the DOM. The node is handed in \u{2014} the template\n// already carries it \u{2014} so this module needs the reactivity core and\n// nothing else, which is why it does not import `dom.js` and why linking\n// it costs a program `signal.js` it already had.\n//\n// **The contract is checked here because here is the only place that can\n// see it (#239).** `mount(node, props) -> { update(props), destroy() }` is\n// a shape no type in the language describes, so `from \"three\" as \"Scene\"`\n// compiles \u{2014} a `foreign` declaration gives the compiler nothing to check\n// it against \u{2014} and used to fail on the first render with an engine\n// `TypeError` raised inside this file, naming a local the reader never\n// wrote and no part of the declaration that caused it.\n//\n// It is not cheap: the check nearly trebles this file, almost all of it\n// the refusals\' own prose, and the module is downloaded whole by every\n// program that writes one of these. BENCHMARKS.md charges it there and\n// records what it did to the margin, because a size argument that quietly\n// stops applying to the file it was made about is worse than the bytes.\n// What it buys is the sentence that turns a trace through a runtime into\n// the name of a declaration to open.\n\nimport { effect, onCleanup } from \'./signal.js\';\n\n/** The contract, spelled once and quoted verbatim in every refusal. */\nconst CONTRACT = \'mount(node, props) -> { update(props), destroy() }\';\n\n/** The claim both refusals of the imported binding open with. */\nconst NOT_A_MOUNT = \'gives a view, so what its `as` clause names must be a mount function; \';\n\n/**\n * Hand an element to a `foreign \u{2026} gives view` (\u{a7}14E.1, \u{a7}14E.3).\n *\n * `node` is a `<div>` the template already carries, so a foreign is a\n * static-markup hole bound like an attribute rather than an anchor pair\n * like `each`, keeping it inside \u{a7}16.2 R2\'s cloning model. `props` is a\n * thunk giving a plain object, one property per `takes` argument in\n * order, read inside an effect. `declared` is the declaration\'s own name\n * in the program, carried here for no reason but the refusals: nothing\n * else in this file reads it, and without it a breach of the contract can\n * only be reported against a runtime the reader did not write.\n *\n * Reactivity is `update`, never re-invocation: re-running `create` would\n * rebuild whatever the module owns \u{2014} a WebGL context, an animation \u{2014} on\n * every write, the failure this form prevents. Nothing crosses back, and\n * the handle\'s *types* are still asserted rather than verified (\u{a7}14E.4) \u{2014}\n * what is checked here is that there is a handle with the two methods,\n * which is the part that has an answer at mount.\n */\nexport function foreign(node, create, props, declared) {\n  let handle = null;\n  let disposed = false;\n\n  effect(() => {\n    // Above the guard, so the edge exists on a run that bails.\n    const next = props();\n    // Disposal cannot retract a run the flush has already queued:\n    // `clearSources` unsubscribes for the future, and a pending run is\n    // still in the drain list. Without this, removing an `each` row in\n    // the same batch as a write that row read calls `update` on a\n    // destroyed handle \u{2014} a fault with no visible symptom.\n    if (disposed) return;\n    if (handle === null) {\n      handle = mounted(create, node, next, declared);\n      return;\n    }\n    handle.update(next);\n  });\n\n  // `owned` disposes **last-registered-first**, so this runs *before* the\n  // effect above is unsubscribed \u{2014} `destroy()` lands while the binding is\n  // still live. That is the opposite of what this form was first written\n  // against, when `owned` disposed in registration order, and it is why\n  // the ordering is not what makes it safe: the `disposed` flag is. Set\n  // it before `destroy()`, and a run the flush had already queued finds\n  // the guard rather than a destroyed handle, whichever order the two\n  // cleanups happen to run in.\n  //\n  // Stated because the alternative is a comment that is true only of a\n  // disposal order nothing here enforces.\n  onCleanup(() => {\n    disposed = true;\n    if (handle !== null) handle.destroy();\n  });\n}\n\n/**\n * Call `create` and return its handle, or refuse in the declaration\'s name.\n *\n * Checked at mount and nowhere afterwards. A handle that answered once\n * cannot stop answering \u{2014} the module would have to replace its own return\n * value, which it no longer holds \u{2014} so re-checking on every write would\n * charge every signal write for a mistake that can only be made once.\n */\nfunction mounted(create, node, props, declared) {\n  if (typeof create !== \'function\') {\n    refuse(\n      declared,\n      NOT_A_MOUNT + \'this one is \' + describe(create) + \'.\',\n      \'Point it at an export of that shape, or at a module of your own that wraps the library\'\n    );\n  }\n  // A class passes `typeof create === \'function\'`, and a class is what\n  // every visual library exports \u{2014} three.js\'s `Scene`, chart.js\'s `Chart`,\n  // maplibre\'s `Map` \u{2014} so this is the case the check exists for rather\n  // than an edge of it. Calling one raises `Class constructor \u{2026} cannot be\n  // invoked without \'new\'`, from this file, about a name the reader never\n  // wrote. That report is what #239 was filed about.\n  if (isClass(create)) {\n    refuse(\n      declared,\n      NOT_A_MOUNT + \'this one is a class, and a class cannot be called without `new`.\',\n      \"A library\'s class is not a mount function: give `\" +\n        declared +\n        \'` a module of your own that constructs it, hands it the node, and returns the handle\'\n    );\n  }\n\n  const handle = create(node, props);\n  if (!conforms(handle)) {\n    refuse(\n      declared,\n      \'gives a view, so mounting it must return a handle; this \' + returned(handle) + \'.\',\n      \'`update` is how a write reaches the module \u{2014} re-invoking mount would rebuild whatever it \' +\n        \'owns, a WebGL context or an animation in flight \u{2014} and `destroy` is how the module gives \' +\n        \'back a frame loop or a context when the node goes. Return both\'\n    );\n  }\n  return handle;\n}\n\n/**\n * Throw the one shape of refusal this file has: claim, contract, repair.\n *\n * One function rather than three literals so that the declaration is named\n * the same way every time and the spec reference cannot drift between\n * them \u{2014} a reader who has seen one of these has seen all three.\n *\n * `repair` carries no closing full stop: this adds one after the spec\n * reference, which is the sentence\'s real end.\n */\nfunction refuse(declared, claim, repair) {\n  throw new Error(\n    \'`\' + declared + \'` \' + claim + \' The contract is \' + CONTRACT + \'. \' + repair + \' (spec \u{a7}14E.1).\'\n  );\n}\n\n/** Whether a handle is one: an object carrying both halves of the contract. */\nfunction conforms(handle) {\n  return (\n    handle !== null &&\n    typeof handle === \'object\' &&\n    typeof handle.update === \'function\' &&\n    typeof handle.destroy === \'function\'\n  );\n}\n\n/** What was imported, as a noun phrase: `a number`, `an object`, `null`. */\nfunction describe(value) {\n  if (value === null) return \'null\';\n  const what = typeof value;\n  if (what === \'undefined\') return \'undefined\';\n  return (\'aeiou\'.includes(what[0]) ? \'an \' : \'a \') + what;\n}\n\n/** What mounting produced, as the tail of \"this \u{2026}\". */\nfunction returned(handle) {\n  if (handle === null || typeof handle !== \'object\') return `returned ${describe(handle)}`;\n  const missing =\n    typeof handle.update === \'function\'\n      ? \'no `destroy`\'\n      : typeof handle.destroy === \'function\'\n        ? \'no `update`\'\n        : \'neither `update` nor `destroy`\';\n  return `returned an object with ${missing}`;\n}\n\n/**\n * Whether `fn` is a class rather than an ordinary function.\n *\n * ECMAScript\'s own distinction rather than a guess at source text: a class\n * constructor\'s `prototype` is non-writable, an ordinary function\'s is\n * writable, and a method or arrow has none at all. Reading the descriptor\n * separates the three without `Function.prototype.toString`, which a\n * minifier, a bound function and a native class can each make lie.\n *\n * A class transpiled down to a plain function \u{2014} what a bundler targeting\n * ES5 emits \u{2014} is not detectable here and does not need to be: it is\n * callable, so it is called, and it is the handle check that refuses it.\n */\nfunction isClass(fn) {\n  const prototype = Object.getOwnPropertyDescriptor(fn, \'prototype\');\n  return prototype !== undefined && prototype.writable === false;\n}\n";
Expand description

The lifecycle of a foreign … gives view: create, update, destroy.

Its own module rather than part of dom.js because a DOM-owning foreign is optional and its machinery is not small: a program that writes none must not download it (§16.3.1). It imports signal.js and nothing else — the node is handed in, so there is no DOM dependency.