@weapp-vite/miniprogram-automator 1.2.22 → 1.3.0

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.
@@ -58,6 +58,41 @@ function createAppInstance(definition) {
58
58
  return instance;
59
59
  }
60
60
  //#endregion
61
+ //#region ../../mpcore/packages/simulator/src/host/attachmentBindingUpdates.ts
62
+ const bindingUpdates = /* @__PURE__ */ new WeakMap();
63
+ const instanceScopes = /* @__PURE__ */ new WeakMap();
64
+ /** 实例经渲染上下文归属页面,不扩展公开 renderer context 的参数契约。 */
65
+ function bindAttachmentBindingScope(instance, scope) {
66
+ instanceScopes.set(instance, instanceScopes.get(scope) ?? scope);
67
+ }
68
+ /** 仅在挂载回调内同步逻辑绑定,不接管视图提交和 setData 回调。 */
69
+ function runWithAttachmentBindingUpdates(scope, update, callback, value) {
70
+ const previous = bindingUpdates.get(scope);
71
+ const state = previous ?? {
72
+ revision: 0,
73
+ update
74
+ };
75
+ const previousUpdate = state.update;
76
+ state.update = update;
77
+ bindingUpdates.set(scope, state);
78
+ try {
79
+ return callback(value);
80
+ } finally {
81
+ if (previous) state.update = previousUpdate;
82
+ else bindingUpdates.delete(scope);
83
+ }
84
+ }
85
+ function getAttachmentBindingRevision(instance) {
86
+ return bindingUpdates.get(instanceScopes.get(instance) ?? instance);
87
+ }
88
+ function synchronizeAttachmentBindings(instance) {
89
+ const state = bindingUpdates.get(instanceScopes.get(instance) ?? instance);
90
+ if (state) {
91
+ state.revision++;
92
+ state.update();
93
+ }
94
+ }
95
+ //#endregion
61
96
  //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/shared.ts
62
97
  const ARRAY_INDEX_PATH_RE$1 = /\[(\d+)\]/g;
63
98
  const ARRAY_INDEX_SEGMENT_RE$1 = /^\d+$/;
@@ -905,6 +940,7 @@ function createComponentInstance(options) {
905
940
  const previousProperties = Object.fromEntries(propertyKeys.map((key) => [key, cloneValue$1(instance.properties[key])]));
906
941
  for (const [key, value] of Object.entries(patch)) assignByPath$1(instance.data, key, value);
907
942
  for (const key of dataKeys) instance.properties[key] = instance.data[key];
943
+ synchronizeAttachmentBindings(instance);
908
944
  runComponentObservers(definition, instance, changedKeys, previousProperties);
909
945
  if (options.requestRender) options.requestRender(callback);
910
946
  else callback?.();
@@ -946,8 +982,11 @@ const componentPageInstances = /* @__PURE__ */ new WeakSet();
946
982
  function isComponentPageAttaching(instance) {
947
983
  return attachingInstances.has(instance);
948
984
  }
949
- function registerComponentPageAttachment(definition, attach) {
950
- definitionAttachments.set(definition, attach);
985
+ function registerComponentPageAttachment(definition, created, attached) {
986
+ definitionAttachments.set(definition, {
987
+ created,
988
+ attached
989
+ });
951
990
  }
952
991
  function bindComponentPageAttachment(instance, definition) {
953
992
  const attach = definitionAttachments.get(definition);
@@ -958,13 +997,14 @@ function bindComponentPageAttachment(instance, definition) {
958
997
  if (typeof options?.styleIsolation === "string") instanceStyleIsolation.set(instance, options.styleIsolation);
959
998
  }
960
999
  }
961
- function attachComponentPage(instance) {
1000
+ function attachComponentPage(instance, updateBindings) {
962
1001
  const attach = instanceAttachments.get(instance);
963
1002
  if (!attach) return false;
964
1003
  instanceAttachments.delete(instance);
965
1004
  attachingInstances.add(instance);
966
1005
  try {
967
- attach.call(instance);
1006
+ attach.created(instance);
1007
+ runWithAttachmentBindingUpdates(instance, updateBindings, attach.attached, instance);
968
1008
  } finally {
969
1009
  attachingInstances.delete(instance);
970
1010
  }
@@ -1114,6 +1154,7 @@ function createPageInstance(route, definition, options = {}, pageState = {}) {
1114
1154
  const rootKey = parseDataPath(key)[0];
1115
1155
  if (rootKey) instance.properties[rootKey] = instance.data[rootKey];
1116
1156
  }
1157
+ synchronizeAttachmentBindings(instance);
1117
1158
  if (pageState.requestRender) pageState.requestRender(callback);
1118
1159
  else callback?.();
1119
1160
  }
@@ -1126,6 +1167,262 @@ function createPageInstance(route, definition, options = {}, pageState = {}) {
1126
1167
  return instance;
1127
1168
  }
1128
1169
  //#endregion
1170
+ //#region ../../mpcore/packages/simulator/src/view/eventBindingParser.ts
1171
+ const LEGACY_EVENT_NAME_RE = /^\w+$/;
1172
+ const EVENT_BINDING_PREFIXES = [
1173
+ {
1174
+ prefix: "capture-bind:",
1175
+ priority: 0,
1176
+ stopAfter: false,
1177
+ phase: "capture"
1178
+ },
1179
+ {
1180
+ prefix: "capture-catch:",
1181
+ priority: 0,
1182
+ stopAfter: true,
1183
+ phase: "capture"
1184
+ },
1185
+ {
1186
+ prefix: "capture-bind",
1187
+ priority: 1,
1188
+ stopAfter: false,
1189
+ phase: "capture"
1190
+ },
1191
+ {
1192
+ prefix: "capture-catch",
1193
+ priority: 1,
1194
+ stopAfter: true,
1195
+ phase: "capture"
1196
+ },
1197
+ {
1198
+ prefix: "bind:",
1199
+ priority: 0,
1200
+ stopAfter: false,
1201
+ phase: "bubble"
1202
+ },
1203
+ {
1204
+ prefix: "catch:",
1205
+ priority: 0,
1206
+ stopAfter: true,
1207
+ phase: "bubble"
1208
+ },
1209
+ {
1210
+ prefix: "bind",
1211
+ priority: 1,
1212
+ stopAfter: false,
1213
+ phase: "bubble"
1214
+ },
1215
+ {
1216
+ prefix: "catch",
1217
+ priority: 1,
1218
+ stopAfter: true,
1219
+ phase: "bubble"
1220
+ }
1221
+ ];
1222
+ function parseEventBinding(attributeName, method) {
1223
+ const definition = EVENT_BINDING_PREFIXES.find((item) => attributeName.startsWith(item.prefix));
1224
+ if (!definition) return null;
1225
+ const eventName = attributeName.slice(definition.prefix.length);
1226
+ if (!eventName || !definition.prefix.endsWith(":") && !LEGACY_EVENT_NAME_RE.test(eventName)) return null;
1227
+ return {
1228
+ eventName,
1229
+ method,
1230
+ phase: definition.phase,
1231
+ priority: definition.priority,
1232
+ stopAfter: definition.stopAfter
1233
+ };
1234
+ }
1235
+ function collectEventBindings(attributes = {}, includeCapture = true) {
1236
+ const eventBindings = /* @__PURE__ */ new Map();
1237
+ let captureEventBindings;
1238
+ const priorities = /* @__PURE__ */ new Map();
1239
+ for (const [attributeName, method] of Object.entries(attributes)) {
1240
+ const candidate = parseEventBinding(attributeName, method);
1241
+ if (!candidate || !includeCapture && candidate.phase === "capture") continue;
1242
+ const eventPriorities = priorities.get(candidate.eventName) ?? {};
1243
+ if (candidate.priority < (eventPriorities[candidate.phase] ?? -1)) continue;
1244
+ (candidate.phase === "bubble" ? eventBindings : captureEventBindings ??= /* @__PURE__ */ new Map()).set(candidate.eventName, {
1245
+ method: candidate.method,
1246
+ stopAfter: candidate.stopAfter
1247
+ });
1248
+ eventPriorities[candidate.phase] = candidate.priority;
1249
+ priorities.set(candidate.eventName, eventPriorities);
1250
+ }
1251
+ return {
1252
+ eventBindings,
1253
+ captureEventBindings
1254
+ };
1255
+ }
1256
+ /** 单事件查询只保留命中的绑定,原生交互不构造整张事件表。 */
1257
+ function resolveEventBindingPhases(attributes, eventName, includeCapture = true) {
1258
+ if (!attributes) return null;
1259
+ let bindings = null;
1260
+ let bubblePriority = -1;
1261
+ let capturePriority = -1;
1262
+ for (const [attributeName, method] of Object.entries(attributes)) {
1263
+ const candidate = parseEventBinding(attributeName, method);
1264
+ if (!candidate || candidate.eventName !== eventName || !includeCapture && candidate.phase === "capture") continue;
1265
+ const priority = candidate.phase === "bubble" ? bubblePriority : capturePriority;
1266
+ if (candidate.priority < priority) continue;
1267
+ bindings ??= {};
1268
+ bindings[candidate.phase] = {
1269
+ method: candidate.method,
1270
+ stopAfter: candidate.stopAfter
1271
+ };
1272
+ if (candidate.phase === "bubble") bubblePriority = candidate.priority;
1273
+ else capturePriority = candidate.priority;
1274
+ }
1275
+ return bindings;
1276
+ }
1277
+ //#endregion
1278
+ //#region ../../mpcore/packages/simulator/src/view/nodeDataset.ts
1279
+ const DATASET_NAME_RE = /-([a-z])/g;
1280
+ function isDatasetAttribute(attributeName) {
1281
+ return attributeName.startsWith("data-") && !attributeName.startsWith("data-sim-");
1282
+ }
1283
+ function toDatasetKey(attributeName) {
1284
+ return attributeName.slice(5).replace(DATASET_NAME_RE, (_match, char) => char.toUpperCase());
1285
+ }
1286
+ function collectNodeDataset(node) {
1287
+ if (node.dataset) return { ...node.dataset };
1288
+ const dataset = {};
1289
+ for (const [key, value] of Object.entries(node.attribs ?? {})) if (isDatasetAttribute(key)) dataset[toDatasetKey(key)] = value;
1290
+ return dataset;
1291
+ }
1292
+ //#endregion
1293
+ //#region ../../mpcore/packages/simulator/src/view/componentEvent.ts
1294
+ const eventNodes = /* @__PURE__ */ new WeakMap();
1295
+ /** 在构造子树之前建立父链,created/observer 中的事件也能访问声明树。 */
1296
+ function registerComponentEventNode(node, scope, parent) {
1297
+ node.parent = parent ?? null;
1298
+ eventNodes.set(node, {
1299
+ scope,
1300
+ target: node
1301
+ });
1302
+ }
1303
+ /** 宿主与作用域共享同一份分阶段绑定,eventBindings 仍是原有的冒泡存储。 */
1304
+ function bindComponentEventHost(node) {
1305
+ const bindings = collectEventBindings(node.attribs);
1306
+ eventNodes.get(node).bindings = bindings;
1307
+ return bindings;
1308
+ }
1309
+ /** 渲染会把宿主折叠进模板根;事件仍保留两层独立的绑定和声明所有者。 */
1310
+ function mergeComponentEventRoot(root, host) {
1311
+ const entry = eventNodes.get(root);
1312
+ if (entry.target === root) entry.target = {
1313
+ attribs: root.attribs,
1314
+ dataset: root.dataset
1315
+ };
1316
+ entry.hosts ??= [];
1317
+ entry.hosts.push(host);
1318
+ root.parent = host.parent;
1319
+ }
1320
+ /** 按渲染完成后的实际宿主父链排序;输入只包含本轮存活的组件作用域。 */
1321
+ function orderComponentAttachmentScopes(scopeIds, componentScopes) {
1322
+ const hostScopes = /* @__PURE__ */ new Map();
1323
+ for (const scopeId of scopeIds) hostScopes.set(componentScopes.get(scopeId).hostNode, scopeId);
1324
+ const ordered = [];
1325
+ const visited = /* @__PURE__ */ new Set();
1326
+ const visit = (node) => {
1327
+ if (!node || visited.has(node)) return;
1328
+ visited.add(node);
1329
+ visit(node.parent);
1330
+ const hosts = eventNodes.get(node)?.hosts;
1331
+ if (hosts) for (let index = hosts.length - 1; index >= 0; index--) visit(hosts[index]);
1332
+ const scopeId = hostScopes.get(node);
1333
+ if (scopeId !== void 0) ordered.push(scopeId);
1334
+ };
1335
+ for (const host of hostScopes.keys()) visit(host);
1336
+ return ordered;
1337
+ }
1338
+ function buildComponentTrigger(componentScopeId, context) {
1339
+ return (instance, eventName, detail, triggerOptions) => {
1340
+ const originScope = context.componentScopes.get(componentScopeId);
1341
+ const origin = originScope?.hostNode;
1342
+ if (!originScope || !origin) return;
1343
+ const bubbles = triggerOptions?.bubbles ?? false;
1344
+ const capturePhase = triggerOptions?.capturePhase ?? false;
1345
+ const composed = triggerOptions?.composed ?? false;
1346
+ const target = {
1347
+ dataset: originScope.dataset ?? collectNodeDataset(origin),
1348
+ id: originScope.hostId ?? ""
1349
+ };
1350
+ const mark = instance.__lastInteractionEvent__?.mark;
1351
+ const listeners = [];
1352
+ const collect = (entry, isOrigin) => {
1353
+ if (!composed && entry.scope.getScopeId() !== originScope.listenerScopeId) return false;
1354
+ const bindings = entry.bindings ? {
1355
+ bubble: entry.bindings.eventBindings?.get(eventName),
1356
+ capture: entry.bindings.captureEventBindings?.get(eventName)
1357
+ } : resolveEventBindingPhases(entry.target.attribs, eventName);
1358
+ if (bindings?.bubble || bindings?.capture) listeners.push({
1359
+ scope: entry.scope,
1360
+ bindings,
1361
+ currentTarget: isOrigin ? target : {
1362
+ dataset: collectNodeDataset(entry.target),
1363
+ id: entry.target.attribs?.id ?? ""
1364
+ },
1365
+ origin: isOrigin
1366
+ });
1367
+ return true;
1368
+ };
1369
+ let current = origin;
1370
+ while (current) {
1371
+ const entry = eventNodes.get(current);
1372
+ if (entry) {
1373
+ if (!collect(entry, current === origin)) break;
1374
+ let crossedBoundary = false;
1375
+ for (const host of entry.hosts ?? []) if (!collect(eventNodes.get(host), false)) {
1376
+ crossedBoundary = true;
1377
+ break;
1378
+ }
1379
+ if (crossedBoundary) break;
1380
+ }
1381
+ if (!bubbles && !capturePhase) break;
1382
+ current = current.parent;
1383
+ }
1384
+ const handlers = [];
1385
+ let stopped = false;
1386
+ if (capturePhase) for (let index = listeners.length - 1; index >= 0; index--) {
1387
+ const listener = listeners[index];
1388
+ const binding = listener.bindings.capture;
1389
+ if (binding) {
1390
+ handlers.push({
1391
+ scope: listener.scope,
1392
+ binding,
1393
+ currentTarget: listener.currentTarget
1394
+ });
1395
+ if (binding.stopAfter) {
1396
+ stopped = true;
1397
+ break;
1398
+ }
1399
+ }
1400
+ }
1401
+ if (!stopped) for (const listener of listeners) {
1402
+ if (!bubbles && !listener.origin) continue;
1403
+ const binding = listener.bindings.bubble;
1404
+ if (binding) {
1405
+ handlers.push({
1406
+ scope: listener.scope,
1407
+ binding,
1408
+ currentTarget: listener.currentTarget
1409
+ });
1410
+ if (binding.stopAfter) break;
1411
+ }
1412
+ }
1413
+ for (const { scope, binding, currentTarget } of handlers) scope.getMethod(binding.method)?.({
1414
+ bubbles,
1415
+ capturePhase,
1416
+ composed,
1417
+ currentTarget,
1418
+ detail,
1419
+ mark,
1420
+ target,
1421
+ type: eventName
1422
+ });
1423
+ };
1424
+ }
1425
+ //#endregion
1129
1426
  //#region ../../mpcore/packages/simulator/src/view/conditionalChildren.ts
1130
1427
  function selectConditionalChildren(children, evaluate) {
1131
1428
  const selected = [];
@@ -1181,11 +1478,77 @@ function customTabBarHostScope() {
1181
1478
  };
1182
1479
  }
1183
1480
  //#endregion
1481
+ //#region ../../mpcore/packages/simulator/src/view/inspectionTree.ts
1482
+ const inspectionChildren = /* @__PURE__ */ new WeakMap();
1483
+ const inspectionParents = /* @__PURE__ */ new WeakMap();
1484
+ const emptyChildren = [];
1485
+ function getInspectionChildren(node) {
1486
+ return inspectionChildren.get(node) ?? node.children ?? emptyChildren;
1487
+ }
1488
+ function getInspectionParent(node) {
1489
+ return inspectionParents.get(node) ?? node.parent ?? null;
1490
+ }
1491
+ function collectRenderedNodes(node, nodes) {
1492
+ if (nodes.has(node)) return;
1493
+ nodes.add(node);
1494
+ for (const child of node.children ?? emptyChildren) collectRenderedNodes(child, nodes);
1495
+ }
1496
+ /** 原生检查器保留未投影声明;可见 DOM、WXML 和事件父链不因此增加节点。 */
1497
+ function registerInspectionTrees(root, pass, scopes) {
1498
+ if (!pass.hasUnprojectedSlots) return;
1499
+ const rendered = /* @__PURE__ */ new Set();
1500
+ collectRenderedNodes(root, rendered);
1501
+ const candidates = /* @__PURE__ */ new Map();
1502
+ for (const [entries, group] of pass.slotDeclarations) {
1503
+ if (scopes.get(group.scopeId)?.hostNode !== group.parent) continue;
1504
+ const host = pass.componentRoots.get(group.scopeId);
1505
+ if (!host) continue;
1506
+ for (const entry of entries) for (const node of pass.renderedSlots.get(entry) ?? emptyChildren) candidates.set(node, host);
1507
+ }
1508
+ const nested = /* @__PURE__ */ new Set();
1509
+ const visited = /* @__PURE__ */ new Set();
1510
+ const indexChildren = (node) => {
1511
+ if (visited.has(node)) return;
1512
+ visited.add(node);
1513
+ for (const child of node.children ?? emptyChildren) {
1514
+ nested.add(child);
1515
+ if (!rendered.has(child)) inspectionParents.set(child, node);
1516
+ indexChildren(child);
1517
+ }
1518
+ };
1519
+ for (const node of candidates.keys()) indexChildren(node);
1520
+ for (const [node, host] of candidates) {
1521
+ if (rendered.has(node) || nested.has(node) || node === host) continue;
1522
+ let children = inspectionChildren.get(host);
1523
+ if (!children) {
1524
+ children = [...host.children ?? emptyChildren];
1525
+ inspectionChildren.set(host, children);
1526
+ }
1527
+ children.push(node);
1528
+ inspectionParents.set(node, host);
1529
+ }
1530
+ }
1531
+ //#endregion
1184
1532
  //#region ../../mpcore/packages/simulator/src/view/loopEntries.ts
1533
+ const LOOP_SCOPE_ESCAPE_RE = /[^\w-]/g;
1534
+ function escapeScopeCodeUnit(character) {
1535
+ return `~${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
1536
+ }
1537
+ function encodeLoopCoordinate(value) {
1538
+ return value.replace(LOOP_SCOPE_ESCAPE_RE, escapeScopeCodeUnit);
1539
+ }
1185
1540
  function resolveLoopEntries(value) {
1186
1541
  if (Array.isArray(value)) return value.map((item, index) => [index, item]);
1187
1542
  return value !== null && typeof value === "object" ? Object.entries(value) : [];
1188
1543
  }
1544
+ function resolveLoopInstanceSuffix(item, index, key, occurrences) {
1545
+ if (!key || !occurrences) return `:for-${encodeLoopCoordinate(String(index))}`;
1546
+ const value = key === "*this" ? item : item?.[key];
1547
+ const normalized = encodeLoopCoordinate(String(value ?? ""));
1548
+ const occurrence = occurrences.get(normalized) ?? 0;
1549
+ occurrences.set(normalized, occurrence + 1);
1550
+ return `:key-${normalized}${occurrence ? `:duplicate-${occurrence}` : ""}`;
1551
+ }
1189
1552
  //#endregion
1190
1553
  //#region ../../mpcore/packages/simulator/src/view/renderedTree.ts
1191
1554
  /** 在循环展开和组件替换完成后,让父节点关系指向本次渲染树。 */
@@ -1194,7 +1557,45 @@ function linkRenderedParents(root, parent = null) {
1194
1557
  for (const child of root.children ?? []) linkRenderedParents(child, root);
1195
1558
  }
1196
1559
  //#endregion
1197
- //#region ../../node_modules/.pnpm/@babel+parser@8.0.6/node_modules/@babel/parser/lib/index.js
1560
+ //#region ../../mpcore/packages/simulator/src/view/renderPass.ts
1561
+ const initializedPages = /* @__PURE__ */ new WeakMap();
1562
+ function createRenderPass(propertyUpdate = false, parent) {
1563
+ return {
1564
+ seenComponentScopes: /* @__PURE__ */ new Set(),
1565
+ componentRoots: /* @__PURE__ */ new Map(),
1566
+ slotDeclarations: /* @__PURE__ */ new Map(),
1567
+ renderedSlots: /* @__PURE__ */ new Map(),
1568
+ hasUnprojectedSlots: false,
1569
+ propertyUpdate,
1570
+ bindingUpdate: parent?.bindingUpdate,
1571
+ bindingRevision: parent?.bindingUpdate?.revision ?? 0
1572
+ };
1573
+ }
1574
+ function createPageRenderPass(page, cache) {
1575
+ const pass = createRenderPass(initializedPages.get(cache)?.has(page) ?? false);
1576
+ pass.bindingUpdate = getAttachmentBindingRevision(page);
1577
+ pass.bindingRevision = pass.bindingUpdate?.revision ?? 0;
1578
+ return pass;
1579
+ }
1580
+ function isRenderPassStale(pass) {
1581
+ return pass.bindingUpdate !== void 0 && pass.bindingUpdate.revision !== pass.bindingRevision;
1582
+ }
1583
+ function markPageTreeConstructed(page, cache) {
1584
+ let pages = initializedPages.get(cache);
1585
+ if (!pages) {
1586
+ pages = /* @__PURE__ */ new WeakSet();
1587
+ initializedPages.set(cache, pages);
1588
+ }
1589
+ pages.add(page);
1590
+ }
1591
+ function registerSlotDeclarations(pass, slots, parent, scopeId) {
1592
+ for (const entries of slots.values()) if (!pass.slotDeclarations.has(entries)) pass.slotDeclarations.set(entries, {
1593
+ parent,
1594
+ scopeId
1595
+ });
1596
+ }
1597
+ //#endregion
1598
+ //#region ../../node_modules/.pnpm/@babel+parser@8.0.7/node_modules/@babel/parser/lib/index.js
1198
1599
  var Position = class {
1199
1600
  constructor(line, col, index) {
1200
1601
  this.line = void 0;
@@ -9263,7 +9664,11 @@ var StatementParser = class extends ExpressionParser {
9263
9664
  if (isDeclaration) node.id = this.parseFunctionId(requireId);
9264
9665
  this.scope.enter(514);
9265
9666
  this.prodParam.enter(functionFlags(isAsync, node.generator));
9266
- if (!isDeclaration) node.id = this.parseFunctionId();
9667
+ if (!isDeclaration && tokenIsIdentifier(this.state.type)) {
9668
+ this.expressionScope.enter(newExpressionScope());
9669
+ node.id = this.parseFunctionId();
9670
+ this.expressionScope.exit();
9671
+ }
9267
9672
  this.parseFunctionParams(node, false);
9268
9673
  this.parseFunctionBodyAndFinish(node, isDeclaration ? "FunctionDeclaration" : "FunctionExpression");
9269
9674
  this.prodParam.exit();
@@ -12636,6 +13041,48 @@ function getParserClass(pluginsMap) {
12636
13041
  return cls;
12637
13042
  }
12638
13043
  //#endregion
13044
+ //#region ../../mpcore/packages/simulator/src/view/templateInterpolation.ts
13045
+ /** 按表达式词法寻找插值边界,字符串和对象内部的大括号不结束绑定。 */
13046
+ function templateInterpolations(source) {
13047
+ const ranges = [];
13048
+ let offset = 0;
13049
+ while (offset < source.length) {
13050
+ const start = source.indexOf("{{", offset);
13051
+ if (start < 0) break;
13052
+ let quote;
13053
+ let depth = 0;
13054
+ let closed = false;
13055
+ for (let i = start + 2; i < source.length; i++) {
13056
+ const char = source[i];
13057
+ if (quote) {
13058
+ if (char === "\\") i++;
13059
+ else if (char === quote) quote = void 0;
13060
+ } else if (char === "'" || char === "\"") quote = char;
13061
+ else if (char === "{") depth++;
13062
+ else if (char === "}") {
13063
+ if (depth === 0 && source[i + 1] === "}") {
13064
+ ranges.push({
13065
+ start,
13066
+ end: i + 2,
13067
+ expression: source.slice(start + 2, i)
13068
+ });
13069
+ offset = i + 2;
13070
+ closed = true;
13071
+ break;
13072
+ }
13073
+ depth--;
13074
+ }
13075
+ }
13076
+ if (!closed) break;
13077
+ }
13078
+ return ranges;
13079
+ }
13080
+ function isTemplateExpression(value) {
13081
+ const trimmed = value.trim();
13082
+ const ranges = templateInterpolations(trimmed);
13083
+ return ranges.length === 1 && ranges[0].start === 0 && ranges[0].end === trimmed.length;
13084
+ }
13085
+ //#endregion
12639
13086
  //#region ../../mpcore/packages/simulator/src/view/wxs.ts
12640
13087
  const wxsFunctions = /* @__PURE__ */ new WeakSet();
12641
13088
  function registerWxsExports(value, visited = /* @__PURE__ */ new Set()) {
@@ -12689,13 +13136,9 @@ const BLOCKED_MEMBER_KEYS = /* @__PURE__ */ new Set([
12689
13136
  "prototype"
12690
13137
  ]);
12691
13138
  const NUMERIC_DOT_PATH_RE = /\.(\d+)(?=[.[\s,}\]]|$)/g;
12692
- function isMustacheOnly$2(value) {
12693
- const trimmed = value.trim();
12694
- return trimmed.startsWith("{{") && trimmed.endsWith("}}") && !trimmed.includes("{{", 2);
12695
- }
12696
13139
  function unwrapMustacheExpression(expression) {
12697
13140
  const normalized = expression.trim();
12698
- if (isMustacheOnly$2(normalized)) return normalized.slice(2, -2).trim();
13141
+ if (isTemplateExpression(normalized)) return normalized.slice(2, -2).trim();
12699
13142
  return normalized;
12700
13143
  }
12701
13144
  function parseTemplateExpression(expression) {
@@ -12875,7 +13318,8 @@ function isComponentAttached(instance) {
12875
13318
  return attachmentStates.get(instance) === "attached";
12876
13319
  }
12877
13320
  function hasPendingComponentAttachments(instances) {
12878
- return [...instances].some((instance) => attachmentStates.get(instance) === "attaching");
13321
+ for (const instance of instances) if (attachmentStates.get(instance) === "attaching") return true;
13322
+ return false;
12879
13323
  }
12880
13324
  function flushComponentReady(instances, ready) {
12881
13325
  const all = [...instances];
@@ -12885,19 +13329,71 @@ function flushComponentReady(instances, ready) {
12885
13329
  for (const instance of pending) ready(instance);
12886
13330
  return pending.length > 0;
12887
13331
  }
12888
- function flushComponentAttachments(instances, attach) {
12889
- const pending = [...instances].filter((instance) => !attachmentStates.has(instance));
12890
- for (const instance of pending) attachmentStates.set(instance, "attaching");
13332
+ function flushComponentAttachments(componentCache, scopeIds, attach) {
13333
+ const pending = /* @__PURE__ */ new Map();
13334
+ for (const scopeId of scopeIds) {
13335
+ const instance = componentCache.get(scopeId);
13336
+ if (!instance) continue;
13337
+ const state = attachmentStates.get(instance);
13338
+ if (state === "attaching") return false;
13339
+ if (!state) pending.set(scopeId, instance);
13340
+ }
13341
+ for (const instance of pending.values()) attachmentStates.set(instance, "attaching");
12891
13342
  try {
12892
- for (const instance of pending) try {
12893
- attach(instance);
12894
- } finally {
12895
- attachmentStates.set(instance, "attached");
13343
+ for (const [scopeId, instance] of pending) {
13344
+ if (componentCache.get(scopeId) !== instance) return "restart";
13345
+ let restart;
13346
+ try {
13347
+ restart = attach(instance);
13348
+ } finally {
13349
+ attachmentStates.set(instance, "attached");
13350
+ }
13351
+ if (restart) return "restart";
12896
13352
  }
12897
13353
  } finally {
12898
- for (const instance of pending) if (attachmentStates.get(instance) === "attaching") attachmentStates.delete(instance);
13354
+ for (const instance of pending.values()) if (attachmentStates.get(instance) === "attaching") attachmentStates.delete(instance);
12899
13355
  }
12900
- return pending.length > 0;
13356
+ return pending.size > 0 ? "attached" : false;
13357
+ }
13358
+ //#endregion
13359
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/construction.ts
13360
+ const constructionStates = /* @__PURE__ */ new WeakMap();
13361
+ const constructingInstances = /* @__PURE__ */ new WeakSet();
13362
+ function isComponentConstructing(instance) {
13363
+ return instance !== void 0 && constructingInstances.has(instance);
13364
+ }
13365
+ function hasPendingComponentConstruction(componentCache) {
13366
+ return constructionStates.has(componentCache);
13367
+ }
13368
+ function beginComponentConstruction(componentCache, scopeId, instance) {
13369
+ let state = constructionStates.get(componentCache);
13370
+ if (!state) {
13371
+ state = {
13372
+ depth: 0,
13373
+ created: /* @__PURE__ */ new Map()
13374
+ };
13375
+ constructionStates.set(componentCache, state);
13376
+ }
13377
+ state.depth++;
13378
+ state.created.set(scopeId, instance);
13379
+ constructingInstances.add(instance);
13380
+ }
13381
+ function discardComponentConstruction(componentCache, componentScopes, scopeId) {
13382
+ const state = constructionStates.get(componentCache);
13383
+ const prefix = `${scopeId}/`;
13384
+ for (const [createdScopeId, instance] of state.created) {
13385
+ if (createdScopeId !== scopeId && !createdScopeId.startsWith(prefix)) continue;
13386
+ if (componentCache.get(createdScopeId) === instance) {
13387
+ componentCache.delete(createdScopeId);
13388
+ componentScopes.delete(createdScopeId);
13389
+ }
13390
+ state.created.delete(createdScopeId);
13391
+ }
13392
+ }
13393
+ function finishComponentConstruction(componentCache, instance) {
13394
+ constructingInstances.delete(instance);
13395
+ const state = constructionStates.get(componentCache);
13396
+ if (--state.depth === 0) constructionStates.delete(componentCache);
12901
13397
  }
12902
13398
  //#endregion
12903
13399
  //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/discardedReady.ts
@@ -12920,12 +13416,41 @@ function flushDiscardedComponentReady(cache, pageScopeId, ready) {
12920
13416
  if (!pending.size) discardedReady.delete(cache);
12921
13417
  }
12922
13418
  //#endregion
13419
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/propertyBindings.ts
13420
+ const consumedPageChanges = /* @__PURE__ */ new WeakMap();
13421
+ function syncComponentProperties(instance, definition, nextProperties, bindingExpressions, changedPageKeys, beforeObservers) {
13422
+ const freshPageChanges = changedPageKeys.length > 0 && consumedPageChanges.get(instance) !== changedPageKeys;
13423
+ if (freshPageChanges) consumedPageChanges.set(instance, changedPageKeys);
13424
+ const changedRootKeys = [];
13425
+ const previousProperties = {};
13426
+ for (const [key, value] of Object.entries(nextProperties)) {
13427
+ const nextValue = normalizeComponentPropertyValue(definition, key, value);
13428
+ const bindingExpression = bindingExpressions[key];
13429
+ const bindingAffected = freshPageChanges && !!bindingExpression && changedPageKeys.some((changedKey) => {
13430
+ return changedKey === bindingExpression || changedKey.startsWith(`${bindingExpression}.`) || changedKey.startsWith(`${bindingExpression}[`);
13431
+ });
13432
+ const previousSnapshot = instance.__propertySnapshots?.[key];
13433
+ if (hasComponentPropertyValueChanged(instance.properties[key], previousSnapshot, nextValue, bindingAffected)) {
13434
+ previousProperties[key] = instance.properties[key];
13435
+ const deliveredValue = cloneValue$1(nextValue);
13436
+ instance.properties[key] = deliveredValue;
13437
+ if (Object.hasOwn(definition.properties ?? {}, key)) instance.data[key] = deliveredValue;
13438
+ changedRootKeys.push(key);
13439
+ }
13440
+ instance.__propertySnapshots ??= {};
13441
+ instance.__propertySnapshots[key] = cloneValue$1(nextValue);
13442
+ }
13443
+ if (changedRootKeys.length === 0) return;
13444
+ beforeObservers?.(instance, "properties");
13445
+ runComponentObservers(definition, instance, changedRootKeys, previousProperties);
13446
+ }
13447
+ //#endregion
12923
13448
  //#region ../../mpcore/packages/simulator/src/runtime/pageLifecycle.ts
12924
13449
  const loadingPages = /* @__PURE__ */ new WeakSet();
12925
13450
  function isPageBeforeReady(page) {
12926
13451
  return loadingPages.has(page);
12927
13452
  }
12928
- function runInitialPageLifecycles(page, query, scheduler, isAlive, renderComponents) {
13453
+ function runInitialPageLifecycles(page, query, scheduler, isAlive, renderComponents, onReady) {
12929
13454
  loadingPages.add(page);
12930
13455
  try {
12931
13456
  renderComponents?.();
@@ -12940,7 +13465,10 @@ function runInitialPageLifecycles(page, query, scheduler, isAlive, renderCompone
12940
13465
  scheduler.setTimeout(() => {
12941
13466
  if (!isAlive()) return;
12942
13467
  page.onReady?.();
12943
- if (isAlive()) page.onRouteDone?.({});
13468
+ if (isAlive()) {
13469
+ page.onRouteDone?.({});
13470
+ onReady?.();
13471
+ }
12944
13472
  }, 0);
12945
13473
  }
12946
13474
  //#endregion
@@ -13005,61 +13533,11 @@ function createPluginArtifactSource(fallback, miniprogramRootPath, plugins) {
13005
13533
  readText: (filePath) => fallback.readText(resolvePhysicalPath(filePath))
13006
13534
  };
13007
13535
  }
13008
- //#endregion
13009
- //#region ../../mpcore/packages/simulator/src/view/eventBinding.ts
13010
- const LEGACY_EVENT_NAME_RE = /^\w+$/;
13011
- const EVENT_BINDING_PREFIXES = [
13012
- {
13013
- prefix: "bind:",
13014
- priority: 0,
13015
- stopAfter: false
13016
- },
13017
- {
13018
- prefix: "catch:",
13019
- priority: 0,
13020
- stopAfter: true
13021
- },
13022
- {
13023
- prefix: "bind",
13024
- priority: 1,
13025
- stopAfter: false
13026
- },
13027
- {
13028
- prefix: "catch",
13029
- priority: 1,
13030
- stopAfter: true
13031
- }
13032
- ];
13033
- function parseEventBinding(attributeName, method) {
13034
- const definition = EVENT_BINDING_PREFIXES.find((item) => attributeName.startsWith(item.prefix));
13035
- if (!definition) return null;
13036
- const eventName = attributeName.slice(definition.prefix.length);
13037
- if (!eventName || !definition.prefix.endsWith(":") && !LEGACY_EVENT_NAME_RE.test(eventName)) return null;
13038
- return {
13039
- eventName,
13040
- method,
13041
- priority: definition.priority,
13042
- stopAfter: definition.stopAfter
13043
- };
13044
- }
13045
- function collectMiniProgramEventBindings(attributes = {}) {
13046
- const bindings = /* @__PURE__ */ new Map();
13047
- const priorities = /* @__PURE__ */ new Map();
13048
- for (const [attributeName, method] of Object.entries(attributes)) {
13049
- const candidate = parseEventBinding(attributeName, method);
13050
- if (!candidate || candidate.priority < (priorities.get(candidate.eventName) ?? -1)) continue;
13051
- bindings.set(candidate.eventName, {
13052
- method: candidate.method,
13053
- stopAfter: candidate.stopAfter
13054
- });
13055
- priorities.set(candidate.eventName, candidate.priority);
13056
- }
13057
- return bindings;
13058
- }
13059
- function resolveMiniProgramEventBinding(attributes, eventName) {
13060
- return collectMiniProgramEventBindings(attributes).get(eventName) ?? null;
13061
- }
13062
13536
  const WEVU_NATIVE_INSTANCE_KEY = "__wevuNativeInstance";
13537
+ /** 宿主值为 1 时,实现带 routeEventId 的路由阶段契约;不代表微信 SDK 版本。 */
13538
+ const WEVU_ROUTE_EVENT_CONTRACT_KEY = "__wevuRouteEventContract";
13539
+ /** 宿主负责 pageScrollTo 的实际滚动事件,框架不再合成第二份通知。 */
13540
+ const WEVU_PAGE_SCROLL_EVENT_CONTRACT_KEY = "__wevuPageScrollEventContract";
13063
13541
  //#endregion
13064
13542
  //#region ../../mpcore/packages/simulator/src/view/selectorQueryScope.ts
13065
13543
  const MPCORE_COMPONENT_SCOPE_ID = "__mpcoreComponentScopeId";
@@ -13168,21 +13646,6 @@ function resolveMiniProgramComponent(ownerFilePath, request, miniprogramRootPath
13168
13646
  return npmPath ? relative(miniprogramRootPath, npmPath).slice(0, -3) : localPath;
13169
13647
  }
13170
13648
  //#endregion
13171
- //#region ../../mpcore/packages/simulator/src/view/nodeDataset.ts
13172
- const DATASET_NAME_RE = /-([a-z])/g;
13173
- function isDatasetAttribute(attributeName) {
13174
- return attributeName.startsWith("data-") && !attributeName.startsWith("data-sim-");
13175
- }
13176
- function toDatasetKey(attributeName) {
13177
- return attributeName.slice(5).replace(DATASET_NAME_RE, (_match, char) => char.toUpperCase());
13178
- }
13179
- function collectNodeDataset(node) {
13180
- if (node.dataset) return { ...node.dataset };
13181
- const dataset = {};
13182
- for (const [key, value] of Object.entries(node.attribs ?? {})) if (isDatasetAttribute(key)) dataset[toDatasetKey(key)] = value;
13183
- return dataset;
13184
- }
13185
- //#endregion
13186
13649
  //#region ../../mpcore/packages/simulator/src/view/templateImports.ts
13187
13650
  function createImportedTemplateState(root, filePath, loadImport, loadWxs) {
13188
13651
  const documents = /* @__PURE__ */ new Map();
@@ -13234,12 +13697,15 @@ function createImportedTemplateState(root, filePath, loadImport, loadWxs) {
13234
13697
  }
13235
13698
  //#endregion
13236
13699
  //#region ../../mpcore/packages/simulator/src/view/templateText.ts
13237
- const TEMPLATE_INTERPOLATION_RE$2 = /\{\{([^{}]+)\}\}/g;
13238
- function interpolateTemplateText(input, data) {
13239
- return input.replace(TEMPLATE_INTERPOLATION_RE$2, (_match, expression) => {
13240
- const value = resolveTemplateExpression(data, expression);
13241
- return value === void 0 ? "" : String(value);
13242
- });
13700
+ function interpolateTemplateText(input, data, emptyNull = false) {
13701
+ let output = "";
13702
+ let offset = 0;
13703
+ for (const range of templateInterpolations(input)) {
13704
+ const value = resolveTemplateExpression(data, range.expression);
13705
+ output += input.slice(offset, range.start) + (value === void 0 || emptyNull && value === null ? "" : String(value));
13706
+ offset = range.end;
13707
+ }
13708
+ return output + input.slice(offset);
13243
13709
  }
13244
13710
  //#endregion
13245
13711
  //#region ../../node_modules/.pnpm/entities@8.1.0/node_modules/entities/dist/decode-codepoint.js
@@ -17015,24 +17481,83 @@ function parseDocument(data, options) {
17015
17481
  return handler.root;
17016
17482
  }
17017
17483
  //#endregion
17484
+ //#region ../../mpcore/packages/simulator/src/view/templateAttributes.ts
17485
+ function decodeAttribute(raw, quote) {
17486
+ const source = raw.replaceAll(`\\${quote}`, quote);
17487
+ let value = "";
17488
+ let offset = 0;
17489
+ for (const range of templateInterpolations(source)) {
17490
+ value += source.slice(offset, range.start).replace(/\\([\s\S])/g, "$1");
17491
+ value += source.slice(range.start, range.end);
17492
+ offset = range.end;
17493
+ }
17494
+ return value + source.slice(offset).replace(/\\([\s\S])/g, "$1");
17495
+ }
17496
+ /** 先保存 WXML 属性词法值,再交给 HTML parser 构建标签树,避免反斜杠引号提前截断属性。 */
17497
+ function maskTemplateAttributes(source) {
17498
+ const attributes = /* @__PURE__ */ new Map();
17499
+ if (!source.includes("\\")) return {
17500
+ source,
17501
+ attributes
17502
+ };
17503
+ let prefix = "__mpcore_quoted_attribute_";
17504
+ while (source.includes(prefix)) prefix += "_";
17505
+ let output = "";
17506
+ let offset = 0;
17507
+ for (let i = 0; i < source.length; i++) {
17508
+ if (source.startsWith("<!--", i)) {
17509
+ const end = source.indexOf("-->", i + 4);
17510
+ i = end < 0 ? source.length : end + 2;
17511
+ continue;
17512
+ }
17513
+ if (source[i] !== "<" || !/[a-z_]/i.test(source[i + 1] ?? "")) continue;
17514
+ for (i++; i < source.length && source[i] !== ">"; i++) {
17515
+ if (source[i] !== "\"" && source[i] !== "'") continue;
17516
+ const quote = source[i];
17517
+ const start = i + 1;
17518
+ for (i++; i < source.length && source[i] !== quote; i++) if (source[i] === "\\") i++;
17519
+ if (i >= source.length) break;
17520
+ const raw = source.slice(start, i);
17521
+ if (!raw.includes("\\")) continue;
17522
+ const token = `${prefix}${attributes.size}`;
17523
+ attributes.set(token, decodeAttribute(raw, quote));
17524
+ output += source.slice(offset, start) + token;
17525
+ offset = i;
17526
+ }
17527
+ }
17528
+ return {
17529
+ source: output + source.slice(offset),
17530
+ attributes
17531
+ };
17532
+ }
17533
+ //#endregion
17018
17534
  //#region ../../mpcore/packages/simulator/src/view/wxsDocument.ts
17019
17535
  const INLINE_WXS_RE = /<wxs\b([^>]*)(?<!\/)>([\s\S]*?)<\/wxs\s*>/g;
17020
17536
  function parseWxsTemplateDocument(templateSource) {
17021
17537
  const scripts = [];
17022
- const document = parseDocument(`<page>${templateSource.replace(INLINE_WXS_RE, (_match, attributes, script) => {
17538
+ const source = templateSource.replace(INLINE_WXS_RE, (_match, attributes, script) => {
17023
17539
  return `<wxs data-sim-wxs="${scripts.push(script) - 1}"${attributes}>wxs-source</wxs>`;
17024
- })}</page>`, {
17540
+ });
17541
+ const masked = maskTemplateAttributes(source);
17542
+ const document = parseDocument(`<page>${masked.source}</page>`, {
17025
17543
  xmlMode: false,
17026
17544
  decodeEntities: false,
17027
17545
  lowerCaseAttributeNames: false,
17028
17546
  lowerCaseTags: false,
17029
17547
  recognizeSelfClosing: true
17030
17548
  });
17549
+ if (!scripts.length && !masked.attributes.size && !/[\r\n]/.test(source)) return document;
17550
+ const hasAttributes = masked.attributes.size > 0;
17031
17551
  const restore = (node) => {
17552
+ if (hasAttributes && node.attribs) {
17553
+ for (const [name, token] of Object.entries(node.attribs)) if (masked.attributes.has(token)) node.attribs[name] = masked.attributes.get(token);
17554
+ }
17032
17555
  if (node.name === "wxs" && Object.hasOwn(node.attribs ?? {}, "data-sim-wxs")) {
17033
17556
  node.children[0].data = scripts[Number(node.attribs["data-sim-wxs"])];
17034
17557
  delete node.attribs["data-sim-wxs"];
17558
+ return;
17035
17559
  }
17560
+ if (node.children) node.children = node.children.filter((child) => child.type !== "text" || !/^[\t\r\n ]+$/.test(child.data ?? "") || !/[\r\n]/.test(child.data ?? ""));
17036
17561
  for (const child of node.children ?? []) restore(child);
17037
17562
  };
17038
17563
  restore(document);
@@ -17040,7 +17565,6 @@ function parseWxsTemplateDocument(templateSource) {
17040
17565
  }
17041
17566
  //#endregion
17042
17567
  //#region ../../mpcore/packages/simulator/src/runtime/render/shared.ts
17043
- const TEMPLATE_INTERPOLATION_RE$1 = /\{\{([^{}]+)\}\}/g;
17044
17568
  const LEADING_SLASH_RE$4 = /^\/+/;
17045
17569
  const EVENT_BINDING_ATTRS = [
17046
17570
  "bindtap",
@@ -17060,8 +17584,7 @@ const STRUCTURAL_ATTRS = [
17060
17584
  const CLASS_SPLIT_RE = /\s+/;
17061
17585
  const JS_FILE_RE = /\.js$/;
17062
17586
  function isMustacheOnly$1(value) {
17063
- const trimmed = value.trim();
17064
- return trimmed.startsWith("{{") && trimmed.endsWith("}}") && !trimmed.includes("{{", 2);
17587
+ return isTemplateExpression(value);
17065
17588
  }
17066
17589
  function collectDataset(node, source) {
17067
17590
  if (node.dataset) return collectNodeDataset(node);
@@ -17086,17 +17609,11 @@ function cloneNode(node) {
17086
17609
  children: node.children?.map((child) => cloneNode(child))
17087
17610
  };
17088
17611
  }
17089
- function resolveValueByPath$1(source, expression) {
17090
- return resolveTemplateExpression(source, expression) ?? "";
17091
- }
17092
17612
  function resolveRawValueByPath$1(source, expression) {
17093
17613
  return resolveTemplateExpression(source, expression);
17094
17614
  }
17095
17615
  function interpolateTemplate$1(input, data) {
17096
- return input.replace(TEMPLATE_INTERPOLATION_RE$1, (_match, expression) => {
17097
- const value = resolveValueByPath$1(data, expression);
17098
- return typeof value === "string" ? value : String(value);
17099
- });
17616
+ return interpolateTemplateText(input, data, true);
17100
17617
  }
17101
17618
  function readTemplateSource(artifactSource, filePath) {
17102
17619
  const source = artifactSource.readText(filePath);
@@ -17223,7 +17740,7 @@ function resolveUsingComponents(context, ownerJsonPath, ownerFilePath) {
17223
17740
  return /* @__PURE__ */ new Map();
17224
17741
  }
17225
17742
  }
17226
- function resolveComponentGenerics(context, hostNode, ownerJsonPath, ownerFilePath, componentFilePath) {
17743
+ function resolveComponentGenerics(context, hostNode, ownerJsonPath, ownerFilePath, componentFilePath, ownerGenerics) {
17227
17744
  const componentJsonPath = path.resolve(context.project.miniprogramRootPath, `${componentFilePath.replace(JS_FILE_RE, "")}.json`);
17228
17745
  const componentGenerics = readComponentConfig(context.artifactSource, componentJsonPath).componentGenerics;
17229
17746
  if (!componentGenerics || typeof componentGenerics !== "object" || Array.isArray(componentGenerics)) return;
@@ -17231,7 +17748,7 @@ function resolveComponentGenerics(context, hostNode, ownerJsonPath, ownerFilePat
17231
17748
  const resolved = /* @__PURE__ */ new Map();
17232
17749
  for (const [genericName, definition] of Object.entries(componentGenerics)) {
17233
17750
  const selectedAlias = hostNode.attribs?.[`generic:${genericName}`];
17234
- const selectedPath = selectedAlias ? ownerComponents.get(selectedAlias) : void 0;
17751
+ const selectedPath = selectedAlias ? ownerGenerics?.get(selectedAlias) ?? ownerComponents.get(selectedAlias) : void 0;
17235
17752
  if (selectedPath) {
17236
17753
  resolved.set(genericName, selectedPath);
17237
17754
  continue;
@@ -17243,66 +17760,6 @@ function resolveComponentGenerics(context, hostNode, ownerJsonPath, ownerFilePat
17243
17760
  }
17244
17761
  return resolved.size > 0 ? resolved : void 0;
17245
17762
  }
17246
- function collectComponentEventBindings(hostNode) {
17247
- return collectMiniProgramEventBindings(hostNode.attribs);
17248
- }
17249
- function buildComponentTrigger(componentScopeId, context, hostNode) {
17250
- const hostDataset = collectDataset(hostNode);
17251
- const hostId = hostNode.attribs?.id ?? "";
17252
- return (instance, eventName, detail, triggerOptions) => {
17253
- const originScope = context.componentScopes.get(componentScopeId);
17254
- const interactionMark = instance.__lastInteractionEvent__?.mark;
17255
- const target = {
17256
- dataset: originScope?.dataset ?? hostDataset,
17257
- id: originScope?.hostId ?? hostId
17258
- };
17259
- let currentScopeId = componentScopeId;
17260
- while (currentScopeId) {
17261
- const currentScope = context.componentScopes.get(currentScopeId);
17262
- const binding = currentScope?.eventBindings?.get(eventName);
17263
- const listenerScope = currentScope?.listenerScopeId ? context.componentScopes.get(currentScope.listenerScopeId) : null;
17264
- const handler = binding && listenerScope ? listenerScope.getMethod(binding.method) : void 0;
17265
- if (handler) handler({
17266
- bubbles: triggerOptions?.bubbles ?? false,
17267
- capturePhase: false,
17268
- composed: triggerOptions?.composed ?? false,
17269
- currentTarget: {
17270
- dataset: currentScope?.dataset ?? hostDataset,
17271
- id: currentScope?.hostId ?? hostId
17272
- },
17273
- detail,
17274
- mark: interactionMark,
17275
- target,
17276
- type: eventName
17277
- });
17278
- if (binding?.stopAfter) break;
17279
- if (!triggerOptions?.bubbles || !triggerOptions?.composed) break;
17280
- currentScopeId = currentScope?.ownerScopeId;
17281
- }
17282
- };
17283
- }
17284
- function syncComponentProperties(instance, definition, nextProperties, bindingExpressions, changedPageKeys) {
17285
- const changedRootKeys = [];
17286
- const previousProperties = {};
17287
- for (const [key, value] of Object.entries(nextProperties)) {
17288
- const nextValue = normalizeComponentPropertyValue(definition, key, value);
17289
- const bindingExpression = bindingExpressions[key];
17290
- const bindingAffected = !!bindingExpression && changedPageKeys.some((changedKey) => {
17291
- return changedKey === bindingExpression || changedKey.startsWith(`${bindingExpression}.`) || changedKey.startsWith(`${bindingExpression}[`);
17292
- });
17293
- const previousSnapshot = instance.__propertySnapshots?.[key];
17294
- if (hasComponentPropertyValueChanged(instance.properties[key], previousSnapshot, nextValue, bindingAffected)) {
17295
- previousProperties[key] = instance.properties[key];
17296
- instance.properties[key] = nextValue;
17297
- if (Object.hasOwn(definition.properties ?? {}, key)) instance.data[key] = nextValue;
17298
- changedRootKeys.push(key);
17299
- }
17300
- instance.__propertySnapshots ??= {};
17301
- instance.__propertySnapshots[key] = cloneValue$1(nextValue);
17302
- }
17303
- if (changedRootKeys.length === 0) return;
17304
- runComponentObservers(definition, instance, changedRootKeys, previousProperties);
17305
- }
17306
17763
  function createComponentScope(clonedNode, scope, componentScopeId, componentInstance, genericComponents, slots) {
17307
17764
  const ownerScopeId = scope.getScopeId().includes("/") ? scope.getScopeId() : void 0;
17308
17765
  return {
@@ -17310,7 +17767,7 @@ function createComponentScope(clonedNode, scope, componentScopeId, componentInst
17310
17767
  classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
17311
17768
  data: { ...componentInstance.data },
17312
17769
  dataset: collectDataset(clonedNode, wxsScopeData(scope)),
17313
- eventBindings: collectComponentEventBindings(clonedNode),
17770
+ ...bindComponentEventHost(clonedNode),
17314
17771
  getMethod: (methodName) => {
17315
17772
  const method = componentInstance?.[methodName];
17316
17773
  return typeof method === "function" ? method.bind(componentInstance) : void 0;
@@ -17318,6 +17775,7 @@ function createComponentScope(clonedNode, scope, componentScopeId, componentInst
17318
17775
  getScopeId: () => componentScopeId,
17319
17776
  genericComponents,
17320
17777
  hostId: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
17778
+ hostNode: clonedNode,
17321
17779
  id: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
17322
17780
  listenerScopeId: scope.getScopeId(),
17323
17781
  ownerScopeId,
@@ -17340,13 +17798,14 @@ function resolveComponentProperties(clonedNode, scope, definition) {
17340
17798
  bindingExpressions
17341
17799
  };
17342
17800
  }
17343
- function createRuntimeComponentInstance(componentScopeId, context, clonedNode, componentEntry, nextProperties, ownerScopeId, beforeCreated) {
17801
+ function createRuntimeComponentInstance(componentScopeId, context, componentEntry, nextProperties, ownerScopeId, renderTemplate, bindEventsBeforeProperties = false) {
17344
17802
  const componentProperties = Object.keys(componentEntry.definition.methods ?? {}).some((key) => key.startsWith("__weapp_vite_")) || Object.hasOwn(componentEntry.definition.properties ?? {}, "__wvSlotOwnerId") ? Object.fromEntries(Object.keys(componentEntry.definition.properties ?? {}).filter((key) => Object.hasOwn(nextProperties, key)).map((key) => [key, nextProperties[key]])) : nextProperties;
17345
- const componentInstance = createComponentInstance({
17803
+ const instanceOptions = {
17346
17804
  definition: componentEntry.definition,
17347
- requestRender: (callback) => context.session.requestRender(callback),
17348
- triggerEvent: buildComponentTrigger(componentScopeId, context, clonedNode)
17349
- });
17805
+ requestRender: (callback) => context.session.requestRender(callback)
17806
+ };
17807
+ const componentInstance = createComponentInstance(instanceOptions);
17808
+ bindAttachmentBindingScope(componentInstance, context);
17350
17809
  setSelectorQueryScopeId(componentInstance, componentScopeId);
17351
17810
  componentInstance.is = componentEntry.filePath.replace(JS_FILE_RE, "");
17352
17811
  componentInstance.createIntersectionObserver = (options) => context.session.createIntersectionObserver(componentInstance, options);
@@ -17354,19 +17813,29 @@ function createRuntimeComponentInstance(componentScopeId, context, clonedNode, c
17354
17813
  componentInstance.createSelectorQuery = () => context.moduleLoader.wx.createSelectorQuery().in(componentInstance);
17355
17814
  componentInstance.selectComponent = (selector) => resolveNativeComponentSelection(context.session.selectComponentWithin(componentScopeId, selector));
17356
17815
  componentInstance.selectAllComponents = (selector) => context.session.selectAllComponentsWithin(componentScopeId, selector).map(resolveNativeComponentSelection);
17357
- componentInstance.selectOwnerComponent = () => ownerScopeId ? context.componentCache.get(ownerScopeId) ?? null : null;
17816
+ componentInstance.selectOwnerComponent = () => ownerScopeId ? resolveNativeComponentSelection(context.componentCache.get(ownerScopeId)) : null;
17817
+ beginComponentConstruction(context.componentCache, componentScopeId, componentInstance);
17358
17818
  context.componentCache.set(componentScopeId, componentInstance);
17359
- beforeCreated?.(componentInstance);
17360
- runComponentLifecycle(componentInstance, "created");
17361
- componentInstance.__propertySnapshots = Object.fromEntries(Object.entries(componentInstance.properties).map(([key, propertyValue]) => [key, cloneValue$1(propertyValue)]));
17362
- syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, componentProperties, {}, []);
17363
- return componentInstance;
17819
+ try {
17820
+ renderTemplate?.(componentInstance, "defaults");
17821
+ runComponentLifecycle(componentInstance, "created");
17822
+ if (bindEventsBeforeProperties) instanceOptions.triggerEvent = buildComponentTrigger(componentScopeId, context);
17823
+ componentInstance.__propertySnapshots = Object.fromEntries(Object.entries(componentInstance.properties).map(([key, propertyValue]) => [key, cloneValue$1(propertyValue)]));
17824
+ syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, componentProperties, {}, [], renderTemplate);
17825
+ if (!bindEventsBeforeProperties) instanceOptions.triggerEvent = buildComponentTrigger(componentScopeId, context);
17826
+ return componentInstance;
17827
+ } catch (error) {
17828
+ discardComponentConstruction(context.componentCache, context.componentScopes, componentScopeId);
17829
+ throw error;
17830
+ } finally {
17831
+ finishComponentConstruction(context.componentCache, componentInstance);
17832
+ }
17364
17833
  }
17365
- function renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, seenComponentScopes) {
17834
+ function renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, renderPass) {
17366
17835
  const componentDocument = parseTemplateDocument(readTemplateSource(context.artifactSource, componentEntry.absoluteTemplatePath));
17367
17836
  const componentRoot = (componentDocument.children ?? [])[0] ?? componentDocument;
17368
17837
  const templateRenderState = prepareTemplateRenderState(context.artifactSource, componentRoot, componentEntry.absoluteTemplatePath, context.project.miniprogramRootPath, getRuntimeWxsLoader(context.moduleLoader, context.artifactSource));
17369
- return renderNodeTree(componentRoot, componentScope, context, path.resolve(context.project.miniprogramRootPath, `${componentEntry.filePath.replace(JS_FILE_RE, "")}.json`), componentEntry.filePath, componentScopeId, seenComponentScopes, templateRenderState);
17838
+ return renderNodeTree(componentRoot, componentScope, context, path.resolve(context.project.miniprogramRootPath, `${componentEntry.filePath.replace(JS_FILE_RE, "")}.json`), componentEntry.filePath, componentScopeId, renderPass, templateRenderState, componentScope.hostNode);
17370
17839
  }
17371
17840
  //#endregion
17372
17841
  //#region ../../mpcore/packages/simulator/src/runtime/render/index.ts
@@ -17380,24 +17849,33 @@ function expandNodeByFor(node, scope) {
17380
17849
  const items = resolveLoopEntries(resolveRawValueByPath$1(wxsScopeData(scope), forExpression));
17381
17850
  const itemName = node.attribs?.["wx:for-item"]?.trim() || "item";
17382
17851
  const indexName = node.attribs?.["wx:for-index"]?.trim() || "index";
17852
+ const key = node.attribs?.["wx:key"]?.trim();
17853
+ const keyOccurrences = key ? /* @__PURE__ */ new Map() : void 0;
17383
17854
  return items.map(([index, item]) => ({
17384
17855
  node: cloneNode(node),
17385
17856
  scope: createLoopScope(scope, itemName, indexName, item, index),
17386
- instanceSuffix: `:for-${index}`
17857
+ instanceSuffix: resolveLoopInstanceSuffix(item, index, key, keyOccurrences)
17387
17858
  }));
17388
17859
  }
17389
- function renderNodeVariants(node, scope, context, ownerJsonPath, ownerFilePath, instancePath, seenComponentScopes, templateRenderState) {
17390
- return expandNodeByFor(node, scope).map(({ node: expandedNode, scope: expandedScope, instanceSuffix }) => renderNodeTree(expandedNode, expandedScope, context, ownerJsonPath, ownerFilePath, `${instancePath}${instanceSuffix}`, seenComponentScopes, templateRenderState));
17860
+ function renderNodeVariants(node, scope, context, ownerJsonPath, ownerFilePath, instancePath, renderPass, templateRenderState, parent) {
17861
+ const nodes = [];
17862
+ if (isRenderPassStale(renderPass)) return nodes;
17863
+ for (const { node: expandedNode, scope: expandedScope, instanceSuffix } of expandNodeByFor(node, scope)) {
17864
+ if (isRenderPassStale(renderPass)) break;
17865
+ nodes.push(renderNodeTree(expandedNode, expandedScope, context, ownerJsonPath, ownerFilePath, `${instancePath}${instanceSuffix}`, renderPass, templateRenderState, parent));
17866
+ }
17867
+ return nodes;
17391
17868
  }
17392
- function renderChildren(children, scope, context, ownerJsonPath, ownerFilePath, instancePath, seenComponentScopes, templateRenderState) {
17869
+ function renderChildren(children, scope, context, ownerJsonPath, ownerFilePath, instancePath, renderPass, templateRenderState, parent) {
17393
17870
  const renderedChildren = [];
17394
17871
  for (const { node: child, index } of selectConditionalChildren(children, (node) => evaluateConditionalBranch(node, scope))) {
17872
+ if (isRenderPassStale(renderPass)) break;
17395
17873
  if (!isTagNode(child)) {
17396
- renderedChildren.push(...renderNodeVariants(child, scope, context, ownerJsonPath, ownerFilePath, `${instancePath}/node-${index}`, seenComponentScopes, templateRenderState));
17874
+ renderedChildren.push(...renderNodeVariants(child, scope, context, ownerJsonPath, ownerFilePath, `${instancePath}/node-${index}`, renderPass, templateRenderState, parent));
17397
17875
  continue;
17398
17876
  }
17399
17877
  if (isTemplateDefinition(child) || child.name === "import" || child.name === "wxs") continue;
17400
- renderedChildren.push(...renderNodeVariants(child, scope, context, ownerJsonPath, ownerFilePath, `${instancePath}/node-${index}`, seenComponentScopes, templateRenderState));
17878
+ renderedChildren.push(...renderNodeVariants(child, scope, context, ownerJsonPath, ownerFilePath, `${instancePath}/node-${index}`, renderPass, templateRenderState, parent));
17401
17879
  }
17402
17880
  return renderedChildren;
17403
17881
  }
@@ -17429,22 +17907,38 @@ function collectComponentSlots(componentNode, scope, ownerJsonPath, ownerFilePat
17429
17907
  }
17430
17908
  return slots;
17431
17909
  }
17432
- function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, instancePath, seenComponentScopes, templateRenderState) {
17910
+ function reconcileSlotDeclarations(context, renderPass) {
17911
+ for (const [entries, { parent, scopeId }] of renderPass.slotDeclarations) {
17912
+ if (isRenderPassStale(renderPass)) return;
17913
+ if (isComponentConstructing(context.componentCache.get(scopeId))) continue;
17914
+ for (const { index } of selectConditionalChildren(entries.map((entry) => entry.node), (node, index) => evaluateConditionalBranch(node, entries[index].scope))) {
17915
+ if (isRenderPassStale(renderPass)) return;
17916
+ const entry = entries[index];
17917
+ if (renderPass.renderedSlots.has(entry)) continue;
17918
+ const nodes = renderNodeVariants(entry.node, entry.scope, context, entry.ownerJsonPath, entry.ownerFilePath, entry.instancePath, renderPass, entry.templateRenderState, parent);
17919
+ renderPass.renderedSlots.set(entry, nodes);
17920
+ renderPass.hasUnprojectedSlots ||= nodes.length > 0;
17921
+ }
17922
+ }
17923
+ }
17924
+ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, instancePath, renderPass, templateRenderState, parent) {
17925
+ if (isRenderPassStale(renderPass)) return node;
17433
17926
  if (scope.wxs !== templateRenderState.wxsModules) scope = {
17434
17927
  ...scope,
17435
17928
  wxs: templateRenderState.wxsModules
17436
17929
  };
17437
17930
  const clonedNode = cloneNode(node);
17931
+ registerComponentEventNode(clonedNode, scope, parent);
17438
17932
  if (!isTagNode(clonedNode)) {
17439
17933
  applyNodeBindings(clonedNode, scope);
17440
17934
  return clonedNode;
17441
17935
  }
17442
- if (isTemplateDefinition(clonedNode)) return {
17443
- type: "tag",
17444
- name: "block",
17445
- attribs: {},
17446
- children: []
17447
- };
17936
+ if (isTemplateDefinition(clonedNode)) {
17937
+ clonedNode.name = "block";
17938
+ clonedNode.attribs = {};
17939
+ clonedNode.children = [];
17940
+ return clonedNode;
17941
+ }
17448
17942
  const templateName = resolveTemplateCall(clonedNode, wxsScopeData(scope));
17449
17943
  if (templateName) {
17450
17944
  const definition = templateRenderState.definitions.get(templateName);
@@ -17453,60 +17947,75 @@ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, inst
17453
17947
  data: resolveTemplateData(clonedNode, wxsScopeData(scope)),
17454
17948
  wxs: definition && templateRenderState.definitionWxsScopes?.get(definition)
17455
17949
  };
17456
- const children = definition && !templateRenderState.stack.includes(templateName) ? renderChildren(definition.children ?? [], templateScope, context, ownerJsonPath, ownerFilePath, `${instancePath}/template-${templateName}`, seenComponentScopes, {
17950
+ clonedNode.name = "block";
17951
+ clonedNode.attribs = {
17952
+ "data-sim-node": instancePath,
17953
+ "data-sim-scope": scope.getScopeId()
17954
+ };
17955
+ clonedNode.children = definition && !templateRenderState.stack.includes(templateName) ? renderChildren(definition.children ?? [], templateScope, context, ownerJsonPath, ownerFilePath, `${instancePath}/template-${templateName}`, renderPass, {
17457
17956
  definitions: templateRenderState.definitionScopes?.get(definition) ?? templateRenderState.definitions,
17458
17957
  definitionScopes: templateRenderState.definitionScopes,
17459
17958
  definitionWxsScopes: templateRenderState.definitionWxsScopes,
17460
17959
  wxsModules: templateScope.wxs,
17461
17960
  stack: [...templateRenderState.stack, templateName]
17462
- }) : [];
17463
- return {
17464
- type: "tag",
17465
- name: "block",
17466
- attribs: {
17467
- "data-sim-node": instancePath,
17468
- "data-sim-scope": scope.getScopeId()
17469
- },
17470
- children
17471
- };
17961
+ }, clonedNode) : [];
17962
+ return clonedNode;
17472
17963
  }
17473
17964
  if (clonedNode.name === "slot") {
17474
17965
  const slotName = clonedNode.attribs?.name?.trim() || "default";
17475
17966
  const projected = scope.slots?.get(slotName) ?? [];
17476
- const children = projected.length ? selectConditionalChildren(projected.map((entry) => entry.node), (node, index) => evaluateConditionalBranch(node, projected[index].scope)).flatMap(({ index }) => {
17477
- const entry = projected[index];
17478
- return renderNodeVariants(entry.node, entry.scope, context, entry.ownerJsonPath, entry.ownerFilePath, entry.instancePath, seenComponentScopes, entry.templateRenderState);
17479
- }) : renderChildren(clonedNode.children ?? [], scope, context, ownerJsonPath, ownerFilePath, `${instancePath}/slot-fallback-${slotName}`, seenComponentScopes, templateRenderState);
17480
- return {
17481
- type: "tag",
17482
- name: "block",
17483
- attribs: {
17484
- "data-sim-node": instancePath,
17485
- "data-sim-scope": scope.getScopeId()
17486
- },
17487
- children
17967
+ const fallbackChildren = clonedNode.children ?? [];
17968
+ bindComponentEventHost(clonedNode);
17969
+ clonedNode.name = "block";
17970
+ clonedNode.attribs = {
17971
+ "data-sim-node": instancePath,
17972
+ "data-sim-scope": scope.getScopeId()
17488
17973
  };
17974
+ const slotOwner = scope.getScopeId();
17975
+ if (projected.length && slotOwner && isComponentConstructing(context.componentCache.get(slotOwner))) {
17976
+ clonedNode.children = [];
17977
+ return clonedNode;
17978
+ }
17979
+ clonedNode.children = projected.length ? selectConditionalChildren(projected.map((entry) => entry.node), (node, index) => evaluateConditionalBranch(node, projected[index].scope)).flatMap(({ index }) => {
17980
+ const entry = projected[index];
17981
+ const nodes = renderNodeVariants(entry.node, entry.scope, context, entry.ownerJsonPath, entry.ownerFilePath, entry.instancePath, renderPass, entry.templateRenderState, clonedNode);
17982
+ renderPass.renderedSlots.set(entry, nodes);
17983
+ return nodes;
17984
+ }) : renderChildren(fallbackChildren, scope, context, ownerJsonPath, ownerFilePath, `${instancePath}/slot-fallback-${slotName}`, renderPass, templateRenderState, clonedNode);
17985
+ return clonedNode;
17489
17986
  }
17490
17987
  const componentEntry = resolveComponentRegistryEntry(context, ownerJsonPath, ownerFilePath, clonedNode.name, scope.genericComponents?.get(clonedNode.name));
17491
17988
  if (componentEntry) {
17492
17989
  const componentScopeId = `${instancePath}/${clonedNode.name}`;
17493
17990
  const ownerScopeId = scope.getScopeId().includes("/") ? scope.getScopeId() : void 0;
17494
17991
  const { nextProperties, bindingExpressions } = resolveComponentProperties(clonedNode, scope, componentEntry.definition);
17495
- const genericComponents = resolveComponentGenerics(context, clonedNode, ownerJsonPath, ownerFilePath, componentEntry.filePath);
17992
+ const genericComponents = resolveComponentGenerics(context, clonedNode, ownerJsonPath, ownerFilePath, componentEntry.filePath, scope.genericComponents);
17496
17993
  const slots = collectComponentSlots(clonedNode, scope, ownerJsonPath, ownerFilePath, componentScopeId, templateRenderState);
17994
+ registerSlotDeclarations(renderPass, slots, clonedNode, componentScopeId);
17995
+ applyNodeBindings(clonedNode, scope);
17996
+ const reconcilePrivateTemplate = (instance, phase) => {
17997
+ let bindingPass;
17998
+ do {
17999
+ const bindingScope = createComponentScope(clonedNode, scope, componentScopeId, instance, genericComponents, slots);
18000
+ context.componentScopes.set(componentScopeId, bindingScope);
18001
+ bindingPass = createRenderPass(phase === "properties", renderPass);
18002
+ renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, bindingScope, componentScopeId, bindingPass);
18003
+ reconcileSlotDeclarations(context, bindingPass);
18004
+ } while (isRenderPassStale(bindingPass));
18005
+ };
17497
18006
  let componentInstance = context.componentCache.get(componentScopeId);
17498
- if (!componentInstance) componentInstance = createRuntimeComponentInstance(componentScopeId, context, clonedNode, componentEntry, nextProperties, ownerScopeId, (instance) => {
17499
- const initialScope = createComponentScope(clonedNode, scope, componentScopeId, instance, genericComponents, slots);
17500
- context.componentScopes.set(componentScopeId, initialScope);
17501
- renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, initialScope, componentScopeId, /* @__PURE__ */ new Set());
17502
- });
17503
- else syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, nextProperties, bindingExpressions, context.changedPageKeys);
17504
- seenComponentScopes.add(componentScopeId);
18007
+ if (!componentInstance) componentInstance = createRuntimeComponentInstance(componentScopeId, context, componentEntry, nextProperties, ownerScopeId, reconcilePrivateTemplate, renderPass.propertyUpdate);
18008
+ else {
18009
+ context.componentScopes.set(componentScopeId, createComponentScope(clonedNode, scope, componentScopeId, componentInstance, genericComponents, slots));
18010
+ syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, nextProperties, bindingExpressions, context.changedPageKeys, reconcilePrivateTemplate);
18011
+ }
18012
+ if (isRenderPassStale(renderPass)) return clonedNode;
18013
+ renderPass.seenComponentScopes.add(componentScopeId);
17505
18014
  const componentScope = createComponentScope(clonedNode, scope, componentScopeId, componentInstance, genericComponents, slots);
17506
18015
  context.componentScopes.set(componentScopeId, componentScope);
17507
- const renderedComponentRoot = renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, seenComponentScopes);
18016
+ const renderedComponentRoot = renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, renderPass);
18017
+ mergeComponentEventRoot(renderedComponentRoot, clonedNode);
17508
18018
  if (renderedComponentRoot.attribs) {
17509
- applyNodeBindings(clonedNode, scope);
17510
18019
  renderedComponentRoot.attribs = {
17511
18020
  ...clonedNode.attribs,
17512
18021
  ...renderedComponentRoot.attribs
@@ -17519,66 +18028,99 @@ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, inst
17519
18028
  renderedComponentRoot.attribs["data-sim-node"] = instancePath;
17520
18029
  renderedComponentRoot.attribs["data-sim-scope"] = componentScopeId;
17521
18030
  }
18031
+ renderPass.componentRoots.set(componentScopeId, renderedComponentRoot);
17522
18032
  return renderedComponentRoot;
17523
18033
  }
17524
18034
  applyNodeBindings(clonedNode, scope);
17525
18035
  clonedNode.attribs["data-sim-node"] = instancePath;
17526
- clonedNode.children = renderChildren(clonedNode.children ?? [], scope, context, ownerJsonPath, ownerFilePath, `${instancePath}/${clonedNode.name}`, seenComponentScopes, templateRenderState);
18036
+ clonedNode.children = renderChildren(clonedNode.children ?? [], scope, context, ownerJsonPath, ownerFilePath, `${instancePath}/${clonedNode.name}`, renderPass, templateRenderState, clonedNode);
17527
18037
  return clonedNode;
17528
18038
  }
18039
+ function reconcileRuntimePageTree(context, page, bindingsOnly = false) {
18040
+ for (;;) {
18041
+ bindAttachmentBindingScope(context, page);
18042
+ if (page.__lastChangedKeys__) context.changedPageKeys = page.__lastChangedKeys__;
18043
+ const route = page.route.replace(LEADING_SLASH_RE$4, "");
18044
+ const resourcePath = context.project.routes.find((item) => item.route === route)?.resourcePath ?? route;
18045
+ const templatePath = path.resolve(context.project.miniprogramRootPath, `${resourcePath}.wxml`);
18046
+ const document = parseTemplateDocument(readTemplateSource(context.artifactSource, templatePath));
18047
+ const pageScopeId = `page:${route}`;
18048
+ const pageScope = {
18049
+ data: page.data,
18050
+ getMethod: (methodName) => {
18051
+ const method = page[methodName];
18052
+ return typeof method === "function" ? method.bind(page) : void 0;
18053
+ },
18054
+ getScopeId: () => pageScopeId,
18055
+ id: "page-root"
18056
+ };
18057
+ for (const scopeId of context.componentScopes.keys()) if (scopeId === pageScopeId || scopeId.startsWith(`${pageScopeId}/`)) context.componentScopes.delete(scopeId);
18058
+ context.componentScopes.set(pageScopeId, pageScope);
18059
+ const renderPass = createPageRenderPass(page, context.componentCache);
18060
+ const root = (document.children ?? [])[0] ?? document;
18061
+ const templateRenderState = prepareTemplateRenderState(context.artifactSource, root, templatePath, context.project.miniprogramRootPath, getRuntimeWxsLoader(context.moduleLoader, context.artifactSource));
18062
+ const renderedRoot = renderNodeTree(root, pageScope, context, path.resolve(context.project.miniprogramRootPath, `${resourcePath}.json`), `${resourcePath}.js`, pageScopeId, renderPass, templateRenderState);
18063
+ const roots = [renderedRoot];
18064
+ if (hasCustomTabBar(context.project.appConfig, route)) roots.push(renderNodeTree({
18065
+ type: "tag",
18066
+ name: "custom-tab-bar",
18067
+ attribs: {},
18068
+ children: []
18069
+ }, customTabBarHostScope(), context, path.resolve(context.project.miniprogramRootPath, "app.json"), "app.js", pageScopeId, renderPass, templateRenderState));
18070
+ reconcileSlotDeclarations(context, renderPass);
18071
+ if (isRenderPassStale(renderPass)) continue;
18072
+ markPageTreeConstructed(page, context.componentCache);
18073
+ const treeRoot = roots.length > 1 ? {
18074
+ type: "root",
18075
+ children: roots
18076
+ } : renderedRoot;
18077
+ linkRenderedParents(treeRoot);
18078
+ registerInspectionTrees(treeRoot, renderPass, context.componentScopes);
18079
+ if (bindingsOnly) return {
18080
+ root: treeRoot,
18081
+ roots
18082
+ };
18083
+ let bindingsRefreshed = false;
18084
+ const updateBindings = () => {
18085
+ bindingsRefreshed = true;
18086
+ if (hasPendingComponentConstruction(context.componentCache)) return;
18087
+ reconcileRuntimePageTree(context, page, true);
18088
+ };
18089
+ const pageAttached = attachComponentPage(page, updateBindings);
18090
+ if (bindingsRefreshed) continue;
18091
+ const pageAttaching = isComponentPageAttaching(page);
18092
+ const scopeIds = orderComponentAttachmentScopes(renderPass.seenComponentScopes, context.componentScopes);
18093
+ const instances = scopeIds.map((scopeId) => context.componentCache.get(scopeId));
18094
+ const attached = !pageAttaching && runWithAttachmentBindingUpdates(page, updateBindings, (scopes) => flushComponentAttachments(context.componentCache, scopes, (instance) => {
18095
+ runComponentLifecycle(instance, "attached");
18096
+ return bindingsRefreshed;
18097
+ }), scopeIds);
18098
+ if (attached === "restart") continue;
18099
+ const canFinalize = !pageAttaching && !hasPendingComponentAttachments(instances);
18100
+ const removed = canFinalize ? [...context.componentCache].filter(([scopeId]) => scopeId.startsWith(`${pageScopeId}/`) && !renderPass.seenComponentScopes.has(scopeId)) : [];
18101
+ for (const [scopeId, instance] of removed) if (isComponentAttached(instance)) runComponentLifecycle(instance, "detached");
18102
+ else scheduleDiscardedComponentReady(context.componentCache, scopeId, instance);
18103
+ const relationsChanged = canFinalize && syncComponentRelations(context.componentCache, renderPass.seenComponentScopes, pageScopeId);
18104
+ for (const [scopeId] of removed) {
18105
+ context.componentCache.delete(scopeId);
18106
+ context.componentScopes.delete(scopeId);
18107
+ }
18108
+ if (pageAttached || attached || relationsChanged) continue;
18109
+ if (canFinalize && !isPageBeforeReady(page)) flushDiscardedComponentReady(context.componentCache, pageScopeId, (instance) => runComponentLifecycle(instance, "ready"));
18110
+ if (canFinalize && !isPageBeforeReady(page) && flushComponentReady(instances, (instance) => {
18111
+ runComponentLifecycle(instance, "ready");
18112
+ if (instance !== context.componentCache.get(customTabBarScopeId(route))) instance.__definition__?.pageLifetimes?.show?.call(instance);
18113
+ })) continue;
18114
+ return {
18115
+ root: treeRoot,
18116
+ roots
18117
+ };
18118
+ }
18119
+ }
17529
18120
  function renderRuntimePageTree(context, page) {
17530
- const route = page.route.replace(LEADING_SLASH_RE$4, "");
17531
- const resourcePath = context.project.routes.find((item) => item.route === route)?.resourcePath ?? route;
17532
- const templatePath = path.resolve(context.project.miniprogramRootPath, `${resourcePath}.wxml`);
17533
- const document = parseTemplateDocument(readTemplateSource(context.artifactSource, templatePath));
17534
- const pageScopeId = `page:${route}`;
17535
- const pageScope = {
17536
- data: page.data,
17537
- getMethod: (methodName) => {
17538
- const method = page[methodName];
17539
- return typeof method === "function" ? method.bind(page) : void 0;
17540
- },
17541
- getScopeId: () => pageScopeId,
17542
- id: "page-root"
17543
- };
17544
- for (const scopeId of context.componentScopes.keys()) if (scopeId === pageScopeId || scopeId.startsWith(`${pageScopeId}/`)) context.componentScopes.delete(scopeId);
17545
- context.componentScopes.set(pageScopeId, pageScope);
17546
- const seenComponentScopes = /* @__PURE__ */ new Set();
17547
- const root = (document.children ?? [])[0] ?? document;
17548
- const templateRenderState = prepareTemplateRenderState(context.artifactSource, root, templatePath, context.project.miniprogramRootPath, getRuntimeWxsLoader(context.moduleLoader, context.artifactSource));
17549
- const renderedRoot = renderNodeTree(root, pageScope, context, path.resolve(context.project.miniprogramRootPath, `${resourcePath}.json`), `${resourcePath}.js`, pageScopeId, seenComponentScopes, templateRenderState);
17550
- const roots = [renderedRoot];
17551
- if (hasCustomTabBar(context.project.appConfig, route)) roots.push(renderNodeTree({
17552
- type: "tag",
17553
- name: "custom-tab-bar",
17554
- attribs: {},
17555
- children: []
17556
- }, customTabBarHostScope(), context, path.resolve(context.project.miniprogramRootPath, "app.json"), "app.js", pageScopeId, seenComponentScopes, templateRenderState));
17557
- const pageAttached = attachComponentPage(page);
17558
- const pageAttaching = isComponentPageAttaching(page);
17559
- const attached = !pageAttaching && flushComponentAttachments([...seenComponentScopes].map((scopeId) => context.componentCache.get(scopeId)), (instance) => runComponentLifecycle(instance, "attached"));
17560
- const removed = [...context.componentCache].filter(([scopeId]) => scopeId.startsWith(`${pageScopeId}/`) && !seenComponentScopes.has(scopeId));
17561
- for (const [scopeId, instance] of removed) if (isComponentAttached(instance)) runComponentLifecycle(instance, "detached");
17562
- else scheduleDiscardedComponentReady(context.componentCache, scopeId, instance);
17563
- const instances = [...seenComponentScopes].map((scopeId) => context.componentCache.get(scopeId));
17564
- const relationsChanged = !pageAttaching && !hasPendingComponentAttachments(instances) && syncComponentRelations(context.componentCache, seenComponentScopes, pageScopeId);
17565
- for (const [scopeId] of removed) {
17566
- context.componentCache.delete(scopeId);
17567
- context.componentScopes.delete(scopeId);
17568
- }
17569
- if (pageAttached || attached || relationsChanged) return renderRuntimePageTree(context, page);
17570
- if (!isPageBeforeReady(page)) flushDiscardedComponentReady(context.componentCache, pageScopeId, (instance) => runComponentLifecycle(instance, "ready"));
17571
- if (!isPageBeforeReady(page) && flushComponentReady(instances, (instance) => {
17572
- runComponentLifecycle(instance, "ready");
17573
- if (instance !== context.componentCache.get(customTabBarScopeId(route))) instance.__definition__?.pageLifetimes?.show?.call(instance);
17574
- })) return renderRuntimePageTree(context, page);
17575
- const treeRoot = roots.length > 1 ? {
17576
- type: "root",
17577
- children: roots
17578
- } : renderedRoot;
17579
- linkRenderedParents(treeRoot);
18121
+ const { root, roots } = reconcileRuntimePageTree(context, page);
17580
18122
  return {
17581
- root: treeRoot,
18123
+ root,
17582
18124
  wxml: roots.map(serializeDomNode$1).join("")
17583
18125
  };
17584
18126
  }
@@ -17602,15 +18144,23 @@ function createDuplicateRegistrationError(kind, id) {
17602
18144
  function callDefinitionMethod(instance, method, args) {
17603
18145
  if (typeof method === "function") return method.apply(instance, args);
17604
18146
  }
18147
+ function composeDefinitionMethods(primary, secondary) {
18148
+ if (typeof primary !== "function" || typeof secondary !== "function" || primary === secondary) return primary ?? secondary;
18149
+ return function composedDefinitionMethod(...args) {
18150
+ const result = primary.apply(this, args);
18151
+ secondary.apply(this, args);
18152
+ return result;
18153
+ };
18154
+ }
17605
18155
  function normalizeComponentPageDefinition(definition) {
17606
18156
  const { lifetimes = {}, methods = {}, pageLifetimes = {}, ...rest } = definition;
17607
18157
  const created = lifetimes.created ?? rest.created;
17608
18158
  const attached = lifetimes.attached ?? rest.attached;
17609
- const ready = lifetimes.ready ?? rest.ready ?? methods.onReady ?? rest.onReady;
18159
+ const ready = composeDefinitionMethods(lifetimes.ready ?? rest.ready, methods.onReady ?? rest.onReady);
17610
18160
  const detached = lifetimes.detached ?? rest.detached ?? methods.onUnload ?? rest.onUnload;
17611
18161
  const load = methods.onLoad ?? rest.onLoad;
17612
- const show = pageLifetimes.show ?? methods.onShow ?? rest.onShow;
17613
- const hide = pageLifetimes.hide ?? methods.onHide ?? rest.onHide;
18162
+ const show = typeof pageLifetimes.show === "function" ? composeDefinitionMethods(pageLifetimes.show, methods.onShow) : methods.onShow ?? rest.onShow;
18163
+ const hide = typeof pageLifetimes.hide === "function" ? composeDefinitionMethods(pageLifetimes.hide, methods.onHide) : methods.onHide ?? rest.onHide;
17614
18164
  const resize = pageLifetimes.resize ?? methods.onResize ?? rest.onResize;
17615
18165
  const routeDone = pageLifetimes.routeDone ?? methods.onRouteDone ?? rest.onRouteDone;
17616
18166
  const pageDefinition = {
@@ -17638,10 +18188,7 @@ function normalizeComponentPageDefinition(definition) {
17638
18188
  return callDefinitionMethod(this, routeDone, args);
17639
18189
  }
17640
18190
  };
17641
- registerComponentPageAttachment(pageDefinition, function() {
17642
- callDefinitionMethod(this, created, []);
17643
- callDefinitionMethod(this, attached, []);
17644
- });
18191
+ registerComponentPageAttachment(pageDefinition, (instance) => callDefinitionMethod(instance, created, []), (instance) => callDefinitionMethod(instance, attached, []));
17645
18192
  return pageDefinition;
17646
18193
  }
17647
18194
  function createHostRegistries() {
@@ -17775,7 +18322,13 @@ var StartupNavigationQueue = class {
17775
18322
  }
17776
18323
  }
17777
18324
  run(option, operation) {
17778
- if (!this.collecting) return operation(option);
18325
+ if (!this.collecting) {
18326
+ const submitted = option && { ...option };
18327
+ this.scheduler.queueMicrotask(() => {
18328
+ this.scheduler.setTimeout(() => operation(submitted), 0);
18329
+ });
18330
+ return;
18331
+ }
17779
18332
  const defer = (callback) => callback ? (...args) => {
17780
18333
  this.scheduler.setTimeout(() => callback(...args), 0);
17781
18334
  } : void 0;
@@ -17826,7 +18379,7 @@ function resolveCapabilityValue(source, schema) {
17826
18379
  }
17827
18380
  return current;
17828
18381
  }
17829
- function createHeadlessWx(driver, runtimeConsole = console) {
18382
+ function createHeadlessWx(driver, runtimeConsole = console, workers) {
17830
18383
  const eventBus = createHeadlessUniEventBus();
17831
18384
  const rpx2px = (value) => {
17832
18385
  const width = driver.getWindowInfoSync().windowWidth;
@@ -17834,6 +18387,10 @@ function createHeadlessWx(driver, runtimeConsole = console) {
17834
18387
  };
17835
18388
  const capabilityTree = {
17836
18389
  canIUse: true,
18390
+ ...workers ? {
18391
+ createWorker: true,
18392
+ preDownloadSubpackage: true
18393
+ } : {},
17837
18394
  canvasToTempFilePath: true,
17838
18395
  chooseImage: { return: {
17839
18396
  errMsg: true,
@@ -18034,6 +18591,14 @@ function createHeadlessWx(driver, runtimeConsole = console) {
18034
18591
  navigateBack: true,
18035
18592
  navigateTo: true,
18036
18593
  nextTick: true,
18594
+ offBeforeAppRoute: true,
18595
+ offBeforePageUnload: true,
18596
+ offAppRoute: true,
18597
+ offAppRouteDone: true,
18598
+ onBeforeAppRoute: true,
18599
+ onBeforePageUnload: true,
18600
+ onAppRoute: true,
18601
+ onAppRouteDone: true,
18037
18602
  offAppHide: true,
18038
18603
  offAppShow: true,
18039
18604
  offNetworkStatusChange: true,
@@ -18081,6 +18646,16 @@ function createHeadlessWx(driver, runtimeConsole = console) {
18081
18646
  };
18082
18647
  return {
18083
18648
  ...eventBus,
18649
+ [WEVU_ROUTE_EVENT_CONTRACT_KEY]: 1,
18650
+ [WEVU_PAGE_SCROLL_EVENT_CONTRACT_KEY]: 1,
18651
+ createWorker: (script) => {
18652
+ if (!workers) throw new Error("Worker host is unavailable");
18653
+ return workers.createWorker(script);
18654
+ },
18655
+ preDownloadSubpackage: (option) => {
18656
+ if (!workers) throw new Error("Worker host is unavailable");
18657
+ workers.preDownloadSubpackage(option);
18658
+ },
18084
18659
  canIUse: (schema) => typeof schema === "string" && schema.trim() !== "" && resolveCapabilityValue(capabilityTree, schema.trim()) != null,
18085
18660
  canvasToTempFilePath: (option) => invokeWxApi(() => driver.canvasToTempFilePath(option), option),
18086
18661
  chooseImage: (option) => invokeWxApi(() => driver.chooseImage(option ?? {}), option),
@@ -18204,10 +18779,16 @@ function createHeadlessWx(driver, runtimeConsole = console) {
18204
18779
  navigateBack: (option) => runNavigationApi(driver, option, (deferred) => invokeWxApi(() => {
18205
18780
  driver.navigateBack(deferred);
18206
18781
  }, deferred)),
18207
- navigateTo: (option) => runNavigationApi(driver, option, (deferred) => invokeWxApi(() => {
18208
- driver.navigateTo(deferred);
18209
- }, deferred)),
18782
+ navigateTo: (option) => runNavigationApi(driver, option, (deferred) => driver.navigateTo(deferred)),
18210
18783
  nextTick: (callback) => driver.nextTick(callback),
18784
+ offBeforeAppRoute: (listener) => driver.offBeforeAppRoute(listener),
18785
+ offBeforePageUnload: (listener) => driver.offBeforePageUnload(listener),
18786
+ offAppRoute: (listener) => driver.offAppRoute(listener),
18787
+ offAppRouteDone: (listener) => driver.offAppRouteDone(listener),
18788
+ onBeforeAppRoute: (listener) => driver.onBeforeAppRoute(listener),
18789
+ onBeforePageUnload: (listener) => driver.onBeforePageUnload(listener),
18790
+ onAppRoute: (listener) => driver.onAppRoute(listener),
18791
+ onAppRouteDone: (listener) => driver.onAppRouteDone(listener),
18211
18792
  offAppHide: (callback) => driver.offAppHide(callback),
18212
18793
  offAppShow: (callback) => driver.offAppShow(callback),
18213
18794
  offNetworkStatusChange: (callback) => driver.offNetworkStatusChange(callback),
@@ -18367,6 +18948,7 @@ var RuntimeScheduler = class {
18367
18948
  intervals = /* @__PURE__ */ new Set();
18368
18949
  timeouts = /* @__PURE__ */ new Set();
18369
18950
  closed = false;
18951
+ canceledHandle;
18370
18952
  onError;
18371
18953
  constructor(onError) {
18372
18954
  this.onError = onError;
@@ -18388,7 +18970,7 @@ var RuntimeScheduler = class {
18388
18970
  this.timeouts.clear();
18389
18971
  }
18390
18972
  queueMicrotask(handler) {
18391
- if (this.closed) throw new Error("Cannot schedule work after the runtime session has closed.");
18973
+ if (this.closed) return;
18392
18974
  queueMicrotask(() => {
18393
18975
  if (this.closed) return;
18394
18976
  try {
@@ -18399,7 +18981,7 @@ var RuntimeScheduler = class {
18399
18981
  });
18400
18982
  }
18401
18983
  setInterval(handler, timeout, ...args) {
18402
- if (this.closed) throw new Error("Cannot schedule work after the runtime session has closed.");
18984
+ if (this.closed) return this.getCanceledHandle();
18403
18985
  const handle = setInterval(() => {
18404
18986
  try {
18405
18987
  handler(...args);
@@ -18411,7 +18993,7 @@ var RuntimeScheduler = class {
18411
18993
  return handle;
18412
18994
  }
18413
18995
  setTimeout(handler, timeout, ...args) {
18414
- if (this.closed) throw new Error("Cannot schedule work after the runtime session has closed.");
18996
+ if (this.closed) return this.getCanceledHandle();
18415
18997
  const handle = setTimeout(() => {
18416
18998
  this.timeouts.delete(handle);
18417
18999
  try {
@@ -18423,6 +19005,14 @@ var RuntimeScheduler = class {
18423
19005
  this.timeouts.add(handle);
18424
19006
  return handle;
18425
19007
  }
19008
+ /** 已销毁 realm 的 Promise 回调不能复活任务,也不能向外泄漏异步拒绝。 */
19009
+ getCanceledHandle() {
19010
+ if (!this.canceledHandle) {
19011
+ this.canceledHandle = setTimeout(() => {}, 0);
19012
+ clearTimeout(this.canceledHandle);
19013
+ }
19014
+ return this.canceledHandle;
19015
+ }
18426
19016
  };
18427
19017
  //#endregion
18428
19018
  //#region ../../mpcore/packages/simulator/src/kernel/runtimeKernel.ts
@@ -18645,6 +19235,34 @@ function loadProject(projectPath) {
18645
19235
  });
18646
19236
  }
18647
19237
  //#endregion
19238
+ //#region ../../mpcore/packages/simulator/src/view/eventBinding.ts
19239
+ function resolveMiniProgramEventBinding(attributes, eventName) {
19240
+ return resolveEventBindingPhases(attributes, eventName, false)?.bubble ?? null;
19241
+ }
19242
+ /** 冒泡时保留原始 target,currentTarget 则属于当前执行绑定的节点。 */
19243
+ function createMiniProgramEventPayload(node, eventName, event, origin = node) {
19244
+ const dataset = collectNodeDataset(node);
19245
+ const nodeId = node.attribs?.id ?? "";
19246
+ const isOrigin = node === origin;
19247
+ return {
19248
+ bubbles: false,
19249
+ capturePhase: false,
19250
+ composed: false,
19251
+ currentTarget: {
19252
+ dataset: isOrigin ? event.currentTarget?.dataset ?? event.dataset ?? dataset : dataset,
19253
+ id: isOrigin ? event.currentTarget?.id ?? event.id ?? nodeId : nodeId
19254
+ },
19255
+ detail: event.detail,
19256
+ mark: event.mark,
19257
+ target: {
19258
+ dataset: event.target?.dataset ?? event.dataset ?? (isOrigin ? dataset : collectNodeDataset(origin)),
19259
+ id: event.target?.id ?? event.id ?? origin.attribs?.id ?? ""
19260
+ },
19261
+ timeStamp: Date.now(),
19262
+ type: eventName
19263
+ };
19264
+ }
19265
+ //#endregion
18648
19266
  //#region ../../node_modules/.pnpm/boolbase@2.0.0/node_modules/boolbase/dist/index.js
18649
19267
  function trueFunc() {
18650
19268
  return true;
@@ -20071,23 +20689,31 @@ function appendNextSiblings(element, adapter) {
20071
20689
  const selectAll = getSelectorFunction((query, elements, options) => query === falseFunc || !elements || elements.length === 0 ? [] : findAll(query, elements, options));
20072
20690
  //#endregion
20073
20691
  //#region ../../mpcore/packages/simulator/src/view/selectors.ts
20074
- const adapter = {
20075
- isTag: (node) => node.type === "tag",
20076
- getAttributeValue: (node, name) => node.attribs?.[name],
20077
- getChildren: (node) => node.children ?? [],
20078
- getName: (node) => node.name ?? "",
20079
- getParent: (node) => node.parent ?? null,
20080
- getSiblings: (node) => node.parent?.children ?? [node],
20081
- getText: (node) => node.type === "text" ? node.data ?? "" : (node.children ?? []).map((child) => adapter.getText(child)).join(""),
20082
- hasAttrib: (node, name) => Object.hasOwn(node.attribs ?? {}, name),
20083
- removeSubsets: (nodes) => {
20084
- const unique = new Set(nodes);
20085
- return [...unique].filter((node) => {
20086
- for (let parent = node.parent; parent; parent = parent.parent) if (unique.has(parent)) return false;
20087
- return true;
20088
- });
20089
- }
20090
- };
20692
+ function createAdapter(getChildren, getParent) {
20693
+ const adapter = {
20694
+ isTag: (node) => node.type === "tag",
20695
+ getAttributeValue: (node, name) => node.attribs?.[name],
20696
+ getChildren,
20697
+ getName: (node) => node.name ?? "",
20698
+ getParent,
20699
+ getSiblings: (node) => {
20700
+ const parent = getParent(node);
20701
+ return parent ? getChildren(parent) : [node];
20702
+ },
20703
+ getText: (node) => node.type === "text" ? node.data ?? "" : getChildren(node).map((child) => adapter.getText(child)).join(""),
20704
+ hasAttrib: (node, name) => Object.hasOwn(node.attribs ?? {}, name),
20705
+ removeSubsets: (nodes) => {
20706
+ const unique = new Set(nodes);
20707
+ return [...unique].filter((node) => {
20708
+ for (let parent = getParent(node); parent; parent = getParent(parent)) if (unique.has(parent)) return false;
20709
+ return true;
20710
+ });
20711
+ }
20712
+ };
20713
+ return adapter;
20714
+ }
20715
+ const renderedAdapter = createAdapter((node) => node.children ?? [], (node) => node.parent ?? null);
20716
+ const inspectionAdapter = createAdapter(getInspectionChildren, getInspectionParent);
20091
20717
  function resolveComponentTags(selectors) {
20092
20718
  return selectors.map((group) => group.map((token) => {
20093
20719
  if (token.type === SelectorType.Pseudo && Array.isArray(token.data)) return {
@@ -20110,7 +20736,7 @@ function resolveComponentTags(selectors) {
20110
20736
  };
20111
20737
  }));
20112
20738
  }
20113
- function querySelectorAll(root, selector) {
20739
+ function queryWithAdapter(root, selector, adapter) {
20114
20740
  const parsed = parse$1(selector);
20115
20741
  const query = resolveComponentTags(parsed);
20116
20742
  const includesPageRoot = root.type === "tag" && root.name === "page" && parsed.some((group) => group.some((token) => token.type === SelectorType.Tag && token.name === "page"));
@@ -20119,6 +20745,12 @@ function querySelectorAll(root, selector) {
20119
20745
  cacheResults: false
20120
20746
  });
20121
20747
  }
20748
+ function querySelectorAll(root, selector) {
20749
+ return queryWithAdapter(root, selector, renderedAdapter);
20750
+ }
20751
+ function queryInspectionSelectorAll(root, selector) {
20752
+ return queryWithAdapter(root, selector, inspectionAdapter);
20753
+ }
20122
20754
  //#endregion
20123
20755
  //#region ../../node_modules/.pnpm/whynot@5.0.0/node_modules/whynot/dist/whynot.umd.cjs
20124
20756
  var require_whynot_umd = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -37866,7 +38498,10 @@ var import_fontoxpath = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin((
37866
38498
  });
37867
38499
  })))(), 1);
37868
38500
  const xpathEngine = Reflect.get(import_fontoxpath, "default") ?? import_fontoxpath;
37869
- function wrapNode(source, parent = null) {
38501
+ function getRenderedChildren(node) {
38502
+ return node.children ?? [];
38503
+ }
38504
+ function wrapNode(source, getChildren, parent = null) {
37870
38505
  const node = {
37871
38506
  attributes: [],
37872
38507
  children: [],
@@ -37889,7 +38524,7 @@ function wrapNode(source, parent = null) {
37889
38524
  prefix: null,
37890
38525
  value
37891
38526
  }));
37892
- node.children = (source.children ?? []).map((child) => wrapNode(child, node));
38527
+ node.children = getChildren(source).map((child) => wrapNode(child, getChildren, node));
37893
38528
  return node;
37894
38529
  }
37895
38530
  function sibling(node, offset) {
@@ -37909,40 +38544,25 @@ const domFacade = {
37909
38544
  getParentNode: (node) => node.parent,
37910
38545
  getPreviousSibling: (node) => sibling(node, -1)
37911
38546
  };
37912
- function queryXPathElements(root, expression) {
38547
+ function queryElements(root, expression, getChildren) {
37913
38548
  if (!expression.trim()) throw new Error("XPath must be a non-empty expression in headless testing runtime.");
37914
38549
  const document = wrapNode(root.type === "root" ? root : {
37915
38550
  type: "root",
37916
38551
  children: [root]
37917
- });
38552
+ }, getChildren);
37918
38553
  return xpathEngine.evaluateXPathToNodes(expression, document, domFacade).filter((node) => node.nodeType === 1).map((node) => node.source);
37919
38554
  }
38555
+ function queryXPathElements(root, expression) {
38556
+ return queryElements(root, expression, getRenderedChildren);
38557
+ }
38558
+ function queryInspectionXPathElements(root, expression) {
38559
+ return queryElements(root, expression, getInspectionChildren);
38560
+ }
37920
38561
  //#endregion
37921
38562
  //#region ../../mpcore/packages/simulator/src/view/nodeHandle.ts
37922
38563
  function escapeText$1(text) {
37923
38564
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
37924
38565
  }
37925
- function createEventPayload$1(node, eventName, event) {
37926
- const dataset = collectNodeDataset(node);
37927
- const nodeId = node.attribs?.id ?? "";
37928
- return {
37929
- bubbles: false,
37930
- capturePhase: false,
37931
- composed: false,
37932
- currentTarget: {
37933
- dataset: event.currentTarget?.dataset ?? event.dataset ?? dataset,
37934
- id: event.currentTarget?.id ?? event.id ?? nodeId
37935
- },
37936
- detail: event.detail,
37937
- mark: event.mark,
37938
- target: {
37939
- dataset: event.target?.dataset ?? event.dataset ?? dataset,
37940
- id: event.target?.id ?? event.id ?? nodeId
37941
- },
37942
- timeStamp: Date.now(),
37943
- type: eventName
37944
- };
37945
- }
37946
38566
  function createValueEventDetail(value, detail) {
37947
38567
  return {
37948
38568
  value,
@@ -37984,7 +38604,7 @@ var HeadlessTestingNodeHandle = class HeadlessTestingNodeHandle {
37984
38604
  this.interactions?.assertActive?.();
37985
38605
  }
37986
38606
  query(selector) {
37987
- const nodes = querySelectorAll(this.node, selector);
38607
+ const nodes = (this.interactions?.projectionOnly ? querySelectorAll : queryInspectionSelectorAll)(this.node, selector);
37988
38608
  const scope = this.node.attribs?.["data-sim-scope"];
37989
38609
  if (!scope || !this.interactions) return nodes;
37990
38610
  return nodes.filter((node) => {
@@ -38003,7 +38623,7 @@ var HeadlessTestingNodeHandle = class HeadlessTestingNodeHandle {
38003
38623
  }
38004
38624
  async getElementsByXpath(expression) {
38005
38625
  this.assertActive();
38006
- return queryXPathElements(this.node, expression).map((node) => new HeadlessTestingNodeHandle(node, this.interactions));
38626
+ return (this.interactions?.projectionOnly ? queryXPathElements : queryInspectionXPathElements)(this.node, expression).map((node) => new HeadlessTestingNodeHandle(node, this.interactions));
38007
38627
  }
38008
38628
  async attr(name) {
38009
38629
  this.assertActive();
@@ -38052,9 +38672,18 @@ var HeadlessTestingNodeHandle = class HeadlessTestingNodeHandle {
38052
38672
  if (!this.interactions) throw new Error("Node interactions are not available for this headless testing node.");
38053
38673
  const normalizedEventName = eventName.trim();
38054
38674
  if (!normalizedEventName) throw new Error("Event name must be a non-empty string in headless testing runtime.");
38675
+ let handlerResults;
38676
+ const nativeHandled = this.interactions.dispatchNativeEvent?.(this.node, normalizedEventName, event, normalizedEventName === "tap" ? (result) => {
38677
+ handlerResults ??= [];
38678
+ handlerResults.push(result);
38679
+ } : void 0);
38680
+ if (normalizedEventName === "tap" && nativeHandled) return handlerResults ? (await Promise.all(handlerResults))[0] : void 0;
38055
38681
  const binding = resolveMiniProgramEventBinding(this.node.attribs, normalizedEventName);
38056
- if (!binding?.method) throw new Error(`No ${normalizedEventName} binding was found on <${this.node.name ?? "unknown"}> in headless testing runtime.`);
38057
- return await this.interactions.callMethod(this.node.attribs?.["data-sim-scope"] ?? null, binding.method, createEventPayload$1(this.node, normalizedEventName, event));
38682
+ if (!binding?.method) {
38683
+ if (nativeHandled) return;
38684
+ throw new Error(`No ${normalizedEventName} binding was found on <${this.node.name ?? "unknown"}> in headless testing runtime.`);
38685
+ }
38686
+ return await this.interactions.callMethod(this.node.attribs?.["data-sim-scope"] ?? null, binding.method, createMiniProgramEventPayload(this.node, normalizedEventName, event));
38058
38687
  }
38059
38688
  async tap(event = {}) {
38060
38689
  return await this.trigger("tap", event);
@@ -38161,35 +38790,9 @@ function renderPageTree(project, page) {
38161
38790
  };
38162
38791
  }
38163
38792
  //#endregion
38164
- //#region ../../mpcore/packages/simulator/src/view/selectorQuery.ts
38165
- const LEADING_MARK_PREFIX_RE = /^mark[:\-]?/;
38166
- const MARK_NAME_RE = /[:\-]([a-z])/g;
38793
+ //#region ../../mpcore/packages/simulator/src/view/nodeGeometry.ts
38167
38794
  const NUMERIC_LIKE_VALUE_RE$1 = /-?\d+(?:\.\d+)?/;
38168
- function toMarkKey(attributeName) {
38169
- return attributeName.replace(LEADING_MARK_PREFIX_RE, "").replace(MARK_NAME_RE, (_match, char) => char.toUpperCase());
38170
- }
38171
- function collectMark(node) {
38172
- const mark = {};
38173
- for (const [key, value] of Object.entries(node.attribs ?? {})) {
38174
- if (!(key.startsWith("mark:") || key.startsWith("mark-"))) continue;
38175
- mark[toMarkKey(key)] = value;
38176
- }
38177
- return mark;
38178
- }
38179
- function createScopedRoot(node) {
38180
- return {
38181
- children: [node],
38182
- type: "root"
38183
- };
38184
- }
38185
- function findNodeByScopeId(root, scopeId) {
38186
- if (root.attribs?.["data-sim-scope"] === scopeId) return root;
38187
- for (const child of root.children ?? []) {
38188
- const match = findNodeByScopeId(child, scopeId);
38189
- if (match) return match;
38190
- }
38191
- return null;
38192
- }
38795
+ const PIXEL_LENGTH_RE = /^\d+(?:\.\d+)?px$|^0$/;
38193
38796
  function parseStyleDeclarations$1(styleValue) {
38194
38797
  const declarations = {};
38195
38798
  if (!styleValue) return declarations;
@@ -38224,18 +38827,229 @@ function resolveRect$1(node, windowInfo) {
38224
38827
  width
38225
38828
  };
38226
38829
  }
38830
+ /** 仅支持显式 px 视口和单个 view 内容盒;未知样式布局不作为已测量滚动范围。 */
38831
+ function resolveNativeScrollBounds(node) {
38832
+ const viewport = parseStyleDeclarations$1(node.attribs?.style);
38833
+ let content;
38834
+ const visit = (parent) => {
38835
+ for (const child of parent.children ?? []) {
38836
+ if (child.type === "comment" || child.type === "text" && !child.data?.trim()) continue;
38837
+ if (child.name === "block") {
38838
+ if (!visit(child)) return false;
38839
+ } else {
38840
+ if (content || child.name !== "view") return false;
38841
+ content = child;
38842
+ }
38843
+ }
38844
+ return true;
38845
+ };
38846
+ if (!visit(node)) return {
38847
+ maxTop: 0,
38848
+ maxLeft: 0
38849
+ };
38850
+ const box = parseStyleDeclarations$1(content?.attribs?.style);
38851
+ const supported = content && !viewport.display && !box.display && !box.position && !box.transform;
38852
+ const extent = (dimension) => {
38853
+ const viewportValue = viewport[dimension];
38854
+ const contentValue = box[dimension];
38855
+ if (!supported || !viewportValue || !contentValue || !PIXEL_LENGTH_RE.test(viewportValue) || !PIXEL_LENGTH_RE.test(contentValue)) return 0;
38856
+ const size = parseNumericLikeValue$1(viewportValue);
38857
+ const contentSize = parseNumericLikeValue$1(contentValue);
38858
+ return size > 0 && Number.isFinite(size) && Number.isFinite(contentSize) ? Math.max(0, contentSize - size) : 0;
38859
+ };
38860
+ return {
38861
+ maxTop: extent("height"),
38862
+ maxLeft: extent("width")
38863
+ };
38864
+ }
38865
+ //#endregion
38866
+ //#region ../../mpcore/packages/simulator/src/view/nativeNode.ts
38867
+ const pageScrollViews = /* @__PURE__ */ new WeakMap();
38868
+ function coordinate(value) {
38869
+ const number = Number(value);
38870
+ return Number.isFinite(number) ? Math.max(0, number) : 0;
38871
+ }
38872
+ function nodeKey(node) {
38873
+ return node.attribs?.["data-sim-node"] ?? "";
38874
+ }
38875
+ /** 原生滚动位置独立于绑定值;未改变的绑定不能覆盖用户滚动。 */
38876
+ function syncNativeScrollViews(page, root, emit) {
38877
+ let states = pageScrollViews.get(page);
38878
+ let present;
38879
+ const visit = (node) => {
38880
+ if (node.name === "scroll-view") {
38881
+ const key = nodeKey(node);
38882
+ present ??= /* @__PURE__ */ new Set();
38883
+ present.add(key);
38884
+ if (!states) {
38885
+ states = /* @__PURE__ */ new Map();
38886
+ pageScrollViews.set(page, states);
38887
+ }
38888
+ const boundTop = coordinate(node.attribs?.["scroll-top"]);
38889
+ const boundLeft = coordinate(node.attribs?.["scroll-left"]);
38890
+ const bounds = resolveNativeScrollBounds(node);
38891
+ const scrollY = node.attribs?.["scroll-y"];
38892
+ const scrollX = node.attribs?.["scroll-x"];
38893
+ const maxTop = scrollY !== void 0 && scrollY !== "false" && scrollY !== "0" ? bounds.maxTop : 0;
38894
+ const maxLeft = scrollX !== void 0 && scrollX !== "false" && scrollX !== "0" ? bounds.maxLeft : 0;
38895
+ let state = states.get(key);
38896
+ const previousTop = state?.scrollTop ?? 0;
38897
+ const previousLeft = state?.scrollLeft ?? 0;
38898
+ if (!state) {
38899
+ state = {
38900
+ boundTop,
38901
+ boundLeft,
38902
+ scrollTop: boundTop,
38903
+ scrollLeft: boundLeft,
38904
+ maxTop,
38905
+ maxLeft
38906
+ };
38907
+ states.set(key, state);
38908
+ } else {
38909
+ if (state.boundTop !== boundTop) {
38910
+ state.scrollTop = boundTop;
38911
+ state.boundTop = boundTop;
38912
+ }
38913
+ if (state.boundLeft !== boundLeft) {
38914
+ state.scrollLeft = boundLeft;
38915
+ state.boundLeft = boundLeft;
38916
+ }
38917
+ }
38918
+ state.maxTop = maxTop;
38919
+ state.maxLeft = maxLeft;
38920
+ state.scrollTop = Math.min(state.scrollTop, maxTop);
38921
+ state.scrollLeft = Math.min(state.scrollLeft, maxLeft);
38922
+ if (state.scrollTop !== previousTop || state.scrollLeft !== previousLeft) {
38923
+ const binding = resolveMiniProgramEventBinding(node.attribs, "scroll");
38924
+ if (binding?.method) {
38925
+ const target = {
38926
+ id: node.attribs?.id ?? "",
38927
+ dataset: collectNodeDataset(node)
38928
+ };
38929
+ emit(node.attribs?.["data-sim-scope"] ?? null, binding.method, {
38930
+ type: "scroll",
38931
+ timeStamp: Date.now(),
38932
+ detail: {
38933
+ scrollTop: state.scrollTop,
38934
+ scrollLeft: state.scrollLeft
38935
+ },
38936
+ target,
38937
+ currentTarget: target
38938
+ });
38939
+ }
38940
+ }
38941
+ }
38942
+ for (const child of node.children ?? []) visit(child);
38943
+ };
38944
+ visit(root);
38945
+ if (states) {
38946
+ for (const key of states.keys()) if (!present?.has(key)) states.delete(key);
38947
+ }
38948
+ }
38949
+ function getNativeScrollOffset(page, node) {
38950
+ const state = pageScrollViews.get(page)?.get(nodeKey(node));
38951
+ return {
38952
+ scrollTop: state?.scrollTop ?? 0,
38953
+ scrollLeft: state?.scrollLeft ?? 0
38954
+ };
38955
+ }
38956
+ /** 原生交互更新宿主状态,导航仍交给统一 wx 路由入口。 */
38957
+ function dispatchNativeNodeEvent(page, node, eventName, event, wx, resolveMethod, onHandlerResult) {
38958
+ if (eventName === "scroll" && node.name === "scroll-view") {
38959
+ const detail = event.detail;
38960
+ const state = pageScrollViews.get(page)?.get(nodeKey(node));
38961
+ if (state) {
38962
+ if (detail?.scrollTop !== void 0) state.scrollTop = Math.min(coordinate(detail.scrollTop), state.maxTop);
38963
+ if (detail?.scrollLeft !== void 0) state.scrollLeft = Math.min(coordinate(detail.scrollLeft), state.maxLeft);
38964
+ }
38965
+ event.detail = Object.assign(event.detail && typeof event.detail === "object" ? event.detail : {}, getNativeScrollOffset(page, node));
38966
+ return true;
38967
+ }
38968
+ if (eventName !== "tap") return false;
38969
+ let current = node;
38970
+ let navigator;
38971
+ let handlers;
38972
+ let handled = false;
38973
+ while (current) {
38974
+ if (!navigator && current.name === "navigator") navigator = current;
38975
+ const binding = resolveMiniProgramEventBinding(current.attribs, "tap");
38976
+ if (binding?.method) {
38977
+ const call = resolveMethod(current.attribs?.["data-sim-scope"] ?? null, binding.method);
38978
+ handlers ??= [];
38979
+ handlers.push({
38980
+ node: current,
38981
+ call
38982
+ });
38983
+ handled = true;
38984
+ }
38985
+ if (binding?.stopAfter) {
38986
+ handled = true;
38987
+ break;
38988
+ }
38989
+ current = current.parent;
38990
+ }
38991
+ if (handlers) for (const handler of handlers) {
38992
+ const result = handler.call(createMiniProgramEventPayload(handler.node, "tap", event, node));
38993
+ onHandlerResult?.(result);
38994
+ }
38995
+ if (!navigator) return handled;
38996
+ const openType = navigator.attribs?.["open-type"] ?? "navigate";
38997
+ const url = navigator.attribs?.url ?? "";
38998
+ switch (openType) {
38999
+ case "navigate":
39000
+ wx.navigateTo({ url });
39001
+ break;
39002
+ case "redirect":
39003
+ wx.redirectTo({ url });
39004
+ break;
39005
+ case "switchTab":
39006
+ wx.switchTab({ url });
39007
+ break;
39008
+ case "reLaunch":
39009
+ wx.reLaunch({ url });
39010
+ break;
39011
+ case "navigateBack":
39012
+ wx.navigateBack({ delta: Number(navigator.attribs?.delta ?? 1) });
39013
+ break;
39014
+ default: return handled;
39015
+ }
39016
+ return true;
39017
+ }
39018
+ //#endregion
39019
+ //#region ../../mpcore/packages/simulator/src/view/selectorQuery.ts
39020
+ const LEADING_MARK_PREFIX_RE = /^mark[:\-]?/;
39021
+ const MARK_NAME_RE = /[:\-]([a-z])/g;
39022
+ function toMarkKey(attributeName) {
39023
+ return attributeName.replace(LEADING_MARK_PREFIX_RE, "").replace(MARK_NAME_RE, (_match, char) => char.toUpperCase());
39024
+ }
39025
+ function collectMark(node) {
39026
+ const mark = {};
39027
+ for (const [key, value] of Object.entries(node.attribs ?? {})) {
39028
+ if (!(key.startsWith("mark:") || key.startsWith("mark-"))) continue;
39029
+ mark[toMarkKey(key)] = value;
39030
+ }
39031
+ return mark;
39032
+ }
39033
+ function createScopedRoot(node) {
39034
+ return {
39035
+ children: [node],
39036
+ type: "root"
39037
+ };
39038
+ }
39039
+ function findNodeByScopeId(root, scopeId) {
39040
+ if (root.attribs?.["data-sim-scope"] === scopeId) return root;
39041
+ for (const child of root.children ?? []) {
39042
+ const match = findNodeByScopeId(child, scopeId);
39043
+ if (match) return match;
39044
+ }
39045
+ return null;
39046
+ }
38227
39047
  function resolveSelectorScrollTop(root, selector) {
38228
39048
  const normalizedSelector = selector?.trim();
38229
39049
  if (!normalizedSelector) return null;
38230
39050
  const match = querySelectorAll(root, normalizedSelector)[0];
38231
39051
  return match ? resolveRect$1(match).top : null;
38232
39052
  }
38233
- function resolveScrollOffset(node) {
38234
- return {
38235
- scrollLeft: parseNumericLikeValue$1(node.attribs?.["data-sim-scroll-left"]),
38236
- scrollTop: parseNumericLikeValue$1(node.attribs?.["data-sim-scroll-top"])
38237
- };
38238
- }
38239
39053
  function resolvePropertyValue(node, propertyName) {
38240
39054
  const normalizedPropertyName = propertyName.trim();
38241
39055
  if (!normalizedPropertyName) return;
@@ -38268,7 +39082,7 @@ function resolveFieldsResult(node, fields, options) {
38268
39082
  result.height = rect.height;
38269
39083
  }
38270
39084
  }
38271
- if (fields.scrollOffset) Object.assign(result, resolveScrollOffset(node));
39085
+ if (fields.scrollOffset) Object.assign(result, getNativeScrollOffset(options.page, node));
38272
39086
  if (Array.isArray(fields.properties) && fields.properties.length > 0) Object.assign(result, pickProperties(node, fields.properties));
38273
39087
  if (Array.isArray(fields.computedStyle) && fields.computedStyle.length > 0) Object.assign(result, pickComputedStyle(node, fields.computedStyle));
38274
39088
  if (fields.context) result.context = options.resolveContext?.(node) ?? { type: "unsupported-context" };
@@ -39090,6 +39904,136 @@ var HeadlessAppLifecycle = class {
39090
39904
  }
39091
39905
  };
39092
39906
  //#endregion
39907
+ //#region ../../mpcore/packages/simulator/src/runtime/workerHost.ts
39908
+ /** 两种 simulator 共用消息与生命周期;每个 worker 独立模块缓存、全局对象及计时器。 */
39909
+ function createWorkerHost(options) {
39910
+ const active = /* @__PURE__ */ new Set();
39911
+ let closed = false;
39912
+ const configuredRoot = () => {
39913
+ return JSON.parse(options.read(join(options.root, "app.json")) ?? "{}").workers;
39914
+ };
39915
+ return {
39916
+ apis: {
39917
+ preDownloadSubpackage(option) {
39918
+ const config = configuredRoot();
39919
+ if (option.packageType !== "workers" || typeof config !== "object" || !config.isSubpackage) {
39920
+ const error = /* @__PURE__ */ new Error("preDownloadSubpackage:fail workers subpackage is not configured");
39921
+ option.fail?.(error);
39922
+ option.complete?.();
39923
+ return;
39924
+ }
39925
+ const result = { errMsg: "preDownloadSubpackage:ok" };
39926
+ option.success?.(result);
39927
+ option.complete?.(result);
39928
+ },
39929
+ createWorker(scriptPath) {
39930
+ if (closed) throw new Error("createWorker:fail runtime has closed");
39931
+ const config = configuredRoot();
39932
+ const directory = typeof config === "string" ? config : config?.path;
39933
+ if (!directory || typeof scriptPath !== "string" || !scriptPath.endsWith(".js")) throw new Error("createWorker:fail invalid worker path");
39934
+ const root = normalize(join(options.root, directory));
39935
+ const entry = normalize(join(options.root, scriptPath));
39936
+ const inside = (file) => file.startsWith(`${root}/`);
39937
+ if (!inside(entry) || options.read(entry) === void 0) throw new Error("createWorker:fail worker entry is outside the configured directory or missing");
39938
+ const messages = /* @__PURE__ */ new Set();
39939
+ const incoming = /* @__PURE__ */ new Set();
39940
+ const errors = /* @__PURE__ */ new Set();
39941
+ const cache = /* @__PURE__ */ new Map();
39942
+ let terminated = false;
39943
+ const scheduler = new RuntimeScheduler((error) => {
39944
+ const value = { message: error instanceof Error ? error.message : String(error) };
39945
+ if (errors.size) for (const listener of errors) listener(value);
39946
+ else options.console.error(value.message);
39947
+ });
39948
+ const post = (listeners, message) => {
39949
+ if (terminated) return;
39950
+ const copy = structuredClone(message);
39951
+ scheduler.setTimeout(() => {
39952
+ for (const listener of [...listeners]) {
39953
+ if (terminated) break;
39954
+ listener(copy);
39955
+ }
39956
+ }, 0);
39957
+ };
39958
+ const globals = {
39959
+ wx: void 0,
39960
+ App: void 0,
39961
+ Page: void 0,
39962
+ Component: void 0,
39963
+ getApp: void 0,
39964
+ console: options.console,
39965
+ worker: {
39966
+ onMessage: (listener) => incoming.add(listener),
39967
+ postMessage: (message) => post(messages, message)
39968
+ },
39969
+ setTimeout: scheduler.setTimeout.bind(scheduler),
39970
+ clearTimeout: scheduler.clearTimeout.bind(scheduler),
39971
+ setInterval: scheduler.setInterval.bind(scheduler),
39972
+ clearInterval: scheduler.clearInterval.bind(scheduler)
39973
+ };
39974
+ globals.globalThis = globals;
39975
+ const execute = options.createExecutor(globals);
39976
+ const load = (file) => {
39977
+ if (terminated) return;
39978
+ if (!inside(file)) throw new Error("Worker require cannot leave its configured directory");
39979
+ if (cache.has(file)) return cache.get(file).exports;
39980
+ const source = options.read(file);
39981
+ if (source === void 0) throw new Error(`Missing worker module: ${file}`);
39982
+ if (file.endsWith(".json")) return JSON.parse(source);
39983
+ const module = { exports: {} };
39984
+ cache.set(file, module);
39985
+ const require = Object.assign((request) => {
39986
+ const target = resolveMiniProgramModule(file, request, root, (candidate) => options.read(candidate) !== void 0);
39987
+ if (!target) throw new Error(`Cannot resolve worker module: ${request}`);
39988
+ return load(target);
39989
+ }, { async: (request) => Promise.resolve().then(() => require(request)) });
39990
+ try {
39991
+ execute(source, file, module, require);
39992
+ } catch (error) {
39993
+ cache.delete(file);
39994
+ throw error;
39995
+ }
39996
+ return module.exports;
39997
+ };
39998
+ const worker = {
39999
+ onMessage: (listener) => {
40000
+ messages.add(listener);
40001
+ },
40002
+ offMessage: (listener) => {
40003
+ if (listener) messages.delete(listener);
40004
+ else messages.clear();
40005
+ },
40006
+ postMessage: (message) => post(incoming, message),
40007
+ onError: (listener) => {
40008
+ errors.add(listener);
40009
+ },
40010
+ offError: (listener) => {
40011
+ if (listener) errors.delete(listener);
40012
+ else errors.clear();
40013
+ },
40014
+ terminate() {
40015
+ if (terminated) return;
40016
+ terminated = true;
40017
+ scheduler.close();
40018
+ messages.clear();
40019
+ incoming.clear();
40020
+ errors.clear();
40021
+ cache.clear();
40022
+ active.delete(worker);
40023
+ }
40024
+ };
40025
+ active.add(worker);
40026
+ scheduler.setTimeout(() => load(entry), 0);
40027
+ return worker;
40028
+ }
40029
+ },
40030
+ close() {
40031
+ closed = true;
40032
+ for (const worker of active) worker.terminate();
40033
+ }
40034
+ };
40035
+ }
40036
+ //#endregion
39093
40037
  //#region ../../mpcore/packages/simulator/src/runtime/runtimeGlobals.ts
39094
40038
  const MINI_PROGRAM_UNAVAILABLE_GLOBAL_NAMES = [
39095
40039
  "window",
@@ -39232,9 +40176,9 @@ function transformEsmToCommonJs(source) {
39232
40176
  function createRequireNotFoundError(request, importer) {
39233
40177
  return /* @__PURE__ */ new Error(`Cannot resolve require("${request}") from ${normalize(importer)} in headless runtime.`);
39234
40178
  }
39235
- function createExecutionContext(registries, getCurrentPages, getApp, wxDriver, kernel, globals, onConsole) {
40179
+ function createExecutionContext(registries, getCurrentPages, getApp, wxDriver, kernel, globals, onConsole, workers) {
39236
40180
  const runtimeConsole = kernel.diagnostics.createConsole(console, onConsole);
39237
- const wx = createHeadlessWx(wxDriver, runtimeConsole);
40181
+ const wx = createHeadlessWx(wxDriver, runtimeConsole, workers);
39238
40182
  return createMiniProgramRuntimeGlobals({
39239
40183
  App(definition) {
39240
40184
  return registerAppDefinition(registries, definition);
@@ -39267,8 +40211,19 @@ function createExecutionContext(registries, getCurrentPages, getApp, wxDriver, k
39267
40211
  }, globals);
39268
40212
  }
39269
40213
  function createModuleLoader(registries, getCurrentPages, getApp, wxDriver, options) {
40214
+ const workers = createWorkerHost({
40215
+ root: options.miniprogramRootPath,
40216
+ read: (file) => options.artifactSource.readText(file),
40217
+ console: options.kernel.diagnostics.createConsole(console, options.onConsole),
40218
+ createExecutor(globals) {
40219
+ const realm = vm.createContext(globals);
40220
+ return (source, file, module, require) => {
40221
+ new vm.Script(`(function(module, exports, require) {${source}\n})`, { filename: file }).runInContext(realm)(module, module.exports, require);
40222
+ };
40223
+ }
40224
+ });
39270
40225
  const moduleCache = /* @__PURE__ */ new Map();
39271
- const executionContext = createExecutionContext(registries, getCurrentPages, getApp, wxDriver, options.kernel, options.globals ?? {}, options.onConsole);
40226
+ const executionContext = createExecutionContext(registries, getCurrentPages, getApp, wxDriver, options.kernel, options.globals ?? {}, options.onConsole, workers.apis);
39272
40227
  executionContext.globalThis = executionContext;
39273
40228
  function executeModule(filePath, loadContext) {
39274
40229
  const resolvedPath = path.resolve(filePath);
@@ -39327,6 +40282,7 @@ function createModuleLoader(registries, getCurrentPages, getApp, wxDriver, optio
39327
40282
  return callback(...args);
39328
40283
  },
39329
40284
  close() {
40285
+ workers.close();
39330
40286
  moduleCache.clear();
39331
40287
  closeRuntimeWxsLoader(loader);
39332
40288
  },
@@ -39359,6 +40315,142 @@ function createModuleLoader(registries, getCurrentPages, getApp, wxDriver, optio
39359
40315
  return loader;
39360
40316
  }
39361
40317
  //#endregion
40318
+ //#region ../../mpcore/packages/simulator/src/runtime/packageAsset.ts
40319
+ const assetExtension = /\.(?:png|jpe?g|gif|svg|cer|mp3|aac|m4a|mp4|wav|ogg|silk|wasm|br|cur|ico|skel|crt|cert|gltf|glb|bin|ktx|hdr|exr|xnet|pem)$/i;
40320
+ /** 包内静态资源只读且每次访问宿主当前产物,不缓存为用户文件,也不允许越过包根目录。 */
40321
+ function createPackageAssetReader(read) {
40322
+ return (filePath) => {
40323
+ if (filePath.includes("\\") || filePath.includes(":") || filePath.includes("\0")) return;
40324
+ const relative = posix.normalize(filePath.replace(/^\/+/, ""));
40325
+ if (relative === ".." || relative.startsWith("../") || !assetExtension.test(relative)) return;
40326
+ return read(relative);
40327
+ };
40328
+ }
40329
+ //#endregion
40330
+ //#region ../../mpcore/packages/simulator/src/runtime/routeEvents.ts
40331
+ /** 两种模拟器共用路由身份与监听状态,完成事件由页面就绪和宿主提交驱动。 */
40332
+ var HeadlessRouteEvents = class {
40333
+ requestRender;
40334
+ isCurrentPage;
40335
+ bus = createHeadlessUniEventBus();
40336
+ pages = /* @__PURE__ */ new WeakMap();
40337
+ nextRouteId = 0;
40338
+ nextWebviewId = 0;
40339
+ activeEvent;
40340
+ activeState;
40341
+ constructor(requestRender, isCurrentPage) {
40342
+ this.requestRender = requestRender;
40343
+ this.isCurrentPage = isCurrentPage;
40344
+ }
40345
+ on(name, listener) {
40346
+ this.bus.$on(name, listener);
40347
+ }
40348
+ off(name, listener) {
40349
+ this.bus.$off(name, listener);
40350
+ }
40351
+ begin(path, query, openType, renderer, page) {
40352
+ this.cancel(this.activeState, /* @__PURE__ */ new Error("navigateTo:fail navigation was superseded"));
40353
+ const previous = page ? this.pages.get(page) : void 0;
40354
+ const event = {
40355
+ path,
40356
+ query: { ...query },
40357
+ openType: this.nextRouteId === 0 ? "appLaunch" : openType,
40358
+ renderer: previous?.event.renderer ?? (renderer === "skyline" || renderer === "xr-frame" ? renderer : "webview"),
40359
+ routeEventId: String(++this.nextRouteId),
40360
+ webviewId: previous?.webviewId ?? ++this.nextWebviewId,
40361
+ timeStamp: Date.now()
40362
+ };
40363
+ this.activeEvent = event;
40364
+ this.bus.$emit("beforeAppRoute", event);
40365
+ return event;
40366
+ }
40367
+ attach(page, event) {
40368
+ const state = this.pages.get(page);
40369
+ const next = {
40370
+ webviewId: event.webviewId,
40371
+ ready: state?.ready ?? false,
40372
+ event,
40373
+ committed: false,
40374
+ completed: false
40375
+ };
40376
+ this.pages.set(page, next);
40377
+ if (this.activeEvent === event) this.activeState = next;
40378
+ }
40379
+ /** 原生 navigateTo 的回调属于本次路由记录,不能以当前页面或提交栈代替完成。 */
40380
+ navigateTo(operation, callbacks) {
40381
+ let state;
40382
+ try {
40383
+ state = this.pages.get(operation());
40384
+ } catch (error) {
40385
+ this.settle(callbacks, error);
40386
+ return;
40387
+ }
40388
+ if (!state || state.canceled) this.settle(callbacks, state?.canceled ?? /* @__PURE__ */ new Error("navigateTo:fail target page was unloaded"));
40389
+ else if (state.completed) this.settle(callbacks);
40390
+ else state.callbacks = callbacks;
40391
+ }
40392
+ commit(page, event) {
40393
+ const state = this.pages.get(page);
40394
+ if (!state || state.event !== event || this.activeEvent !== event || !this.isCurrentPage(page)) return;
40395
+ state.committed = true;
40396
+ this.bus.$emit("appRoute", event);
40397
+ if (state.ready) this.complete(page, state);
40398
+ }
40399
+ ready(page) {
40400
+ const state = this.pages.get(page);
40401
+ if (!state) return;
40402
+ state.ready = true;
40403
+ if (state.committed) this.complete(page, state);
40404
+ }
40405
+ complete(page, state) {
40406
+ this.requestRender(() => {
40407
+ if (state.completed || state.canceled || this.pages.get(page) !== state || this.activeEvent !== state.event || !this.isCurrentPage(page)) return;
40408
+ state.completed = true;
40409
+ const callbacks = state.callbacks;
40410
+ state.callbacks = void 0;
40411
+ try {
40412
+ this.bus.$emit("appRouteDone", state.event);
40413
+ } finally {
40414
+ this.settle(callbacks);
40415
+ }
40416
+ });
40417
+ }
40418
+ settle(callbacks, error) {
40419
+ try {
40420
+ if (error) callbacks?.fail?.(error);
40421
+ else callbacks?.success?.();
40422
+ } finally {
40423
+ callbacks?.complete?.();
40424
+ }
40425
+ }
40426
+ cancel(state, error) {
40427
+ if (!state || state.completed || state.canceled) return;
40428
+ state.canceled = error;
40429
+ const callbacks = state.callbacks;
40430
+ state.callbacks = void 0;
40431
+ this.settle(callbacks, error);
40432
+ }
40433
+ beforeUnload(page, event) {
40434
+ const state = this.pages.get(page);
40435
+ if (event && state) this.bus.$emit("beforePageUnload", {
40436
+ ...event,
40437
+ path: page.route,
40438
+ query: { ...page.options },
40439
+ renderer: state.event.renderer,
40440
+ webviewId: state.webviewId,
40441
+ page
40442
+ });
40443
+ this.cancel(state, /* @__PURE__ */ new Error("navigateTo:fail target page was unloaded"));
40444
+ this.pages.delete(page);
40445
+ }
40446
+ close() {
40447
+ this.cancel(this.activeState, /* @__PURE__ */ new Error("navigateTo:fail runtime session closed"));
40448
+ this.activeState = void 0;
40449
+ this.activeEvent = void 0;
40450
+ this.bus.$off();
40451
+ }
40452
+ };
40453
+ //#endregion
39362
40454
  //#region ../../mpcore/packages/simulator/src/runtime/systemInfo.ts
39363
40455
  function createDefaultLocationResult() {
39364
40456
  return {
@@ -40153,7 +41245,7 @@ function createHeadlessWxState(scheduler = nativeScheduler, options = {}) {
40153
41245
  const readFile = (filePath, encoding) => {
40154
41246
  normalizeEncoding(encoding);
40155
41247
  const normalizedPath = normalizeFsPath(filePath);
40156
- const fileContent = files.get(normalizedPath);
41248
+ const fileContent = files.get(normalizedPath) ?? options.readPackageFile?.(normalizedPath);
40157
41249
  if (fileContent == null) throw new Error(`readFile:fail no such file or directory, open '${normalizedPath}'`);
40158
41250
  return {
40159
41251
  data: fileContent,
@@ -41201,6 +42293,7 @@ var HeadlessSession = class {
41201
42293
  appInstance = null;
41202
42294
  appLifecycle = new HeadlessAppLifecycle();
41203
42295
  moduleLoader;
42296
+ routeEvents = new HeadlessRouteEvents((callback) => this.requestRender(callback), (page) => this.currentPageInstance === page && !this.kernel.isClosed);
41204
42297
  eventListeners = /* @__PURE__ */ new Map();
41205
42298
  registries;
41206
42299
  currentPageInstance = null;
@@ -41228,7 +42321,10 @@ var HeadlessSession = class {
41228
42321
  constructor(options) {
41229
42322
  if (!options.project && !options.projectPath) throw new Error("Headless runtime requires projectPath or project.");
41230
42323
  this.project = options.project ?? loadProject(options.projectPath);
41231
- this.wxState = createHeadlessWxState(this.kernel.scheduler, { strictMocks: options.strictHostMocks });
42324
+ this.wxState = createHeadlessWxState(this.kernel.scheduler, {
42325
+ strictMocks: options.strictHostMocks,
42326
+ readPackageFile: createPackageAssetReader((relative) => this.project.artifactSource.readText(path.join(this.project.miniprogramRootPath, relative)))
42327
+ });
41232
42328
  this.registries = createHostRegistries();
41233
42329
  const rawTabBarList = Array.isArray(this.project.appConfig.tabBar?.list) ? this.project.appConfig.tabBar.list : [];
41234
42330
  this.tabBarRoutes = /* @__PURE__ */ new Set();
@@ -41278,7 +42374,7 @@ var HeadlessSession = class {
41278
42374
  getMenuButtonBoundingClientRect: () => deriveMenuButtonBoundingClientRect(this.systemInfo),
41279
42375
  getNetworkType: () => this.wxState.getNetworkType(),
41280
42376
  navigateBack: (option) => this.navigateBack(option?.delta),
41281
- navigateTo: (option) => this.navigateTo(option.url),
42377
+ navigateTo: (option) => this.routeEvents.navigateTo(() => this.navigateTo(option.url), option),
41282
42378
  pageScrollTo: (option) => this.pageScrollTo(option),
41283
42379
  openDocument: (option) => this.wxState.openDocument(option),
41284
42380
  clearStorageSync: () => this.wxState.clearStorageSync(),
@@ -41295,6 +42391,14 @@ var HeadlessSession = class {
41295
42391
  saveImageToPhotosAlbum: (option) => this.wxState.saveImageToPhotosAlbum(option),
41296
42392
  saveVideoToPhotosAlbum: (option) => this.wxState.saveVideoToPhotosAlbum(option),
41297
42393
  nextTick: (callback) => this.kernel.scheduler.queueMicrotask(() => callback?.()),
42394
+ offBeforeAppRoute: (listener) => this.routeEvents.off("beforeAppRoute", listener),
42395
+ offBeforePageUnload: (listener) => this.routeEvents.off("beforePageUnload", listener),
42396
+ offAppRoute: (listener) => this.routeEvents.off("appRoute", listener),
42397
+ offAppRouteDone: (listener) => this.routeEvents.off("appRouteDone", listener),
42398
+ onBeforeAppRoute: (listener) => this.routeEvents.on("beforeAppRoute", listener),
42399
+ onBeforePageUnload: (listener) => this.routeEvents.on("beforePageUnload", listener),
42400
+ onAppRoute: (listener) => this.routeEvents.on("appRoute", listener),
42401
+ onAppRouteDone: (listener) => this.routeEvents.on("appRouteDone", listener),
41298
42402
  offAppHide: (callback) => this.appLifecycle.offAppHide(callback),
41299
42403
  offAppShow: (callback) => this.appLifecycle.offAppShow(callback),
41300
42404
  offNetworkStatusChange: (callback) => this.wxState.offNetworkStatusChange(callback),
@@ -41347,15 +42451,28 @@ var HeadlessSession = class {
41347
42451
  }
41348
42452
  close() {
41349
42453
  if (this.kernel.isClosed) return;
41350
- this.unloadAllPages();
42454
+ let failure;
42455
+ try {
42456
+ this.unloadAllPages(void 0, true);
42457
+ } catch (error) {
42458
+ failure = { error };
42459
+ }
41351
42460
  this.canvasContexts.clear();
41352
42461
  this.renderRequestCallbacks.length = 0;
41353
42462
  this.renderRequestPending = false;
41354
42463
  this.appLifecycle.close();
42464
+ this.routeEvents.close();
41355
42465
  this.wxState.close();
41356
42466
  this.moduleLoader.close();
41357
- this.eventListeners.clear();
41358
42467
  this.kernel.close();
42468
+ try {
42469
+ this.emit("close");
42470
+ } catch (error) {
42471
+ failure ??= { error };
42472
+ } finally {
42473
+ this.eventListeners.clear();
42474
+ }
42475
+ if (failure) throw failure.error;
41359
42476
  }
41360
42477
  getDiagnostics() {
41361
42478
  return this.kernel.diagnostics.getEntries();
@@ -41372,7 +42489,14 @@ var HeadlessSession = class {
41372
42489
  if (listeners?.size === 0) this.eventListeners.delete(eventName);
41373
42490
  }
41374
42491
  emit(eventName, ...args) {
41375
- for (const handler of [...this.eventListeners.get(eventName) ?? []]) handler(...args);
42492
+ let failure;
42493
+ for (const handler of [...this.eventListeners.get(eventName) ?? []]) try {
42494
+ handler(...args);
42495
+ } catch (error) {
42496
+ if (eventName !== "close") throw error;
42497
+ failure ??= { error };
42498
+ }
42499
+ if (failure) throw failure.error;
41376
42500
  }
41377
42501
  get isClosed() {
41378
42502
  return this.kernel.isClosed;
@@ -41454,6 +42578,11 @@ var HeadlessSession = class {
41454
42578
  selectOwnerComponent: (scopeId) => this.selectOwnerComponent(scopeId)
41455
42579
  }
41456
42580
  }, current);
42581
+ syncNativeScrollViews(current, rendered.root, (scopeId, method, event) => {
42582
+ this.kernel.scheduler.queueMicrotask(() => {
42583
+ if (this.currentPageInstance === current) this.callScopeMethod(scopeId, method, event);
42584
+ });
42585
+ });
41457
42586
  const componentScopePrefix = `page:${stripLeadingSlash(current.route)}`;
41458
42587
  for (const [scopeId, instance] of this.componentCache.entries()) if (scopeId.startsWith(componentScopePrefix)) this.selectorQueryScopeSnapshots.set(instance, {
41459
42588
  page: current,
@@ -41462,6 +42591,11 @@ var HeadlessSession = class {
41462
42591
  });
41463
42592
  return rendered;
41464
42593
  }
42594
+ dispatchNativeNodeEvent(node, eventName, event, onHandlerResult) {
42595
+ const current = this.requireCurrentPage("dispatchNativeNodeEvent()");
42596
+ if (eventName === "tap") this.renderCurrentPage();
42597
+ return dispatchNativeNodeEvent(current, node, eventName, event, this.moduleLoader.wx, (scopeId, method) => this.resolveScopeMethod(current, scopeId, method), onHandlerResult);
42598
+ }
41465
42599
  requestRender(callback) {
41466
42600
  this.assertActive();
41467
42601
  if (callback) this.renderRequestCallbacks.push(callback);
@@ -41509,21 +42643,27 @@ var HeadlessSession = class {
41509
42643
  callScopeMethod(scopeId, methodName, event) {
41510
42644
  const current = this.requireCurrentPage(`scope method "${methodName}"`);
41511
42645
  this.renderCurrentPage();
41512
- if (!scopeId || scopeId === `page:${stripLeadingSlash(current.route)}`) {
42646
+ return this.resolveScopeMethod(current, scopeId, methodName)(event);
42647
+ }
42648
+ /** 解析本次交互的实例,后续回调不随重渲染或导航切换所有者。 */
42649
+ resolveScopeMethod(current, scopeId, methodName) {
42650
+ if (!scopeId || scopeId === `page:${stripLeadingSlash(current.route)}`) return (event) => {
41513
42651
  const method = current[methodName];
41514
42652
  if (typeof method !== "function") throw new TypeError(`Method "${methodName}" does not exist on headless page ${current.route}.`);
41515
42653
  return method.call(current, event);
41516
- }
42654
+ };
41517
42655
  const instance = this.componentCache.get(scopeId);
41518
42656
  if (!instance) throw new Error(`Unknown scope "${scopeId}" in headless runtime.`);
41519
- instance.__lastInteractionEvent__ = {
41520
- currentTarget: event.currentTarget,
41521
- mark: event.mark,
41522
- target: event.target
42657
+ return (event) => {
42658
+ instance.__lastInteractionEvent__ = {
42659
+ currentTarget: event.currentTarget,
42660
+ mark: event.mark,
42661
+ target: event.target
42662
+ };
42663
+ const method = instance[methodName];
42664
+ if (typeof method !== "function") throw new TypeError(`Method "${methodName}" does not exist on headless component scope "${scopeId}".`);
42665
+ return method.call(instance, event);
41523
42666
  };
41524
- const method = instance[methodName];
41525
- if (typeof method !== "function") throw new TypeError(`Method "${methodName}" does not exist on headless component scope "${scopeId}".`);
41526
- return method.call(instance, event);
41527
42667
  }
41528
42668
  callScopeMethodDirect(scopeId, methodName, ...args) {
41529
42669
  const normalizedScopeId = scopeId.trim();
@@ -41549,19 +42689,19 @@ var HeadlessSession = class {
41549
42689
  return this.wxState.getShareMenu();
41550
42690
  }
41551
42691
  selectComponent(selector) {
41552
- this.renderCurrentPage();
42692
+ if (!hasPendingComponentConstruction(this.componentCache)) this.renderCurrentPage();
41553
42693
  return this.selectComponentsWithin(null, selector)[0] ?? null;
41554
42694
  }
41555
42695
  selectAllComponents(selector) {
41556
- this.renderCurrentPage();
42696
+ if (!hasPendingComponentConstruction(this.componentCache)) this.renderCurrentPage();
41557
42697
  return this.selectComponentsWithin(null, selector);
41558
42698
  }
41559
42699
  selectComponentWithin(scopeId, selector) {
41560
- this.renderCurrentPage();
42700
+ if (!hasPendingComponentConstruction(this.componentCache)) this.renderCurrentPage();
41561
42701
  return this.selectComponentsWithin(scopeId, selector)[0] ?? null;
41562
42702
  }
41563
42703
  selectAllComponentsWithin(scopeId, selector) {
41564
- this.renderCurrentPage();
42704
+ if (!hasPendingComponentConstruction(this.componentCache)) this.renderCurrentPage();
41565
42705
  return this.selectComponentsWithin(scopeId, selector);
41566
42706
  }
41567
42707
  selectOwnerComponent(scopeId) {
@@ -41804,11 +42944,13 @@ var HeadlessSession = class {
41804
42944
  const target = this.resolveNavigationTarget(url);
41805
42945
  const launchedPage = this.bootstrapNavigation(createAppLaunchOptions(target.normalizedRoute, target.query));
41806
42946
  if (launchedPage) return launchedPage;
41807
- this.unloadAllPages();
41808
- const pageInstance = this.createFreshPage(target);
42947
+ const event = this.beginRoute(target, "reLaunch");
42948
+ this.unloadAllPages(event);
42949
+ const pageInstance = this.createFreshPage(target, event);
41809
42950
  this.pages.push(pageInstance);
41810
42951
  this.currentPageInstance = pageInstance;
41811
42952
  this.runInitialPageLifecycles(pageInstance, target.query);
42953
+ this.routeEvents.commit(pageInstance, event);
41812
42954
  return pageInstance;
41813
42955
  }
41814
42956
  navigateTo(url) {
@@ -41817,12 +42959,14 @@ var HeadlessSession = class {
41817
42959
  if (launchedPage) return launchedPage;
41818
42960
  if (this.pages.length >= PAGE_STACK_LIMIT) throw new Error(`Cannot navigateTo() beyond a ${PAGE_STACK_LIMIT}-page stack in headless runtime.`);
41819
42961
  if (this.isTabBarRoute(target.routeRecord.route)) throw new Error(`wx.navigateTo() cannot open a tabBar page in headless runtime: ${url}`);
42962
+ const event = this.beginRoute(target, "navigateTo");
41820
42963
  this.currentPageInstance?.onHide?.();
41821
42964
  if (this.currentPageInstance) this.runPageComponentLifetime(this.currentPageInstance.route, "hide");
41822
- const pageInstance = this.createFreshPage(target);
42965
+ const pageInstance = this.createFreshPage(target, event);
41823
42966
  this.pages.push(pageInstance);
41824
42967
  this.currentPageInstance = pageInstance;
41825
42968
  this.runInitialPageLifecycles(pageInstance, target.query);
42969
+ this.routeEvents.commit(pageInstance, event);
41826
42970
  return pageInstance;
41827
42971
  }
41828
42972
  redirectTo(url) {
@@ -41830,15 +42974,17 @@ var HeadlessSession = class {
41830
42974
  const launchedPage = this.bootstrapNavigation(createAppLaunchOptions(target.normalizedRoute, target.query));
41831
42975
  if (launchedPage) return launchedPage;
41832
42976
  if (this.isTabBarRoute(target.routeRecord.route)) throw new Error(`wx.redirectTo() cannot open a tabBar page in headless runtime: ${url}`);
42977
+ const event = this.beginRoute(target, "redirectTo");
41833
42978
  const current = this.currentPageInstance;
41834
42979
  if (current) {
42980
+ this.unloadPage(current, event);
41835
42981
  this.pages.pop();
41836
- this.unloadPage(current);
41837
42982
  }
41838
- const pageInstance = this.createFreshPage(target);
42983
+ const pageInstance = this.createFreshPage(target, event);
41839
42984
  this.pages.push(pageInstance);
41840
42985
  this.currentPageInstance = pageInstance;
41841
42986
  this.runInitialPageLifecycles(pageInstance, target.query);
42987
+ this.routeEvents.commit(pageInstance, event);
41842
42988
  return pageInstance;
41843
42989
  }
41844
42990
  navigateBack(delta = 1) {
@@ -41846,12 +42992,17 @@ var HeadlessSession = class {
41846
42992
  if (launchedPage) return launchedPage;
41847
42993
  if (this.pages.length <= 1) return this.currentPageInstance;
41848
42994
  const removableCount = Math.min(Number.isFinite(delta) ? Math.max(1, Math.trunc(delta)) : 1, this.pages.length - 1);
41849
- const removedPages = this.pages.splice(this.pages.length - removableCount, removableCount);
41850
- for (const page of removedPages.reverse()) this.unloadPage(page);
41851
- const nextPage = this.pages.at(-1) ?? null;
42995
+ const nextPage = this.pages[this.pages.length - removableCount - 1];
42996
+ const event = this.routeEvents.begin(nextPage.route, nextPage.options, "navigateBack", void 0, nextPage);
42997
+ this.routeEvents.attach(nextPage, event);
42998
+ for (let index = 0; index < removableCount; index++) {
42999
+ this.unloadPage(this.pages.at(-1), event);
43000
+ this.pages.pop();
43001
+ }
41852
43002
  this.currentPageInstance = nextPage;
41853
43003
  nextPage?.onShow?.();
41854
43004
  if (nextPage) this.runPageComponentLifetime(nextPage.route, "show");
43005
+ this.routeEvents.commit(nextPage, event);
41855
43006
  return nextPage;
41856
43007
  }
41857
43008
  switchTab(url) {
@@ -41869,6 +43020,8 @@ var HeadlessSession = class {
41869
43020
  const current = this.currentPageInstance;
41870
43021
  const cachedTarget = this.tabPages.get(target.routeRecord.route) ?? null;
41871
43022
  if (current === cachedTarget && current) return () => {};
43023
+ const event = this.beginRoute(target, "switchTab", cachedTarget ?? void 0);
43024
+ if (cachedTarget) this.routeEvents.attach(cachedTarget, event);
41872
43025
  if (current && current !== cachedTarget) {
41873
43026
  current.onHide?.();
41874
43027
  this.runPageComponentLifetime(current.route, "hide");
@@ -41878,7 +43031,7 @@ var HeadlessSession = class {
41878
43031
  let nextPage = cachedTarget;
41879
43032
  let shouldRunInitialLifecycles = false;
41880
43033
  if (!nextPage) {
41881
- nextPage = this.createFreshPage(target);
43034
+ nextPage = this.createFreshPage(target, event);
41882
43035
  this.tabPages.set(target.routeRecord.route, nextPage);
41883
43036
  shouldRunInitialLifecycles = true;
41884
43037
  }
@@ -41891,7 +43044,8 @@ var HeadlessSession = class {
41891
43044
  this.runPageComponentLifetime(nextPage.route, "show");
41892
43045
  }
41893
43046
  return () => {
41894
- for (const page of pagesToUnload) this.unloadPage(page);
43047
+ for (const page of pagesToUnload) this.unloadPage(page, event);
43048
+ this.routeEvents.commit(nextPage, event);
41895
43049
  };
41896
43050
  }
41897
43051
  pageScrollTo(option) {
@@ -41986,17 +43140,20 @@ var HeadlessSession = class {
41986
43140
  windowInfo: this.getWindowInfo()
41987
43141
  });
41988
43142
  }
41989
- createFreshPage(target) {
43143
+ beginRoute(target, openType, page) {
43144
+ return this.routeEvents.begin(target.normalizedRoute, target.query, openType, target.pageConfig?.renderer ?? this.project.appConfig.renderer, page);
43145
+ }
43146
+ createFreshPage(target, event) {
41990
43147
  const resourcePath = target.routeRecord.resourcePath ?? target.routeRecord.route;
41991
43148
  const pageModulePath = path.resolve(this.project.miniprogramRootPath, `${resourcePath}.js`);
41992
- const pageConfigPath = path.resolve(this.project.miniprogramRootPath, `${resourcePath}.json`);
41993
43149
  const pageDefinition = this.moduleLoader.executePageModule(pageModulePath, target.routeRecord.route);
41994
- const pageConfig = readJsonObject(this.project, pageConfigPath);
43150
+ const pageConfig = target.pageConfig;
41995
43151
  const pageInstance = createPageInstance(`/${target.routeRecord.route}`, pageDefinition, target.query, {
41996
43152
  background: resolveBackgroundSnapshot(this.project.appConfig, pageConfig),
41997
43153
  navigationBar: resolveNavigationBarSnapshot(this.project.appConfig, pageConfig),
41998
43154
  requestRender: (callback) => this.requestRender(callback)
41999
43155
  });
43156
+ this.routeEvents.attach(pageInstance, event);
42000
43157
  pageInstance.createIntersectionObserver = (options) => this.createIntersectionObserver(pageInstance, options);
42001
43158
  pageInstance.createMediaQueryObserver = () => this.createMediaQueryObserver(pageInstance);
42002
43159
  pageInstance.selectComponent = (selector) => resolveNativeComponentSelection(this.selectComponent(selector));
@@ -42009,7 +43166,7 @@ var HeadlessSession = class {
42009
43166
  return pageInstance;
42010
43167
  }
42011
43168
  runInitialPageLifecycles(pageInstance, query) {
42012
- runInitialPageLifecycles(pageInstance, query, this.kernel.scheduler, () => this.pages.includes(pageInstance) || this.tabPages.get(pageInstance.route) === pageInstance, () => this.renderCurrentPage());
43169
+ runInitialPageLifecycles(pageInstance, query, this.kernel.scheduler, () => this.pages.includes(pageInstance) || this.tabPages.get(pageInstance.route) === pageInstance, () => this.renderCurrentPage(), () => this.routeEvents.ready(pageInstance));
42013
43170
  }
42014
43171
  isTabBarRoute(route) {
42015
43172
  return this.tabBarRoutes.has(stripLeadingSlash(route));
@@ -42038,24 +43195,45 @@ var HeadlessSession = class {
42038
43195
  return {
42039
43196
  normalizedRoute,
42040
43197
  query,
42041
- routeRecord
43198
+ routeRecord,
43199
+ pageConfig: readJsonObject(this.project, path.resolve(this.project.miniprogramRootPath, `${routeRecord.resourcePath ?? routeRecord.route}.json`))
42042
43200
  };
42043
43201
  }
42044
- unloadAllPages() {
43202
+ unloadAllPages(event, closing = false) {
42045
43203
  const pagesToUnload = /* @__PURE__ */ new Set([...this.pages, ...this.tabPages.values()]);
42046
- for (const page of [...pagesToUnload].reverse()) this.unloadPage(page);
43204
+ let failure;
43205
+ for (const page of [...pagesToUnload].reverse()) try {
43206
+ this.unloadPage(page, event, closing);
43207
+ } catch (error) {
43208
+ if (!closing) throw error;
43209
+ failure ??= { error };
43210
+ }
42047
43211
  this.pages.length = 0;
42048
43212
  this.tabPages.clear();
42049
43213
  this.componentCache.clear();
42050
43214
  this.componentScopes.clear();
42051
43215
  this.clearMediaQueryObservers();
42052
43216
  this.currentPageInstance = null;
43217
+ if (failure) throw failure.error;
42053
43218
  }
42054
- unloadPage(page) {
42055
- page.onUnload?.();
43219
+ unloadPage(page, event, closing = false) {
43220
+ let failure;
43221
+ try {
43222
+ this.routeEvents.beforeUnload(page, event);
43223
+ page.onUnload?.();
43224
+ } catch (error) {
43225
+ if (!closing) throw error;
43226
+ failure = { error };
43227
+ }
42056
43228
  this.clearMediaQueryObservers(page);
42057
- this.detachPageComponents(page.route);
43229
+ try {
43230
+ this.detachPageComponents(page.route, closing);
43231
+ } catch (error) {
43232
+ if (!closing) throw error;
43233
+ failure ??= { error };
43234
+ }
42058
43235
  this.tabPages.delete(stripLeadingSlash(page.route));
43236
+ if (failure) throw failure.error;
42059
43237
  }
42060
43238
  requireCurrentPage(action) {
42061
43239
  this.bootstrap();
@@ -42083,15 +43261,27 @@ var HeadlessSession = class {
42083
43261
  const directlyOwned = rootScopeId ? matches.filter(([, scope]) => scope.ownerScopeId === rootScopeId) : [];
42084
43262
  return (directlyOwned.length > 0 ? directlyOwned : matches).map(([candidateScopeId]) => this.componentCache.get(candidateScopeId)).filter(Boolean);
42085
43263
  }
42086
- detachPageComponents(route) {
43264
+ detachPageComponents(route, closing = false) {
42087
43265
  const prefix = `page:${stripLeadingSlash(route)}`;
42088
43266
  const removed = [...this.componentCache].filter(([scopeId]) => scopeId.startsWith(prefix));
42089
- for (const [, instance] of removed) runComponentLifecycle(instance, "detached");
42090
- detachComponentRelations(removed.map(([, instance]) => instance));
43267
+ let failure;
43268
+ for (const [, instance] of removed) try {
43269
+ runComponentLifecycle(instance, "detached");
43270
+ } catch (error) {
43271
+ if (!closing) throw error;
43272
+ failure ??= { error };
43273
+ }
43274
+ try {
43275
+ detachComponentRelations(removed.map(([, instance]) => instance));
43276
+ } catch (error) {
43277
+ if (!closing) throw error;
43278
+ failure ??= { error };
43279
+ }
42091
43280
  for (const [scopeId] of removed) {
42092
43281
  this.componentCache.delete(scopeId);
42093
43282
  this.componentScopes.delete(scopeId);
42094
43283
  }
43284
+ if (failure) throw failure.error;
42095
43285
  }
42096
43286
  notifyMediaQueryObservers() {
42097
43287
  for (const entry of [...this.mediaQueryObservers]) {
@@ -42251,6 +43441,8 @@ function createPageRootNodeHandle(options) {
42251
43441
  const tree = session?.getCurrentPages().includes(page) ? session.renderCurrentPage() : renderPageTree(project, page);
42252
43442
  return new HeadlessTestingNodeHandle(tree.root.type === "root" && !options.includeHostRoots ? tree.root.children?.[0] ?? tree.root : tree.root, {
42253
43443
  assertActive: options.assertActive,
43444
+ projectionOnly: options.projectionOnly,
43445
+ dispatchNativeEvent: (node, eventName, event, onHandlerResult) => session?.getCurrentPages().at(-1) === page ? session.dispatchNativeNodeEvent(node, eventName, event, onHandlerResult) : false,
42254
43446
  callMethod: (scopeId, methodName, event) => {
42255
43447
  if (session?.getCurrentPages().includes(page)) return session.callScopeMethod(scopeId, methodName, event);
42256
43448
  const method = page[methodName];
@@ -42612,7 +43804,18 @@ var HeadlessTestingPageHandle = class {
42612
43804
  renderedAccess() {
42613
43805
  return {
42614
43806
  assertActive: () => this.assertActive(),
42615
- findAll: async (selector) => await this.$$(selector),
43807
+ findAll: async (selector) => {
43808
+ this.assertActive();
43809
+ const root = createPageRootNodeHandle({
43810
+ assertActive: () => this.assertActive(),
43811
+ createPageHandle: () => this,
43812
+ page: this.page,
43813
+ project: this.project,
43814
+ projectionOnly: true,
43815
+ session: this.session
43816
+ });
43817
+ return selector === "page" ? [root] : await root.$$(selector);
43818
+ },
42616
43819
  waitFor: async (ms) => await this.waitFor(ms),
42617
43820
  wxml: async () => await this.wxml()
42618
43821
  };
@@ -42690,11 +43893,19 @@ var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
42690
43893
  var HeadlessTestingSessionHandle = class {
42691
43894
  project;
42692
43895
  session;
43896
+ pendingNavigations = /* @__PURE__ */ new Set();
42693
43897
  constructor(project, session) {
42694
43898
  this.project = project;
42695
43899
  this.session = session;
43900
+ this.session.on("close", () => {
43901
+ for (const reject of this.pendingNavigations) reject(/* @__PURE__ */ new Error("The headless testing session closed before navigation completed."));
43902
+ });
42696
43903
  }
42697
43904
  async close() {
43905
+ this.disconnect();
43906
+ }
43907
+ /** 无头会话独占本地 runtime;同步释放与 automator 的连接清理契约一致。 */
43908
+ disconnect() {
42698
43909
  this.session.close();
42699
43910
  }
42700
43911
  async toolInfo() {
@@ -42734,6 +43945,10 @@ var HeadlessTestingSessionHandle = class {
42734
43945
  const normalizedMethodName = normalizeNonEmptyInput(methodName, "wx method name");
42735
43946
  return await runWithTimeout(() => this.session.callWxMethod(normalizedMethodName, ...args), options.timeout, `Timed out calling wx.${normalizedMethodName} in headless testing runtime.`);
42736
43947
  }
43948
+ /** 返回与 DevTools automator 兼容的运行时系统信息。 */
43949
+ async systemInfo() {
43950
+ return await this.callWxMethod("getSystemInfoSync");
43951
+ }
42737
43952
  async scopeSnapshot(scopeId) {
42738
43953
  const normalizedScopeId = normalizeNonEmptyInput(scopeId, "Scope id");
42739
43954
  return this.session.getScopeSnapshot(normalizedScopeId);
@@ -42761,23 +43976,23 @@ var HeadlessTestingSessionHandle = class {
42761
43976
  }, `Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`, options);
42762
43977
  }
42763
43978
  async reLaunch(route) {
42764
- this.invokeNavigation("reLaunch", { url: route });
43979
+ await this.invokeNavigation("reLaunch", { url: route });
42765
43980
  return await this.requireCurrentPage("reLaunch", route);
42766
43981
  }
42767
43982
  async navigateTo(route) {
42768
- this.invokeNavigation("navigateTo", { url: route });
43983
+ await this.invokeNavigation("navigateTo", { url: route });
42769
43984
  return await this.requireCurrentPage("navigateTo", route);
42770
43985
  }
42771
43986
  async redirectTo(route) {
42772
- this.invokeNavigation("redirectTo", { url: route });
43987
+ await this.invokeNavigation("redirectTo", { url: route });
42773
43988
  return await this.requireCurrentPage("redirectTo", route);
42774
43989
  }
42775
43990
  async navigateBack(delta = 1) {
42776
- this.invokeNavigation("navigateBack", { delta });
43991
+ await this.invokeNavigation("navigateBack", { delta });
42777
43992
  return await this.currentPage();
42778
43993
  }
42779
43994
  async switchTab(route) {
42780
- this.invokeNavigation("switchTab", { url: route });
43995
+ await this.invokeNavigation("switchTab", { url: route });
42781
43996
  return await this.requireCurrentPage("switchTab", route);
42782
43997
  }
42783
43998
  async waitForCurrentPage(route, options = {}) {
@@ -42814,21 +44029,30 @@ var HeadlessTestingSessionHandle = class {
42814
44029
  this.session.triggerRouteDone(options);
42815
44030
  }
42816
44031
  invokeNavigation(methodName, options) {
42817
- const failure = { received: false };
42818
- this.session.callWxMethod(methodName, {
42819
- ...options,
42820
- fail(error) {
42821
- failure.received = true;
42822
- failure.error = error;
44032
+ return new Promise((resolve, reject) => {
44033
+ const rejectNavigation = (error) => {
44034
+ this.pendingNavigations.delete(rejectNavigation);
44035
+ reject(error);
44036
+ };
44037
+ this.pendingNavigations.add(rejectNavigation);
44038
+ try {
44039
+ this.session.callWxMethod(methodName, {
44040
+ ...options,
44041
+ success: () => {
44042
+ this.pendingNavigations.delete(rejectNavigation);
44043
+ resolve();
44044
+ },
44045
+ fail: (error) => {
44046
+ const details = error && typeof error === "object" ? error : void 0;
44047
+ const reason = typeof details?.errMsg === "string" ? details.errMsg : typeof details?.message === "string" ? details.message : String(error);
44048
+ const target = options.url ? ` route "${options.url}"` : "";
44049
+ rejectNavigation(new Error(`Failed to ${methodName}${target} through the headless wx runtime: ${reason}`, { cause: error }));
44050
+ }
44051
+ });
44052
+ } catch (error) {
44053
+ rejectNavigation(error);
42823
44054
  }
42824
44055
  });
42825
- if (failure.received) {
42826
- const error = failure.error;
42827
- const details = error && typeof error === "object" ? error : void 0;
42828
- const reason = typeof details?.errMsg === "string" ? details.errMsg : typeof details?.message === "string" ? details.message : String(error);
42829
- const target = options.url ? ` route "${options.url}"` : "";
42830
- throw new Error(`Failed to ${methodName}${target} through the headless wx runtime: ${reason}`, { cause: error });
42831
- }
42832
44056
  }
42833
44057
  async requireCurrentPage(methodName, route) {
42834
44058
  const page = await this.currentPage();