@webjsdev/core 0.7.46 → 0.7.47
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 +5 -5
- package/dist/webjs-core-browser.js.map +3 -3
- package/dist/webjs-core-testing.js +20 -20
- package/dist/webjs-core-testing.js.map +2 -2
- package/dist/webjs-core.js +7 -7
- package/dist/webjs-core.js.map +3 -3
- package/index-browser.js +4 -0
- package/index.d.ts +3 -0
- package/index.js +4 -0
- package/package.json +2 -2
- package/src/component.js +69 -0
- package/src/form-action.d.ts +16 -0
- package/src/form-action.js +1127 -33
- package/src/optimistic.js +7 -2
- package/src/render-client.js +977 -108
- package/src/render-server.js +401 -62
- package/src/router-client.js +324 -21
- package/src/webjs-config.d.ts +49 -17
package/index-browser.js
CHANGED
|
@@ -31,6 +31,10 @@ export { escapeText, escapeAttr } from './src/escape.js';
|
|
|
31
31
|
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
32
32
|
export { cspNonce } from './src/csp-nonce.js';
|
|
33
33
|
export { asset } from './src/asset-url.js';
|
|
34
|
+
// Form actions (#1155). The field name is shared with the server dispatcher;
|
|
35
|
+
// `setFormActionResolver` is server-only wiring and is stripped here, because
|
|
36
|
+
// a browser stub carries its own identity and needs no resolver.
|
|
37
|
+
export { FORM_ACTION_FIELD, FORM_ACTION_ID_KEY } from './src/form-action.js';
|
|
34
38
|
export { repeat, isRepeat } from './src/repeat.js';
|
|
35
39
|
export { Suspense, isSuspense } from './src/suspense.js';
|
|
36
40
|
export { connectWS } from './src/websocket-client.js';
|
package/index.d.ts
CHANGED
|
@@ -52,6 +52,8 @@ export type {
|
|
|
52
52
|
WebjsRedirectRule,
|
|
53
53
|
WebjsTrailingSlash,
|
|
54
54
|
WebjsCspConfig,
|
|
55
|
+
WebjsDoctorConfig,
|
|
56
|
+
WebjsDoctorSeverity,
|
|
55
57
|
} from './src/webjs-config.d.ts';
|
|
56
58
|
|
|
57
59
|
// Compile-time serializability typing for server actions (#488): the opt-in
|
|
@@ -105,6 +107,7 @@ export { Task, TaskStatus } from './src/task.js';
|
|
|
105
107
|
export { signal, computed, effect, batch, isSignal, Signal } from './src/signal.js';
|
|
106
108
|
export { cspNonce, setCspNonceProvider } from './src/csp-nonce.js';
|
|
107
109
|
export { asset, setAssetUrlProvider } from './src/asset-url.js';
|
|
110
|
+
export { FORM_ACTION_FIELD, FORM_ACTION_ID_KEY, setFormActionResolver } from './src/form-action.js';
|
|
108
111
|
export { takeSeed, scanSeeds, SEED_MISS } from './src/action-seed-client.js';
|
|
109
112
|
export { markStale, registerKeyTags, consumeStale, parseTagHeader, fetchMark } from './src/action-cache-client.js';
|
|
110
113
|
// Client action-abort plumbing (#492): a superseded async render aborts its
|
package/index.js
CHANGED
|
@@ -16,6 +16,10 @@ export { escapeText, escapeAttr } from './src/escape.js';
|
|
|
16
16
|
export { notFound, redirect, forbidden, unauthorized, isNotFound, isRedirect, isForbidden, isUnauthorized } from './src/nav.js';
|
|
17
17
|
export { cspNonce, setCspNonceProvider } from './src/csp-nonce.js';
|
|
18
18
|
export { asset, setAssetUrlProvider } from './src/asset-url.js';
|
|
19
|
+
// Form actions (#1155). `FORM_ACTION_FIELD` is the hidden field both renderers
|
|
20
|
+
// emit and the server's form dispatcher reads; `setFormActionResolver` is the
|
|
21
|
+
// server-only wiring that maps a real action function back to its identity.
|
|
22
|
+
export { FORM_ACTION_FIELD, FORM_ACTION_ID_KEY, setFormActionResolver } from './src/form-action.js';
|
|
19
23
|
export { repeat, isRepeat } from './src/repeat.js';
|
|
20
24
|
export { Suspense, isSuspense } from './src/suspense.js';
|
|
21
25
|
export { connectWS } from './src/websocket-client.js';
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.47",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
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",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
package/src/component.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { render as clientRender } from './render-client.js';
|
|
2
2
|
import { setActiveActionSignal } from './action-abort-client.js';
|
|
3
|
+
import { carriesFunction } from './form-action.js';
|
|
3
4
|
import { isCSS, adoptStyles } from './css.js';
|
|
4
5
|
import { register, tagOf } from './registry.js';
|
|
5
6
|
import { parse as deserializeProp } from './serialize.js';
|
|
@@ -118,6 +119,44 @@ function safeString(v) {
|
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Warn that a function value was dropped rather than reflected (#1169).
|
|
124
|
+
*
|
|
125
|
+
* A silently missing attribute is its own confusion, so say what went and why.
|
|
126
|
+
* The message deliberately does NOT include the value: printing the source is
|
|
127
|
+
* the leak this guard exists to prevent, and a server log is not always a
|
|
128
|
+
* private place.
|
|
129
|
+
*
|
|
130
|
+
* UNCONDITIONAL, matching the `.prop=${fn}` unserializable-value drop in
|
|
131
|
+
* `render-server.js`, which is the sibling path this guard mirrors. Two
|
|
132
|
+
* reasons not to gate it on a dev flag. It fires only on a genuine mistake (a
|
|
133
|
+
* function is never a meaningful attribute value), so there is no volume to
|
|
134
|
+
* suppress, and reflection runs per assignment rather than per frame.
|
|
135
|
+
*
|
|
136
|
+
* More decisively, a `NODE_ENV` gate does not survive the dist build.
|
|
137
|
+
* `scripts/build-framework-dist.js` runs esbuild with `platform: 'browser'`
|
|
138
|
+
* and `minify: true`, which substitutes `process.env.NODE_ENV` with
|
|
139
|
+
* `"production"` and folds the check to a constant. The SSR half of the
|
|
140
|
+
* warning would then be unreachable in every published build, which is how
|
|
141
|
+
* every installed app runs, and SSR is the half that matters most, since that
|
|
142
|
+
* is where the leaked source reached visitors.
|
|
143
|
+
*
|
|
144
|
+
* @param {{ constructor: unknown, tagName?: string }} host
|
|
145
|
+
* @param {string} propName
|
|
146
|
+
* @param {string} attrName
|
|
147
|
+
*/
|
|
148
|
+
function warnFunctionReflection(host, propName, attrName) {
|
|
149
|
+
if (typeof console === 'undefined' || !console.warn) return;
|
|
150
|
+
const tag = tagOf(/** @type any */ (host.constructor)) || host.tagName?.toLowerCase() || 'unknown';
|
|
151
|
+
console.warn(
|
|
152
|
+
`[webjs] reflect:true property "${propName}" on <${tag}> holds a function `
|
|
153
|
+
+ `(or an array carrying one), which has no HTML attribute representation. `
|
|
154
|
+
+ `Removing "${attrName}" instead of stringifying it (a stringified function `
|
|
155
|
+
+ `writes its source into the page, so a server action's body would ship to `
|
|
156
|
+
+ `the browser). Pass a string, or drop reflect on this property.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
121
160
|
/**
|
|
122
161
|
* A minimal base for HTML Custom Elements that mirrors Lit's ergonomics
|
|
123
162
|
* while staying JSDoc-only and no-build.
|
|
@@ -685,13 +724,43 @@ class WebComponentBase extends Base {
|
|
|
685
724
|
const serialized = decl.converter.toAttribute(value, decl.type);
|
|
686
725
|
if (serialized == null) this.removeAttribute(attrName);
|
|
687
726
|
else this.setAttribute(attrName, serialized);
|
|
727
|
+
} else if (typeof value === 'function') {
|
|
728
|
+
// #1169: the value IS a function, and no branch below has a
|
|
729
|
+
// meaningful serialization for one. `String(fn)` is the function's
|
|
730
|
+
// SOURCE, so a reflected `'use server'` action would ship its whole
|
|
731
|
+
// body, closure secrets included, to every visitor, and
|
|
732
|
+
// `JSON.stringify(fn)` is `undefined`, which `setAttribute` writes as
|
|
733
|
+
// the literal string "undefined". Treat it like `null` and remove the
|
|
734
|
+
// attribute, matching what the `.prop=${fn}` SSR binding already does
|
|
735
|
+
// for an unserializable value.
|
|
736
|
+
//
|
|
737
|
+
// Placed AFTER the converter branch on purpose: a custom
|
|
738
|
+
// `toAttribute` is author-controlled, so its author has taken
|
|
739
|
+
// responsibility for serializing whatever they are handed.
|
|
740
|
+
this.removeAttribute(attrName);
|
|
741
|
+
warnFunctionReflection(this, propName, attrName);
|
|
688
742
|
} else if (decl.type === Boolean) {
|
|
689
743
|
if (value) this.setAttribute(attrName, '');
|
|
690
744
|
else this.removeAttribute(attrName);
|
|
691
745
|
} else if (value == null) {
|
|
692
746
|
this.removeAttribute(attrName);
|
|
693
747
|
} else if (decl.type === Object || decl.type === Array) {
|
|
748
|
+
// A JSON-typed prop CARRYING a function is safe and stays whole:
|
|
749
|
+
// `JSON.stringify` drops a function to `null` in an array and omits
|
|
750
|
+
// the key in an object, so `[1, 2, fn]` serializes to `[1,2,null]`
|
|
751
|
+
// with no source and no data loss. Refusing here would discard the
|
|
752
|
+
// `1` and the `2` on a path that never leaked.
|
|
694
753
|
this.setAttribute(attrName, JSON.stringify(value));
|
|
754
|
+
} else if (carriesFunction(value)) {
|
|
755
|
+
// The string fall-through is the one place a CARRIED function still
|
|
756
|
+
// leaks. `String([fn])` is `Array.prototype.join`, which runs
|
|
757
|
+
// `String()` on every element, so `[serverAction]` writes the same
|
|
758
|
+
// source `serverAction` does. Hence the recursive predicate the
|
|
759
|
+
// form-action guard already uses, rather than a bare `typeof`, but
|
|
760
|
+
// scoped to this branch rather than applied above, where JSON
|
|
761
|
+
// handles the same shape losslessly.
|
|
762
|
+
this.removeAttribute(attrName);
|
|
763
|
+
warnFunctionReflection(this, propName, attrName);
|
|
695
764
|
} else {
|
|
696
765
|
this.setAttribute(attrName, String(value));
|
|
697
766
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Form actions (#1155): `<form action=${importedAction}>` and its wire. */
|
|
2
|
+
|
|
3
|
+
/** The hidden field carrying a bound action's identity into the submission. */
|
|
4
|
+
export const FORM_ACTION_FIELD: '__webjs_action';
|
|
5
|
+
|
|
6
|
+
/** The property a generated client RPC stub carries its own identity on. */
|
|
7
|
+
export const FORM_ACTION_ID_KEY: '$$webjsAction';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Server-only wiring: maps a REAL server-action function back to its
|
|
11
|
+
* `<hash>/<fn>` identity. `@webjsdev/server` installs it at boot; the browser
|
|
12
|
+
* bundle drops it, because a stub carries its identity on itself.
|
|
13
|
+
*/
|
|
14
|
+
export function setFormActionResolver(
|
|
15
|
+
fn: (fn: Function) => string | null | Promise<string | null>,
|
|
16
|
+
): void;
|