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