solid-tag-runtime 0.0.11 → 0.0.13
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/ARCHITECTURE.md +167 -38
- package/README.md +76 -885
- package/docs/README.md +23 -0
- package/docs/api/compile-cache.md +103 -0
- package/docs/api/html.md +83 -0
- package/docs/api/runtime.md +87 -0
- package/docs/compile-cache.md +271 -0
- package/docs/getting-started.md +105 -0
- package/docs/html-runtime.md +112 -0
- package/docs/lifecycle-events.md +77 -0
- package/docs/modules.md +93 -0
- package/docs/rendering.md +65 -0
- package/docs/solid-render.md +111 -0
- package/examples/basic.js +41 -0
- package/examples/compile-cache.js +35 -0
- package/examples/main.tsx +318 -0
- package/examples/render.html +79 -0
- package/index.d.ts +183 -27
- package/package.json +5 -3
- package/src/compile-cache.js +340 -0
- package/src/compiler.js +86 -38
- package/src/html.js +191 -19
- package/src/index.js +6 -0
- package/src/runtime.js +656 -74
package/src/html.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SolidTagRuntimeError } from "./errors.js";
|
|
1
|
+
import { ModuleResolutionError, SolidTagRuntimeError } from "./errors.js";
|
|
2
2
|
import { createEventDispatcher } from "./events.js";
|
|
3
3
|
import { HTMLRenderError, createDefaultHTMLRenderAdapter, resolveRenderComponent } from "./render.js";
|
|
4
4
|
|
|
@@ -83,6 +83,9 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
83
83
|
queue: Promise.resolve(),
|
|
84
84
|
lastError: undefined,
|
|
85
85
|
connected: false,
|
|
86
|
+
registrationDepth: 0,
|
|
87
|
+
registrationPasses: 0,
|
|
88
|
+
solidRenderRetryScheduled: false,
|
|
86
89
|
initial: emptyRegistrationResult(),
|
|
87
90
|
};
|
|
88
91
|
|
|
@@ -127,10 +130,13 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
127
130
|
|
|
128
131
|
controller.api = api;
|
|
129
132
|
activeControllers.add(controller);
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
133
|
+
// Do not define/upgrade <solid-render> here. Existing declarative script
|
|
134
|
+
// modules must be installed first; otherwise connectedCallback() can import
|
|
135
|
+
// a module before register()/observe() has defined the preceding <script>.
|
|
136
|
+
// register()/observe() register the element after their initial script batch.
|
|
137
|
+
controller.runtimeModuleDefinedUnsubscribe = runtime.subscribe?.("module-defined", event => {
|
|
138
|
+
scheduleSolidRenderRetryForModule(controller, event.id);
|
|
139
|
+
});
|
|
134
140
|
controller.runtimeDisposeUnsubscribe = runtime.subscribe?.("runtime-disposed", () => {
|
|
135
141
|
void disposeController(controller, { runtimeDisposed: true });
|
|
136
142
|
});
|
|
@@ -147,21 +153,23 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
147
153
|
const origin = callOptions.origin ?? "initial-scan";
|
|
148
154
|
const discovered = Array.from(root.querySelectorAll(selector));
|
|
149
155
|
const elements = [];
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const accepted = acceptsDiscoveredElement(controller, element, callOptions);
|
|
153
|
-
emitHTML(controller, {
|
|
154
|
-
type: "element-discovered",
|
|
155
|
-
element,
|
|
156
|
-
moduleId: nonEmpty(element?.getAttribute?.("module")),
|
|
157
|
-
origin,
|
|
158
|
-
accepted,
|
|
159
|
-
});
|
|
160
|
-
if (accepted) elements.push(element);
|
|
161
|
-
else maybeWarnUnresolvedRenderScope(controller, element, callOptions, origin);
|
|
162
|
-
}
|
|
156
|
+
const pendingBeforeUpgrade = collectConnectedSolidRenderElements(controller, root);
|
|
157
|
+
controller.registrationDepth += 1;
|
|
163
158
|
|
|
164
159
|
try {
|
|
160
|
+
for (const element of discovered) {
|
|
161
|
+
const accepted = acceptsDiscoveredElement(controller, element, callOptions);
|
|
162
|
+
emitHTML(controller, {
|
|
163
|
+
type: "element-discovered",
|
|
164
|
+
element,
|
|
165
|
+
moduleId: nonEmpty(element?.getAttribute?.("module")),
|
|
166
|
+
origin,
|
|
167
|
+
accepted,
|
|
168
|
+
});
|
|
169
|
+
if (accepted) elements.push(element);
|
|
170
|
+
else maybeWarnUnresolvedRenderScope(controller, element, callOptions, origin);
|
|
171
|
+
}
|
|
172
|
+
|
|
165
173
|
const result = await processElements(controller, elements, {
|
|
166
174
|
...callOptions,
|
|
167
175
|
root,
|
|
@@ -169,11 +177,21 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
169
177
|
executeEntries: callOptions.executeEntries ?? controller.executeEntries,
|
|
170
178
|
executeRenders: callOptions.executeRenders ?? controller.executeRenders,
|
|
171
179
|
});
|
|
180
|
+
|
|
181
|
+
// Existing upgraded instances may have connected before this controller's
|
|
182
|
+
// declaration batch was installed. Retry those after the batch completes.
|
|
172
183
|
registerSolidRenderElement();
|
|
184
|
+
controller.registrationPasses += 1;
|
|
185
|
+
await retrySolidRenderElements(controller, pendingBeforeUpgrade, {
|
|
186
|
+
reason: "registration-complete",
|
|
187
|
+
forceMissingError: true,
|
|
188
|
+
});
|
|
173
189
|
return result;
|
|
174
190
|
} catch (error) {
|
|
175
191
|
emitHTMLError(controller, "register", error, { origin });
|
|
176
192
|
throw error;
|
|
193
|
+
} finally {
|
|
194
|
+
controller.registrationDepth = Math.max(0, controller.registrationDepth - 1);
|
|
177
195
|
}
|
|
178
196
|
}
|
|
179
197
|
|
|
@@ -186,6 +204,8 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
186
204
|
|
|
187
205
|
const root = getRoot(mergeOptions(controller, callOptions), "HTML runtime observe");
|
|
188
206
|
controller.observerOptions = { ...callOptions };
|
|
207
|
+
const pendingBeforeUpgrade = collectConnectedSolidRenderElements(controller, root);
|
|
208
|
+
controller.registrationDepth += 1;
|
|
189
209
|
|
|
190
210
|
try {
|
|
191
211
|
controller.initial = await connectObserver(controller, root, callOptions, {
|
|
@@ -193,9 +213,16 @@ export function createHTMLRuntime(runtime, options = {}) {
|
|
|
193
213
|
initial: true,
|
|
194
214
|
});
|
|
195
215
|
registerSolidRenderElement();
|
|
216
|
+
controller.registrationPasses += 1;
|
|
217
|
+
await retrySolidRenderElements(controller, pendingBeforeUpgrade, {
|
|
218
|
+
reason: "observation-ready",
|
|
219
|
+
forceMissingError: true,
|
|
220
|
+
});
|
|
196
221
|
} catch (error) {
|
|
197
222
|
disconnect();
|
|
198
223
|
throw error;
|
|
224
|
+
} finally {
|
|
225
|
+
controller.registrationDepth = Math.max(0, controller.registrationDepth - 1);
|
|
199
226
|
}
|
|
200
227
|
|
|
201
228
|
return api;
|
|
@@ -645,6 +672,8 @@ export function registerSolidRenderElement(options = {}) {
|
|
|
645
672
|
mount: undefined,
|
|
646
673
|
controller: undefined,
|
|
647
674
|
propObserver: undefined,
|
|
675
|
+
pendingModuleResolution: undefined,
|
|
676
|
+
pendingCheckGeneration: undefined,
|
|
648
677
|
});
|
|
649
678
|
}
|
|
650
679
|
|
|
@@ -700,6 +729,8 @@ function getSolidRenderState(element) {
|
|
|
700
729
|
mount: undefined,
|
|
701
730
|
controller: undefined,
|
|
702
731
|
propObserver: undefined,
|
|
732
|
+
pendingModuleResolution: undefined,
|
|
733
|
+
pendingCheckGeneration: undefined,
|
|
703
734
|
};
|
|
704
735
|
solidRenderInternals.set(element, state);
|
|
705
736
|
}
|
|
@@ -738,10 +769,12 @@ function startSolidRenderPropObserver(element, state) {
|
|
|
738
769
|
state.propObserver = observer;
|
|
739
770
|
}
|
|
740
771
|
|
|
741
|
-
async function remountSolidRender(element, reason) {
|
|
772
|
+
async function remountSolidRender(element, reason, options = {}) {
|
|
742
773
|
const state = getSolidRenderState(element);
|
|
743
774
|
if (!state.connected) return;
|
|
744
775
|
const generation = ++state.generation;
|
|
776
|
+
state.pendingModuleResolution = undefined;
|
|
777
|
+
state.pendingCheckGeneration = undefined;
|
|
745
778
|
await disposeSolidRenderMount(element, state, reason === "connect" ? "reconnect" : reason);
|
|
746
779
|
if (!state.connected || generation !== state.generation) return;
|
|
747
780
|
|
|
@@ -792,6 +825,8 @@ async function remountSolidRender(element, reason) {
|
|
|
792
825
|
|
|
793
826
|
mount.mode = "container";
|
|
794
827
|
state.mount = mount;
|
|
828
|
+
state.pendingModuleResolution = undefined;
|
|
829
|
+
state.pendingCheckGeneration = undefined;
|
|
795
830
|
emitHTML(controller, {
|
|
796
831
|
type: "render-mounted",
|
|
797
832
|
element,
|
|
@@ -807,6 +842,26 @@ async function remountSolidRender(element, reason) {
|
|
|
807
842
|
// failure belongs to the abandoned generation just like a stale success,
|
|
808
843
|
// so it must not overwrite/report against the current element state.
|
|
809
844
|
if (!state.connected || generation !== state.generation) return;
|
|
845
|
+
if (error instanceof ModuleResolutionError && controller) {
|
|
846
|
+
state.pendingModuleResolution = {
|
|
847
|
+
moduleId: nonEmpty(element.getAttribute?.("module")),
|
|
848
|
+
error,
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// During startup or observer-driven DOM insertion a connected custom
|
|
852
|
+
// element can run before the matching declarative module has been
|
|
853
|
+
// installed. Treat that as a pending reference only until the relevant
|
|
854
|
+
// registration/observer queue has had a chance to settle.
|
|
855
|
+
if (!options.forceMissingError && (
|
|
856
|
+
controller.registrationDepth > 0
|
|
857
|
+
|| controller.connected
|
|
858
|
+
|| controller.registrationPasses === 0
|
|
859
|
+
)) {
|
|
860
|
+
schedulePendingSolidRenderResolutionCheck(element, state, controller, generation);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
810
865
|
if (controller) {
|
|
811
866
|
emitHTMLError(controller, "solid-render", error, {
|
|
812
867
|
element,
|
|
@@ -985,6 +1040,116 @@ function mostSpecificController(controllers) {
|
|
|
985
1040
|
return undefined;
|
|
986
1041
|
}
|
|
987
1042
|
|
|
1043
|
+
function schedulePendingSolidRenderResolutionCheck(element, state, controller, generation) {
|
|
1044
|
+
if (state.pendingCheckGeneration === generation) return;
|
|
1045
|
+
state.pendingCheckGeneration = generation;
|
|
1046
|
+
|
|
1047
|
+
const schedule = globalThis.setTimeout ?? ((callback) => Promise.resolve().then(callback));
|
|
1048
|
+
schedule(async () => {
|
|
1049
|
+
if (!state.connected || generation !== state.generation) return;
|
|
1050
|
+
if (!state.pendingModuleResolution || state.mount) return;
|
|
1051
|
+
|
|
1052
|
+
// MutationObserver callbacks run before the next task. If this missing
|
|
1053
|
+
// module came from a script + solid-render insertion batch, controller.queue
|
|
1054
|
+
// now points at that registration work. Wait for it before deciding the
|
|
1055
|
+
// reference is genuinely unresolved.
|
|
1056
|
+
if (controller.connected) {
|
|
1057
|
+
try {
|
|
1058
|
+
await controller.queue;
|
|
1059
|
+
} catch {
|
|
1060
|
+
// Observer errors are reported through the normal controller path.
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if (!state.connected || generation !== state.generation) return;
|
|
1065
|
+
if (!state.pendingModuleResolution || state.mount) return;
|
|
1066
|
+
|
|
1067
|
+
// A direct register() may still be loading external source. Do not report
|
|
1068
|
+
// a transient missing module while that pass is active.
|
|
1069
|
+
if (controller.registrationDepth > 0) {
|
|
1070
|
+
state.pendingCheckGeneration = undefined;
|
|
1071
|
+
schedulePendingSolidRenderResolutionCheck(element, state, controller, generation);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
state.pendingCheckGeneration = undefined;
|
|
1076
|
+
await remountSolidRender(element, "pending-module-settled", {
|
|
1077
|
+
forceMissingError: true,
|
|
1078
|
+
});
|
|
1079
|
+
}, 0);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
function collectConnectedSolidRenderElements(controller, root) {
|
|
1083
|
+
const elements = new Set();
|
|
1084
|
+
|
|
1085
|
+
for (const element of controller.renderElements ?? []) {
|
|
1086
|
+
const state = solidRenderInternals.get(element);
|
|
1087
|
+
if (state?.connected && rootContainsElement(root, element)) elements.add(element);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (root && typeof root.querySelectorAll === "function") {
|
|
1091
|
+
for (const element of Array.from(root.querySelectorAll("solid-render") ?? [])) {
|
|
1092
|
+
const state = solidRenderInternals.get(element);
|
|
1093
|
+
if (state?.connected) elements.add(element);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return [...elements];
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
async function retrySolidRenderElements(controller, elements, options = {}) {
|
|
1101
|
+
for (const element of elements ?? []) {
|
|
1102
|
+
const state = solidRenderInternals.get(element);
|
|
1103
|
+
if (!state?.connected) continue;
|
|
1104
|
+
if (state.mount && !state.pendingModuleResolution) continue;
|
|
1105
|
+
|
|
1106
|
+
let selected;
|
|
1107
|
+
try {
|
|
1108
|
+
selected = resolveHTMLControllerForElement(element);
|
|
1109
|
+
} catch {
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
if (selected !== controller) continue;
|
|
1113
|
+
|
|
1114
|
+
await remountSolidRender(element, options.reason ?? "retry", {
|
|
1115
|
+
forceMissingError: options.forceMissingError === true,
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function scheduleSolidRenderRetryForModule(controller, definedId) {
|
|
1121
|
+
if (controller.disposed || controller.solidRenderRetryScheduled) return;
|
|
1122
|
+
controller.solidRenderRetryScheduled = true;
|
|
1123
|
+
|
|
1124
|
+
const schedule = globalThis.queueMicrotask ?? (callback => Promise.resolve().then(callback));
|
|
1125
|
+
schedule(() => {
|
|
1126
|
+
controller.solidRenderRetryScheduled = false;
|
|
1127
|
+
if (controller.disposed) return;
|
|
1128
|
+
|
|
1129
|
+
const matches = [];
|
|
1130
|
+
for (const element of controller.renderElements) {
|
|
1131
|
+
const state = solidRenderInternals.get(element);
|
|
1132
|
+
if (!state?.connected || !state.pendingModuleResolution) continue;
|
|
1133
|
+
const requested = nonEmpty(element.getAttribute?.("module"));
|
|
1134
|
+
if (!requested) continue;
|
|
1135
|
+
let resolved;
|
|
1136
|
+
try {
|
|
1137
|
+
resolved = controller.runtime.resolve(requested);
|
|
1138
|
+
} catch {
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
if (resolved === definedId) matches.push(element);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
if (matches.length > 0) {
|
|
1145
|
+
void retrySolidRenderElements(controller, matches, {
|
|
1146
|
+
reason: "module-defined",
|
|
1147
|
+
forceMissingError: false,
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
|
|
988
1153
|
function dispatchSolidRenderError(element, error) {
|
|
989
1154
|
try {
|
|
990
1155
|
const EventImpl = globalThis.CustomEvent;
|
|
@@ -1024,6 +1189,8 @@ async function disposeController(controller, options = {}) {
|
|
|
1024
1189
|
}
|
|
1025
1190
|
|
|
1026
1191
|
activeControllers.delete(controller);
|
|
1192
|
+
controller.runtimeModuleDefinedUnsubscribe?.();
|
|
1193
|
+
controller.runtimeModuleDefinedUnsubscribe = undefined;
|
|
1027
1194
|
controller.runtimeDisposeUnsubscribe?.();
|
|
1028
1195
|
controller.runtimeDisposeUnsubscribe = undefined;
|
|
1029
1196
|
controller.events.clear();
|
|
@@ -1163,6 +1330,11 @@ async function enqueueObserverScan(controller, callOptions = {}) {
|
|
|
1163
1330
|
}
|
|
1164
1331
|
|
|
1165
1332
|
await cleanupDetachedRenderedDeclarations(controller, root);
|
|
1333
|
+
await retrySolidRenderElements(
|
|
1334
|
+
controller,
|
|
1335
|
+
collectConnectedSolidRenderElements(controller, root),
|
|
1336
|
+
{ reason: "observer-batch-complete", forceMissingError: true },
|
|
1337
|
+
);
|
|
1166
1338
|
|
|
1167
1339
|
emitHTML(controller, {
|
|
1168
1340
|
type: "observer-batch",
|