native-sfc 0.0.1 → 0.0.2
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/README.md +115 -6
- package/dist/index.bundled.js +408 -32
- package/dist/index.cdn.js +416 -38
- package/dist/index.d.ts +61 -12
- package/dist/index.js +416 -38
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Load a single HTML file as a Web Component.
|
|
4
4
|
|
|
5
|
+
```html
|
|
6
|
+
<!-- index.html -->
|
|
7
|
+
<my-counter></my-counter>
|
|
8
|
+
<script type="module">
|
|
9
|
+
import { loadComponent } from "https://esm.sh/native-sfc";
|
|
10
|
+
loadComponent("my-counter", "./my-counter.html");
|
|
11
|
+
</script>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<!-- my-counter.html -->
|
|
16
|
+
<button @click="setCount(count() + 1)">Count is: {{ count() }}</button>
|
|
17
|
+
<script type="module">
|
|
18
|
+
import { signal } from "https://esm.sh/native-sfc";
|
|
19
|
+
export default class MyCounter extends HTMLElement {
|
|
20
|
+
setup() {
|
|
21
|
+
const [count, setCount] = signal(0);
|
|
22
|
+
return { count, setCount };
|
|
23
|
+
}
|
|
24
|
+
connectedCallback() {}
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
```
|
|
28
|
+
|
|
5
29
|
## How it works
|
|
6
30
|
|
|
7
31
|
The component loader fetches everything (HTML, JS, CSS) as text,
|
|
@@ -13,22 +37,107 @@ then processes them to create a Web Component.
|
|
|
13
37
|
4. **Global Styles**: Styles with `global` attribute are moved to the outer document instead of the shadow root
|
|
14
38
|
5. **URL Rewriting**: All relative URLs in `src` and `href` attributes are rewritten to absolute URLs based on the component file location
|
|
15
39
|
|
|
16
|
-
## API
|
|
40
|
+
## Component API
|
|
17
41
|
|
|
18
|
-
### `loadComponent(name, url
|
|
42
|
+
### `loadComponent(name: string, url: string)`
|
|
19
43
|
|
|
20
44
|
Load a component from a HTML file and register it as a custom element.
|
|
21
45
|
|
|
22
46
|
- `name`: The custom element name (e.g., `"my-component"`)
|
|
23
47
|
- `url`: The URL to the component HTML file (relative to the importer)
|
|
24
|
-
- `afterConstructor`: Optional callback executed after the component constructor
|
|
25
48
|
|
|
26
|
-
### `defineComponent(
|
|
49
|
+
### `defineComponent(setup: ({ onConnected?, onDisconnected? }) => void)`
|
|
27
50
|
|
|
28
51
|
A helper function for dual-mode component definition:
|
|
29
52
|
|
|
30
|
-
- When used inside a `loadComponent`-imported module, it defines a web component class
|
|
31
|
-
- When used in normal document context, it runs the function with `document` as root
|
|
53
|
+
- When used inside a `loadComponent`-imported module, it defines a web component class with lifecycle callbacks
|
|
54
|
+
- When used in normal document context, it runs the setup function with `document` as root
|
|
55
|
+
- The setup function receives `{ onConnected, onDisconnected }` callbacks
|
|
56
|
+
- Return an object from setup to expose reactive state to the template
|
|
57
|
+
|
|
58
|
+
## Signals API
|
|
59
|
+
|
|
60
|
+
### `signal<T>(initialValue: T): [() => T, (v: T) => void]`
|
|
61
|
+
|
|
62
|
+
Creates a reactive signal with a getter and setter.
|
|
63
|
+
|
|
64
|
+
- Returns a tuple: `[getter, setter]`
|
|
65
|
+
- `getter()`: Returns the current value
|
|
66
|
+
- `setter(value)`: Updates the signal value and triggers reactivity
|
|
67
|
+
|
|
68
|
+
### `computed<T>(fn: () => T): () => T`
|
|
69
|
+
|
|
70
|
+
Creates a computed value that automatically tracks dependencies.
|
|
71
|
+
|
|
72
|
+
- `fn`: Function that computes and returns the value
|
|
73
|
+
- Returns a getter function that returns the computed result
|
|
74
|
+
- Automatically updates when dependencies change
|
|
75
|
+
|
|
76
|
+
### `effect(fn: VoidFunction): VoidFunction`
|
|
77
|
+
|
|
78
|
+
Creates a reactive effect that runs whenever its dependencies change.
|
|
79
|
+
|
|
80
|
+
- `fn`: Function to execute
|
|
81
|
+
- Returns a cleanup function to stop the effect
|
|
82
|
+
- Useful for side effects and subscriptions
|
|
83
|
+
|
|
84
|
+
### `effectScope(fn: VoidFunction): VoidFunction`
|
|
85
|
+
|
|
86
|
+
Creates an effect scope to batch multiple effects together.
|
|
87
|
+
|
|
88
|
+
- `fn`: Function containing effect definitions
|
|
89
|
+
- Returns a cleanup function to stop all effects in the scope
|
|
90
|
+
- Useful for organizing related effects
|
|
91
|
+
|
|
92
|
+
## HTML Template API
|
|
93
|
+
|
|
94
|
+
### .property
|
|
95
|
+
|
|
96
|
+
Binds a DOM property to a reactive expression.
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<input .value="someSignal()" />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### :attribute
|
|
103
|
+
|
|
104
|
+
Binds a DOM attribute to a reactive expression.
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<img :src="imageUrl()" />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### @event
|
|
111
|
+
|
|
112
|
+
Binds a DOM event to a reactive expression.
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<button @click="handleClick()" />
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### {{ expression }}
|
|
119
|
+
|
|
120
|
+
Embeds a reactive expression inside text content.
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<p>Total: {{ total() }}</p>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### #if="condition"
|
|
127
|
+
|
|
128
|
+
Conditionally renders an element based on a reactive expression.
|
|
129
|
+
|
|
130
|
+
```html
|
|
131
|
+
<div #if="isVisible()">This content is visible only if isVisible() is true.</div>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### #for="arrayExpression"
|
|
135
|
+
|
|
136
|
+
Renders a list of elements based on a reactive array expression.
|
|
137
|
+
|
|
138
|
+
```html
|
|
139
|
+
<li #for="items().map(item => ({ item }))">{{ item.name }}</li>
|
|
140
|
+
```
|
|
32
141
|
|
|
33
142
|
## Limitations
|
|
34
143
|
|
package/dist/index.bundled.js
CHANGED
|
@@ -2050,32 +2050,59 @@ function o() {
|
|
|
2050
2050
|
throw Object.assign(Error(`Parse error ${c}:${t.slice(0, n).split("\n").length}:${n - t.lastIndexOf("\n", n - 1)}`), { idx: n });
|
|
2051
2051
|
}
|
|
2052
2052
|
|
|
2053
|
+
// src/events.ts
|
|
2054
|
+
var eventTarget = new EventTarget();
|
|
2055
|
+
function emit(eventName, detail) {
|
|
2056
|
+
const event = new CustomEvent(eventName, { detail });
|
|
2057
|
+
eventTarget.dispatchEvent(event);
|
|
2058
|
+
}
|
|
2059
|
+
function on(eventName, listener) {
|
|
2060
|
+
eventTarget.addEventListener(eventName, listener);
|
|
2061
|
+
return () => {
|
|
2062
|
+
eventTarget.removeEventListener(eventName, listener);
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
// src/config.ts
|
|
2067
|
+
var config = {
|
|
2068
|
+
fetch: globalThis.fetch,
|
|
2069
|
+
rewriteModule: (code, sourceUrl) => `import.meta.url=${JSON.stringify(sourceUrl)};
|
|
2070
|
+
${code}`,
|
|
2071
|
+
on
|
|
2072
|
+
};
|
|
2073
|
+
Object.preventExtensions(config);
|
|
2074
|
+
|
|
2053
2075
|
// src/rewriter.ts
|
|
2054
|
-
function rewriteModule(code, sourceUrl) {
|
|
2076
|
+
async function rewriteModule(code, sourceUrl) {
|
|
2055
2077
|
const [imports] = parse2(code);
|
|
2056
2078
|
const rewritableImports = imports.filter((i2) => {
|
|
2057
2079
|
const specifier = code.slice(i2.s, i2.e);
|
|
2058
|
-
return !isBrowserUrl(specifier)
|
|
2080
|
+
return !isBrowserUrl(specifier);
|
|
2059
2081
|
});
|
|
2060
2082
|
for (const importEntry of rewritableImports.reverse()) {
|
|
2061
2083
|
const specifier = code.slice(importEntry.s, importEntry.e);
|
|
2062
2084
|
let rewritten = specifier;
|
|
2063
2085
|
if (specifier.startsWith(".") || specifier.startsWith("/")) {
|
|
2064
2086
|
rewritten = new URL(specifier, sourceUrl).href;
|
|
2087
|
+
} else if (specifier.startsWith("node:")) {
|
|
2088
|
+
const module = specifier.slice(5);
|
|
2089
|
+
rewritten = `https://raw.esm.sh/@jspm/core/nodelibs/browser/${module}.js`;
|
|
2090
|
+
} else if (specifier.startsWith("npm:")) {
|
|
2091
|
+
rewritten = `https://esm.sh/${specifier.slice(4)}`;
|
|
2065
2092
|
} else {
|
|
2066
2093
|
rewritten = `https://esm.sh/${specifier}`;
|
|
2067
2094
|
}
|
|
2068
2095
|
code = code.slice(0, importEntry.s) + rewritten + code.slice(importEntry.e);
|
|
2069
2096
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2097
|
+
const { rewriteModule: rewriteModule2 } = config;
|
|
2098
|
+
return await rewriteModule2(code, sourceUrl);
|
|
2072
2099
|
}
|
|
2073
2100
|
function isBrowserUrl(url) {
|
|
2074
|
-
return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("blob:http://") || url.startsWith("blob:https://")
|
|
2101
|
+
return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("blob:http://") || url.startsWith("blob:https://");
|
|
2075
2102
|
}
|
|
2076
2103
|
var blobMap = /* @__PURE__ */ new Map();
|
|
2077
2104
|
async function esm(code, sourceUrl) {
|
|
2078
|
-
code = rewriteModule(code, sourceUrl);
|
|
2105
|
+
code = await rewriteModule(code, sourceUrl);
|
|
2079
2106
|
const blob = new Blob([code], { type: "text/javascript" });
|
|
2080
2107
|
const blobUrl = URL.createObjectURL(blob);
|
|
2081
2108
|
blobMap.set(blobUrl, sourceUrl);
|
|
@@ -2114,14 +2141,11 @@ function warn(...args) {
|
|
|
2114
2141
|
}
|
|
2115
2142
|
|
|
2116
2143
|
// src/network.ts
|
|
2117
|
-
var fetch = globalThis.fetch;
|
|
2118
|
-
function defineFetch(customFetch) {
|
|
2119
|
-
fetch = customFetch;
|
|
2120
|
-
}
|
|
2121
2144
|
async function requestText(url, userFriendlySource) {
|
|
2122
2145
|
return request(url, userFriendlySource).then((res) => res.text());
|
|
2123
2146
|
}
|
|
2124
2147
|
async function request(url, userFriendlySource) {
|
|
2148
|
+
const { fetch } = config;
|
|
2125
2149
|
let response;
|
|
2126
2150
|
try {
|
|
2127
2151
|
response = await fetch(url);
|
|
@@ -2138,22 +2162,351 @@ async function request(url, userFriendlySource) {
|
|
|
2138
2162
|
return response;
|
|
2139
2163
|
}
|
|
2140
2164
|
|
|
2141
|
-
// src/
|
|
2142
|
-
var
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2165
|
+
// src/signals.ts
|
|
2166
|
+
var activeEffect = null;
|
|
2167
|
+
var jobQueue = [];
|
|
2168
|
+
var isFlushPending = false;
|
|
2169
|
+
function queueJob(job) {
|
|
2170
|
+
if (!jobQueue.includes(job)) {
|
|
2171
|
+
jobQueue.push(job);
|
|
2172
|
+
}
|
|
2173
|
+
if (!isFlushPending) {
|
|
2174
|
+
isFlushPending = true;
|
|
2175
|
+
Promise.resolve().then(flushJobs);
|
|
2176
|
+
}
|
|
2146
2177
|
}
|
|
2147
|
-
function
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2178
|
+
function flushJobs() {
|
|
2179
|
+
isFlushPending = false;
|
|
2180
|
+
const jobs = [...jobQueue];
|
|
2181
|
+
jobQueue.length = 0;
|
|
2182
|
+
jobs.forEach((job) => job());
|
|
2183
|
+
}
|
|
2184
|
+
function cleanup(effect2) {
|
|
2185
|
+
effect2.deps.forEach((dep) => {
|
|
2186
|
+
dep.delete(effect2);
|
|
2187
|
+
});
|
|
2188
|
+
effect2.deps.clear();
|
|
2189
|
+
}
|
|
2190
|
+
function createReactiveEffect(fn, deps = /* @__PURE__ */ new Set(), options) {
|
|
2191
|
+
const effect2 = fn;
|
|
2192
|
+
effect2.deps = deps;
|
|
2193
|
+
effect2.options = options;
|
|
2194
|
+
return effect2;
|
|
2195
|
+
}
|
|
2196
|
+
var EffectScope = class {
|
|
2197
|
+
effects = [];
|
|
2198
|
+
active = true;
|
|
2199
|
+
run(fn) {
|
|
2200
|
+
if (!this.active) return;
|
|
2201
|
+
const prevScope = activeScope;
|
|
2202
|
+
activeScope = this;
|
|
2203
|
+
try {
|
|
2204
|
+
return fn();
|
|
2205
|
+
} finally {
|
|
2206
|
+
activeScope = prevScope;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
add(stopFn) {
|
|
2210
|
+
if (this.active) {
|
|
2211
|
+
this.effects.push(stopFn);
|
|
2212
|
+
} else {
|
|
2213
|
+
stopFn();
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
stop() {
|
|
2217
|
+
if (this.active) {
|
|
2218
|
+
this.effects.forEach((stop) => stop());
|
|
2219
|
+
this.effects = [];
|
|
2220
|
+
this.active = false;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
var activeScope = null;
|
|
2225
|
+
function effectScope(fn) {
|
|
2226
|
+
const scope = new EffectScope();
|
|
2227
|
+
if (fn) scope.run(fn);
|
|
2228
|
+
return () => scope.stop();
|
|
2229
|
+
}
|
|
2230
|
+
function effect(fn) {
|
|
2231
|
+
const effect2 = createReactiveEffect(
|
|
2232
|
+
() => {
|
|
2233
|
+
cleanup(effect2);
|
|
2234
|
+
const prevEffect = activeEffect;
|
|
2235
|
+
activeEffect = effect2;
|
|
2236
|
+
try {
|
|
2237
|
+
fn();
|
|
2238
|
+
} finally {
|
|
2239
|
+
activeEffect = prevEffect;
|
|
2240
|
+
}
|
|
2241
|
+
},
|
|
2242
|
+
/* @__PURE__ */ new Set(),
|
|
2243
|
+
{ scheduler: queueJob }
|
|
2244
|
+
);
|
|
2245
|
+
effect2();
|
|
2246
|
+
const stop = () => {
|
|
2247
|
+
cleanup(effect2);
|
|
2151
2248
|
};
|
|
2249
|
+
if (activeScope) {
|
|
2250
|
+
activeScope.add(stop);
|
|
2251
|
+
}
|
|
2252
|
+
return stop;
|
|
2253
|
+
}
|
|
2254
|
+
function signal(initialValue) {
|
|
2255
|
+
let value = initialValue;
|
|
2256
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
2257
|
+
const read = () => {
|
|
2258
|
+
if (activeEffect) {
|
|
2259
|
+
subscribers.add(activeEffect);
|
|
2260
|
+
activeEffect.deps.add(subscribers);
|
|
2261
|
+
}
|
|
2262
|
+
return value;
|
|
2263
|
+
};
|
|
2264
|
+
read.toString = () => {
|
|
2265
|
+
throw new NativeSFCError(
|
|
2266
|
+
`signal<<${value}>>: This is a signal reader, you MUST call it to get the value.`
|
|
2267
|
+
);
|
|
2268
|
+
};
|
|
2269
|
+
const write = (newValue) => {
|
|
2270
|
+
if (value !== newValue) {
|
|
2271
|
+
value = newValue;
|
|
2272
|
+
const effectsToRun = new Set(subscribers);
|
|
2273
|
+
effectsToRun.forEach((effect2) => {
|
|
2274
|
+
if (effect2.options?.scheduler) {
|
|
2275
|
+
effect2.options.scheduler(effect2);
|
|
2276
|
+
} else {
|
|
2277
|
+
effect2();
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
write.toString = () => {
|
|
2283
|
+
throw new NativeSFCError(
|
|
2284
|
+
`signal<<${value}>>: This is a signal writer, you MUST call it to set the value.`
|
|
2285
|
+
);
|
|
2286
|
+
};
|
|
2287
|
+
return [read, write];
|
|
2288
|
+
}
|
|
2289
|
+
function computed(fn) {
|
|
2290
|
+
let value;
|
|
2291
|
+
let dirty = true;
|
|
2292
|
+
let isComputing = false;
|
|
2293
|
+
const runner = () => {
|
|
2294
|
+
if (!dirty) {
|
|
2295
|
+
dirty = true;
|
|
2296
|
+
trigger(subscribers);
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
const internalEffect = createReactiveEffect(runner);
|
|
2300
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
2301
|
+
const trigger = (subs) => {
|
|
2302
|
+
const effectsToRun = new Set(subs);
|
|
2303
|
+
effectsToRun.forEach((effect2) => {
|
|
2304
|
+
if (effect2.options?.scheduler) {
|
|
2305
|
+
effect2.options.scheduler(effect2);
|
|
2306
|
+
} else {
|
|
2307
|
+
effect2();
|
|
2308
|
+
}
|
|
2309
|
+
});
|
|
2310
|
+
};
|
|
2311
|
+
const read = () => {
|
|
2312
|
+
if (isComputing) {
|
|
2313
|
+
throw new NativeSFCError(`Circular dependency detected in computed<<${value}>>`);
|
|
2314
|
+
}
|
|
2315
|
+
if (activeEffect) {
|
|
2316
|
+
subscribers.add(activeEffect);
|
|
2317
|
+
activeEffect.deps.add(subscribers);
|
|
2318
|
+
}
|
|
2319
|
+
if (dirty) {
|
|
2320
|
+
isComputing = true;
|
|
2321
|
+
const prevEffect = activeEffect;
|
|
2322
|
+
activeEffect = internalEffect;
|
|
2323
|
+
cleanup(internalEffect);
|
|
2324
|
+
try {
|
|
2325
|
+
value = fn();
|
|
2326
|
+
dirty = false;
|
|
2327
|
+
} finally {
|
|
2328
|
+
activeEffect = prevEffect;
|
|
2329
|
+
isComputing = false;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
return value;
|
|
2333
|
+
};
|
|
2334
|
+
read.toString = () => {
|
|
2335
|
+
throw new NativeSFCError(
|
|
2336
|
+
`computed<<${value}>>: This is a computed reader, you MUST call it to get the value.`
|
|
2337
|
+
);
|
|
2338
|
+
};
|
|
2339
|
+
return read;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// src/template.ts
|
|
2343
|
+
function toCamelCase(str) {
|
|
2344
|
+
return str.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
2345
|
+
}
|
|
2346
|
+
function parseTextContent(text) {
|
|
2347
|
+
const regex = /\{\{(.+?)\}\}/g;
|
|
2348
|
+
const parts = [];
|
|
2349
|
+
let lastIndex = 0;
|
|
2350
|
+
let match;
|
|
2351
|
+
while ((match = regex.exec(text)) !== null) {
|
|
2352
|
+
if (match.index > lastIndex) {
|
|
2353
|
+
parts.push({
|
|
2354
|
+
type: "static",
|
|
2355
|
+
content: text.slice(lastIndex, match.index)
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
parts.push({
|
|
2359
|
+
type: "dynamic",
|
|
2360
|
+
content: match[1].trim()
|
|
2361
|
+
});
|
|
2362
|
+
lastIndex = regex.lastIndex;
|
|
2363
|
+
}
|
|
2364
|
+
if (lastIndex < text.length) {
|
|
2365
|
+
parts.push({
|
|
2366
|
+
type: "static",
|
|
2367
|
+
content: text.slice(lastIndex)
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
return parts;
|
|
2371
|
+
}
|
|
2372
|
+
function reactiveNodes(nodes, context) {
|
|
2373
|
+
const evalExpr = (expr, additionalContext = {}) => {
|
|
2374
|
+
const ctx = typeof context === "object" ? Object.assign({}, context, additionalContext) : additionalContext;
|
|
2375
|
+
const keys = Object.keys(ctx);
|
|
2376
|
+
const values = Object.values(ctx);
|
|
2377
|
+
const func = new Function(...keys, `return ${expr.trimStart()}`);
|
|
2378
|
+
return func(...values);
|
|
2379
|
+
};
|
|
2380
|
+
const recursive = (nodes2) => {
|
|
2381
|
+
for (const node of nodes2) {
|
|
2382
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
2383
|
+
const element = node;
|
|
2384
|
+
const ifAttr = element.getAttribute("#if");
|
|
2385
|
+
if (ifAttr) {
|
|
2386
|
+
if (element.hasAttribute("#for")) {
|
|
2387
|
+
console.warn("Cannot use #if and #for on the same element");
|
|
2388
|
+
}
|
|
2389
|
+
const template = element.cloneNode(true);
|
|
2390
|
+
const parent = element.parentNode;
|
|
2391
|
+
const placeholder = document.createComment("if");
|
|
2392
|
+
parent?.replaceChild(placeholder, element);
|
|
2393
|
+
template.removeAttribute("#if");
|
|
2394
|
+
let renderedNode = null;
|
|
2395
|
+
let cleanup2 = null;
|
|
2396
|
+
effect(() => {
|
|
2397
|
+
const condition = evalExpr(ifAttr);
|
|
2398
|
+
if (condition) {
|
|
2399
|
+
if (!renderedNode) {
|
|
2400
|
+
const clone = template.cloneNode(true);
|
|
2401
|
+
cleanup2 = reactiveNodes([clone], context);
|
|
2402
|
+
placeholder.parentNode?.insertBefore(clone, placeholder.nextSibling);
|
|
2403
|
+
renderedNode = clone;
|
|
2404
|
+
}
|
|
2405
|
+
} else {
|
|
2406
|
+
if (renderedNode) {
|
|
2407
|
+
cleanup2?.();
|
|
2408
|
+
renderedNode.remove();
|
|
2409
|
+
renderedNode = null;
|
|
2410
|
+
cleanup2 = null;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2416
|
+
const forAttr = element.getAttribute("#for");
|
|
2417
|
+
if (forAttr) {
|
|
2418
|
+
const template = element.cloneNode(true);
|
|
2419
|
+
const parent = element.parentNode;
|
|
2420
|
+
const placeholder = document.createComment("for");
|
|
2421
|
+
parent?.replaceChild(placeholder, element);
|
|
2422
|
+
template.removeAttribute("#for");
|
|
2423
|
+
let renderedItems = [];
|
|
2424
|
+
effect(() => {
|
|
2425
|
+
const contexts = evalExpr(forAttr);
|
|
2426
|
+
if (!Array.isArray(contexts)) {
|
|
2427
|
+
console.warn("#for expression must return an array");
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
renderedItems.forEach(({ node: node2, cleanup: cleanup2 }) => {
|
|
2431
|
+
cleanup2();
|
|
2432
|
+
node2.remove();
|
|
2433
|
+
});
|
|
2434
|
+
renderedItems = [];
|
|
2435
|
+
contexts.forEach((itemContext) => {
|
|
2436
|
+
const clone = template.cloneNode(true);
|
|
2437
|
+
const cleanup2 = reactiveNodes([clone], { ...context, ...itemContext });
|
|
2438
|
+
placeholder.parentNode?.insertBefore(clone, placeholder.nextSibling);
|
|
2439
|
+
renderedItems.push({ node: clone, cleanup: cleanup2 });
|
|
2440
|
+
});
|
|
2441
|
+
});
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
for (const attr of Array.from(element.attributes)) {
|
|
2445
|
+
if (attr.name.startsWith(".")) {
|
|
2446
|
+
const propName = toCamelCase(attr.name.slice(1));
|
|
2447
|
+
const expr = attr.value;
|
|
2448
|
+
effect(() => {
|
|
2449
|
+
const value = evalExpr(expr);
|
|
2450
|
+
Reflect.set(element, propName, value);
|
|
2451
|
+
});
|
|
2452
|
+
element.removeAttribute(attr.name);
|
|
2453
|
+
} else if (attr.name.startsWith(":")) {
|
|
2454
|
+
const attrName = attr.name.slice(1);
|
|
2455
|
+
const expr = attr.value;
|
|
2456
|
+
effect(() => {
|
|
2457
|
+
const value = evalExpr(expr);
|
|
2458
|
+
element.setAttribute(attrName, value);
|
|
2459
|
+
});
|
|
2460
|
+
element.removeAttribute(attr.name);
|
|
2461
|
+
} else if (attr.name.startsWith("@")) {
|
|
2462
|
+
const eventName = attr.name.slice(1);
|
|
2463
|
+
const expr = attr.value;
|
|
2464
|
+
const listener = computed(() => (event) => {
|
|
2465
|
+
evalExpr(expr, { event });
|
|
2466
|
+
})();
|
|
2467
|
+
element.addEventListener(eventName, listener);
|
|
2468
|
+
element.removeAttribute(attr.name);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
2472
|
+
const textNode = node;
|
|
2473
|
+
const text = textNode.textContent || "";
|
|
2474
|
+
const parts = parseTextContent(text);
|
|
2475
|
+
if (parts.some((part) => part.type === "dynamic")) {
|
|
2476
|
+
const parentNode = textNode.parentNode;
|
|
2477
|
+
if (parentNode) {
|
|
2478
|
+
const fragment = document.createDocumentFragment();
|
|
2479
|
+
const textNodes = [];
|
|
2480
|
+
for (const part of parts) {
|
|
2481
|
+
const newTextNode = document.createTextNode(
|
|
2482
|
+
part.type === "static" ? part.content : ""
|
|
2483
|
+
);
|
|
2484
|
+
fragment.appendChild(newTextNode);
|
|
2485
|
+
textNodes.push(newTextNode);
|
|
2486
|
+
if (part.type === "dynamic") {
|
|
2487
|
+
effect(() => {
|
|
2488
|
+
const value = evalExpr(part.content);
|
|
2489
|
+
newTextNode.textContent = String(value);
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
parentNode.replaceChild(fragment, textNode);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
if (node.childNodes.length > 0) {
|
|
2498
|
+
recursive(node.childNodes);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
return effectScope(() => {
|
|
2503
|
+
recursive(nodes);
|
|
2504
|
+
});
|
|
2152
2505
|
}
|
|
2153
2506
|
|
|
2154
2507
|
// src/components.ts
|
|
2155
2508
|
var loadedComponentsRecord = /* @__PURE__ */ new Map();
|
|
2156
|
-
async function loadComponent(name, url
|
|
2509
|
+
async function loadComponent(name, url) {
|
|
2157
2510
|
const importerUrl = getImporterUrl() || location.href;
|
|
2158
2511
|
url = new URL(url, importerUrl).href;
|
|
2159
2512
|
emit("component-loading", { name, url });
|
|
@@ -2187,11 +2540,11 @@ async function loadComponent(name, url, afterConstructor) {
|
|
|
2187
2540
|
);
|
|
2188
2541
|
}
|
|
2189
2542
|
const define = (component2) => {
|
|
2190
|
-
const
|
|
2191
|
-
customElements.define(name,
|
|
2543
|
+
const CEC = extendsElement(component2, doc.body.innerHTML, adoptedStyleSheets);
|
|
2544
|
+
customElements.define(name, CEC);
|
|
2192
2545
|
emit("component-defined", { name, url });
|
|
2193
|
-
loadedComponentsRecord.set(name, { cec, url });
|
|
2194
|
-
return
|
|
2546
|
+
loadedComponentsRecord.set(name, { cec: CEC, url });
|
|
2547
|
+
return CEC;
|
|
2195
2548
|
};
|
|
2196
2549
|
if (!component || !defaultExportIsComponent) {
|
|
2197
2550
|
return define(HTMLElement);
|
|
@@ -2199,7 +2552,7 @@ async function loadComponent(name, url, afterConstructor) {
|
|
|
2199
2552
|
return define(component);
|
|
2200
2553
|
}
|
|
2201
2554
|
}
|
|
2202
|
-
function extendsElement(BaseClass = HTMLElement, innerHTML, adoptedStyleSheets
|
|
2555
|
+
function extendsElement(BaseClass = HTMLElement, innerHTML, adoptedStyleSheets) {
|
|
2203
2556
|
return class extends BaseClass {
|
|
2204
2557
|
constructor(...args) {
|
|
2205
2558
|
super(innerHTML, adoptedStyleSheets);
|
|
@@ -2210,22 +2563,42 @@ function extendsElement(BaseClass = HTMLElement, innerHTML, adoptedStyleSheets,
|
|
|
2210
2563
|
shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
|
|
2211
2564
|
}
|
|
2212
2565
|
}
|
|
2213
|
-
if (
|
|
2214
|
-
|
|
2566
|
+
if ("setup" in this && typeof this.setup === "function") {
|
|
2567
|
+
const context = this.setup();
|
|
2568
|
+
reactiveNodes(this.shadowRoot.childNodes, context);
|
|
2569
|
+
} else {
|
|
2570
|
+
reactiveNodes(this.shadowRoot.childNodes, {});
|
|
2215
2571
|
}
|
|
2216
2572
|
}
|
|
2217
2573
|
};
|
|
2218
2574
|
}
|
|
2219
|
-
function defineComponent(
|
|
2575
|
+
function defineComponent(setup) {
|
|
2220
2576
|
const whoDefineMe = parse(new Error().stack).at(-1).file;
|
|
2221
2577
|
if (blobMap.has(whoDefineMe)) {
|
|
2222
2578
|
return class extends HTMLElement {
|
|
2579
|
+
_onConnectedEvents = [];
|
|
2580
|
+
_onDisconnectedEvents = [];
|
|
2581
|
+
setup() {
|
|
2582
|
+
return setup({
|
|
2583
|
+
onConnected: (event) => this._onConnectedEvents.push(event),
|
|
2584
|
+
onDisconnected: (event) => this._onDisconnectedEvents.push(event)
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2223
2587
|
connectedCallback() {
|
|
2224
|
-
|
|
2588
|
+
const root = this.shadowRoot || this.attachShadow({ mode: "open" });
|
|
2589
|
+
this._onConnectedEvents.forEach((cb) => cb.call(void 0, root));
|
|
2590
|
+
}
|
|
2591
|
+
disconnectedCallback() {
|
|
2592
|
+
const root = this.shadowRoot;
|
|
2593
|
+
this._onDisconnectedEvents.forEach((cb) => cb.call(void 0, root));
|
|
2225
2594
|
}
|
|
2226
2595
|
};
|
|
2227
2596
|
}
|
|
2228
|
-
return
|
|
2597
|
+
return setup({
|
|
2598
|
+
onConnected: (cb) => cb(document),
|
|
2599
|
+
onDisconnected: () => {
|
|
2600
|
+
}
|
|
2601
|
+
});
|
|
2229
2602
|
}
|
|
2230
2603
|
function filterGlobalStyle(doc) {
|
|
2231
2604
|
for (const styleElement of doc.querySelectorAll("style")) {
|
|
@@ -2308,10 +2681,13 @@ async function evaluateModules(doc, url) {
|
|
|
2308
2681
|
}
|
|
2309
2682
|
export {
|
|
2310
2683
|
NativeSFCError,
|
|
2684
|
+
computed,
|
|
2685
|
+
config,
|
|
2311
2686
|
defineComponent,
|
|
2312
|
-
|
|
2687
|
+
effect,
|
|
2688
|
+
effectScope,
|
|
2313
2689
|
loadComponent,
|
|
2314
|
-
|
|
2690
|
+
signal
|
|
2315
2691
|
};
|
|
2316
2692
|
//! we provide an extra argument to user's component constructor
|
|
2317
2693
|
//! if the user's constructor does not create a shadow root, we will create one here
|