@qwik.dev/core 2.0.0-beta.13 → 2.0.0-beta.15

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.
Files changed (53) hide show
  1. package/dist/backpatch/package.json +1 -1
  2. package/dist/build/package.json +1 -1
  3. package/dist/cli.mjs +5633 -0
  4. package/dist/core-internal.d.ts +123 -65
  5. package/dist/core.min.mjs +1 -1
  6. package/dist/core.mjs +548 -246
  7. package/dist/core.mjs.map +1 -1
  8. package/dist/core.prod.mjs +349 -198
  9. package/dist/loader/index.mjs +2 -2
  10. package/dist/loader/package.json +1 -1
  11. package/dist/optimizer.mjs +1290 -1294
  12. package/dist/qwikloader.debug.js +43 -10
  13. package/dist/qwikloader.js +1 -1
  14. package/dist/server.mjs +5 -5
  15. package/dist/starters/adapters/aws-lambda/package.json +2 -1
  16. package/dist/starters/adapters/azure-swa/package.json +2 -1
  17. package/dist/starters/adapters/bun/package.json +2 -1
  18. package/dist/starters/adapters/cloud-run/package.json +2 -1
  19. package/dist/starters/adapters/cloudflare-pages/package.json +2 -1
  20. package/dist/starters/adapters/deno/package.json +2 -1
  21. package/dist/starters/adapters/express/package.json +2 -1
  22. package/dist/starters/adapters/fastify/package.json +2 -1
  23. package/dist/starters/adapters/firebase/package.json +2 -1
  24. package/dist/starters/adapters/netlify-edge/package.json +2 -1
  25. package/dist/starters/adapters/node-server/package.json +2 -1
  26. package/dist/starters/adapters/ssg/package.json +2 -1
  27. package/dist/starters/adapters/vercel-edge/package.json +2 -1
  28. package/dist/starters/features/csr/package.json +1 -1
  29. package/dist/starters/features/storybook/.storybook/tsconfig.json +0 -1
  30. package/dist/starters/features/styled-vanilla-extract/package.json +2 -1
  31. package/dist/testing/index.d.ts +8 -9
  32. package/dist/testing/index.mjs +483 -222
  33. package/dist/testing/package.json +1 -1
  34. package/package.json +14 -48
  35. package/{qwik-cli.cjs → qwik-cli.mjs} +1 -1
  36. package/dist/backpatch/index.cjs +0 -6
  37. package/dist/build/index.cjs +0 -35
  38. package/dist/build/index.cjs.map +0 -7
  39. package/dist/build/index.dev.cjs +0 -37
  40. package/dist/build/index.dev.cjs.map +0 -7
  41. package/dist/build/index.prod.cjs +0 -37
  42. package/dist/build/index.prod.cjs.map +0 -7
  43. package/dist/cli.cjs +0 -12956
  44. package/dist/core.cjs +0 -13036
  45. package/dist/core.cjs.map +0 -1
  46. package/dist/core.prod.cjs +0 -6377
  47. package/dist/insights/index.qwik.cjs +0 -1
  48. package/dist/insights/vite/index.cjs +0 -1
  49. package/dist/loader/index.cjs +0 -4
  50. package/dist/optimizer.cjs +0 -217
  51. package/dist/preloader.cjs +0 -266
  52. package/dist/server.cjs +0 -3294
  53. package/dist/testing/index.cjs +0 -36225
package/dist/core.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @license
3
- * @qwik.dev/core 2.0.0-beta.13-dev+cb19ff7
3
+ * @qwik.dev/core 2.0.0-beta.15-dev+920f1a4
4
4
  * Copyright QwikDev. All Rights Reserved.
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://github.com/QwikDev/qwik/blob/main/LICENSE
@@ -14,7 +14,7 @@ import { p } from '@qwik.dev/core/preloader';
14
14
  *
15
15
  * @public
16
16
  */
17
- const version = "2.0.0-beta.13-dev+cb19ff7";
17
+ const version = "2.0.0-beta.15-dev+920f1a4";
18
18
 
19
19
  // same as isDev but separate so we can test
20
20
  const qDev = globalThis.qDev !== false;
@@ -251,7 +251,6 @@ const ELEMENT_KEY = 'q:key';
251
251
  const ELEMENT_PROPS = 'q:props';
252
252
  const ELEMENT_SEQ = 'q:seq';
253
253
  const ELEMENT_SEQ_IDX = 'q:seqIdx';
254
- const Q_PREFIX = 'q:';
255
254
  /** Non serializable markers - always begins with `:` character */
256
255
  const NON_SERIALIZABLE_MARKER_PREFIX = ':';
257
256
  const USE_ON_LOCAL = NON_SERIALIZABLE_MARKER_PREFIX + 'on';
@@ -916,7 +915,7 @@ const STORE_ALL_PROPS = Symbol('store.all');
916
915
  class SignalImpl {
917
916
  $untrackedValue$;
918
917
  /** Store a list of effects which are dependent on this signal. */
919
- $effects$ = null;
918
+ $effects$ = undefined;
920
919
  $container$ = null;
921
920
  $wrappedSignal$ = null;
922
921
  constructor(container, value) {
@@ -928,7 +927,7 @@ class SignalImpl {
928
927
  * remained the same object
929
928
  */
930
929
  force() {
931
- this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$effects$);
930
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, this, this.$effects$);
932
931
  }
933
932
  get untrackedValue() {
934
933
  return this.$untrackedValue$;
@@ -943,7 +942,7 @@ class SignalImpl {
943
942
  set value(value) {
944
943
  if (value !== this.$untrackedValue$) {
945
944
  this.$untrackedValue$ = value;
946
- this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$effects$);
945
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, this, this.$effects$);
947
946
  }
948
947
  }
949
948
  // prevent accidental use as value
@@ -1005,6 +1004,8 @@ const _CONST_PROPS = Symbol('CONST');
1005
1004
  const _VAR_PROPS = Symbol('VAR');
1006
1005
  /** @internal */
1007
1006
  const _OWNER = Symbol('OWNER');
1007
+ /** @internal */
1008
+ const _PROPS_HANDLER = Symbol('PROPS_HANDLER');
1008
1009
  /** @internal @deprecated v1 compat */
1009
1010
  const _IMMUTABLE = Symbol('IMMUTABLE');
1010
1011
  /** @internal */
@@ -1080,6 +1081,13 @@ const getEventDataFromHtmlAttribute = (htmlKey) => {
1080
1081
  }
1081
1082
  return ['document', htmlKey.substring(12)];
1082
1083
  };
1084
+ const getScopedEventName = (scope, eventName) => {
1085
+ const suffix = ':' + eventName;
1086
+ return scope ? scope + suffix : suffix;
1087
+ };
1088
+ const getLoaderScopedEventName = (scope, scopedEvent) => {
1089
+ return scope ? '-' + scopedEvent : scopedEvent;
1090
+ };
1083
1091
 
1084
1092
  /** @internal */
1085
1093
  const EMPTY_ARRAY = [];
@@ -1093,6 +1101,8 @@ function createPropsProxy(owner) {
1093
1101
  }
1094
1102
  class PropsProxyHandler {
1095
1103
  owner;
1104
+ $effects$ = undefined;
1105
+ $container$ = null;
1096
1106
  constructor(owner) {
1097
1107
  this.owner = owner;
1098
1108
  }
@@ -1101,12 +1111,15 @@ class PropsProxyHandler {
1101
1111
  if (prop === _CONST_PROPS) {
1102
1112
  return this.owner.constProps;
1103
1113
  }
1104
- if (prop === _VAR_PROPS) {
1114
+ else if (prop === _VAR_PROPS) {
1105
1115
  return this.owner.varProps;
1106
1116
  }
1107
- if (prop === _OWNER) {
1117
+ else if (prop === _OWNER) {
1108
1118
  return this.owner;
1109
1119
  }
1120
+ else if (prop === _PROPS_HANDLER) {
1121
+ return this;
1122
+ }
1110
1123
  let value;
1111
1124
  if (prop === 'children') {
1112
1125
  value = this.owner.children;
@@ -1119,6 +1132,9 @@ class PropsProxyHandler {
1119
1132
  }
1120
1133
  }
1121
1134
  value = directGetPropsProxyProp(this.owner, prop);
1135
+ if (prop in this.owner.varProps) {
1136
+ addPropsProxyEffect(this, prop);
1137
+ }
1122
1138
  }
1123
1139
  // a proxied value that the optimizer made
1124
1140
  return value instanceof WrappedSignalImpl && value.$flags$ & 4 /* WrappedSignalFlags.UNWRAP */
@@ -1133,6 +1149,12 @@ class PropsProxyHandler {
1133
1149
  else if (prop === 'children') {
1134
1150
  this.owner.children = value;
1135
1151
  }
1152
+ else if (prop === _CONST_PROPS) {
1153
+ this.owner.constProps = value;
1154
+ }
1155
+ else if (prop === _VAR_PROPS) {
1156
+ this.owner.varProps = value;
1157
+ }
1136
1158
  else {
1137
1159
  if (typeof prop === 'string' && typeof this.owner.type === 'string') {
1138
1160
  const attr = jsxEventToHtmlAttribute(prop);
@@ -1141,28 +1163,27 @@ class PropsProxyHandler {
1141
1163
  }
1142
1164
  }
1143
1165
  if (this.owner.constProps && prop in this.owner.constProps) {
1144
- this.owner.constProps[prop] = undefined;
1145
- if (!(prop in this.owner.varProps)) {
1146
- this.owner.toSort = true;
1147
- }
1148
- this.owner.varProps[prop] = value;
1166
+ // delete the prop from the const props first
1167
+ delete this.owner.constProps[prop];
1149
1168
  }
1150
- else {
1151
- if (this.owner.varProps === EMPTY_OBJ) {
1152
- this.owner.varProps = {};
1153
- }
1154
- else {
1155
- if (!(prop in this.owner.varProps)) {
1156
- this.owner.toSort = true;
1157
- }
1158
- }
1169
+ if (this.owner.varProps === EMPTY_OBJ) {
1170
+ this.owner.varProps = {};
1171
+ }
1172
+ else if (!(prop in this.owner.varProps)) {
1173
+ this.owner.toSort = true;
1174
+ }
1175
+ if (this.owner.varProps[prop] !== value) {
1159
1176
  this.owner.varProps[prop] = value;
1177
+ triggerPropsProxyEffect(this, prop);
1160
1178
  }
1161
1179
  }
1162
1180
  return true;
1163
1181
  }
1164
1182
  deleteProperty(_, prop) {
1165
1183
  let didDelete = delete this.owner.varProps[prop];
1184
+ if (didDelete) {
1185
+ triggerPropsProxyEffect(this, prop);
1186
+ }
1166
1187
  if (this.owner.constProps) {
1167
1188
  didDelete = delete this.owner.constProps[prop] || didDelete;
1168
1189
  }
@@ -1179,13 +1200,19 @@ class PropsProxyHandler {
1179
1200
  else if (prop === _CONST_PROPS || prop === _VAR_PROPS) {
1180
1201
  return true;
1181
1202
  }
1182
- if (typeof prop === 'string' && typeof this.owner.type === 'string') {
1183
- const attr = jsxEventToHtmlAttribute(prop);
1184
- if (attr) {
1185
- prop = attr;
1203
+ const inVarProps = prop in this.owner.varProps;
1204
+ if (typeof prop === 'string') {
1205
+ if (inVarProps) {
1206
+ addPropsProxyEffect(this, prop);
1207
+ }
1208
+ if (typeof this.owner.type === 'string') {
1209
+ const attr = jsxEventToHtmlAttribute(prop);
1210
+ if (attr) {
1211
+ prop = attr;
1212
+ }
1186
1213
  }
1187
1214
  }
1188
- return (prop in this.owner.varProps || (this.owner.constProps ? prop in this.owner.constProps : false));
1215
+ return inVarProps || (this.owner.constProps ? prop in this.owner.constProps : false);
1189
1216
  }
1190
1217
  getOwnPropertyDescriptor(_, p) {
1191
1218
  const value = p === 'children'
@@ -1214,6 +1241,34 @@ class PropsProxyHandler {
1214
1241
  return out;
1215
1242
  }
1216
1243
  }
1244
+ const addPropsProxyEffect = (propsProxy, prop) => {
1245
+ // Lazily grab the container from the invoke context
1246
+ const ctx = tryGetInvokeContext();
1247
+ if (ctx) {
1248
+ if (propsProxy.$container$ === null) {
1249
+ if (ctx.$container$) {
1250
+ propsProxy.$container$ = ctx.$container$;
1251
+ }
1252
+ }
1253
+ else {
1254
+ assertTrue(!ctx.$container$ || ctx.$container$ === propsProxy.$container$, 'Do not use props across containers');
1255
+ }
1256
+ }
1257
+ const effectSubscriber = ctx?.$effectSubscriber$;
1258
+ if (effectSubscriber) {
1259
+ addStoreEffect(propsProxy.owner._proxy, prop, propsProxy, effectSubscriber);
1260
+ }
1261
+ };
1262
+ const triggerPropsProxyEffect = (propsProxy, prop) => {
1263
+ const effects = getEffects$1(propsProxy.$effects$, prop);
1264
+ if (effects) {
1265
+ propsProxy.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, propsProxy, effects);
1266
+ }
1267
+ };
1268
+ function getEffects$1(effects, prop) {
1269
+ // TODO: Handle STORE_ALL_PROPS
1270
+ return effects?.get(prop);
1271
+ }
1217
1272
  /**
1218
1273
  * Instead of using PropsProxyHandler getter (which could create a component-level subscription).
1219
1274
  * Use this function to get the props directly from a const or var props.
@@ -1245,6 +1300,19 @@ const isPropsProxy = (obj) => {
1245
1300
  return obj && _VAR_PROPS in obj;
1246
1301
  };
1247
1302
 
1303
+ const cleanupDestroyable = (destroyable) => {
1304
+ const destroy = destroyable.$destroy$;
1305
+ if (destroy) {
1306
+ destroyable.$destroy$ = null;
1307
+ try {
1308
+ destroy();
1309
+ }
1310
+ catch (err) {
1311
+ logError(err);
1312
+ }
1313
+ }
1314
+ };
1315
+
1248
1316
  function getSubscriber(effect, prop, data) {
1249
1317
  if (!effect[_EFFECT_BACK_REF]) {
1250
1318
  if (isServer && isSsrNode(effect)) {
@@ -1284,6 +1352,7 @@ const trackFn = (target, container) => (obj, prop) => {
1284
1352
  return obj.value;
1285
1353
  }
1286
1354
  else if (isObject(obj) && isStore(obj)) {
1355
+ // TODO: handle props proxy
1287
1356
  // track whole store
1288
1357
  addStoreEffect(getStoreTarget(obj), STORE_ALL_PROPS, getStoreHandler(obj), ctx.$effectSubscriber$);
1289
1358
  return obj;
@@ -1301,14 +1370,14 @@ const cleanupFn = (target, handleError) => {
1301
1370
  cleanupFns = [];
1302
1371
  target.$destroy$ = noSerialize(() => {
1303
1372
  target.$destroy$ = null;
1304
- cleanupFns.forEach((fn) => {
1373
+ for (const fn of cleanupFns) {
1305
1374
  try {
1306
1375
  fn();
1307
1376
  }
1308
1377
  catch (err) {
1309
1378
  handleError(err);
1310
1379
  }
1311
- });
1380
+ }
1312
1381
  });
1313
1382
  }
1314
1383
  cleanupFns.push(fn);
@@ -1334,7 +1403,7 @@ class ComputedSignalImpl extends SignalImpl {
1334
1403
  */
1335
1404
  $computeQrl$;
1336
1405
  $flags$;
1337
- [_EFFECT_BACK_REF] = null;
1406
+ [_EFFECT_BACK_REF] = undefined;
1338
1407
  constructor(container, fn,
1339
1408
  // We need a separate flag to know when the computation needs running because
1340
1409
  // we need the old value to know if effects need running after computation
@@ -1348,7 +1417,7 @@ class ComputedSignalImpl extends SignalImpl {
1348
1417
  }
1349
1418
  invalidate() {
1350
1419
  this.$flags$ |= 1 /* SignalFlags.INVALID */;
1351
- this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$effects$);
1420
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, this, this.$effects$);
1352
1421
  }
1353
1422
  /**
1354
1423
  * Use this to force running subscribers, for example when the calculated value has mutated but
@@ -1416,12 +1485,13 @@ class ComputedSignalImpl extends SignalImpl {
1416
1485
  */
1417
1486
  class AsyncComputedSignalImpl extends ComputedSignalImpl {
1418
1487
  $untrackedLoading$ = false;
1419
- $untrackedError$ = null;
1420
- $loadingEffects$ = null;
1421
- $errorEffects$ = null;
1488
+ $untrackedError$ = undefined;
1489
+ $loadingEffects$ = undefined;
1490
+ $errorEffects$ = undefined;
1422
1491
  $destroy$;
1423
1492
  $promiseValue$ = NEEDS_COMPUTATION;
1424
- [_EFFECT_BACK_REF] = null;
1493
+ $promise$ = null;
1494
+ [_EFFECT_BACK_REF] = undefined;
1425
1495
  constructor(container, fn, flags = 1 /* SignalFlags.INVALID */) {
1426
1496
  super(container, fn, flags);
1427
1497
  }
@@ -1435,7 +1505,7 @@ class AsyncComputedSignalImpl extends ComputedSignalImpl {
1435
1505
  set untrackedLoading(value) {
1436
1506
  if (value !== this.$untrackedLoading$) {
1437
1507
  this.$untrackedLoading$ = value;
1438
- this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$loadingEffects$);
1508
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, this, this.$loadingEffects$);
1439
1509
  }
1440
1510
  }
1441
1511
  get untrackedLoading() {
@@ -1448,7 +1518,7 @@ class AsyncComputedSignalImpl extends ComputedSignalImpl {
1448
1518
  set untrackedError(value) {
1449
1519
  if (value !== this.$untrackedError$) {
1450
1520
  this.$untrackedError$ = value;
1451
- this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, this.$errorEffects$);
1521
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, this, this.$errorEffects$);
1452
1522
  }
1453
1523
  }
1454
1524
  get untrackedError() {
@@ -1456,9 +1526,12 @@ class AsyncComputedSignalImpl extends ComputedSignalImpl {
1456
1526
  }
1457
1527
  invalidate() {
1458
1528
  super.invalidate();
1459
- this.$promiseValue$ = NEEDS_COMPUTATION;
1529
+ // clear the promise, we need to get function again
1530
+ this.$promise$ = null;
1460
1531
  }
1461
- async resolve() {
1532
+ async promise() {
1533
+ // make sure we get a new promise during the next computation
1534
+ this.$promise$ = null;
1462
1535
  await retryOnPromise(() => this.$computeIfNeeded$());
1463
1536
  return this.$untrackedValue$;
1464
1537
  }
@@ -1466,34 +1539,69 @@ class AsyncComputedSignalImpl extends ComputedSignalImpl {
1466
1539
  if (!(this.$flags$ & 1 /* SignalFlags.INVALID */)) {
1467
1540
  return;
1468
1541
  }
1469
- const [cleanup] = cleanupFn(this, (err) => this.$container$?.handleError(err, null));
1470
- const untrackedValue = this.$promiseValue$ === NEEDS_COMPUTATION
1471
- ? this.$computeQrl$.getFn()({
1472
- track: trackFn(this, this.$container$),
1473
- cleanup,
1474
- })
1542
+ const untrackedValue =
1543
+ // first time
1544
+ this.$promiseValue$ === NEEDS_COMPUTATION ||
1545
+ // or after invalidation
1546
+ this.$promise$ === null
1547
+ ? this.$promiseComputation$()
1475
1548
  : this.$promiseValue$;
1476
1549
  if (isPromise(untrackedValue)) {
1550
+ const isFirstComputation = this.$promiseValue$ === NEEDS_COMPUTATION;
1477
1551
  this.untrackedLoading = true;
1478
- this.untrackedError = null;
1479
- throw untrackedValue
1552
+ this.untrackedError = undefined;
1553
+ if (this.$promiseValue$ !== NEEDS_COMPUTATION) {
1554
+ // skip cleanup after resuming
1555
+ cleanupDestroyable(this);
1556
+ }
1557
+ const promise = untrackedValue
1480
1558
  .then((promiseValue) => {
1481
1559
  this.$promiseValue$ = promiseValue;
1482
1560
  this.untrackedLoading = false;
1483
- this.untrackedError = null;
1561
+ this.untrackedError = undefined;
1562
+ if (this.setValue(promiseValue)) {
1563
+ scheduleEffects(this.$container$, this, this.$effects$);
1564
+ }
1484
1565
  })
1485
1566
  .catch((err) => {
1567
+ if (isPromise(err)) {
1568
+ // ignore promise errors, they will be handled
1569
+ return;
1570
+ }
1486
1571
  this.$promiseValue$ = err;
1487
1572
  this.untrackedLoading = false;
1488
1573
  this.untrackedError = err;
1489
1574
  });
1575
+ if (isFirstComputation) {
1576
+ // we want to throw only the first time
1577
+ // the next time we will return stale value
1578
+ throw promise;
1579
+ }
1580
+ else {
1581
+ // Return the promise so the scheduler can track it as a running chore
1582
+ return promise;
1583
+ }
1584
+ }
1585
+ else {
1586
+ this.setValue(untrackedValue);
1587
+ }
1588
+ }
1589
+ async $promiseComputation$() {
1590
+ if (!this.$promise$) {
1591
+ const [cleanup] = cleanupFn(this, (err) => this.$container$?.handleError(err, null));
1592
+ this.$promise$ = this.$computeQrl$.getFn()({
1593
+ track: trackFn(this, this.$container$),
1594
+ cleanup,
1595
+ });
1490
1596
  }
1491
- this.$promiseValue$ = NEEDS_COMPUTATION;
1597
+ return this.$promise$;
1598
+ }
1599
+ setValue(value) {
1492
1600
  this.$flags$ &= -2 /* SignalFlags.INVALID */;
1493
- const didChange = untrackedValue !== this.$untrackedValue$;
1601
+ const didChange = value !== this.$untrackedValue$;
1494
1602
  if (didChange) {
1603
+ this.$untrackedValue$ = value;
1495
1604
  this.$flags$ |= 2 /* SignalFlags.RUN_EFFECTS */;
1496
- this.$untrackedValue$ = untrackedValue;
1497
1605
  }
1498
1606
  return didChange;
1499
1607
  }
@@ -1683,25 +1791,32 @@ const _wrapProp = (...args) => {
1683
1791
  }
1684
1792
  if (isPropsProxy(obj)) {
1685
1793
  const constProps = obj[_CONST_PROPS];
1794
+ const varProps = obj[_VAR_PROPS];
1686
1795
  if (constProps && prop in constProps) {
1687
1796
  // Const props don't need wrapping
1688
1797
  return constProps[prop];
1689
1798
  }
1799
+ else if (prop in varProps) {
1800
+ const value = varProps[prop];
1801
+ return wrapIfNotSignal(value, args);
1802
+ }
1690
1803
  }
1691
1804
  else {
1692
1805
  const target = getStoreTarget(obj);
1693
1806
  if (target) {
1694
1807
  const value = target[prop];
1695
- const wrappedValue = isSignal(value)
1696
- ? // If the value is already a signal, we don't need to wrap it again
1697
- value
1698
- : getWrapped(args);
1699
- return wrappedValue;
1808
+ return wrapIfNotSignal(value, args);
1700
1809
  }
1701
1810
  }
1702
1811
  // the object is not reactive, so we can just return the value
1703
1812
  return obj[prop];
1704
1813
  };
1814
+ const wrapIfNotSignal = (value, args) => {
1815
+ return (isSignal(value)
1816
+ ? // If the value is already a signal, we don't need to wrap it again
1817
+ value
1818
+ : getWrapped(args));
1819
+ };
1705
1820
  /** @internal @deprecated v1 compat */
1706
1821
  const _wrapSignal = (obj, prop) => {
1707
1822
  const r = _wrapProp(obj, prop);
@@ -1716,8 +1831,8 @@ class WrappedSignalImpl extends SignalImpl {
1716
1831
  $func$;
1717
1832
  $funcStr$;
1718
1833
  $flags$;
1719
- $hostElement$ = null;
1720
- [_EFFECT_BACK_REF] = null;
1834
+ $hostElement$ = undefined;
1835
+ [_EFFECT_BACK_REF] = undefined;
1721
1836
  constructor(container, fn, args, fnStr,
1722
1837
  // We need a separate flag to know when the computation needs running because
1723
1838
  // we need the old value to know if effects need running after computation
@@ -1793,7 +1908,7 @@ class WrappedSignalImpl extends SignalImpl {
1793
1908
 
1794
1909
  /** Class for back reference to the EffectSubscription */
1795
1910
  let BackRef$1 = class BackRef {
1796
- [_EFFECT_BACK_REF] = null;
1911
+ [_EFFECT_BACK_REF] = undefined;
1797
1912
  };
1798
1913
  function clearAllEffects(container, consumer) {
1799
1914
  if (vnode_isVNode(consumer) && vnode_isElementVNode(consumer)) {
@@ -1806,6 +1921,7 @@ function clearAllEffects(container, consumer) {
1806
1921
  for (const [, effect] of effects) {
1807
1922
  clearEffectSubscription(container, effect);
1808
1923
  }
1924
+ effects.clear();
1809
1925
  }
1810
1926
  function clearEffectSubscription(container, effect) {
1811
1927
  const backRefs = effect[2 /* EffectSubscriptionProp.BACK_REF */];
@@ -1819,12 +1935,17 @@ function clearEffectSubscription(container, effect) {
1819
1935
  else if (producer instanceof AsyncComputedSignalImpl) {
1820
1936
  clearAsyncComputedSignal(producer, effect);
1821
1937
  }
1938
+ else if (isPropsProxy(producer)) {
1939
+ const propsHandler = producer[_PROPS_HANDLER];
1940
+ clearStoreOrProps(propsHandler, effect);
1941
+ }
1822
1942
  else if (container.$storeProxyMap$.has(producer)) {
1823
1943
  const target = container.$storeProxyMap$.get(producer);
1824
1944
  const storeHandler = getStoreHandler(target);
1825
- clearStore(storeHandler, effect);
1945
+ clearStoreOrProps(storeHandler, effect);
1826
1946
  }
1827
1947
  }
1948
+ backRefs.clear();
1828
1949
  }
1829
1950
  function clearSignal(container, producer, effect) {
1830
1951
  const effects = producer.$effects$;
@@ -1832,7 +1953,7 @@ function clearSignal(container, producer, effect) {
1832
1953
  effects.delete(effect);
1833
1954
  }
1834
1955
  if (producer instanceof WrappedSignalImpl) {
1835
- producer.$hostElement$ = null;
1956
+ producer.$hostElement$ = undefined;
1836
1957
  clearAllEffects(container, producer);
1837
1958
  }
1838
1959
  }
@@ -1846,12 +1967,15 @@ function clearAsyncComputedSignal(producer, effect) {
1846
1967
  pendingEffects.delete(effect);
1847
1968
  }
1848
1969
  }
1849
- function clearStore(producer, effect) {
1970
+ function clearStoreOrProps(producer, effect) {
1850
1971
  const effects = producer?.$effects$;
1851
1972
  if (effects) {
1852
- for (const propEffects of effects.values()) {
1973
+ for (const [prop, propEffects] of effects.entries()) {
1853
1974
  if (propEffects.has(effect)) {
1854
1975
  propEffects.delete(effect);
1976
+ if (propEffects.size === 0) {
1977
+ effects.delete(prop);
1978
+ }
1855
1979
  }
1856
1980
  }
1857
1981
  }
@@ -2663,23 +2787,22 @@ const vnode_applyJournal = (journal) => {
2663
2787
  key = 'class';
2664
2788
  }
2665
2789
  const value = journal[idx++];
2790
+ const shouldRemove = value == null || value === false;
2666
2791
  if (isBooleanAttr(element, key)) {
2667
2792
  element[key] = parseBoolean(value);
2668
2793
  }
2669
- else if (key === 'value' && key in element) {
2670
- element.value = String(value);
2671
- }
2672
2794
  else if (key === dangerouslySetInnerHTML) {
2673
2795
  element.innerHTML = value;
2674
2796
  element.setAttribute(QContainerAttr, "html" /* QContainerValue.HTML */);
2675
2797
  }
2798
+ else if (shouldRemove) {
2799
+ element.removeAttribute(key);
2800
+ }
2801
+ else if (key === 'value' && key in element) {
2802
+ element.value = String(value);
2803
+ }
2676
2804
  else {
2677
- if (value == null || value === false) {
2678
- element.removeAttribute(key);
2679
- }
2680
- else {
2681
- element.setAttribute(key, String(value));
2682
- }
2805
+ element.setAttribute(key, String(value));
2683
2806
  }
2684
2807
  break;
2685
2808
  case 3 /* VNodeJournalOpCode.HoistStyles */:
@@ -3419,6 +3542,7 @@ function materializeFromVNodeData(vParent, vData, element, child) {
3419
3542
  const nodeIsElement = isElement(node);
3420
3543
  return !nodeIsElement || (nodeIsElement && shouldSkipElement(node));
3421
3544
  };
3545
+ let components = null;
3422
3546
  processVNodeData$1(vData, (peek, consumeValue, consume, getChar, nextToConsumeIdx) => {
3423
3547
  if (isNumber(peek())) {
3424
3548
  // Element counts get encoded as numbers.
@@ -3445,6 +3569,7 @@ function materializeFromVNodeData(vParent, vData, element, child) {
3445
3569
  vParent.setAttr(QScopedStyle, consumeValue(), null);
3446
3570
  }
3447
3571
  else if (peek() === VNodeDataChar.RENDER_FN) {
3572
+ (components ||= []).push(vParent);
3448
3573
  vParent.setAttr(OnRenderProp, consumeValue(), null);
3449
3574
  }
3450
3575
  else if (peek() === VNodeDataChar.ID) {
@@ -3540,6 +3665,15 @@ function materializeFromVNodeData(vParent, vData, element, child) {
3540
3665
  // Text nodes get encoded as alphanumeric characters.
3541
3666
  }
3542
3667
  });
3668
+ if (components) {
3669
+ if (!container) {
3670
+ container = getDomContainer(element);
3671
+ }
3672
+ for (const component of components) {
3673
+ container.ensureProjectionResolved(component);
3674
+ }
3675
+ components = null;
3676
+ }
3543
3677
  vParent.lastChild = vLast;
3544
3678
  return vFirst;
3545
3679
  }
@@ -4290,14 +4424,14 @@ const _jsxSplit = (type, varProps, constProps, children, flags, key, dev) => {
4290
4424
  for (const k in varProps) {
4291
4425
  if (k === 'children') {
4292
4426
  children ||= varProps.children;
4293
- varProps.children = undefined;
4427
+ delete varProps.children;
4294
4428
  }
4295
4429
  else if (k === 'key') {
4296
4430
  key ||= varProps.key;
4297
- varProps.key = undefined;
4431
+ delete varProps.key;
4298
4432
  }
4299
4433
  else if (constProps && k in constProps) {
4300
- varProps[k] = undefined;
4434
+ delete varProps[k];
4301
4435
  }
4302
4436
  }
4303
4437
  }
@@ -4370,14 +4504,17 @@ const RenderOnce = (props, key) => {
4370
4504
  };
4371
4505
 
4372
4506
  /** @internal */
4373
- const useTaskQrl = (qrl) => {
4507
+ const useTaskQrl = (qrl, opts) => {
4374
4508
  const { val, set, iCtx, i } = useSequentialScope();
4375
4509
  if (val) {
4376
4510
  return;
4377
4511
  }
4378
4512
  assertQrl(qrl);
4379
4513
  set(1);
4380
- const task = new Task(8 /* TaskFlags.DIRTY */ | 2 /* TaskFlags.TASK */, i, iCtx.$hostElement$, qrl, undefined, null);
4514
+ const taskFlags =
4515
+ // enabled by default
4516
+ opts?.deferUpdates === false ? 0 : 16 /* TaskFlags.RENDER_BLOCKING */;
4517
+ const task = new Task(8 /* TaskFlags.DIRTY */ | 2 /* TaskFlags.TASK */ | taskFlags, i, iCtx.$hostElement$, qrl, undefined, null);
4381
4518
  // In V2 we add the task to the sequential scope. We need to do this
4382
4519
  // in order to be able to retrieve it later when the parent element is
4383
4520
  // deleted and we need to be able to release the task subscriptions.
@@ -4390,7 +4527,7 @@ const useTaskQrl = (qrl) => {
4390
4527
  };
4391
4528
  const runTask = (task, container, host) => {
4392
4529
  task.$flags$ &= -9 /* TaskFlags.DIRTY */;
4393
- cleanupTask(task);
4530
+ cleanupDestroyable(task);
4394
4531
  const iCtx = newInvokeContext(container.$locale$, host, undefined, TaskEvent);
4395
4532
  iCtx.$container$ = container;
4396
4533
  const taskFn = task.$qrl$.getFn(iCtx, () => clearAllEffects(container, task));
@@ -4407,18 +4544,6 @@ const runTask = (task, container, host) => {
4407
4544
  }
4408
4545
  });
4409
4546
  };
4410
- const cleanupTask = (task) => {
4411
- const destroy = task.$destroy$;
4412
- if (destroy) {
4413
- task.$destroy$ = null;
4414
- try {
4415
- destroy();
4416
- }
4417
- catch (err) {
4418
- logError(err);
4419
- }
4420
- }
4421
- };
4422
4547
  class Task extends BackRef$1 {
4423
4548
  $flags$;
4424
4549
  $index$;
@@ -4587,7 +4712,7 @@ const isResourceReturn = (obj) => {
4587
4712
  };
4588
4713
  const runResource = (task, container, host) => {
4589
4714
  task.$flags$ &= -9 /* TaskFlags.DIRTY */;
4590
- cleanupTask(task);
4715
+ cleanupDestroyable(task);
4591
4716
  const iCtx = newInvokeContext(container.$locale$, host, undefined, ResourceEvent);
4592
4717
  iCtx.$container$ = container;
4593
4718
  const taskFn = task.$qrl$.getFn(iCtx, () => clearAllEffects(container, task));
@@ -4676,7 +4801,7 @@ const runResource = (task, container, host) => {
4676
4801
  promise,
4677
4802
  delay(timeout).then(() => {
4678
4803
  if (setState(false, new Error('timeout'))) {
4679
- cleanupTask(task);
4804
+ cleanupDestroyable(task);
4680
4805
  }
4681
4806
  }),
4682
4807
  ]);
@@ -4814,16 +4939,17 @@ function qrlToString(serializationContext, value, raw) {
4814
4939
  // TODO test that provided stringified fn is used
4815
4940
  symbol = String(serializationContext.$addSyncFn$(null, 0, fn));
4816
4941
  }
4817
- if (!value.$capture$ && Array.isArray(value.$captureRef$) && value.$captureRef$.length > 0) {
4942
+ let capturedIds = null;
4943
+ if (Array.isArray(value.$captureRef$) && value.$captureRef$.length > 0) {
4818
4944
  // We refer by id so every capture needs to be a root
4819
- value.$capture$ = value.$captureRef$.map((ref) => `${serializationContext.$addRoot$(ref)}`);
4945
+ capturedIds = value.$captureRef$.map((ref) => `${serializationContext.$addRoot$(ref)}`);
4820
4946
  }
4821
4947
  if (raw) {
4822
- return [chunk, symbol, value.$capture$];
4948
+ return [chunk, symbol, capturedIds];
4823
4949
  }
4824
4950
  let qrlStringInline = `${chunk}#${symbol}`;
4825
- if (value.$capture$ && value.$capture$.length > 0) {
4826
- qrlStringInline += `[${value.$capture$.join(' ')}]`;
4951
+ if (capturedIds && capturedIds.length > 0) {
4952
+ qrlStringInline += `[${capturedIds.join(' ')}]`;
4827
4953
  }
4828
4954
  return qrlStringInline;
4829
4955
  }
@@ -5664,6 +5790,7 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
5664
5790
  */
5665
5791
  const stack = [];
5666
5792
  const asyncQueue = [];
5793
+ const asyncAttributePromises = [];
5667
5794
  ////////////////////////////////
5668
5795
  //// Traverse state variables
5669
5796
  ////////////////////////////////
@@ -5730,8 +5857,9 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
5730
5857
  else if (isSignal(jsxValue)) {
5731
5858
  expectVirtual("S" /* VirtualType.WrappedSignal */, null);
5732
5859
  const unwrappedSignal = jsxValue instanceof WrappedSignalImpl ? jsxValue.$unwrapIfSignal$() : jsxValue;
5733
- const currentSignal = vCurrent?.[_EFFECT_BACK_REF]?.get("." /* EffectProperty.VNODE */)?.[0 /* EffectSubscriptionProp.CONSUMER */];
5734
- if (currentSignal !== unwrappedSignal) {
5860
+ const hasUnwrappedSignal = vCurrent?.[_EFFECT_BACK_REF]
5861
+ ?.get("." /* EffectProperty.VNODE */)?.[2 /* EffectSubscriptionProp.BACK_REF */]?.has(unwrappedSignal);
5862
+ if (!hasUnwrappedSignal) {
5735
5863
  const vHost = (vNewNode || vCurrent);
5736
5864
  descend(resolveSignalAndDescend(() => trackSignalAndAssignHost(unwrappedSignal, vHost, "." /* EffectProperty.VNODE */, container)), true);
5737
5865
  }
@@ -6077,6 +6205,14 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6077
6205
  diff(jsxNode, vHostNode);
6078
6206
  }
6079
6207
  }
6208
+ // Wait for all async attribute promises to complete, then check for more work
6209
+ if (asyncAttributePromises.length) {
6210
+ const promises = asyncAttributePromises.splice(0);
6211
+ return Promise.all(promises).then(() => {
6212
+ // After attributes are set, check if there's more work in the queue
6213
+ return drainAsyncQueue();
6214
+ });
6215
+ }
6080
6216
  }
6081
6217
  function expectNoChildren() {
6082
6218
  const vFirstChild = vCurrent && vnode_getFirstChild(vCurrent);
@@ -6128,11 +6264,11 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6128
6264
  // only svg elements can have namespace attributes
6129
6265
  const namespace = getAttributeNamespace(key);
6130
6266
  if (namespace) {
6131
- element.setAttributeNS(namespace, key, String(value));
6267
+ element.setAttributeNS(namespace, key, value);
6132
6268
  return;
6133
6269
  }
6134
6270
  }
6135
- element.setAttribute(key, String(value));
6271
+ element.setAttribute(key, value);
6136
6272
  }
6137
6273
  }
6138
6274
  const { constProps } = jsx;
@@ -6146,15 +6282,17 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6146
6282
  if (isHtmlAttributeAnEventName(key)) {
6147
6283
  const data = getEventDataFromHtmlAttribute(key);
6148
6284
  if (data) {
6149
- const scope = data[0];
6150
- const eventName = data[1];
6285
+ const [scope, eventName] = data;
6286
+ const scopedEvent = getScopedEventName(scope, eventName);
6287
+ const loaderScopedEvent = getLoaderScopedEventName(scope, scopedEvent);
6151
6288
  if (eventName) {
6152
- vNewNode.setProp(HANDLER_PREFIX + ':' + scope + ':' + eventName, value);
6289
+ vNewNode.setProp(HANDLER_PREFIX + ':' + scopedEvent, value);
6153
6290
  if (scope) {
6154
6291
  // window and document need attrs so qwik loader can find them
6155
6292
  vNewNode.setAttr(key, '', journal);
6156
6293
  }
6157
- registerQwikLoaderEvent(eventName);
6294
+ // register an event for qwik loader (window/document prefixed with '-')
6295
+ registerQwikLoaderEvent(loaderScopedEvent);
6158
6296
  }
6159
6297
  }
6160
6298
  needsQDispatchEventPatch = true;
@@ -6183,7 +6321,8 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6183
6321
  }
6184
6322
  if (isPromise(value)) {
6185
6323
  const vHost = vNewNode;
6186
- value.then((resolvedValue) => setAttribute(key, resolvedValue, vHost));
6324
+ const attributePromise = value.then((resolvedValue) => setAttribute(key, resolvedValue, vHost));
6325
+ asyncAttributePromises.push(attributePromise);
6187
6326
  continue;
6188
6327
  }
6189
6328
  if (key === dangerouslySetInnerHTML) {
@@ -6307,10 +6446,37 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6307
6446
  let srcIdx = 0;
6308
6447
  let dstIdx = 0;
6309
6448
  let patchEventDispatch = false;
6310
- const setAttribute = (key, value, vHost) => {
6311
- vHost.setAttr(key, value !== null ? serializeAttribute(key, value, scopedStyleIdPrefix) : null, journal);
6449
+ /**
6450
+ * Optimized setAttribute that bypasses redundant checks when we already know:
6451
+ *
6452
+ * - The index in dstAttrs (no need for binary search)
6453
+ * - The vnode is ElementVNode (no instanceof check)
6454
+ * - The value has changed (no comparison needed)
6455
+ */
6456
+ const setAttributeDirect = (vnode, key, value, dstIdx, isNewKey) => {
6457
+ const serializedValue = value != null ? serializeAttribute(key, value, scopedStyleIdPrefix) : null;
6458
+ if (isNewKey) {
6459
+ // Adding new key - splice into sorted position
6460
+ if (serializedValue != null) {
6461
+ dstAttrs.splice(dstIdx, 0, key, serializedValue);
6462
+ journal.push(2 /* VNodeJournalOpCode.SetAttribute */, vnode.element, key, serializedValue);
6463
+ }
6464
+ }
6465
+ else {
6466
+ // Updating or removing existing key at dstIdx
6467
+ if (serializedValue != null) {
6468
+ // Update existing value
6469
+ dstAttrs[dstIdx + 1] = serializedValue;
6470
+ journal.push(2 /* VNodeJournalOpCode.SetAttribute */, vnode.element, key, serializedValue);
6471
+ }
6472
+ else {
6473
+ // Remove key (value is null)
6474
+ dstAttrs.splice(dstIdx, 2);
6475
+ journal.push(2 /* VNodeJournalOpCode.SetAttribute */, vnode.element, key, null);
6476
+ }
6477
+ }
6312
6478
  };
6313
- const record = (key, value) => {
6479
+ const record = (key, value, dstIdx, isNewKey) => {
6314
6480
  if (key.startsWith(':')) {
6315
6481
  vnode.setProp(key, value);
6316
6482
  return;
@@ -6354,19 +6520,31 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6354
6520
  }
6355
6521
  }
6356
6522
  if (isPromise(value)) {
6523
+ // For async values, we can't use the known index since it will be stale by the time
6524
+ // the promise resolves. Do a binary search to find the current index.
6357
6525
  const vHost = vnode;
6358
- value.then((resolvedValue) => setAttribute(key, resolvedValue, vHost));
6526
+ const attributePromise = value.then((resolvedValue) => {
6527
+ const idx = mapApp_findIndx(dstAttrs, key, 0);
6528
+ const isNewKey = idx < 0;
6529
+ const currentDstIdx = isNewKey ? idx ^ -1 : idx;
6530
+ setAttributeDirect(vHost, key, resolvedValue, currentDstIdx, isNewKey);
6531
+ });
6532
+ asyncAttributePromises.push(attributePromise);
6359
6533
  return;
6360
6534
  }
6361
- setAttribute(key, value, vnode);
6535
+ // Always use optimized direct path - we know the index from the merge algorithm
6536
+ setAttributeDirect(vnode, key, value, dstIdx, isNewKey);
6362
6537
  };
6363
6538
  const recordJsxEvent = (key, value) => {
6364
6539
  const data = getEventDataFromHtmlAttribute(key);
6365
6540
  if (data) {
6366
6541
  const [scope, eventName] = data;
6367
- record(':' + scope + ':' + eventName, value);
6368
- // register an event for qwik loader
6369
- registerQwikLoaderEvent(eventName);
6542
+ const scopedEvent = getScopedEventName(scope, eventName);
6543
+ const loaderScopedEvent = getLoaderScopedEventName(scope, scopedEvent);
6544
+ // Pass dummy index values since ':' prefixed keys take early return via setProp
6545
+ record(':' + scopedEvent, value, 0, false);
6546
+ // register an event for qwik loader (window/document prefixed with '-')
6547
+ registerQwikLoaderEvent(loaderScopedEvent);
6370
6548
  patchEventDispatch = true;
6371
6549
  }
6372
6550
  };
@@ -6375,8 +6553,8 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6375
6553
  while (srcIdx < srcAttrs.length || dstIdx < dstAttrs.length) {
6376
6554
  const srcKey = srcIdx < srcAttrs.length ? srcAttrs[srcIdx] : undefined;
6377
6555
  const dstKey = dstIdx < dstAttrs.length ? dstAttrs[dstIdx] : undefined;
6378
- // Skip special keys in destination (HANDLER_PREFIX, Q_PREFIX)
6379
- if (dstKey?.startsWith(HANDLER_PREFIX) || dstKey?.startsWith(Q_PREFIX)) {
6556
+ // Skip special keys in destination HANDLER_PREFIX
6557
+ if (dstKey?.startsWith(HANDLER_PREFIX)) {
6380
6558
  dstIdx += 2; // skip key and value
6381
6559
  continue;
6382
6560
  }
@@ -6387,7 +6565,7 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6387
6565
  dstIdx += 2; // skip key and value
6388
6566
  }
6389
6567
  else {
6390
- record(dstKey, null);
6568
+ record(dstKey, null, dstIdx, false);
6391
6569
  // After removal, dstAttrs shrinks by 2, so don't advance dstIdx
6392
6570
  }
6393
6571
  }
@@ -6398,7 +6576,7 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6398
6576
  recordJsxEvent(srcKey, srcValue);
6399
6577
  }
6400
6578
  else {
6401
- record(srcKey, srcValue);
6579
+ record(srcKey, srcValue, dstIdx, true);
6402
6580
  }
6403
6581
  srcIdx += 2; // skip key and value
6404
6582
  // After addition, dstAttrs grows by 2 at sorted position, advance dstIdx
@@ -6414,7 +6592,7 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6414
6592
  recordJsxEvent(srcKey, srcValue);
6415
6593
  }
6416
6594
  else {
6417
- record(srcKey, srcValue);
6595
+ record(srcKey, srcValue, dstIdx, false);
6418
6596
  }
6419
6597
  }
6420
6598
  else if (isEventHandler && !vnode.element.qDispatchEvent) {
@@ -6432,7 +6610,7 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6432
6610
  recordJsxEvent(srcKey, srcValue);
6433
6611
  }
6434
6612
  else {
6435
- record(srcKey, srcValue);
6613
+ record(srcKey, srcValue, dstIdx, true);
6436
6614
  }
6437
6615
  srcIdx += 2; // skip key and value
6438
6616
  // After addition, dstAttrs grows at sorted position (before dstIdx), advance dstIdx
@@ -6445,7 +6623,7 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6445
6623
  dstIdx += 2; // skip key and value
6446
6624
  }
6447
6625
  else {
6448
- record(dstKey, null);
6626
+ record(dstKey, null, dstIdx, false);
6449
6627
  // After removal, dstAttrs shrinks at dstIdx, so don't advance dstIdx
6450
6628
  }
6451
6629
  }
@@ -6647,32 +6825,11 @@ const vnode_diff = (container, jsxNode, vStartNode, scopedStyleIdPrefix) => {
6647
6825
  deleteFromSideBuffer(null, lookupKey);
6648
6826
  }
6649
6827
  if (host) {
6650
- let vNodeProps = host.getProp(ELEMENT_PROPS, container.$getObjectById$);
6651
- let propsAreDifferent = false;
6828
+ const vNodeProps = host.getProp(ELEMENT_PROPS, container.$getObjectById$);
6652
6829
  if (!shouldRender) {
6653
- propsAreDifferent =
6654
- propsDiffer(jsxProps[_CONST_PROPS], vNodeProps?.[_CONST_PROPS]) ||
6655
- propsDiffer(jsxProps[_VAR_PROPS], vNodeProps?.[_VAR_PROPS]);
6656
- shouldRender = shouldRender || propsAreDifferent;
6830
+ shouldRender ||= handleProps(host, jsxProps, vNodeProps, container);
6657
6831
  }
6658
6832
  if (shouldRender) {
6659
- if (propsAreDifferent) {
6660
- if (vNodeProps) {
6661
- // Reuse the same props instance, qrls can use the current props instance
6662
- // as a capture ref, so we can't change it.
6663
- // We need to do this directly, because normally we would subscribe to the signals
6664
- // if any signal is there.
6665
- vNodeProps[_CONST_PROPS] = jsxProps[_CONST_PROPS];
6666
- vNodeProps[_VAR_PROPS] = jsxProps[_VAR_PROPS];
6667
- vNodeProps[_OWNER] = jsxProps[_OWNER];
6668
- }
6669
- else if (jsxProps) {
6670
- // If there is no props instance, create a new one.
6671
- // We can do this because we are not using the props instance for anything else.
6672
- host.setProp(ELEMENT_PROPS, jsxProps);
6673
- vNodeProps = jsxProps;
6674
- }
6675
- }
6676
6833
  // Assign the new QRL instance to the host.
6677
6834
  // Unfortunately it is created every time, something to fix in the optimizer.
6678
6835
  host.setProp(OnRenderProp, componentQRL);
@@ -6809,7 +6966,37 @@ function getComponentHash(vNode, getObject) {
6809
6966
  * ```
6810
6967
  */
6811
6968
  function Projection() { }
6812
- function propsDiffer(src, dst) {
6969
+ function handleProps(host, jsxProps, vNodeProps, container) {
6970
+ let shouldRender = false;
6971
+ let propsAreDifferent = false;
6972
+ if (vNodeProps) {
6973
+ const effects = vNodeProps[_PROPS_HANDLER].$effects$;
6974
+ const constPropsDifferent = handleChangedProps(jsxProps[_CONST_PROPS], vNodeProps[_CONST_PROPS], vNodeProps[_PROPS_HANDLER], container, false);
6975
+ propsAreDifferent = constPropsDifferent;
6976
+ shouldRender ||= constPropsDifferent;
6977
+ if (effects && effects.size > 0) {
6978
+ const varPropsDifferent = handleChangedProps(jsxProps[_VAR_PROPS], vNodeProps[_VAR_PROPS], vNodeProps[_PROPS_HANDLER], container);
6979
+ propsAreDifferent ||= varPropsDifferent;
6980
+ // don't mark as should render, effects will take care of it
6981
+ // shouldRender ||= varPropsDifferent;
6982
+ }
6983
+ }
6984
+ if (propsAreDifferent) {
6985
+ if (vNodeProps) {
6986
+ // Reuse the same props instance, qrls can use the current props instance
6987
+ // as a capture ref, so we can't change it.
6988
+ vNodeProps[_OWNER] = jsxProps[_OWNER];
6989
+ }
6990
+ else if (jsxProps) {
6991
+ // If there is no props instance, create a new one.
6992
+ // We can do this because we are not using the props instance for anything else.
6993
+ host.setProp(ELEMENT_PROPS, jsxProps);
6994
+ vNodeProps = jsxProps;
6995
+ }
6996
+ }
6997
+ return shouldRender;
6998
+ }
6999
+ function handleChangedProps(src, dst, propsHandler, container, triggerEffects = true) {
6813
7000
  const srcEmpty = isPropsEmpty(src);
6814
7001
  const dstEmpty = isPropsEmpty(dst);
6815
7002
  if (srcEmpty && dstEmpty) {
@@ -6837,15 +7024,23 @@ function propsDiffer(src, dst) {
6837
7024
  if (srcLen !== dstLen) {
6838
7025
  return true;
6839
7026
  }
7027
+ let changed = false;
7028
+ propsHandler.$container$ = container;
6840
7029
  for (const key of srcKeys) {
6841
7030
  if (key === 'children' || key === QBackRefs) {
6842
7031
  continue;
6843
7032
  }
6844
7033
  if (!Object.prototype.hasOwnProperty.call(dst, key) || src[key] !== dst[key]) {
6845
- return true;
7034
+ changed = true;
7035
+ if (triggerEffects) {
7036
+ triggerPropsProxyEffect(propsHandler, key);
7037
+ }
7038
+ else {
7039
+ return true;
7040
+ }
6846
7041
  }
6847
7042
  }
6848
- return false;
7043
+ return changed;
6849
7044
  }
6850
7045
  function isPropsEmpty(props) {
6851
7046
  if (!props) {
@@ -6877,28 +7072,30 @@ function cleanup(container, vNode) {
6877
7072
  if (type & 3 /* VNodeFlags.ELEMENT_OR_VIRTUAL_MASK */) {
6878
7073
  clearAllEffects(container, vCursor);
6879
7074
  markVNodeAsDeleted(vCursor);
6880
- // Only elements and virtual nodes need to be traversed for children
6881
- if (type & 2 /* VNodeFlags.Virtual */) {
7075
+ const isComponent = type & 2 /* VNodeFlags.Virtual */ &&
7076
+ vCursor.getProp(OnRenderProp, null) !== null;
7077
+ if (isComponent) {
7078
+ // cleanup q:seq content
6882
7079
  const seq = container.getHostProp(vCursor, ELEMENT_SEQ);
6883
7080
  if (seq) {
6884
7081
  for (let i = 0; i < seq.length; i++) {
6885
7082
  const obj = seq[i];
6886
- if (isTask(obj)) {
6887
- const task = obj;
6888
- clearAllEffects(container, task);
6889
- if (task.$flags$ & 1 /* TaskFlags.VISIBLE_TASK */) {
6890
- container.$scheduler$(32 /* ChoreType.CLEANUP_VISIBLE */, task);
7083
+ if (isObject(obj)) {
7084
+ const objIsTask = isTask(obj);
7085
+ if (objIsTask && obj.$flags$ & 1 /* TaskFlags.VISIBLE_TASK */) {
7086
+ container.$scheduler$(32 /* ChoreType.CLEANUP_VISIBLE */, obj);
7087
+ // don't call cleanupDestroyable yet, do it by the scheduler
7088
+ continue;
6891
7089
  }
6892
- else {
6893
- cleanupTask(task);
7090
+ else if (obj instanceof SignalImpl || isStore(obj)) {
7091
+ clearAllEffects(container, obj);
7092
+ }
7093
+ if (objIsTask || obj instanceof AsyncComputedSignalImpl) {
7094
+ cleanupDestroyable(obj);
6894
7095
  }
6895
7096
  }
6896
7097
  }
6897
7098
  }
6898
- }
6899
- const isComponent = type & 2 /* VNodeFlags.Virtual */ &&
6900
- vCursor.getProp(OnRenderProp, null) !== null;
6901
- if (isComponent) {
6902
7099
  // SPECIAL CASE: If we are a component, we need to descend into the projected content and release the content.
6903
7100
  const attrs = vnode_getProps(vCursor);
6904
7101
  for (let i = 0; i < attrs.length; i = i + 2) {
@@ -7126,7 +7323,8 @@ function findAncestorBlockingChore(chore, type) {
7126
7323
  if (blockingChore.$type$ < 16 /* ChoreType.VISIBLE */ &&
7127
7324
  blockingChore.$type$ !== 3 /* ChoreType.TASK */ &&
7128
7325
  blockingChore.$type$ !== 1 /* ChoreType.QRL_RESOLVE */ &&
7129
- blockingChore.$type$ !== 2 /* ChoreType.RUN_QRL */) {
7326
+ blockingChore.$type$ !== 2 /* ChoreType.RUN_QRL */ &&
7327
+ blockingChore.$state$ === ChoreState.NONE) {
7130
7328
  return blockingChore;
7131
7329
  }
7132
7330
  }
@@ -7151,19 +7349,25 @@ function findBlockingChore(chore, choreQueue, blockedChores, runningChores, cont
7151
7349
  // Check in choreQueue
7152
7350
  // TODO(perf): better to iterate in reverse order?
7153
7351
  for (const candidate of choreQueue) {
7154
- if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) {
7352
+ if (candidate.$type$ === rule.blockingType &&
7353
+ rule.match(chore, candidate, container) &&
7354
+ candidate.$state$ === ChoreState.NONE) {
7155
7355
  return candidate;
7156
7356
  }
7157
7357
  }
7158
7358
  // Check in blockedChores
7159
7359
  for (const candidate of blockedChores) {
7160
- if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) {
7360
+ if (candidate.$type$ === rule.blockingType &&
7361
+ rule.match(chore, candidate, container) &&
7362
+ candidate.$state$ === ChoreState.NONE) {
7161
7363
  return candidate;
7162
7364
  }
7163
7365
  }
7164
7366
  // Check in runningChores
7165
7367
  for (const candidate of runningChores) {
7166
- if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) {
7368
+ if (candidate.$type$ === rule.blockingType &&
7369
+ rule.match(chore, candidate, container) &&
7370
+ candidate.$state$ !== ChoreState.FAILED) {
7167
7371
  return candidate;
7168
7372
  }
7169
7373
  }
@@ -7189,7 +7393,9 @@ function findBlockingChoreForVisible(chore, runningChores, container) {
7189
7393
  continue;
7190
7394
  }
7191
7395
  for (const candidate of runningChores) {
7192
- if (candidate.$type$ === rule.blockingType && rule.match(chore, candidate, container)) {
7396
+ if (candidate.$type$ === rule.blockingType &&
7397
+ rule.match(chore, candidate, container) &&
7398
+ candidate.$state$ !== ChoreState.FAILED) {
7193
7399
  return candidate;
7194
7400
  }
7195
7401
  }
@@ -7430,6 +7636,7 @@ function choreComparator(a, b) {
7430
7636
  if (a.$type$ === 7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */ &&
7431
7637
  b.$type$ === 7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */ &&
7432
7638
  ((a.$target$ instanceof StoreHandler && b.$target$ instanceof StoreHandler) ||
7639
+ (a.$target$ instanceof PropsProxyHandler && b.$target$ instanceof PropsProxyHandler) ||
7433
7640
  (a.$target$ instanceof AsyncComputedSignalImpl &&
7434
7641
  b.$target$ instanceof AsyncComputedSignalImpl)) &&
7435
7642
  a.$payload$ !== b.$payload$) {
@@ -7547,6 +7754,8 @@ const createScheduler = (container, journalFlush, choreQueue, blockedChores, run
7547
7754
  let currentTime = performance.now();
7548
7755
  const nextTick = createNextTick(drainChoreQueue);
7549
7756
  let flushTimerId = null;
7757
+ let blockingChoresCount = 0;
7758
+ let currentChore = null;
7550
7759
  function drainInNextTick() {
7551
7760
  if (!drainScheduled) {
7552
7761
  drainScheduled = true;
@@ -7620,24 +7829,30 @@ Problematic chore:
7620
7829
 
7621
7830
  This is often caused by modifying a signal in an already rendered component during SSR.`;
7622
7831
  logWarn(warningMessage);
7832
+ // Decrement counter if this was a blocking chore that we're skipping
7833
+ if (isRenderBlocking(type)) {
7834
+ blockingChoresCount--;
7835
+ }
7623
7836
  return chore;
7624
7837
  }
7625
7838
  }
7626
7839
  }
7627
7840
  const shouldBlock = chore.$type$ !== 1 /* ChoreType.QRL_RESOLVE */ && chore.$type$ !== 2 /* ChoreType.RUN_QRL */;
7628
7841
  if (shouldBlock) {
7842
+ const runningChore = getRunningChore(chore);
7843
+ if (runningChore) {
7844
+ if (isResourceChore(runningChore)) {
7845
+ addBlockedChore(chore, runningChore, blockedChores);
7846
+ }
7847
+ return chore;
7848
+ }
7629
7849
  const blockingChore = findBlockingChore(chore, choreQueue, blockedChores, runningChores, container);
7630
7850
  if (blockingChore) {
7631
7851
  addBlockedChore(chore, blockingChore, blockedChores);
7632
7852
  return chore;
7633
7853
  }
7634
- const runningChore = getRunningChore(chore);
7635
- if (runningChore) {
7636
- addBlockedChore(chore, runningChore, blockedChores);
7637
- return chore;
7638
- }
7639
7854
  }
7640
- addChore(chore, choreQueue);
7855
+ addChoreAndIncrementBlockingCounter(chore, choreQueue);
7641
7856
  const runImmediately = (isServer && type === 6 /* ChoreType.COMPONENT */) || type === 2 /* ChoreType.RUN_QRL */;
7642
7857
  if (runImmediately && !isDraining) {
7643
7858
  immediateDrain();
@@ -7686,6 +7901,9 @@ This is often caused by modifying a signal in an already rendered component duri
7686
7901
  }, delay);
7687
7902
  }
7688
7903
  function applyJournalFlush() {
7904
+ if (blockingChoresCount > 0) {
7905
+ return;
7906
+ }
7689
7907
  if (!isJournalFlushRunning) {
7690
7908
  // prevent multiple journal flushes from running at the same time
7691
7909
  isJournalFlushRunning = true;
@@ -7752,7 +7970,7 @@ This is often caused by modifying a signal in an already rendered component duri
7752
7970
  if (vnode_isVNode(blockedChore.$host$)) {
7753
7971
  blockedChore.$host$.blockedChores?.delete(blockedChore);
7754
7972
  }
7755
- addChore(blockedChore, choreQueue);
7973
+ addChoreAndIncrementBlockingCounter(blockedChore, choreQueue);
7756
7974
  blockedChoresScheduled = true;
7757
7975
  }
7758
7976
  }
@@ -7762,12 +7980,12 @@ This is often caused by modifying a signal in an already rendered component duri
7762
7980
  drainInNextTick();
7763
7981
  }
7764
7982
  };
7765
- let currentChore = null;
7766
7983
  try {
7767
7984
  while (choreQueue.length) {
7768
7985
  currentTime = performance.now();
7769
7986
  const chore = (currentChore = choreQueue.shift());
7770
7987
  if (chore.$state$ !== ChoreState.NONE) {
7988
+ // Chore was already processed, counter already decremented in finishChore/handleError
7771
7989
  continue;
7772
7990
  }
7773
7991
  if (vNodeAlreadyDeleted(chore) &&
@@ -7778,6 +7996,10 @@ This is often caused by modifying a signal in an already rendered component duri
7778
7996
  if (vnode_isVNode(chore.$host$)) {
7779
7997
  chore.$host$.chores?.delete(chore);
7780
7998
  }
7999
+ // Decrement counter if this was a blocking chore that we're skipping
8000
+ if (isRenderBlocking(chore.$type$)) {
8001
+ blockingChoresCount--;
8002
+ }
7781
8003
  continue;
7782
8004
  }
7783
8005
  if (chore.$type$ === 16 /* ChoreType.VISIBLE */) {
@@ -7786,7 +8008,7 @@ This is often caused by modifying a signal in an already rendered component duri
7786
8008
  applyJournalFlush();
7787
8009
  const blockingChore = findBlockingChoreForVisible(chore, runningChores, container);
7788
8010
  if (blockingChore && blockingChore.$state$ === ChoreState.RUNNING) {
7789
- addBlockedChore(chore, blockingChore, blockedChores);
8011
+ (blockingChore.$blockedChores$ ||= new ChoreArray()).add(chore);
7790
8012
  continue;
7791
8013
  }
7792
8014
  }
@@ -7853,10 +8075,18 @@ This is often caused by modifying a signal in an already rendered component duri
7853
8075
  if (vnode_isVNode(chore.$host$)) {
7854
8076
  chore.$host$.chores?.delete(chore);
7855
8077
  }
8078
+ // Decrement blocking counter if this chore was blocking journal flush
8079
+ if (isRenderBlocking(chore.$type$)) {
8080
+ blockingChoresCount--;
8081
+ }
7856
8082
  }
7857
8083
  function handleError(chore, e) {
7858
8084
  chore.$endTime$ = performance.now();
7859
8085
  chore.$state$ = ChoreState.FAILED;
8086
+ // Decrement blocking counter if this chore was blocking journal flush
8087
+ if (isRenderBlocking(chore.$type$)) {
8088
+ blockingChoresCount--;
8089
+ }
7860
8090
  // If we used the result as promise, this won't exist
7861
8091
  chore.$reject$?.(e);
7862
8092
  container.handleError(e, chore.$host$);
@@ -7894,14 +8124,21 @@ This is often caused by modifying a signal in an already rendered component duri
7894
8124
  returnValue = runResource(payload, container, host);
7895
8125
  }
7896
8126
  else {
7897
- returnValue = runTask(payload, container, host);
8127
+ const task = payload;
8128
+ returnValue = runTask(task, container, host);
8129
+ if (task.$flags$ & 16 /* TaskFlags.RENDER_BLOCKING */) {
8130
+ blockingChoresCount++;
8131
+ returnValue = maybeThen(returnValue, () => {
8132
+ blockingChoresCount--;
8133
+ });
8134
+ }
7898
8135
  }
7899
8136
  }
7900
8137
  break;
7901
8138
  case 32 /* ChoreType.CLEANUP_VISIBLE */:
7902
8139
  {
7903
8140
  const task = chore.$payload$;
7904
- cleanupTask(task);
8141
+ cleanupDestroyable(task);
7905
8142
  }
7906
8143
  break;
7907
8144
  case 4 /* ChoreType.NODE_DIFF */:
@@ -7995,22 +8232,42 @@ This is often caused by modifying a signal in an already rendered component duri
7995
8232
  }
7996
8233
  return null;
7997
8234
  }
8235
+ function addChoreAndIncrementBlockingCounter(chore, choreArray) {
8236
+ if (addChore(chore, choreArray)) {
8237
+ blockingChoresCount++;
8238
+ }
8239
+ }
7998
8240
  };
8241
+ function addChore(chore, choreArray) {
8242
+ const idx = choreArray.add(chore);
8243
+ if (idx < 0) {
8244
+ if (vnode_isVNode(chore.$host$)) {
8245
+ (chore.$host$.chores ||= new ChoreArray()).add(chore);
8246
+ }
8247
+ return isRenderBlocking(chore.$type$);
8248
+ }
8249
+ return false;
8250
+ }
7999
8251
  function vNodeAlreadyDeleted(chore) {
8000
8252
  return !!(chore.$host$ && vnode_isVNode(chore.$host$) && chore.$host$.flags & 32 /* VNodeFlags.Deleted */);
8001
8253
  }
8254
+ function isResourceChore(chore) {
8255
+ return (chore.$type$ === 3 /* ChoreType.TASK */ &&
8256
+ !!chore.$payload$ &&
8257
+ !!(chore.$payload$.$flags$ & 4 /* TaskFlags.RESOURCE */));
8258
+ }
8002
8259
  function addBlockedChore(blockedChore, blockingChore, blockedChores) {
8260
+ if (!isResourceChore(blockedChore) && choreComparator(blockedChore, blockingChore) === 0) {
8261
+ return;
8262
+ }
8003
8263
  (blockingChore.$blockedChores$ ||= new ChoreArray()).add(blockedChore);
8004
8264
  blockedChores.add(blockedChore);
8005
8265
  if (vnode_isVNode(blockedChore.$host$)) {
8006
8266
  (blockedChore.$host$.blockedChores ||= new ChoreArray()).add(blockedChore);
8007
8267
  }
8008
8268
  }
8009
- function addChore(chore, choreArray) {
8010
- const idx = choreArray.add(chore);
8011
- if (idx < 0 && vnode_isVNode(chore.$host$)) {
8012
- (chore.$host$.chores ||= new ChoreArray()).add(chore);
8013
- }
8269
+ function isRenderBlocking(type) {
8270
+ return type === 4 /* ChoreType.NODE_DIFF */ || type === 6 /* ChoreType.COMPONENT */;
8014
8271
  }
8015
8272
  function choreTypeToName(type) {
8016
8273
  return ({
@@ -8101,9 +8358,9 @@ function debugTrace(action, arg, queue, blockedChores) {
8101
8358
  }
8102
8359
  }
8103
8360
  // Blocked chores section
8104
- if (blockedChores && blockedChores.size > 0) {
8361
+ if (blockedChores && blockedChores.length > 0) {
8105
8362
  lines.push('');
8106
- lines.push(`🚫 Blocked Chores (${blockedChores.size} items):`);
8363
+ lines.push(`🚫 Blocked Chores (${blockedChores.length} items):`);
8107
8364
  Array.from(blockedChores).forEach((chore, index) => {
8108
8365
  const type = debugChoreTypeToString(chore.$type$);
8109
8366
  const host = String(chore.$host$).replaceAll(/\n.*/gim, '');
@@ -8179,16 +8436,16 @@ async function serialize(serializationContext) {
8179
8436
  let parent;
8180
8437
  const qrlMap = new Map();
8181
8438
  /** Helper to output an array */
8182
- const outputArray = (value, keepNulls, writeFn) => {
8439
+ const outputArray = (value, keepUndefined, writeFn) => {
8183
8440
  $writer$.write('[');
8184
8441
  let separator = false;
8185
8442
  let length;
8186
- if (keepNulls) {
8443
+ if (keepUndefined) {
8187
8444
  length = value.length;
8188
8445
  }
8189
8446
  else {
8190
8447
  length = value.length - 1;
8191
- while (length >= 0 && value[length] === null) {
8448
+ while (length >= 0 && value[length] === undefined) {
8192
8449
  length--;
8193
8450
  }
8194
8451
  length++;
@@ -8205,7 +8462,7 @@ async function serialize(serializationContext) {
8205
8462
  $writer$.write(']');
8206
8463
  };
8207
8464
  /** Output a type,value pair. If the value is an array, it calls writeValue on each item. */
8208
- const output = (type, value, keepNulls) => {
8465
+ const output = (type, value, keepUndefined) => {
8209
8466
  $writer$.write(`${type},`);
8210
8467
  if (typeof value === 'number') {
8211
8468
  $writer$.write(value.toString());
@@ -8222,7 +8479,7 @@ async function serialize(serializationContext) {
8222
8479
  $writer$.write(lastIdx === 0 ? s : s.slice(lastIdx));
8223
8480
  }
8224
8481
  else {
8225
- outputArray(value, keepNulls, (valueItem, idx) => {
8482
+ outputArray(value, !!keepUndefined, (valueItem, idx) => {
8226
8483
  writeValue(valueItem, idx);
8227
8484
  });
8228
8485
  }
@@ -8413,7 +8670,12 @@ async function serialize(serializationContext) {
8413
8670
  const writeObjectValue = (value) => {
8414
8671
  if (isPropsProxy(value)) {
8415
8672
  const owner = value[_OWNER];
8416
- output(32 /* TypeIds.PropsProxy */, [_serializationWeakRef(owner), owner.varProps, owner.constProps]);
8673
+ output(32 /* TypeIds.PropsProxy */, [
8674
+ _serializationWeakRef(owner),
8675
+ owner.varProps,
8676
+ owner.constProps,
8677
+ value[_PROPS_HANDLER].$effects$,
8678
+ ]);
8417
8679
  }
8418
8680
  else if (value instanceof SubscriptionData) {
8419
8681
  output(33 /* TypeIds.SubscriptionData */, [value.data.$scopedStyleIdPrefix$, value.data.$isConst$]);
@@ -8443,7 +8705,7 @@ async function serialize(serializationContext) {
8443
8705
  }
8444
8706
  }
8445
8707
  const out = [storeTarget, flags, effects, ...innerStores];
8446
- while (out[out.length - 1] == null) {
8708
+ while (out[out.length - 1] === undefined) {
8447
8709
  out.pop();
8448
8710
  }
8449
8711
  output(29 /* TypeIds.Store */, out);
@@ -8453,7 +8715,7 @@ async function serialize(serializationContext) {
8453
8715
  const result = value[SerializerSymbol](value);
8454
8716
  if (isPromise(result)) {
8455
8717
  const forwardRef = resolvePromise(result, $addRoot$, (resolved, resolvedValue) => {
8456
- return new PromiseResult(28 /* TypeIds.SerializerSignal */, resolved, resolvedValue, null, null);
8718
+ return new PromiseResult(28 /* TypeIds.SerializerSignal */, resolved, resolvedValue, undefined, undefined);
8457
8719
  });
8458
8720
  output(2 /* TypeIds.ForwardRef */, forwardRef);
8459
8721
  }
@@ -8488,10 +8750,16 @@ async function serialize(serializationContext) {
8488
8750
  else if (value instanceof SignalImpl) {
8489
8751
  if (value instanceof SerializerSignalImpl) {
8490
8752
  addPreloadQrl(value.$computeQrl$);
8491
- const forwardRefId = resolvePromise(getCustomSerializerPromise(value, value.$untrackedValue$), $addRoot$, (resolved, resolvedValue) => {
8492
- return new PromiseResult(28 /* TypeIds.SerializerSignal */, resolved, resolvedValue, value.$effects$, value.$computeQrl$);
8493
- });
8494
- output(2 /* TypeIds.ForwardRef */, forwardRefId);
8753
+ const maybeValue = getCustomSerializerPromise(value, value.$untrackedValue$);
8754
+ if (isPromise(maybeValue)) {
8755
+ const forwardRefId = resolvePromise(maybeValue, $addRoot$, (resolved, resolvedValue) => {
8756
+ return new PromiseResult(28 /* TypeIds.SerializerSignal */, resolved, resolvedValue, value.$effects$, value.$computeQrl$);
8757
+ });
8758
+ output(2 /* TypeIds.ForwardRef */, forwardRefId);
8759
+ }
8760
+ else {
8761
+ output(28 /* TypeIds.SerializerSignal */, [value.$computeQrl$, value.$effects$, maybeValue]);
8762
+ }
8495
8763
  return;
8496
8764
  }
8497
8765
  if (value instanceof WrappedSignalImpl) {
@@ -8524,13 +8792,28 @@ async function serialize(serializationContext) {
8524
8792
  if (isAsync) {
8525
8793
  out.push(value.$loadingEffects$, value.$errorEffects$, value.$untrackedLoading$, value.$untrackedError$);
8526
8794
  }
8795
+ let keepUndefined = false;
8527
8796
  if (v !== NEEDS_COMPUTATION) {
8528
8797
  out.push(v);
8798
+ if (!isAsync && v === undefined) {
8799
+ /**
8800
+ * If value is undefined, we need to keep it in the output. If we don't do that, later
8801
+ * during resuming, the value will be set to symbol(invalid) with flag invalid, and
8802
+ * thats is incorrect.
8803
+ */
8804
+ keepUndefined = true;
8805
+ }
8529
8806
  }
8530
- output(isAsync ? 27 /* TypeIds.AsyncComputedSignal */ : 26 /* TypeIds.ComputedSignal */, out);
8807
+ output(isAsync ? 27 /* TypeIds.AsyncComputedSignal */ : 26 /* TypeIds.ComputedSignal */, out, keepUndefined);
8531
8808
  }
8532
8809
  else {
8533
- output(24 /* TypeIds.Signal */, [value.$untrackedValue$, ...(value.$effects$ || [])]);
8810
+ const v = value.$untrackedValue$;
8811
+ const keepUndefined = v === undefined;
8812
+ const out = [v];
8813
+ if (value.$effects$) {
8814
+ out.push(...value.$effects$);
8815
+ }
8816
+ output(24 /* TypeIds.Signal */, out, keepUndefined);
8534
8817
  }
8535
8818
  }
8536
8819
  else if (value instanceof URL) {
@@ -8614,9 +8897,9 @@ async function serialize(serializationContext) {
8614
8897
  value.varProps,
8615
8898
  value.constProps,
8616
8899
  value.children,
8617
- value.toSort || null,
8900
+ value.toSort || undefined,
8618
8901
  ];
8619
- while (out[out.length - 1] == null) {
8902
+ while (out[out.length - 1] === undefined) {
8620
8903
  out.pop();
8621
8904
  }
8622
8905
  output(31 /* TypeIds.JSXNode */, out);
@@ -8630,7 +8913,7 @@ async function serialize(serializationContext) {
8630
8913
  value[_EFFECT_BACK_REF],
8631
8914
  value.$state$,
8632
8915
  ];
8633
- while (out[out.length - 1] == null) {
8916
+ while (out[out.length - 1] === undefined) {
8634
8917
  out.pop();
8635
8918
  }
8636
8919
  output(21 /* TypeIds.Task */, out);
@@ -8757,7 +9040,7 @@ class PromiseResult {
8757
9040
  $value$;
8758
9041
  $effects$;
8759
9042
  $qrl$;
8760
- constructor($type$, $resolved$, $value$, $effects$ = null, $qrl$ = null) {
9043
+ constructor($type$, $resolved$, $value$, $effects$ = undefined, $qrl$ = undefined) {
8761
9044
  this.$type$ = $type$;
8762
9045
  this.$resolved$ = $resolved$;
8763
9046
  this.$value$ = $value$;
@@ -8766,20 +9049,24 @@ class PromiseResult {
8766
9049
  }
8767
9050
  }
8768
9051
  function getCustomSerializerPromise(signal, value) {
8769
- return new Promise((resolve) => {
8770
- signal.$computeQrl$.resolve().then((arg) => {
8771
- let data;
8772
- if (arg.serialize) {
8773
- data = arg.serialize(value);
8774
- }
8775
- else if (SerializerSymbol in value) {
8776
- data = value[SerializerSymbol](value);
8777
- }
8778
- if (data === undefined) {
8779
- data = NEEDS_COMPUTATION;
8780
- }
8781
- resolve(data);
8782
- });
9052
+ if (value === NEEDS_COMPUTATION) {
9053
+ return value;
9054
+ }
9055
+ return maybeThen((signal.$computeQrl$.resolved || signal.$computeQrl$.resolve()), (arg) => {
9056
+ let data;
9057
+ if (typeof arg === 'function') {
9058
+ arg = arg();
9059
+ }
9060
+ if (arg.serialize) {
9061
+ data = arg.serialize(value);
9062
+ }
9063
+ else if (typeof value === 'object' && SerializerSymbol in value) {
9064
+ data = value[SerializerSymbol](value);
9065
+ }
9066
+ if (data === undefined) {
9067
+ data = NEEDS_COMPUTATION;
9068
+ }
9069
+ return data;
8783
9070
  });
8784
9071
  }
8785
9072
  const discoverValuesForVNodeData = (vnodeData, callback) => {
@@ -8830,7 +9117,7 @@ function serializeWrappingFn(serializationContext, value) {
8830
9117
  return [syncFnId, value.$args$];
8831
9118
  }
8832
9119
  function filterEffectBackRefs(effectBackRef) {
8833
- let effectBackRefToSerialize = null;
9120
+ let effectBackRefToSerialize = undefined;
8834
9121
  if (effectBackRef) {
8835
9122
  for (const [effectProp, effect] of effectBackRef) {
8836
9123
  if (effect[2 /* EffectSubscriptionProp.BACK_REF */]) {
@@ -9010,7 +9297,7 @@ class _SharedContainer {
9010
9297
  throw Error('Not implemented');
9011
9298
  };
9012
9299
  const choreQueue = new ChoreArray();
9013
- const blockedChores = new Set();
9300
+ const blockedChores = new ChoreArray();
9014
9301
  const runningChores = new Set();
9015
9302
  this.$scheduler$ = createScheduler(this, journalFlush, choreQueue, blockedChores, runningChores);
9016
9303
  }
@@ -9095,7 +9382,7 @@ async function _walkJSX(ssr, value, options) {
9095
9382
  }
9096
9383
  function processJSXNode(ssr, enqueue, value, options) {
9097
9384
  // console.log('processJSXNode', value);
9098
- if (value === null || value === undefined) {
9385
+ if (value == null) {
9099
9386
  ssr.textNode('');
9100
9387
  }
9101
9388
  else if (typeof value === 'boolean') {
@@ -9152,12 +9439,12 @@ function processJSXNode(ssr, enqueue, value, options) {
9152
9439
  appendQwikInspectorAttribute(jsx, qwikInspectorAttrValue);
9153
9440
  }
9154
9441
  }
9155
- const innerHTML = ssr.openElement(type, varPropsToSsrAttrs(jsx.varProps, jsx.constProps, {
9442
+ const innerHTML = ssr.openElement(type, toSsrAttrs(jsx.varProps, {
9156
9443
  serializationCtx: ssr.serializationCtx,
9157
9444
  styleScopedId: options.styleScoped,
9158
9445
  key: jsx.key,
9159
9446
  toSort: jsx.toSort,
9160
- }), constPropsToSsrAttrs(jsx.constProps, jsx.varProps, {
9447
+ }), toSsrAttrs(jsx.constProps, {
9161
9448
  serializationCtx: ssr.serializationCtx,
9162
9449
  styleScopedId: options.styleScoped,
9163
9450
  }), qwikInspectorAttrValue);
@@ -9254,12 +9541,22 @@ function processJSXNode(ssr, enqueue, value, options) {
9254
9541
  const componentFrame = ssr.getParentComponentFrame();
9255
9542
  componentFrame.distributeChildrenIntoSlots(jsx.children, options.styleScoped, options.parentComponentFrame);
9256
9543
  const jsxOutput = applyQwikComponentBody(ssr, jsx, type);
9257
- const compStyleComponentId = addComponentStylePrefix(host.getProp(QScopedStyle));
9258
9544
  enqueue(new ParentComponentData(options.styleScoped, options.parentComponentFrame));
9259
9545
  enqueue(ssr.closeComponent);
9260
- enqueue(jsxOutput);
9261
- isPromise(jsxOutput) && enqueue(Promise);
9262
- enqueue(new ParentComponentData(compStyleComponentId, componentFrame));
9546
+ if (isPromise(jsxOutput)) {
9547
+ // Defer reading QScopedStyle until after the promise resolves
9548
+ enqueue(async () => {
9549
+ const resolvedOutput = await jsxOutput;
9550
+ const compStyleComponentId = addComponentStylePrefix(host.getProp(QScopedStyle));
9551
+ enqueue(resolvedOutput);
9552
+ enqueue(new ParentComponentData(compStyleComponentId, componentFrame));
9553
+ });
9554
+ }
9555
+ else {
9556
+ enqueue(jsxOutput);
9557
+ const compStyleComponentId = addComponentStylePrefix(host.getProp(QScopedStyle));
9558
+ enqueue(new ParentComponentData(compStyleComponentId, componentFrame));
9559
+ }
9263
9560
  }
9264
9561
  else {
9265
9562
  const inlineComponentProps = [ELEMENT_KEY, jsx.key];
@@ -9276,12 +9573,6 @@ function processJSXNode(ssr, enqueue, value, options) {
9276
9573
  }
9277
9574
  }
9278
9575
  }
9279
- function varPropsToSsrAttrs(varProps, constProps, options) {
9280
- return toSsrAttrs(varProps, options);
9281
- }
9282
- function constPropsToSsrAttrs(constProps, varProps, options) {
9283
- return toSsrAttrs(constProps, options);
9284
- }
9285
9576
  function toSsrAttrs(record, options) {
9286
9577
  if (record == null) {
9287
9578
  return null;
@@ -9344,7 +9635,7 @@ function setEvent(serializationCtx, key, rawValue) {
9344
9635
  *
9345
9636
  * For internal qrls (starting with `_`) we assume that they do the right thing.
9346
9637
  */
9347
- if (!qrl.$symbol$.startsWith('_') && (qrl.$captureRef$ || qrl.$capture$)) {
9638
+ if (!qrl.$symbol$.startsWith('_') && qrl.$captureRef$?.length) {
9348
9639
  qrl = createQRL(null, '_run', _run, null, null, [qrl]);
9349
9640
  }
9350
9641
  return qrlToString(serializationCtx, qrl);
@@ -9375,14 +9666,16 @@ function addQwikEventToSerializationContext(serializationCtx, key, qrl) {
9375
9666
  // TODO extract window/document too so qwikloader can precisely listen
9376
9667
  const data = getEventDataFromHtmlAttribute(key);
9377
9668
  if (data) {
9378
- const eventName = data[1];
9379
- serializationCtx.$eventNames$.add(eventName);
9669
+ const [scope, eventName] = data;
9670
+ const scopedEvent = getScopedEventName(scope, eventName);
9671
+ const loaderScopedEvent = getLoaderScopedEventName(scope, scopedEvent);
9672
+ serializationCtx.$eventNames$.add(loaderScopedEvent);
9380
9673
  serializationCtx.$eventQrls$.add(qrl);
9381
9674
  }
9382
9675
  }
9383
9676
  function addPreventDefaultEventToSerializationContext(serializationCtx, key) {
9384
- // skip first 15 chars, this is length of the `preventdefault:`
9385
- const eventName = key.substring(15);
9677
+ // skip first 15 chars, this is length of the `preventdefault`, leave the ":"
9678
+ const eventName = key.substring(14);
9386
9679
  if (eventName) {
9387
9680
  serializationCtx.$eventNames$.add(eventName);
9388
9681
  }
@@ -9520,10 +9813,11 @@ const inflate = (container, target, typeId, data) => {
9520
9813
  asyncComputed.$loadingEffects$ = new Set(d[2]);
9521
9814
  asyncComputed.$errorEffects$ = new Set(d[3]);
9522
9815
  asyncComputed.$untrackedLoading$ = d[4];
9523
- asyncComputed.$untrackedError$ = d[5] || null;
9816
+ asyncComputed.$untrackedError$ = d[5];
9524
9817
  const hasValue = d.length > 6;
9525
9818
  if (hasValue) {
9526
9819
  asyncComputed.$untrackedValue$ = d[6];
9820
+ asyncComputed.$promiseValue$ = d[6];
9527
9821
  }
9528
9822
  asyncComputed.$flags$ |= 1 /* SignalFlags.INVALID */;
9529
9823
  break;
@@ -9534,7 +9828,9 @@ const inflate = (container, target, typeId, data) => {
9534
9828
  const computed = target;
9535
9829
  const d = data;
9536
9830
  computed.$computeQrl$ = d[0];
9537
- computed.$effects$ = new Set(d[1]);
9831
+ if (d[1]) {
9832
+ computed.$effects$ = new Set(d[1]);
9833
+ }
9538
9834
  const hasValue = d.length > 2;
9539
9835
  if (hasValue) {
9540
9836
  computed.$untrackedValue$ = d[2];
@@ -9629,6 +9925,7 @@ const inflate = (container, target, typeId, data) => {
9629
9925
  owner._proxy = propsProxy;
9630
9926
  }
9631
9927
  propsProxy[_OWNER] = owner;
9928
+ propsProxy[_PROPS_HANDLER].$effects$ = d[3];
9632
9929
  break;
9633
9930
  case 33 /* TypeIds.SubscriptionData */: {
9634
9931
  const effectData = target;
@@ -10185,6 +10482,9 @@ class DomContainer extends _SharedContainer {
10185
10482
  preprocessState(this.$rawStateData$, this);
10186
10483
  this.$stateData$ = wrapDeserializerProxy(this, this.$rawStateData$);
10187
10484
  }
10485
+ if (!qTest && element.isConnected) {
10486
+ element.dispatchEvent(new CustomEvent('qresume', { bubbles: true }));
10487
+ }
10188
10488
  }
10189
10489
  $setRawState$(id, vParent) {
10190
10490
  this.$stateData$[id] = vParent;
@@ -10620,7 +10920,7 @@ const getOrCreateStore = (obj, flags, container) => {
10620
10920
  class StoreHandler {
10621
10921
  $flags$;
10622
10922
  $container$;
10623
- $effects$ = null;
10923
+ $effects$ = undefined;
10624
10924
  constructor($flags$, $container$) {
10625
10925
  this.$flags$ = $flags$;
10626
10926
  this.$container$ = $container$;
@@ -10630,7 +10930,7 @@ class StoreHandler {
10630
10930
  }
10631
10931
  force(prop) {
10632
10932
  const target = getStoreTarget(this);
10633
- this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, getEffects(target, prop, this.$effects$));
10933
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, this, getEffects(target, prop, this.$effects$));
10634
10934
  }
10635
10935
  get(target, prop) {
10636
10936
  // TODO(perf): handle better `slice` calls
@@ -10699,7 +10999,7 @@ class StoreHandler {
10699
10999
  if (!Array.isArray(target)) {
10700
11000
  // If the target is an array, we don't need to trigger effects.
10701
11001
  // Changing the length property will trigger effects.
10702
- this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, this, getEffects(target, prop, this.$effects$));
11002
+ this.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, this, getEffects(target, prop, this.$effects$));
10703
11003
  }
10704
11004
  return true;
10705
11005
  }
@@ -10755,13 +11055,14 @@ function addStoreEffect(target, prop, store, effectSubscription) {
10755
11055
  // to unsubscribe from. So we need to store the reference from the effect back
10756
11056
  // to this signal.
10757
11057
  ensureContainsBackRef(effectSubscription, target);
11058
+ // TODO is this needed with the preloader?
10758
11059
  addQrlToSerializationCtx(effectSubscription, store.$container$);
10759
11060
  }
10760
11061
  function setNewValueAndTriggerEffects(prop, value, target, currentStore) {
10761
11062
  target[prop] = value;
10762
11063
  const effects = getEffects(target, prop, currentStore.$effects$);
10763
11064
  if (effects) {
10764
- currentStore.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, null, currentStore, effects);
11065
+ currentStore.$container$?.$scheduler$(7 /* ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS */, undefined, currentStore, effects);
10765
11066
  }
10766
11067
  }
10767
11068
  function getEffects(target, prop, storeEffects) {
@@ -10786,7 +11087,7 @@ function getEffects(target, prop, storeEffects) {
10786
11087
  effectsToTrigger.add(effect);
10787
11088
  }
10788
11089
  }
10789
- return effectsToTrigger || null;
11090
+ return effectsToTrigger;
10790
11091
  }
10791
11092
 
10792
11093
  const canSerialize = (value, seen = new WeakSet()) => {
@@ -12576,21 +12877,22 @@ const useVisibleTaskQrl = (qrl, opts) => {
12576
12877
  const { val, set, i, iCtx } = useSequentialScope();
12577
12878
  const eagerness = opts?.strategy ?? 'intersection-observer';
12578
12879
  if (val) {
12579
- if (isServerPlatform()) {
12580
- useRunTask(val, eagerness);
12880
+ if (!(val.$flags$ & 32 /* TaskFlags.EVENTS_REGISTERED */) && !isServerPlatform()) {
12881
+ val.$flags$ |= 32 /* TaskFlags.EVENTS_REGISTERED */;
12882
+ useRegisterTaskEvents(val, eagerness);
12581
12883
  }
12582
12884
  return;
12583
12885
  }
12584
12886
  assertQrl(qrl);
12585
12887
  const task = new Task(1 /* TaskFlags.VISIBLE_TASK */, i, iCtx.$hostElement$, qrl, undefined, null);
12586
12888
  set(task);
12587
- useRunTask(task, eagerness);
12889
+ useRegisterTaskEvents(task, eagerness);
12588
12890
  if (!isServerPlatform()) {
12589
12891
  qrl.resolve(iCtx.$element$);
12590
12892
  iCtx.$container$.$scheduler$(16 /* ChoreType.VISIBLE */, task);
12591
12893
  }
12592
12894
  };
12593
- const useRunTask = (task, eagerness) => {
12895
+ const useRegisterTaskEvents = (task, eagerness) => {
12594
12896
  if (eagerness === 'intersection-observer') {
12595
12897
  useOn('qvisible', getTaskHandlerQrl(task));
12596
12898
  }