@webjsdev/core 0.7.58 → 0.7.60
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.
- package/dist/webjs-core-browser.js +4 -4
- package/dist/webjs-core-browser.js.map +2 -2
- package/dist/webjs-core-lazy-loader.js +1 -1
- package/dist/webjs-core-lazy-loader.js.map +2 -2
- package/dist/webjs-core-testing.js +13 -13
- package/dist/webjs-core-testing.js.map +2 -2
- package/dist/webjs-core.js +5 -5
- package/dist/webjs-core.js.map +2 -2
- package/package.json +1 -1
- package/src/attribute-reader.js +55 -7
- package/src/component/lifecycle.js +7 -5
- package/src/lazy-loader.js +25 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.60",
|
|
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",
|
package/src/attribute-reader.js
CHANGED
|
@@ -109,9 +109,8 @@ export function readAttributeValue(def, value) {
|
|
|
109
109
|
//
|
|
110
110
|
// The two readers now see the SAME attribute set, so this fallback is not
|
|
111
111
|
// the only thing they agree on (#1341). Both resolve a name through
|
|
112
|
-
// `resolveAttributeProperty` below, which matches the same
|
|
113
|
-
// `
|
|
114
|
-
// and nothing else, so it skips a `state: true` prop and ignores a name that
|
|
112
|
+
// `resolveAttributeProperty` below, which matches the same names
|
|
113
|
+
// `observedAttributes` lists (via `observedAttributeNames`) and nothing else, so it skips a `state: true` prop and ignores a name that
|
|
115
114
|
// maps to no attribute-backed property exactly as the observed list does.
|
|
116
115
|
// The SSR caller lowercases its source name first, because the browser
|
|
117
116
|
// lowercases while parsing. And both are handed a value with every
|
|
@@ -148,8 +147,9 @@ function hyphenate(s) {
|
|
|
148
147
|
* SSR read MORE than a browser can deliver, which is the bug this function
|
|
149
148
|
* exists to remove, so do not add it.
|
|
150
149
|
*
|
|
151
|
-
* The match is EXACTLY `
|
|
152
|
-
* `
|
|
150
|
+
* The match is EXACTLY the names `observedAttributeNames` lists (the
|
|
151
|
+
* `d.attribute || hyphenate(k)` primary plus, for a camelCase prop with no
|
|
152
|
+
* custom attribute, its lowercased alias, #1540), and nothing else. A `props[attrName] ||
|
|
153
153
|
* props[camelCase(attrName)]` fallback used to sit after the loop, so a prop
|
|
154
154
|
* declaring a custom attribute also answered to its PROPERTY name. Both readers
|
|
155
155
|
* carried that code, which read like an agreement and was not one: the browser
|
|
@@ -168,11 +168,59 @@ function hyphenate(s) {
|
|
|
168
168
|
* which case the caller must IGNORE the attribute entirely (#1341).
|
|
169
169
|
*/
|
|
170
170
|
export function resolveAttributeProperty(Cls, attrName) {
|
|
171
|
+
// Primary names first, so a prop literally named `foobar` keeps `foobar`
|
|
172
|
+
// even when a sibling `fooBar` would answer to it as an alias.
|
|
173
|
+
let aliasHit;
|
|
174
|
+
for (const { propName, def, primary, alias } of attributeBackedProps(Cls)) {
|
|
175
|
+
if (primary === attrName) return { propName, def };
|
|
176
|
+
if (aliasHit === undefined && alias === attrName) aliasHit = { propName, def };
|
|
177
|
+
}
|
|
178
|
+
return aliasHit;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Every attribute name the browser must observe for `Cls`, the list
|
|
183
|
+
* `observedAttributes` returns. Built from the same `attributeBackedProps` walk
|
|
184
|
+
* `resolveAttributeProperty` matches against, so a name is observed exactly
|
|
185
|
+
* when it resolves, on both sides (#1341, #1540).
|
|
186
|
+
*
|
|
187
|
+
* @param {any} Cls the component class
|
|
188
|
+
* @returns {string[]}
|
|
189
|
+
*/
|
|
190
|
+
export function observedAttributeNames(Cls) {
|
|
191
|
+
const names = new Set();
|
|
192
|
+
const entries = attributeBackedProps(Cls);
|
|
193
|
+
for (const { primary } of entries) names.add(primary);
|
|
194
|
+
for (const { alias } of entries) if (alias) names.add(alias);
|
|
195
|
+
return [...names];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The attribute-backed props of `Cls` with the names each answers to.
|
|
200
|
+
*
|
|
201
|
+
* `primary` is the custom `attribute` option, matched VERBATIM, or else the
|
|
202
|
+
* kebab-cased property name. `alias` is the LOWERCASED property name, lit's
|
|
203
|
+
* default attribute name, present only when the prop has no custom `attribute`
|
|
204
|
+
* and the lowercased name differs from the kebab one. Without it,
|
|
205
|
+
* `<quiz-player quizId=${id}>` against `{ quizId: Number }` reached neither
|
|
206
|
+
* side: the parser lowercases the name to `quizid`, which matched nothing, so
|
|
207
|
+
* the prop silently stayed undefined on the server AND the client (#1540).
|
|
208
|
+
* The alias is only ever lowercase, which is the one form the platform can
|
|
209
|
+
* deliver, so this does not reopen the SSR-reads-more gap #1341 closed.
|
|
210
|
+
*
|
|
211
|
+
* @param {any} Cls
|
|
212
|
+
* @returns {{ propName: string, def: PropertyDeclaration, primary: string, alias: string | undefined }[]}
|
|
213
|
+
*/
|
|
214
|
+
function attributeBackedProps(Cls) {
|
|
171
215
|
const props = (Cls && Cls.properties) || {};
|
|
216
|
+
const out = [];
|
|
172
217
|
for (const [k, decl] of Object.entries(props)) {
|
|
173
218
|
const d = typeof decl === 'object' && decl !== null ? decl : { type: decl };
|
|
174
219
|
if (d.state) continue;
|
|
175
|
-
|
|
220
|
+
const primary = d.attribute || hyphenate(k);
|
|
221
|
+
const lower = k.toLowerCase();
|
|
222
|
+
const alias = !d.attribute && lower !== primary ? lower : undefined;
|
|
223
|
+
out.push({ propName: k, def: d, primary, alias });
|
|
176
224
|
}
|
|
177
|
-
return
|
|
225
|
+
return out;
|
|
178
226
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { render as clientRender } from '../render-client.js';
|
|
2
|
-
import { readAttributeValue, resolveAttributeProperty } from '../attribute-reader.js';
|
|
2
|
+
import { readAttributeValue, resolveAttributeProperty, observedAttributeNames } from '../attribute-reader.js';
|
|
3
3
|
import { setActiveActionSignal, activeActionSignal } from '../action-abort-client.js';
|
|
4
4
|
import { carriesFunction } from '../form-action.js';
|
|
5
5
|
import { isCSS, adoptStyles } from '../css.js';
|
|
@@ -184,10 +184,12 @@ class WebComponentBase extends Base {
|
|
|
184
184
|
* @returns {string[]}
|
|
185
185
|
*/
|
|
186
186
|
static get observedAttributes() {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
// Derived from the same walk `resolveAttributeProperty` matches against, so
|
|
188
|
+
// every observed name resolves and nothing else does. A camelCase prop with
|
|
189
|
+
// no custom `attribute` is observed under its kebab name AND its lowercased
|
|
190
|
+
// name (lit's default), since the parser turns `quizId=` into `quizid`
|
|
191
|
+
// (#1540).
|
|
192
|
+
return observedAttributeNames(this);
|
|
191
193
|
}
|
|
192
194
|
|
|
193
195
|
constructor() {
|
package/src/lazy-loader.js
CHANGED
|
@@ -22,6 +22,17 @@
|
|
|
22
22
|
* // ...
|
|
23
23
|
* }
|
|
24
24
|
* MyWidget.register('my-lazy-widget');
|
|
25
|
+
*
|
|
26
|
+
* A component that another shipping module imports for registration
|
|
27
|
+
* (`import './my-lazy-widget.ts'`) stays lazy too (#1524): the server keeps
|
|
28
|
+
* that import for SSR, and the browser copy of the importer is served with
|
|
29
|
+
* the line rewritten to an `observeLazy({...})` call, so the module loads
|
|
30
|
+
* when an element with the tag is first visible: a hidden tab panel when it
|
|
31
|
+
* shows, a dialog's content when it opens, an element added later by a
|
|
32
|
+
* client render (the MutationObserver below catches it).
|
|
33
|
+
*
|
|
34
|
+
* The scan reads the light DOM (`document.querySelectorAll`), so a lazy tag
|
|
35
|
+
* rendered inside another component's shadow root is not found.
|
|
25
36
|
*/
|
|
26
37
|
|
|
27
38
|
/** @type {Map<string, string>} tag → module URL */
|
|
@@ -30,6 +41,14 @@ const pending = new Map();
|
|
|
30
41
|
/** @type {IntersectionObserver | null} */
|
|
31
42
|
let observer = null;
|
|
32
43
|
|
|
44
|
+
/**
|
|
45
|
+
* ONE MutationObserver for the whole page, however often `observeLazy` runs
|
|
46
|
+
* (#1524): the boot script calls it, and so does every served module whose
|
|
47
|
+
* side-effect import of a `static lazy` component was deferred to it.
|
|
48
|
+
* @type {MutationObserver | null}
|
|
49
|
+
*/
|
|
50
|
+
let mutations = null;
|
|
51
|
+
|
|
33
52
|
/** @type {Map<string, Promise<unknown>>} in-flight module loads, keyed by URL */
|
|
34
53
|
const inflight = new Map();
|
|
35
54
|
|
|
@@ -78,15 +97,18 @@ export function observeLazy(entries) {
|
|
|
78
97
|
scan();
|
|
79
98
|
|
|
80
99
|
// Also watch for dynamically added elements via MutationObserver.
|
|
81
|
-
if (typeof MutationObserver !== 'undefined') {
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
if (!mutations && typeof MutationObserver !== 'undefined') {
|
|
101
|
+
mutations = new MutationObserver(() => scan());
|
|
102
|
+
mutations.observe(document.body, { childList: true, subtree: true });
|
|
84
103
|
}
|
|
85
104
|
}
|
|
86
105
|
|
|
87
106
|
function scan() {
|
|
88
107
|
if (!observer || !pending.size) return;
|
|
89
108
|
for (const tag of pending.keys()) {
|
|
109
|
+
// Already defined some other way (an eager import elsewhere): nothing
|
|
110
|
+
// left to load for this tag.
|
|
111
|
+
if (customElements.get(tag)) { pending.delete(tag); continue; }
|
|
90
112
|
for (const el of document.querySelectorAll(tag)) {
|
|
91
113
|
observer.observe(el);
|
|
92
114
|
}
|