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/dist/index.js CHANGED
@@ -1,36 +1,65 @@
1
1
  // entrypoint for bundler
2
2
 
3
3
  // src/components.ts
4
- import * as stackTraceParser2 from "stacktrace-parser";
4
+ import * as stackTraceParser from "stacktrace-parser";
5
5
 
6
6
  // src/rewriter.ts
7
- import { parse } from "es-module-lexer/js";
8
- import * as stackTraceParser from "stacktrace-parser";
9
- function rewriteModule(code, sourceUrl) {
10
- const [imports] = parse(code);
7
+ import { parse as parseESM } from "es-module-lexer/js";
8
+ import { parse as parseStackTrace } from "stacktrace-parser";
9
+
10
+ // src/events.ts
11
+ var eventTarget = new EventTarget();
12
+ function emit(eventName, detail) {
13
+ const event = new CustomEvent(eventName, { detail });
14
+ eventTarget.dispatchEvent(event);
15
+ }
16
+ function on(eventName, listener) {
17
+ eventTarget.addEventListener(eventName, listener);
18
+ return () => {
19
+ eventTarget.removeEventListener(eventName, listener);
20
+ };
21
+ }
22
+
23
+ // src/config.ts
24
+ var config = {
25
+ fetch: globalThis.fetch,
26
+ rewriteModule: (code, sourceUrl) => `import.meta.url=${JSON.stringify(sourceUrl)};
27
+ ${code}`,
28
+ on
29
+ };
30
+ Object.preventExtensions(config);
31
+
32
+ // src/rewriter.ts
33
+ async function rewriteModule(code, sourceUrl) {
34
+ const [imports] = parseESM(code);
11
35
  const rewritableImports = imports.filter((i) => {
12
36
  const specifier = code.slice(i.s, i.e);
13
- return !isBrowserUrl(specifier) && !specifier.startsWith("data:");
37
+ return !isBrowserUrl(specifier);
14
38
  });
15
39
  for (const importEntry of rewritableImports.reverse()) {
16
40
  const specifier = code.slice(importEntry.s, importEntry.e);
17
41
  let rewritten = specifier;
18
42
  if (specifier.startsWith(".") || specifier.startsWith("/")) {
19
43
  rewritten = new URL(specifier, sourceUrl).href;
44
+ } else if (specifier.startsWith("node:")) {
45
+ const module = specifier.slice(5);
46
+ rewritten = `https://raw.esm.sh/@jspm/core/nodelibs/browser/${module}.js`;
47
+ } else if (specifier.startsWith("npm:")) {
48
+ rewritten = `https://esm.sh/${specifier.slice(4)}`;
20
49
  } else {
21
50
  rewritten = `https://esm.sh/${specifier}`;
22
51
  }
23
52
  code = code.slice(0, importEntry.s) + rewritten + code.slice(importEntry.e);
24
53
  }
25
- return `import.meta.url=${JSON.stringify(sourceUrl)};
26
- ${code}`;
54
+ const { rewriteModule: rewriteModule2 } = config;
55
+ return await rewriteModule2(code, sourceUrl);
27
56
  }
28
57
  function isBrowserUrl(url) {
29
- return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("blob:http://") || url.startsWith("blob:https://") || url.startsWith("data:");
58
+ return url.startsWith("http://") || url.startsWith("https://") || url.startsWith("blob:http://") || url.startsWith("blob:https://");
30
59
  }
31
60
  var blobMap = /* @__PURE__ */ new Map();
32
61
  async function esm(code, sourceUrl) {
33
- code = rewriteModule(code, sourceUrl);
62
+ code = await rewriteModule(code, sourceUrl);
34
63
  const blob = new Blob([code], { type: "text/javascript" });
35
64
  const blobUrl = URL.createObjectURL(blob);
36
65
  blobMap.set(blobUrl, sourceUrl);
@@ -42,7 +71,7 @@ async function esm(code, sourceUrl) {
42
71
  }
43
72
  }
44
73
  function getImporterUrl() {
45
- const stack = stackTraceParser.parse(new Error().stack);
74
+ const stack = parseStackTrace(new Error().stack);
46
75
  for (const { file } of stack) {
47
76
  if (file && file !== import.meta.url) {
48
77
  if (file.startsWith("blob:")) {
@@ -69,14 +98,11 @@ function warn(...args) {
69
98
  }
70
99
 
71
100
  // src/network.ts
72
- var fetch = globalThis.fetch;
73
- function defineFetch(customFetch) {
74
- fetch = customFetch;
75
- }
76
101
  async function requestText(url, userFriendlySource) {
77
102
  return request(url, userFriendlySource).then((res) => res.text());
78
103
  }
79
104
  async function request(url, userFriendlySource) {
105
+ const { fetch } = config;
80
106
  let response;
81
107
  try {
82
108
  response = await fetch(url);
@@ -93,22 +119,351 @@ async function request(url, userFriendlySource) {
93
119
  return response;
94
120
  }
95
121
 
96
- // src/events.ts
97
- var eventTarget = new EventTarget();
98
- function emit(eventName, detail) {
99
- const event = new CustomEvent(eventName, { detail });
100
- eventTarget.dispatchEvent(event);
122
+ // src/signals.ts
123
+ var activeEffect = null;
124
+ var jobQueue = [];
125
+ var isFlushPending = false;
126
+ function queueJob(job) {
127
+ if (!jobQueue.includes(job)) {
128
+ jobQueue.push(job);
129
+ }
130
+ if (!isFlushPending) {
131
+ isFlushPending = true;
132
+ Promise.resolve().then(flushJobs);
133
+ }
101
134
  }
102
- function on(eventName, listener) {
103
- eventTarget.addEventListener(eventName, listener);
104
- return () => {
105
- eventTarget.removeEventListener(eventName, listener);
135
+ function flushJobs() {
136
+ isFlushPending = false;
137
+ const jobs = [...jobQueue];
138
+ jobQueue.length = 0;
139
+ jobs.forEach((job) => job());
140
+ }
141
+ function cleanup(effect2) {
142
+ effect2.deps.forEach((dep) => {
143
+ dep.delete(effect2);
144
+ });
145
+ effect2.deps.clear();
146
+ }
147
+ function createReactiveEffect(fn, deps = /* @__PURE__ */ new Set(), options) {
148
+ const effect2 = fn;
149
+ effect2.deps = deps;
150
+ effect2.options = options;
151
+ return effect2;
152
+ }
153
+ var EffectScope = class {
154
+ effects = [];
155
+ active = true;
156
+ run(fn) {
157
+ if (!this.active) return;
158
+ const prevScope = activeScope;
159
+ activeScope = this;
160
+ try {
161
+ return fn();
162
+ } finally {
163
+ activeScope = prevScope;
164
+ }
165
+ }
166
+ add(stopFn) {
167
+ if (this.active) {
168
+ this.effects.push(stopFn);
169
+ } else {
170
+ stopFn();
171
+ }
172
+ }
173
+ stop() {
174
+ if (this.active) {
175
+ this.effects.forEach((stop) => stop());
176
+ this.effects = [];
177
+ this.active = false;
178
+ }
179
+ }
180
+ };
181
+ var activeScope = null;
182
+ function effectScope(fn) {
183
+ const scope = new EffectScope();
184
+ if (fn) scope.run(fn);
185
+ return () => scope.stop();
186
+ }
187
+ function effect(fn) {
188
+ const effect2 = createReactiveEffect(
189
+ () => {
190
+ cleanup(effect2);
191
+ const prevEffect = activeEffect;
192
+ activeEffect = effect2;
193
+ try {
194
+ fn();
195
+ } finally {
196
+ activeEffect = prevEffect;
197
+ }
198
+ },
199
+ /* @__PURE__ */ new Set(),
200
+ { scheduler: queueJob }
201
+ );
202
+ effect2();
203
+ const stop = () => {
204
+ cleanup(effect2);
106
205
  };
206
+ if (activeScope) {
207
+ activeScope.add(stop);
208
+ }
209
+ return stop;
210
+ }
211
+ function signal(initialValue) {
212
+ let value = initialValue;
213
+ const subscribers = /* @__PURE__ */ new Set();
214
+ const read = () => {
215
+ if (activeEffect) {
216
+ subscribers.add(activeEffect);
217
+ activeEffect.deps.add(subscribers);
218
+ }
219
+ return value;
220
+ };
221
+ read.toString = () => {
222
+ throw new NativeSFCError(
223
+ `signal<<${value}>>: This is a signal reader, you MUST call it to get the value.`
224
+ );
225
+ };
226
+ const write = (newValue) => {
227
+ if (value !== newValue) {
228
+ value = newValue;
229
+ const effectsToRun = new Set(subscribers);
230
+ effectsToRun.forEach((effect2) => {
231
+ if (effect2.options?.scheduler) {
232
+ effect2.options.scheduler(effect2);
233
+ } else {
234
+ effect2();
235
+ }
236
+ });
237
+ }
238
+ };
239
+ write.toString = () => {
240
+ throw new NativeSFCError(
241
+ `signal<<${value}>>: This is a signal writer, you MUST call it to set the value.`
242
+ );
243
+ };
244
+ return [read, write];
245
+ }
246
+ function computed(fn) {
247
+ let value;
248
+ let dirty = true;
249
+ let isComputing = false;
250
+ const runner = () => {
251
+ if (!dirty) {
252
+ dirty = true;
253
+ trigger(subscribers);
254
+ }
255
+ };
256
+ const internalEffect = createReactiveEffect(runner);
257
+ const subscribers = /* @__PURE__ */ new Set();
258
+ const trigger = (subs) => {
259
+ const effectsToRun = new Set(subs);
260
+ effectsToRun.forEach((effect2) => {
261
+ if (effect2.options?.scheduler) {
262
+ effect2.options.scheduler(effect2);
263
+ } else {
264
+ effect2();
265
+ }
266
+ });
267
+ };
268
+ const read = () => {
269
+ if (isComputing) {
270
+ throw new NativeSFCError(`Circular dependency detected in computed<<${value}>>`);
271
+ }
272
+ if (activeEffect) {
273
+ subscribers.add(activeEffect);
274
+ activeEffect.deps.add(subscribers);
275
+ }
276
+ if (dirty) {
277
+ isComputing = true;
278
+ const prevEffect = activeEffect;
279
+ activeEffect = internalEffect;
280
+ cleanup(internalEffect);
281
+ try {
282
+ value = fn();
283
+ dirty = false;
284
+ } finally {
285
+ activeEffect = prevEffect;
286
+ isComputing = false;
287
+ }
288
+ }
289
+ return value;
290
+ };
291
+ read.toString = () => {
292
+ throw new NativeSFCError(
293
+ `computed<<${value}>>: This is a computed reader, you MUST call it to get the value.`
294
+ );
295
+ };
296
+ return read;
297
+ }
298
+
299
+ // src/template.ts
300
+ function toCamelCase(str) {
301
+ return str.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
302
+ }
303
+ function parseTextContent(text) {
304
+ const regex = /\{\{(.+?)\}\}/g;
305
+ const parts = [];
306
+ let lastIndex = 0;
307
+ let match;
308
+ while ((match = regex.exec(text)) !== null) {
309
+ if (match.index > lastIndex) {
310
+ parts.push({
311
+ type: "static",
312
+ content: text.slice(lastIndex, match.index)
313
+ });
314
+ }
315
+ parts.push({
316
+ type: "dynamic",
317
+ content: match[1].trim()
318
+ });
319
+ lastIndex = regex.lastIndex;
320
+ }
321
+ if (lastIndex < text.length) {
322
+ parts.push({
323
+ type: "static",
324
+ content: text.slice(lastIndex)
325
+ });
326
+ }
327
+ return parts;
328
+ }
329
+ function reactiveNodes(nodes, context) {
330
+ const evalExpr = (expr, additionalContext = {}) => {
331
+ const ctx = typeof context === "object" ? Object.assign({}, context, additionalContext) : additionalContext;
332
+ const keys = Object.keys(ctx);
333
+ const values = Object.values(ctx);
334
+ const func = new Function(...keys, `return ${expr.trimStart()}`);
335
+ return func(...values);
336
+ };
337
+ const recursive = (nodes2) => {
338
+ for (const node of nodes2) {
339
+ if (node.nodeType === Node.ELEMENT_NODE) {
340
+ const element = node;
341
+ const ifAttr = element.getAttribute("#if");
342
+ if (ifAttr) {
343
+ if (element.hasAttribute("#for")) {
344
+ console.warn("Cannot use #if and #for on the same element");
345
+ }
346
+ const template = element.cloneNode(true);
347
+ const parent = element.parentNode;
348
+ const placeholder = document.createComment("if");
349
+ parent?.replaceChild(placeholder, element);
350
+ template.removeAttribute("#if");
351
+ let renderedNode = null;
352
+ let cleanup2 = null;
353
+ effect(() => {
354
+ const condition = evalExpr(ifAttr);
355
+ if (condition) {
356
+ if (!renderedNode) {
357
+ const clone = template.cloneNode(true);
358
+ cleanup2 = reactiveNodes([clone], context);
359
+ placeholder.parentNode?.insertBefore(clone, placeholder.nextSibling);
360
+ renderedNode = clone;
361
+ }
362
+ } else {
363
+ if (renderedNode) {
364
+ cleanup2?.();
365
+ renderedNode.remove();
366
+ renderedNode = null;
367
+ cleanup2 = null;
368
+ }
369
+ }
370
+ });
371
+ continue;
372
+ }
373
+ const forAttr = element.getAttribute("#for");
374
+ if (forAttr) {
375
+ const template = element.cloneNode(true);
376
+ const parent = element.parentNode;
377
+ const placeholder = document.createComment("for");
378
+ parent?.replaceChild(placeholder, element);
379
+ template.removeAttribute("#for");
380
+ let renderedItems = [];
381
+ effect(() => {
382
+ const contexts = evalExpr(forAttr);
383
+ if (!Array.isArray(contexts)) {
384
+ console.warn("#for expression must return an array");
385
+ return;
386
+ }
387
+ renderedItems.forEach(({ node: node2, cleanup: cleanup2 }) => {
388
+ cleanup2();
389
+ node2.remove();
390
+ });
391
+ renderedItems = [];
392
+ contexts.forEach((itemContext) => {
393
+ const clone = template.cloneNode(true);
394
+ const cleanup2 = reactiveNodes([clone], { ...context, ...itemContext });
395
+ placeholder.parentNode?.insertBefore(clone, placeholder.nextSibling);
396
+ renderedItems.push({ node: clone, cleanup: cleanup2 });
397
+ });
398
+ });
399
+ continue;
400
+ }
401
+ for (const attr of Array.from(element.attributes)) {
402
+ if (attr.name.startsWith(".")) {
403
+ const propName = toCamelCase(attr.name.slice(1));
404
+ const expr = attr.value;
405
+ effect(() => {
406
+ const value = evalExpr(expr);
407
+ Reflect.set(element, propName, value);
408
+ });
409
+ element.removeAttribute(attr.name);
410
+ } else if (attr.name.startsWith(":")) {
411
+ const attrName = attr.name.slice(1);
412
+ const expr = attr.value;
413
+ effect(() => {
414
+ const value = evalExpr(expr);
415
+ element.setAttribute(attrName, value);
416
+ });
417
+ element.removeAttribute(attr.name);
418
+ } else if (attr.name.startsWith("@")) {
419
+ const eventName = attr.name.slice(1);
420
+ const expr = attr.value;
421
+ const listener = computed(() => (event) => {
422
+ evalExpr(expr, { event });
423
+ })();
424
+ element.addEventListener(eventName, listener);
425
+ element.removeAttribute(attr.name);
426
+ }
427
+ }
428
+ } else if (node.nodeType === Node.TEXT_NODE) {
429
+ const textNode = node;
430
+ const text = textNode.textContent || "";
431
+ const parts = parseTextContent(text);
432
+ if (parts.some((part) => part.type === "dynamic")) {
433
+ const parentNode = textNode.parentNode;
434
+ if (parentNode) {
435
+ const fragment = document.createDocumentFragment();
436
+ const textNodes = [];
437
+ for (const part of parts) {
438
+ const newTextNode = document.createTextNode(
439
+ part.type === "static" ? part.content : ""
440
+ );
441
+ fragment.appendChild(newTextNode);
442
+ textNodes.push(newTextNode);
443
+ if (part.type === "dynamic") {
444
+ effect(() => {
445
+ const value = evalExpr(part.content);
446
+ newTextNode.textContent = String(value);
447
+ });
448
+ }
449
+ }
450
+ parentNode.replaceChild(fragment, textNode);
451
+ }
452
+ }
453
+ }
454
+ if (node.childNodes.length > 0) {
455
+ recursive(node.childNodes);
456
+ }
457
+ }
458
+ };
459
+ return effectScope(() => {
460
+ recursive(nodes);
461
+ });
107
462
  }
108
463
 
109
464
  // src/components.ts
110
465
  var loadedComponentsRecord = /* @__PURE__ */ new Map();
111
- async function loadComponent(name, url, afterConstructor) {
466
+ async function loadComponent(name, url) {
112
467
  const importerUrl = getImporterUrl() || location.href;
113
468
  url = new URL(url, importerUrl).href;
114
469
  emit("component-loading", { name, url });
@@ -142,11 +497,11 @@ async function loadComponent(name, url, afterConstructor) {
142
497
  );
143
498
  }
144
499
  const define = (component2) => {
145
- const cec = extendsElement(component2, doc.body.innerHTML, adoptedStyleSheets, afterConstructor);
146
- customElements.define(name, cec);
500
+ const CEC = extendsElement(component2, doc.body.innerHTML, adoptedStyleSheets);
501
+ customElements.define(name, CEC);
147
502
  emit("component-defined", { name, url });
148
- loadedComponentsRecord.set(name, { cec, url });
149
- return cec;
503
+ loadedComponentsRecord.set(name, { cec: CEC, url });
504
+ return CEC;
150
505
  };
151
506
  if (!component || !defaultExportIsComponent) {
152
507
  return define(HTMLElement);
@@ -154,7 +509,7 @@ async function loadComponent(name, url, afterConstructor) {
154
509
  return define(component);
155
510
  }
156
511
  }
157
- function extendsElement(BaseClass = HTMLElement, innerHTML, adoptedStyleSheets, afterConstructor) {
512
+ function extendsElement(BaseClass = HTMLElement, innerHTML, adoptedStyleSheets) {
158
513
  return class extends BaseClass {
159
514
  constructor(...args) {
160
515
  super(innerHTML, adoptedStyleSheets);
@@ -165,22 +520,42 @@ function extendsElement(BaseClass = HTMLElement, innerHTML, adoptedStyleSheets,
165
520
  shadowRoot.adoptedStyleSheets = adoptedStyleSheets;
166
521
  }
167
522
  }
168
- if (afterConstructor) {
169
- afterConstructor.call(this);
523
+ if ("setup" in this && typeof this.setup === "function") {
524
+ const context = this.setup();
525
+ reactiveNodes(this.shadowRoot.childNodes, context);
526
+ } else {
527
+ reactiveNodes(this.shadowRoot.childNodes, {});
170
528
  }
171
529
  }
172
530
  };
173
531
  }
174
- function defineComponent(fc) {
175
- const whoDefineMe = stackTraceParser2.parse(new Error().stack).at(-1).file;
532
+ function defineComponent(setup) {
533
+ const whoDefineMe = stackTraceParser.parse(new Error().stack).at(-1).file;
176
534
  if (blobMap.has(whoDefineMe)) {
177
535
  return class extends HTMLElement {
536
+ _onConnectedEvents = [];
537
+ _onDisconnectedEvents = [];
538
+ setup() {
539
+ return setup({
540
+ onConnected: (event) => this._onConnectedEvents.push(event),
541
+ onDisconnected: (event) => this._onDisconnectedEvents.push(event)
542
+ });
543
+ }
178
544
  connectedCallback() {
179
- fc.call(this, this.shadowRoot || this.attachShadow({ mode: "open" }));
545
+ const root = this.shadowRoot || this.attachShadow({ mode: "open" });
546
+ this._onConnectedEvents.forEach((cb) => cb.call(void 0, root));
547
+ }
548
+ disconnectedCallback() {
549
+ const root = this.shadowRoot;
550
+ this._onDisconnectedEvents.forEach((cb) => cb.call(void 0, root));
180
551
  }
181
552
  };
182
553
  }
183
- return fc.call(globalThis, document);
554
+ return setup({
555
+ onConnected: (cb) => cb(document),
556
+ onDisconnected: () => {
557
+ }
558
+ });
184
559
  }
185
560
  function filterGlobalStyle(doc) {
186
561
  for (const styleElement of doc.querySelectorAll("style")) {
@@ -263,10 +638,13 @@ async function evaluateModules(doc, url) {
263
638
  }
264
639
  export {
265
640
  NativeSFCError,
641
+ computed,
642
+ config,
266
643
  defineComponent,
267
- defineFetch,
644
+ effect,
645
+ effectScope,
268
646
  loadComponent,
269
- on
647
+ signal
270
648
  };
271
649
  //! we provide an extra argument to user's component constructor
272
650
  //! if the user's constructor does not create a shadow root, we will create one here
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-sfc",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -16,9 +16,9 @@
16
16
  "stacktrace-parser": "^0.1.11"
17
17
  },
18
18
  "devDependencies": {
19
- "@types/node": "^25.0.6",
19
+ "@types/node": "^25.2.3",
20
20
  "dts-bundle-generator": "^9.5.1",
21
- "esbuild": "^0.27.2"
21
+ "esbuild": "^0.27.3"
22
22
  },
23
23
  "author": "YieldRay",
24
24
  "license": "MIT",