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 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, afterConstructor?)`
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(fc)`
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
 
@@ -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) && !specifier.startsWith("data:");
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
- return `import.meta.url=${JSON.stringify(sourceUrl)};
2071
- ${code}`;
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://") || url.startsWith("data:");
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/events.ts
2142
- var eventTarget = new EventTarget();
2143
- function emit(eventName, detail) {
2144
- const event = new CustomEvent(eventName, { detail });
2145
- eventTarget.dispatchEvent(event);
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 on(eventName, listener) {
2148
- eventTarget.addEventListener(eventName, listener);
2149
- return () => {
2150
- eventTarget.removeEventListener(eventName, listener);
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, afterConstructor) {
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 cec = extendsElement(component2, doc.body.innerHTML, adoptedStyleSheets, afterConstructor);
2191
- customElements.define(name, cec);
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 cec;
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, afterConstructor) {
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 (afterConstructor) {
2214
- afterConstructor.call(this);
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(fc) {
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
- fc.call(this, this.shadowRoot || this.attachShadow({ mode: "open" }));
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
- return fc.call(globalThis, document);
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
- defineFetch,
2705
+ effect,
2706
+ effectScope,
2313
2707
  loadComponent,
2314
- on
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`)