solid-tag-runtime 0.0.7 → 0.0.8

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/src/runtime.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  SolidTagRuntimeError,
6
6
  } from "./errors.js";
7
7
  import { createDefaultModuleUrlBackend } from "./url-backend.js";
8
+ import { createEventDispatcher } from "./events.js";
8
9
 
9
10
  const HOST_REGISTRY_KEY = "__solidTagRuntimeHostModules__";
10
11
  let runtimeSequence = 0;
@@ -27,7 +28,11 @@ export function createRuntime(options = {}) {
27
28
  const records = new Map();
28
29
  let anonymousSequence = 0;
29
30
  let disposed = false;
30
- const subscribers = new Set();
31
+ const events = createEventDispatcher({
32
+ onListenerError(error) {
33
+ globalThis.console?.error?.("solid-tag-runtime lifecycle subscriber error", error);
34
+ },
35
+ });
31
36
  let eventBufferDepth = 0;
32
37
  const eventBuffer = [];
33
38
 
@@ -102,7 +107,13 @@ export function createRuntime(options = {}) {
102
107
 
103
108
  eventBufferDepth += 1;
104
109
  try {
105
- return prepared.map(item => defineSource(item.id, item.source, { format: item.format }, "defineMany"));
110
+ const ids = prepared.map(item => defineSource(item.id, item.source, { format: item.format }, "defineMany"));
111
+ emit({
112
+ type: "modules-defined",
113
+ ids: [...ids],
114
+ modules: ids.map(id => toModuleInfo(records.get(id))),
115
+ });
116
+ return ids;
106
117
  } finally {
107
118
  eventBufferDepth -= 1;
108
119
  if (eventBufferDepth === 0) flushEventBuffer();
@@ -146,6 +157,17 @@ export function createRuntime(options = {}) {
146
157
  replaced: Boolean(existing),
147
158
  module: toModuleInfo(record),
148
159
  });
160
+ if (operation === "update") {
161
+ emit({
162
+ type: "module-updated",
163
+ id: normalized,
164
+ kind: record.kind,
165
+ previousVersion: existing?.version ?? 0,
166
+ version: record.version,
167
+ format: record.format,
168
+ module: toModuleInfo(record),
169
+ });
170
+ }
149
171
  return normalized;
150
172
  }
151
173
 
@@ -251,9 +273,11 @@ export function createRuntime(options = {}) {
251
273
  }
252
274
 
253
275
  if (!record.importPromise) {
254
- record.state = "evaluating";
255
- emit({ type: "module-evaluating", id: record.id, kind: record.kind, version: record.version });
256
- record.importPromise = ensureSourceUrl(record, []).then(url => import(url)).then(namespace => {
276
+ record.importPromise = ensureSourceUrl(record, []).then(url => {
277
+ record.state = "evaluating";
278
+ emit({ type: "module-evaluating", id: record.id, kind: record.kind, version: record.version });
279
+ return import(url);
280
+ }).then(namespace => {
257
281
  record.namespace = namespace;
258
282
  record.state = "ready";
259
283
  emit({ type: "module-evaluated", id: record.id, kind: record.kind, version: record.version, module: toModuleInfo(record) });
@@ -421,13 +445,9 @@ export function createRuntime(options = {}) {
421
445
  return ids;
422
446
  }
423
447
 
424
- function subscribe(listener) {
448
+ function subscribe(typesOrListener, maybeListener) {
425
449
  assertActive();
426
- if (typeof listener !== "function") {
427
- throw new TypeError("subscribe(listener) expects a function.");
428
- }
429
- subscribers.add(listener);
430
- return () => subscribers.delete(listener);
450
+ return events.subscribe(typesOrListener, maybeListener);
431
451
  }
432
452
 
433
453
  function listModules() {
@@ -458,7 +478,7 @@ export function createRuntime(options = {}) {
458
478
  records.clear();
459
479
  hostNamespaces.clear();
460
480
  globalRegistry.delete(runtimeId);
461
- subscribers.clear();
481
+ events.clear();
462
482
  }
463
483
 
464
484
  function resolvePublic(specifier, importer) {
@@ -470,11 +490,19 @@ export function createRuntime(options = {}) {
470
490
 
471
491
  if (stack.includes(record.id)) {
472
492
  const cycleStart = stack.indexOf(record.id);
473
- throw new CircularDependencyError([...stack.slice(cycleStart), record.id]);
493
+ const error = new CircularDependencyError([...stack.slice(cycleStart), record.id]);
494
+ emitError(record, "link", error);
495
+ throw error;
474
496
  }
475
497
 
476
498
  const nextStack = [...stack, record.id];
477
499
  record.state = "linking";
500
+ emit({
501
+ type: "module-linking",
502
+ id: record.id,
503
+ kind: record.kind,
504
+ version: record.version,
505
+ });
478
506
 
479
507
  let analysis;
480
508
  try {
@@ -487,79 +515,126 @@ export function createRuntime(options = {}) {
487
515
  }
488
516
 
489
517
  const resolvedImports = [];
518
+ const linkedDependencies = [];
490
519
  const nextDependencies = new Set();
491
520
 
492
521
  for (const reference of analysis.imports ?? []) {
493
- const resolved = resolveSpecifier(reference.specifier, record.id);
522
+ let resolved;
523
+ try {
524
+ resolved = resolveSpecifier(reference.specifier, record.id);
525
+ } catch (error) {
526
+ record.state = "error";
527
+ emitError(record, "resolve", error);
528
+ throw error;
529
+ }
530
+
494
531
  const dependencyRecord = records.get(resolved);
495
532
  let replacement = resolved;
496
533
 
497
- if (dependencyRecord) {
498
- nextDependencies.add(dependencyRecord.id);
499
-
500
- if (dependencyRecord.kind === "source") {
501
- replacement = await ensureSourceUrl(dependencyRecord, nextStack);
502
- } else if (dependencyRecord.kind === "host") {
503
- replacement = await ensureHostUrl(dependencyRecord);
504
- } else {
505
- replacement = dependencyRecord.externalUrl;
534
+ try {
535
+ if (dependencyRecord) {
536
+ nextDependencies.add(dependencyRecord.id);
537
+
538
+ if (dependencyRecord.kind === "source") {
539
+ replacement = await ensureSourceUrl(dependencyRecord, nextStack);
540
+ } else if (dependencyRecord.kind === "host") {
541
+ replacement = await ensureHostUrl(dependencyRecord);
542
+ } else {
543
+ replacement = dependencyRecord.externalUrl;
544
+ }
545
+ } else if (isRelativeSpecifier(reference.specifier) || reference.specifier.startsWith("/")) {
546
+ throw new ModuleResolutionError(reference.specifier, record.id);
547
+ } else if (!allowNativeImports && !isAbsoluteUrl(resolved)) {
548
+ throw new ModuleResolutionError(reference.specifier, record.id);
506
549
  }
507
- } else if (isRelativeSpecifier(reference.specifier) || reference.specifier.startsWith("/")) {
508
- throw new ModuleResolutionError(reference.specifier, record.id);
509
- } else if (!allowNativeImports && !isAbsoluteUrl(resolved)) {
510
- throw new ModuleResolutionError(reference.specifier, record.id);
550
+ } catch (error) {
551
+ record.state = "error";
552
+ emitError(record, error instanceof ModuleResolutionError ? "resolve" : "link", error);
553
+ throw error;
511
554
  }
512
555
 
513
556
  resolvedImports.push({ ...reference, replacement });
557
+ linkedDependencies.push({
558
+ specifier: reference.specifier,
559
+ resolvedId: dependencyRecord?.id ?? resolved,
560
+ });
514
561
  }
515
562
 
516
- let linkedSource = applyImportReplacements(record.source, resolvedImports);
517
-
518
- // JSX produced by solid-tag needs @solidjs/html. Resolve it through the
519
- // same module system so host applications can share their existing runtime.
520
- let htmlImportSource;
521
- const needsHtmlRuntime = record.format !== "js" && analysis.needsHtmlRuntime !== false;
522
- if (needsHtmlRuntime) {
523
- const htmlResolved = resolveSpecifier("@solidjs/html", record.id);
524
- const htmlRecord = records.get(htmlResolved);
525
-
526
- if (htmlRecord) {
527
- nextDependencies.add(htmlRecord.id);
528
- htmlImportSource = htmlRecord.kind === "source"
529
- ? await ensureSourceUrl(htmlRecord, nextStack)
530
- : htmlRecord.kind === "host"
531
- ? await ensureHostUrl(htmlRecord)
532
- : htmlRecord.externalUrl;
533
- } else if (allowNativeImports) {
534
- htmlImportSource = htmlResolved;
535
- } else {
536
- throw new ModuleResolutionError("@solidjs/html", record.id);
563
+ let linkedSource;
564
+ try {
565
+ linkedSource = applyImportReplacements(record.source, resolvedImports);
566
+
567
+ // JSX produced by solid-tag needs @solidjs/html. Resolve it through the
568
+ // same module system so host applications can share their existing runtime.
569
+ let htmlImportSource;
570
+ const needsHtmlRuntime = record.format !== "js" && analysis.needsHtmlRuntime !== false;
571
+ if (needsHtmlRuntime) {
572
+ const htmlResolved = resolveSpecifier("@solidjs/html", record.id);
573
+ const htmlRecord = records.get(htmlResolved);
574
+
575
+ if (htmlRecord) {
576
+ nextDependencies.add(htmlRecord.id);
577
+ htmlImportSource = htmlRecord.kind === "source"
578
+ ? await ensureSourceUrl(htmlRecord, nextStack)
579
+ : htmlRecord.kind === "host"
580
+ ? await ensureHostUrl(htmlRecord)
581
+ : htmlRecord.externalUrl;
582
+ } else if (allowNativeImports) {
583
+ htmlImportSource = htmlResolved;
584
+ } else {
585
+ throw new ModuleResolutionError("@solidjs/html", record.id);
586
+ }
587
+
588
+ linkedDependencies.push({
589
+ specifier: "@solidjs/html",
590
+ resolvedId: htmlRecord?.id ?? htmlResolved,
591
+ });
537
592
  }
538
- }
539
593
 
540
- syncDependencyEdges(record, nextDependencies);
594
+ syncDependencyEdges(record, nextDependencies);
595
+ emit({
596
+ type: "module-linked",
597
+ id: record.id,
598
+ kind: record.kind,
599
+ version: record.version,
600
+ dependencies: linkedDependencies,
601
+ module: toModuleInfo(record),
602
+ });
541
603
 
542
- try {
543
- if (record.format === "js") {
544
- record.compiledCode = linkedSource;
545
- record.diagnostics = [];
546
- } else {
547
- const transformed = compiler.transform(linkedSource, {
548
- id: record.id,
549
- format: record.format,
550
- htmlImportSource,
551
- });
552
- record.compiledCode = transformed.code;
553
- record.diagnostics = [...(transformed.diagnostics ?? [])];
604
+ try {
605
+ if (record.format === "js") {
606
+ record.compiledCode = linkedSource;
607
+ record.diagnostics = [];
608
+ } else {
609
+ const transformed = compiler.transform(linkedSource, {
610
+ id: record.id,
611
+ format: record.format,
612
+ htmlImportSource,
613
+ });
614
+ record.compiledCode = transformed.code;
615
+ record.diagnostics = [...(transformed.diagnostics ?? [])];
616
+ }
617
+ } catch (error) {
618
+ record.state = "error";
619
+ const wrapped = new ModuleCompileError(record.id, error);
620
+ emitError(record, "compile", wrapped);
621
+ throw wrapped;
554
622
  }
555
623
  } catch (error) {
556
- record.state = "error";
557
- const wrapped = new ModuleCompileError(record.id, error);
558
- emitError(record, "transform", wrapped);
559
- throw wrapped;
624
+ if (record.state !== "error") {
625
+ record.state = "error";
626
+ emitError(record, error instanceof ModuleResolutionError ? "resolve" : "link", error);
627
+ }
628
+ throw error;
560
629
  }
561
630
 
562
- record.url = moduleUrlBackend.create(appendSourceUrl(record.compiledCode, record.id));
631
+ try {
632
+ record.url = moduleUrlBackend.create(appendSourceUrl(record.compiledCode, record.id));
633
+ } catch (error) {
634
+ record.state = "error";
635
+ emitError(record, "url-create", error);
636
+ throw error;
637
+ }
563
638
  record.state = "compiled";
564
639
  emit({
565
640
  type: "module-compiled",
@@ -597,30 +672,69 @@ export function createRuntime(options = {}) {
597
672
  );
598
673
  }
599
674
 
600
- record.url = moduleUrlBackend.create(appendSourceUrl(lines.join("\n"), `host:${record.id}`));
675
+ try {
676
+ record.url = moduleUrlBackend.create(appendSourceUrl(lines.join("\n"), `host:${record.id}`));
677
+ } catch (error) {
678
+ emitError(record, "host-bridge", error);
679
+ throw error;
680
+ }
601
681
  return record.url;
602
682
  }
603
683
 
604
684
  function resolveSpecifier(specifier, importer) {
605
685
  const raw = String(specifier);
686
+ emit({
687
+ type: "module-resolving",
688
+ specifier: raw,
689
+ importer: importer ?? null,
690
+ });
606
691
 
607
- if (typeof customResolve === "function") {
608
- const custom = customResolve(raw, importer, api);
609
- if (typeof custom === "string" && custom) return normalizeResolved(custom, importer);
610
- }
611
-
612
- if (records.has(raw)) return raw;
613
- if (isAbsoluteUrl(raw)) return raw;
692
+ let resolved;
693
+ let resolutionKind;
614
694
 
615
- if (raw.startsWith("/")) return normalizePath(raw);
695
+ try {
696
+ if (typeof customResolve === "function") {
697
+ const custom = customResolve(raw, importer, api);
698
+ if (typeof custom === "string" && custom) {
699
+ resolved = normalizeResolved(custom, importer);
700
+ resolutionKind = "custom";
701
+ }
702
+ }
616
703
 
617
- if (isRelativeSpecifier(raw)) {
618
- if (!importer) return normalizePath(`/${raw}`);
619
- if (isAbsoluteUrl(importer)) return new URL(raw, importer).href;
620
- return normalizePath(`${dirname(importer)}/${raw}`);
704
+ if (resolved == null && records.has(raw)) {
705
+ resolved = raw;
706
+ resolutionKind = "registered";
707
+ }
708
+ if (resolved == null && isAbsoluteUrl(raw)) {
709
+ resolved = raw;
710
+ resolutionKind = "url";
711
+ }
712
+ if (resolved == null && raw.startsWith("/")) {
713
+ resolved = normalizePath(raw);
714
+ resolutionKind = "absolute";
715
+ }
716
+ if (resolved == null && isRelativeSpecifier(raw)) {
717
+ if (!importer) resolved = normalizePath(`/${raw}`);
718
+ else if (isAbsoluteUrl(importer)) resolved = new URL(raw, importer).href;
719
+ else resolved = normalizePath(`${dirname(importer)}/${raw}`);
720
+ resolutionKind = "relative";
721
+ }
722
+ if (resolved == null) {
723
+ resolved = raw;
724
+ resolutionKind = "native";
725
+ }
726
+ } catch (error) {
727
+ throw error;
621
728
  }
622
729
 
623
- return raw;
730
+ emit({
731
+ type: "module-resolved",
732
+ specifier: raw,
733
+ importer: importer ?? null,
734
+ resolvedId: resolved,
735
+ resolutionKind,
736
+ });
737
+ return resolved;
624
738
  }
625
739
 
626
740
  function syncDependencyEdges(record, nextDependencies) {
@@ -642,7 +756,7 @@ export function createRuntime(options = {}) {
642
756
  }
643
757
 
644
758
  function emit(event) {
645
- if (subscribers.size === 0) return;
759
+ if (events.size === 0) return;
646
760
  const payload = {
647
761
  runtimeId,
648
762
  timestamp: Date.now(),
@@ -652,23 +766,13 @@ export function createRuntime(options = {}) {
652
766
  eventBuffer.push(payload);
653
767
  return;
654
768
  }
655
- dispatchEvent(payload);
769
+ events.emit(payload);
656
770
  }
657
771
 
658
772
  function flushEventBuffer() {
659
773
  if (eventBuffer.length === 0) return;
660
774
  const pending = eventBuffer.splice(0, eventBuffer.length);
661
- for (const event of pending) dispatchEvent(event);
662
- }
663
-
664
- function dispatchEvent(payload) {
665
- for (const listener of [...subscribers]) {
666
- try {
667
- listener(payload);
668
- } catch (error) {
669
- globalThis.console?.error?.("solid-tag-runtime lifecycle subscriber error", error);
670
- }
671
- }
775
+ for (const event of pending) events.emit(event);
672
776
  }
673
777
 
674
778
  function emitError(record, phase, error) {