@webjsdev/core 0.7.49 → 0.7.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/webjs-core-browser.js +5 -5
  2. package/dist/webjs-core-browser.js.map +3 -3
  3. package/dist/webjs-core-testing.js +21 -21
  4. package/dist/webjs-core-testing.js.map +3 -3
  5. package/dist/webjs-core.js +7 -7
  6. package/dist/webjs-core.js.map +3 -3
  7. package/index-browser.js +1 -1
  8. package/index.d.ts +1 -1
  9. package/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/attribute-reader.js +178 -0
  12. package/src/component/lifecycle.js +1481 -0
  13. package/src/component/reactive.js +171 -0
  14. package/src/component/server-element.js +248 -0
  15. package/src/component.d.ts +10 -0
  16. package/src/component.js +2 -1931
  17. package/src/html-entities.js +2179 -0
  18. package/src/render-client/parts.js +2007 -0
  19. package/src/render-client/reconciler.js +388 -0
  20. package/src/render-client/template-compiler.js +525 -0
  21. package/src/render-client.js +1 -2939
  22. package/src/render-server/attrs.js +160 -0
  23. package/src/render-server/dsd.js +445 -0
  24. package/src/render-server/html-scan.js +402 -0
  25. package/src/render-server/slots.js +213 -0
  26. package/src/render-server/stream.js +163 -0
  27. package/src/render-server/template-renderer.js +923 -0
  28. package/src/render-server/text.js +163 -0
  29. package/src/render-server.js +6 -2334
  30. package/src/router-client/anchors.js +44 -0
  31. package/src/router-client/boundaries.js +221 -0
  32. package/src/router-client/constants.js +187 -0
  33. package/src/router-client/diagnostics.js +377 -0
  34. package/src/router-client/dom-differ.js +709 -0
  35. package/src/router-client/dom-parse.js +150 -0
  36. package/src/router-client/events.js +139 -0
  37. package/src/router-client/fetch-apply.js +387 -0
  38. package/src/router-client/form-encoder.js +135 -0
  39. package/src/router-client/frames.js +329 -0
  40. package/src/router-client/head-merge.js +344 -0
  41. package/src/router-client/nav-error.js +105 -0
  42. package/src/router-client/navigator.js +781 -0
  43. package/src/router-client/prefetch.js +824 -0
  44. package/src/router-client/scroll.js +259 -0
  45. package/src/router-client/snapshot-cache.js +97 -0
  46. package/src/router-client/state.js +92 -0
  47. package/src/router-client/stream.js +206 -0
  48. package/src/router-client/swap.js +451 -0
  49. package/src/router-client/upgrade.js +261 -0
  50. package/src/router-client/view-transition.js +223 -0
  51. package/src/router-client.d.ts +10 -1
  52. package/src/router-client.js +75 -5345
  53. package/src/slot/assignment.js +304 -0
  54. package/src/slot/interception.js +558 -0
  55. package/src/slot/polyfills.js +365 -0
  56. package/src/slot/project.js +432 -0
  57. package/src/slot/sensors.js +303 -0
  58. package/src/slot/state.js +245 -0
  59. package/src/slot/symbols.js +85 -0
  60. package/src/slot.js +7 -2199
  61. package/src/webjs-config.d.ts +13 -2
package/index-browser.js CHANGED
@@ -60,7 +60,7 @@ import './src/router-client.js';
60
60
  // it, `import { loadFrame } from '@webjsdev/core/client-router'` type-checks,
61
61
  // works in dev (where the subpath maps to `src/router-client.js`) and is
62
62
  // `undefined` in production.
63
- export { enableClientRouter, disableClientRouter, navigate, revalidate, loadFrame } from './src/router-client.js';
63
+ export { enableClientRouter, disableClientRouter, navigate, revalidate, refreshPage, loadFrame } from './src/router-client.js';
64
64
  export { WebjsFrame } from './src/webjs-frame.js';
65
65
  export { WebjsStream, renderStream } from './src/webjs-stream.js';
66
66
 
package/index.d.ts CHANGED
@@ -81,7 +81,7 @@ export { repeat, isRepeat } from './src/repeat.js';
81
81
  export { Suspense, isSuspense } from './src/suspense.js';
82
82
  export { connectWS } from './src/websocket-client.js';
83
83
  export { richFetch } from './src/rich-fetch.js';
84
- export { enableClientRouter, disableClientRouter, revalidate, loadFrame } from './src/router-client.js';
84
+ export { enableClientRouter, disableClientRouter, revalidate, refreshPage, loadFrame } from './src/router-client.js';
85
85
 
86
86
  // `navigate` is typed against the generated `Route` union (#258) rather than
87
87
  // the JSDoc `string`. Until an app runs `webjs types`, `Route` resolves to
package/index.js CHANGED
@@ -28,7 +28,7 @@ export {
28
28
  stringify, parse,
29
29
  serialize, deserialize,
30
30
  } from './src/serialize.js';
31
- export { enableClientRouter, disableClientRouter, navigate, revalidate, loadFrame } from './src/router-client.js';
31
+ export { enableClientRouter, disableClientRouter, navigate, revalidate, refreshPage, loadFrame } from './src/router-client.js';
32
32
  export { WebjsFrame } from './src/webjs-frame.js';
33
33
  export { WebjsStream, renderStream } from './src/webjs-stream.js';
34
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/core",
3
- "version": "0.7.49",
3
+ "version": "0.7.51",
4
4
  "type": "module",
5
5
  "description": "The runtime for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Ships the html and css template tags, the WebComponent base class, signals, directives, and the isomorphic renderers.",
6
6
  "types": "./index.d.ts",
@@ -0,0 +1,178 @@
1
+ /**
2
+ * THE attribute reader, singular.
3
+ *
4
+ * `attributeChangedCallback` in `component.js` and `applyAttrsToInstance` in
5
+ * `render-server.js` are both thin callers of the two functions below, so the
6
+ * client and the SSR pass cannot drift on precedence, on a fallback, or on
7
+ * which attributes they read at all. They had drifted four ways before this
8
+ * module existed: the unparseable-JSON fallback (#1253), a missing converter
9
+ * arm (#1340), and three name-resolution gaps plus the entity decoding (#1341).
10
+ *
11
+ * The split between the two exports is the one place the sides legitimately
12
+ * differ. `resolveAttributeProperty` takes the name the PLATFORM would deliver,
13
+ * so the SSR caller lowercases its source name first and the client passes the
14
+ * browser's name straight through. Everything after that is common.
15
+ *
16
+ * WHY ITS OWN MODULE, rather than living beside the declaration semantics it
17
+ * interprets in `component.js`. That was the first shape, and `component.d.ts`
18
+ * cannot carry the declaration for it: `index.d.ts` re-exports that overlay
19
+ * with a bare `export *`, so a value declared there joins the ROOT public type
20
+ * surface of `@webjsdev/core` while `index.js` exports no such runtime value.
21
+ * An app writing `import { readAttributeValue } from '@webjsdev/core'` would
22
+ * then type-check and crash at load, which is exactly the phantom the #1031
23
+ * guard in `test/types/dts-no-phantom-exports.test.mjs` exists to catch, and it
24
+ * caught this one. Re-exporting the function from `index.js` would silence the
25
+ * guard by making an internal framework seam app-facing API, which is the wrong
26
+ * trade. A module with no `.d.ts` overlay keeps the seam internal and typed
27
+ * from its own JSDoc, which is how `escape.js` and `binding-prefixes.js`
28
+ * already work.
29
+ *
30
+ * It also drops an edge rather than adding one: `render-server.js` reaches the
31
+ * reader without importing `component.js` at all.
32
+ *
33
+ * lit solves the same problem structurally, though in the other direction.
34
+ * `@lit-labs/ssr`'s LitElementRenderer forwards its `attributeChangedCallback`
35
+ * into the element's own `_$attributeToProperty` through
36
+ * `lit-element/private-ssr-support.js`, so its server pass runs the browser's
37
+ * reader rather than a copy of it. lit can reach INTO the element because its
38
+ * elements are TypeScript with a generated declaration; WebJs ships buildless
39
+ * JS under a hand-written overlay, so the shared code moves out instead.
40
+ */
41
+
42
+ /**
43
+ * Read one attribute string into its declared property value.
44
+ *
45
+ * The caller owns only the one platform-specific step, lowercasing the source
46
+ * name at SSR; `resolveAttributeProperty` below owns NAME resolution and
47
+ * declaration normalisation for both sides, and this function owns the value.
48
+ * The two callers used to reach an attribute by different routes and NOT see
49
+ * the same attribute set, which #1341 closed.
50
+ *
51
+ * @param {import('./component.js').PropertyDeclaration} def normalised declaration (`{ type, … }`)
52
+ * @param {string|null} value the attribute text
53
+ *
54
+ * BOTH callers hand in an already-decoded value, so no branch decodes anything
55
+ * (#1341). The client's came out of the DOM, which decodes every character
56
+ * reference before any reader sees it; the SSR caller runs `decodeAttrEntities`
57
+ * once at its own call site, ahead of this function, over EVERY branch rather
58
+ * than only the ones that parse. This used to be a third `decode` parameter
59
+ * applied to the JSON and converter branches alone, which left a `String`-typed
60
+ * prop holding the raw source text at SSR while `getAttribute()` on the client
61
+ * returned the decoded one.
62
+ * @returns {unknown}
63
+ */
64
+ export function readAttributeValue(def, value) {
65
+ if (def.converter && def.converter.fromAttribute) {
66
+ // Deliberately UNGUARDED, and deliberately first (#1340). An author who
67
+ // supplies a converter owns the whole read, which is the same rule
68
+ // `_reflectAttribute` already states for `toAttribute`: its guards sit
69
+ // AFTER the converter branch because a custom converter is
70
+ // author-controlled. So a converter that throws throws, on both sides. At
71
+ // SSR that lands in per-component error isolation (render-server.js), which
72
+ // surfaces an error box in dev and an empty element at a 200 in prod with
73
+ // the cause in the server log; on the client it escapes
74
+ // attributeChangedCallback during upgrade. Catching on one side only would
75
+ // manufacture a fresh divergence (an SSR paint holding a fallback against a
76
+ // browser holding the constructor value), which is worse than both sides
77
+ // failing. lit propagates too (reactive-element `_$attributeToProperty`).
78
+ //
79
+ // A converter RETURN that SSR cannot serialise needs no guard here either.
80
+ // Unreflected it is only a property, and a function in a text hole renders
81
+ // as nothing. Reflected, the #1169 function guard and the #1253
82
+ // unserializable guard in `_reflectAttribute` already run on the SSR side
83
+ // and cover whatever the converter produced.
84
+ //
85
+ // The converter is handed DECODED text, which matters more here than
86
+ // anywhere: the documented reason to write one is a type the built-in
87
+ // converters cannot parse (Date, Map, Set), those parse their input, and
88
+ // `escapeAttr` encodes every `"` in an emitted attribute, so an encoded
89
+ // `{"a":1}` would throw inside the author's converter and (since
90
+ // the throw is deliberately uncaught) render an empty component at a 200.
91
+ // Both callers now decode before calling, so it gets the same text on both
92
+ // sides for hand-written markup as well as for anything WebJs emitted
93
+ // (#1341); this used to be a `decode` argument applied on this branch and
94
+ // the JSON one.
95
+ return def.converter.fromAttribute(value, def.type);
96
+ }
97
+ if (def.type === Number) return value == null ? null : Number(value);
98
+ if (def.type === Boolean) return value != null && value !== 'false';
99
+ if (def.type === Object || def.type === Array) {
100
+ // An attribute that is not parseable JSON yields `null` rather than the raw
101
+ // string (#1253), because a STRING is never a valid value for a property
102
+ // the author declared `Object` or `Array`, whatever put it there. lit's
103
+ // `defaultConverter.fromAttribute` lands on the same `null`, for the reason
104
+ // its own comment gives: an element does not complain about being
105
+ // mis-configured. Both readers reach this line, so they cannot disagree.
106
+ //
107
+ // An attribute that was never PRESENT does not reach either reader, so such
108
+ // a prop simply keeps its constructor value.
109
+ //
110
+ // The two readers now see the SAME attribute set, so this fallback is not
111
+ // the only thing they agree on (#1341). Both resolve a name through
112
+ // `resolveAttributeProperty` below, which matches the same
113
+ // `d.attribute || hyphenate(k)` expression `observedAttributes` maps over
114
+ // and nothing else, so it skips a `state: true` prop and ignores a name that
115
+ // maps to no attribute-backed property exactly as the observed list does.
116
+ // The SSR caller lowercases its source name first, because the browser
117
+ // lowercases while parsing. And both are handed a value with every
118
+ // character reference already decoded, legacy semicolon-less names included.
119
+ if (value == null) return null;
120
+ try { return JSON.parse(value); } catch { return null; }
121
+ }
122
+ return value;
123
+ }
124
+
125
+ /** Kebab-case a property name for its default HTML attribute. @param {string} s */
126
+ function hyphenate(s) {
127
+ return s.replace(/([A-Z])/g, '-$1').toLowerCase();
128
+ }
129
+
130
+ /**
131
+ * Resolve an attribute NAME to the property it feeds, and to that property's
132
+ * normalised declaration.
133
+ *
134
+ * `state: true` props are skipped, because they are absent from
135
+ * `observedAttributes` and so the browser never delivers their attribute to any
136
+ * reader. lit reaches the same outcome the same way, resolving through the very
137
+ * map `observedAttributes` is built from (`reactive-element.ts`
138
+ * `_$attributeToProperty`).
139
+ *
140
+ * `attrName` must already be the name the PLATFORM would deliver. The browser
141
+ * lowercases attribute names while parsing, so the SSR caller lowercases its
142
+ * source name before calling and the client caller passes the browser's name
143
+ * through untouched. That one step is the only thing that differs between the
144
+ * two sides, which is why it lives in the callers and not here. A custom
145
+ * `attribute` option is matched VERBATIM, as lit matches it and as a browser
146
+ * would, so a camelCase custom attribute stays unreachable from markup rather
147
+ * than being reachable at SSR only. Lowercasing the DECLARED side would make
148
+ * SSR read MORE than a browser can deliver, which is the bug this function
149
+ * exists to remove, so do not add it.
150
+ *
151
+ * The match is EXACTLY `d.attribute || hyphenate(k)`, the same expression
152
+ * `observedAttributes` maps over, and nothing else. A `props[attrName] ||
153
+ * props[camelCase(attrName)]` fallback used to sit after the loop, so a prop
154
+ * declaring a custom attribute also answered to its PROPERTY name. Both readers
155
+ * carried that code, which read like an agreement and was not one: the browser
156
+ * only ever calls in with a name from `observedAttributes`, and that list holds
157
+ * the declared attribute alone, so the fallback was unreachable on the client
158
+ * and live on the server. Measured, `open: prop(Boolean, { attribute:
159
+ * 'is-open' })` with `<my-el open>` SSR'd `true` and upgraded to the
160
+ * constructor value. Deriving the match from the same expression the observed
161
+ * list is built from is what makes the two sides agree by construction rather
162
+ * than by two copies staying in step (#1341).
163
+ *
164
+ * @param {any} Cls the component class
165
+ * @param {string} attrName
166
+ * @returns {{ propName: string, def: PropertyDeclaration } | undefined}
167
+ * `undefined` when the attribute maps to no attribute-backed property, in
168
+ * which case the caller must IGNORE the attribute entirely (#1341).
169
+ */
170
+ export function resolveAttributeProperty(Cls, attrName) {
171
+ const props = (Cls && Cls.properties) || {};
172
+ for (const [k, decl] of Object.entries(props)) {
173
+ const d = typeof decl === 'object' && decl !== null ? decl : { type: decl };
174
+ if (d.state) continue;
175
+ if ((d.attribute || hyphenate(k)) === attrName) return { propName: k, def: d };
176
+ }
177
+ return undefined;
178
+ }