diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index 6250b012ce4..b060d0cfb56 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -808,7 +808,7 @@ } ], "kind": "Function", - "content": "Create an inlined QRL. This is mostly useful on the server side for serialization.\n\n\n```typescript\ninlinedQrl: (symbol: T | null, symbolName: string, lexicalScopeCapture?: any[]) => QRL\n```\n\n\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nsymbol\n\n\n\n\nT \\| null\n\n\n\n\nThe object/function to register, or `null` to retrieve a previously registered one by hash\n\n\n
\n\nsymbolName\n\n\n\n\nstring\n\n\n\n\nThe name of the symbol.\n\n\n
\n\nlexicalScopeCapture\n\n\n\n\nany\\[\\]\n\n\n\n\n_(Optional)_ A set of lexically scoped variables to capture.\n\n\n
\n\n**Returns:**\n\n[QRL](#qrl)<T>", + "content": "Create an inlined QRL. This is mostly useful on the server side for serialization.\n\n\n```typescript\ninlinedQrl: (symbol: T | null, symbolName: string, lexicalScopeCapture?: any[] | null) => QRL\n```\n\n\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nsymbol\n\n\n\n\nT \\| null\n\n\n\n\nThe object/function to register, or `null` to retrieve a previously registered one by hash\n\n\n
\n\nsymbolName\n\n\n\n\nstring\n\n\n\n\nThe name of the symbol.\n\n\n
\n\nlexicalScopeCapture\n\n\n\n\nany\\[\\] \\| null\n\n\n\n\n_(Optional)_ A set of lexically scoped variables to capture.\n\n\n
\n\n**Returns:**\n\n[QRL](#qrl)<T>", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/qrl/qrl.ts", "mdFile": "core.inlinedqrl.md" }, diff --git a/packages/docs/src/routes/api/qwik/index.mdx b/packages/docs/src/routes/api/qwik/index.mdx index ba9b4abdcc9..94b5d3bd0de 100644 --- a/packages/docs/src/routes/api/qwik/index.mdx +++ b/packages/docs/src/routes/api/qwik/index.mdx @@ -1713,7 +1713,7 @@ Create an inlined QRL. This is mostly useful on the server side for serializatio inlinedQrl: ( symbol: T | null, symbolName: string, - lexicalScopeCapture?: any[], + lexicalScopeCapture?: any[] | null, ) => QRL; ``` @@ -1762,7 +1762,7 @@ lexicalScopeCapture -any[] +any[] \| null diff --git a/packages/qwik/src/core/client/dom-container.ts b/packages/qwik/src/core/client/dom-container.ts index bac0dcc921a..0532c5c5ee2 100644 --- a/packages/qwik/src/core/client/dom-container.ts +++ b/packages/qwik/src/core/client/dom-container.ts @@ -136,6 +136,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer { this.element = element; this.$buildBase$ = element.getAttribute(QBaseAttr)!; this.$instanceHash$ = element.getAttribute(QInstanceAttr)!; + console.log('======= \n\ncontainer instance hash', this.$instanceHash$); this.qManifestHash = element.getAttribute(QManifestHashAttr)!; this.rootVNode = vnode_newUnMaterializedElement(this.element); this.$rawStateData$ = []; diff --git a/packages/qwik/src/core/qwik.core.api.md b/packages/qwik/src/core/qwik.core.api.md index d5d91a026fe..ba0474e5edb 100644 --- a/packages/qwik/src/core/qwik.core.api.md +++ b/packages/qwik/src/core/qwik.core.api.md @@ -394,13 +394,13 @@ export const _IMMUTABLE: unique symbol; export const implicit$FirstArg: (fn: (qrl: QRL, ...rest: REST) => RET) => ((qrl: FIRST, ...rest: REST) => RET); // @public -export const inlinedQrl: (symbol: T | null, symbolName: string, lexicalScopeCapture?: any[]) => QRL; +export const inlinedQrl: (symbol: T | null, symbolName: string, lexicalScopeCapture?: any[] | null) => QRL; // Warning: (ae-forgotten-export) The symbol "QRLDev" needs to be exported by the entry point index.d.ts // Warning: (ae-internal-missing-underscore) The name "inlinedQrlDEV" should be prefixed with an underscore because the declaration is marked as @internal // // @internal (undocumented) -export const inlinedQrlDEV: (symbol: T, symbolName: string, opts: QRLDev, lexicalScopeCapture?: any[]) => QRL; +export const inlinedQrlDEV: (symbol: T, symbolName: string, opts: QRLDev, lexicalScopeCapture?: any[] | null) => QRL; export { isBrowser } @@ -570,10 +570,10 @@ export type NativeUIEvent = UIEvent; export type NativeWheelEvent = WheelEvent; // @internal (undocumented) -export const _noopQrl: (symbolName: string, lexicalScopeCapture?: any[]) => QRL; +export const _noopQrl: (symbolName: string, lexicalScopeCapture?: any[] | null) => QRL; // @internal (undocumented) -export const _noopQrlDEV: (symbolName: string, opts: QRLDev, lexicalScopeCapture?: any[]) => QRL; +export const _noopQrlDEV: (symbolName: string, opts: QRLDev, lexicalScopeCapture?: any[] | null) => QRL; // @public export type NoSerialize = (T & { diff --git a/packages/qwik/src/core/reactive-primitives/impl/signal-impl.ts b/packages/qwik/src/core/reactive-primitives/impl/signal-impl.ts index 6845433c9d1..e257f485ab9 100644 --- a/packages/qwik/src/core/reactive-primitives/impl/signal-impl.ts +++ b/packages/qwik/src/core/reactive-primitives/impl/signal-impl.ts @@ -133,7 +133,7 @@ export const setupSignalValueAccess = ( } else { assertTrue( !ctx.$container$ || ctx.$container$ === target.$container$, - 'Do not use signals across containers' + `Do not use signals across containers ${!!ctx.$container$?.addBackpatchEntry} ${!!target.$container$?.addBackpatchEntry}, ${ctx.$container$ === target.$container$}` ); } const effectSubscriber = ctx.$effectSubscriber$; diff --git a/packages/qwik/src/core/shared/jsx/jsx-node.ts b/packages/qwik/src/core/shared/jsx/jsx-node.ts index 20652bf3fce..8d1eeadb27c 100644 --- a/packages/qwik/src/core/shared/jsx/jsx-node.ts +++ b/packages/qwik/src/core/shared/jsx/jsx-node.ts @@ -158,10 +158,10 @@ const handleBindProp = (props: Props, prop: string) => { if (value) { if (prop === BIND_CHECKED) { props.checked = value; - props['on:input'] = createQRL(null, '_chk', _chk, null, null, [value]); + props['on:input'] = createQRL(null, '_chk', _chk, null, [value]); } else { props.value = value; - props['on:input'] = createQRL(null, '_val', _val, null, null, [value]); + props['on:input'] = createQRL(null, '_val', _val, null, [value]); } return true; } diff --git a/packages/qwik/src/core/shared/qrl/qrl-class.ts b/packages/qwik/src/core/shared/qrl/qrl-class.ts index 1e8a91ac3f6..5b009e8df98 100644 --- a/packages/qwik/src/core/shared/qrl/qrl-class.ts +++ b/packages/qwik/src/core/shared/qrl/qrl-class.ts @@ -35,7 +35,6 @@ export type QRLInternalMethods = { readonly $symbol$: string; readonly $hash$: string; - $capture$: string[] | null; $captureRef$: unknown[] | null; dev: QRLDev | null; @@ -65,7 +64,6 @@ export const createQRL = ( symbol: string, symbolRef: null | ValueOrPromise, symbolFn: null | (() => Promise>), - capture: null | Readonly, captureRef: Readonly | null ): QRLInternal => { if (qDev && qSerialize) { @@ -128,7 +126,7 @@ export const createQRL = ( // Wrap functions to provide their lexical scope const wrapFn = (fn: TYPE): TYPE => { - if (typeof fn !== 'function' || (!capture?.length && !captureRef?.length)) { + if (typeof fn !== 'function' || !captureRef?.length) { return fn; } return function (this: unknown, ...args: QrlArgs) { @@ -236,7 +234,6 @@ export const createQRL = ( $hash$: hash, getFn: bindFnToContext, - $capture$: capture, $captureRef$: captureRef, dev: null, resolved: undefined, diff --git a/packages/qwik/src/core/shared/qrl/qrl.public.ts b/packages/qwik/src/core/shared/qrl/qrl.public.ts index a0f9daaf09f..5e3bee90eab 100644 --- a/packages/qwik/src/core/shared/qrl/qrl.public.ts +++ b/packages/qwik/src/core/shared/qrl/qrl.public.ts @@ -254,7 +254,7 @@ export const $ = (expression: T): QRL => { ); } - return createQRL(null, 's' + runtimeSymbolId++, expression, null, null, null); + return createQRL(null, 's' + runtimeSymbolId++, expression, null, null); }; /** @private Use To avoid optimizer replacement */ export const dollar = $; @@ -293,7 +293,7 @@ export const sync$ = (fn: T): SyncQRL => { fn = new Function('return ' + fn.toString())() as any; } - return createQRL('', SYNC_QRL, fn, null, null, null) as any; + return createQRL('', SYNC_QRL, fn, null, null) as any; }; /** @@ -314,5 +314,5 @@ export const _qrlSync = function ( serializedFn = fn.toString(); } (fn as any).serialized = serializedFn; - return createQRL('', SYNC_QRL, fn, null, null, null) as any; + return createQRL('', SYNC_QRL, fn, null, null) as any; }; diff --git a/packages/qwik/src/core/shared/qrl/qrl.ts b/packages/qwik/src/core/shared/qrl/qrl.ts index acd52857995..0109e7770d8 100644 --- a/packages/qwik/src/core/shared/qrl/qrl.ts +++ b/packages/qwik/src/core/shared/qrl/qrl.ts @@ -75,7 +75,7 @@ export const qrl = ( } // Unwrap subscribers - return createQRL(chunk, symbol, null, symbolFn, null, lexicalScopeCapture); + return createQRL(chunk, symbol, null, symbolFn, lexicalScopeCapture); }; /** @@ -90,25 +90,25 @@ export const qrl = ( export const inlinedQrl = ( symbol: T | null, symbolName: string, - lexicalScopeCapture: any[] = EMPTY_ARRAY + lexicalScopeCapture: any[] | null = null ): QRL => { // Unwrap subscribers - return createQRL(null, symbolName, symbol, null, null, lexicalScopeCapture); + return createQRL(null, symbolName, symbol, null, lexicalScopeCapture); }; /** @internal */ export const _noopQrl = ( symbolName: string, - lexicalScopeCapture: any[] = EMPTY_ARRAY + lexicalScopeCapture: any[] | null = null ): QRL => { - return createQRL(null, symbolName, null, null, null, lexicalScopeCapture); + return createQRL(null, symbolName, null, null, lexicalScopeCapture); }; /** @internal */ export const _noopQrlDEV = ( symbolName: string, opts: QRLDev, - lexicalScopeCapture: any[] = EMPTY_ARRAY + lexicalScopeCapture: any[] | null = null ): QRL => { const newQrl = _noopQrl(symbolName, lexicalScopeCapture) as QRLInternal; newQrl.dev = opts; @@ -120,7 +120,7 @@ export const qrlDEV = ( chunkOrFn: string | (() => Promise), symbol: string, opts: QRLDev, - lexicalScopeCapture: any[] = EMPTY_ARRAY + lexicalScopeCapture?: any[] ): QRL => { const newQrl = qrl(chunkOrFn, symbol, lexicalScopeCapture, 1) as QRLInternal; newQrl.dev = opts; @@ -132,7 +132,7 @@ export const inlinedQrlDEV = ( symbol: T, symbolName: string, opts: QRLDev, - lexicalScopeCapture: any[] = EMPTY_ARRAY + lexicalScopeCapture: any[] | null = null ): QRL => { const qrl = inlinedQrl(symbol, symbolName, lexicalScopeCapture) as QRLInternal; qrl.dev = opts; diff --git a/packages/qwik/src/core/shared/qrl/qrl.unit.ts b/packages/qwik/src/core/shared/qrl/qrl.unit.ts index 2a53cee0619..fe1497f8731 100644 --- a/packages/qwik/src/core/shared/qrl/qrl.unit.ts +++ b/packages/qwik/src/core/shared/qrl/qrl.unit.ts @@ -61,31 +61,25 @@ describe('serialization', () => { matchProps(parseQRL('./chunk#s1'), { $chunk$: './chunk', $symbol$: 's1', - $capture$: null, }); matchProps(parseQRL('./chunk#s1[1 2]'), { $chunk$: './chunk', $symbol$: 's1', - $capture$: [1, 2], }); matchProps(parseQRL('./chunk#s1[1 2]'), { $chunk$: './chunk', $symbol$: 's1', - $capture$: [1, 2], }); matchProps(parseQRL('./chunk#s1[1 2]'), { $chunk$: './chunk', $symbol$: 's1', - $capture$: [1, 2], }); matchProps(parseQRL('./chunk[1 2]'), { $chunk$: './chunk', - $capture$: [1, 2], }); matchProps(parseQRL('./path#symbol[2]'), { $chunk$: './path', $symbol$: 'symbol', - $capture$: [2], }); matchProps( parseQRL( @@ -94,7 +88,6 @@ describe('serialization', () => { { $chunk$: '/src/path%2d/foo_symbol.js?_qrl_parent=/home/user/project/src/path/foo.js', $symbol$: 'symbol', - $capture$: [2], } ); }); @@ -109,36 +102,22 @@ describe('serialization', () => { new WeakMap() ); assert.equal( - qrlToString(serializationContext, createQRL('./chunk', '', null, null, null, null)), + qrlToString(serializationContext, createQRL('./chunk', '', null, null, null)), 'chunk#' ); assert.equal( - qrlToString(serializationContext, createQRL('./c', 's1', null, null, null, null)), + qrlToString(serializationContext, createQRL('./c', 's1', null, null, null)), 'c#s1' ); + assert.equal(qrlToString(serializationContext, createQRL('./c', 's1', null, null, [])), 'c#s1'); assert.equal( - qrlToString(serializationContext, createQRL('./c', 's1', null, null, [], null)), - 'c#s1' - ); - assert.equal( - qrlToString( - serializationContext, - createQRL( - './c', - 's1', - null, - null, - // should be ignored - [1, '2'] as any, - [{}, {}] - ) - ), + qrlToString(serializationContext, createQRL('./c', 's1', null, null, [{}, {}])), 'c#s1[0 1]' ); assert.equal( qrlToString( serializationContext, - createQRL('src/routes/[...index]/a+b/c?foo', 's1', null, null, null, [{}, {}]) + createQRL('src/routes/[...index]/a+b/c?foo', 's1', null, null, [{}, {}]) ), 'src/routes/[...index]/a+b/c?foo#s1[2 3]' ); @@ -176,7 +155,7 @@ describe('serialization', () => { describe('createQRL', () => { test('should create QRL', () => { - const q = createQRL('chunk', 'symbol', 'resolved', null, null, null); + const q = createQRL('chunk', 'symbol', 'resolved', null, null); matchProps(q, { $chunk$: 'chunk', $symbol$: 'symbol', @@ -184,11 +163,11 @@ describe('createQRL', () => { }); }); test('should have .resolved: given scalar', async () => { - const q = createQRL('chunk', 'symbol', 'resolved', null, null, null); + const q = createQRL('chunk', 'symbol', 'resolved', null, null); assert.equal(q.resolved, 'resolved'); }); test('should have .resolved: given promise for scalar', async () => { - const q = createQRL('chunk', 'symbol', Promise.resolve('resolved'), null, null, null); + const q = createQRL('chunk', 'symbol', Promise.resolve('resolved'), null, null); assert.equal(q.resolved, undefined); assert.equal(await q.resolve(), 'resolved'); assert.equal(q.resolved, 'resolved'); @@ -199,7 +178,6 @@ describe('createQRL', () => { 'symbol', null, () => Promise.resolve({ symbol: 'resolved' }), - null, null ); assert.equal(q.resolved, undefined); @@ -209,17 +187,17 @@ describe('createQRL', () => { const fn = () => 'hi'; test('should have .resolved: given function without captures', async () => { - const q = createQRL('chunk', 'symbol', fn, null, null, null); + const q = createQRL('chunk', 'symbol', fn, null, null); assert.equal(q.resolved, fn); }); test('should have .resolved: given promise for function without captures', async () => { - const q = createQRL('chunk', 'symbol', Promise.resolve(fn), null, null, null); + const q = createQRL('chunk', 'symbol', Promise.resolve(fn), null, null); assert.equal(q.resolved, undefined); assert.equal(await q.resolve(), fn); assert.equal(q.resolved, fn); }); test('should have .resolved: promise for function without captures', async () => { - const q = createQRL('chunk', 'symbol', null, () => Promise.resolve({ symbol: fn }), null, null); + const q = createQRL('chunk', 'symbol', null, () => Promise.resolve({ symbol: fn }), null); assert.equal(q.resolved, undefined); assert.equal(await q.resolve(), fn); assert.equal(q.resolved, fn); @@ -227,13 +205,13 @@ describe('createQRL', () => { const capFn = () => useLexicalScope(); test('should have .resolved: given function with captures', async () => { - const q = createQRL('chunk', 'symbol', capFn, null, null, ['hi']); + const q = createQRL('chunk', 'symbol', capFn, null, ['hi']); assert.isDefined(q.resolved); assert.notEqual(q.resolved, capFn); assert.deepEqual(q.resolved!(), ['hi']); }); test('should have .resolved: given promise for function with captures', async () => { - const q = createQRL('chunk', 'symbol', Promise.resolve(capFn), null, null, ['hi']); + const q = createQRL('chunk', 'symbol', Promise.resolve(capFn), null, ['hi']); assert.equal(q.resolved, undefined); assert.deepEqual(await q(), ['hi']); assert.notEqual(q.resolved, capFn); @@ -245,7 +223,6 @@ describe('createQRL', () => { 'symbol', null, () => Promise.resolve({ symbol: capFn }), - null, ['hi'] ); assert.equal(q.resolved, undefined); diff --git a/packages/qwik/src/core/shared/scheduler-rules.unit.tsx b/packages/qwik/src/core/shared/scheduler-rules.unit.tsx index 74f3438cb05..8dd22cf9d43 100644 --- a/packages/qwik/src/core/shared/scheduler-rules.unit.tsx +++ b/packages/qwik/src/core/shared/scheduler-rules.unit.tsx @@ -60,7 +60,7 @@ function createMockTask( } function createMockQRL(symbol: string): QRL { - return createQRL(null, symbol, null, null, null, null) as QRL; + return createQRL(null, symbol, null, null, null) as QRL; } describe('findBlockingChore', () => { diff --git a/packages/qwik/src/core/shared/serdes/allocate.ts b/packages/qwik/src/core/shared/serdes/allocate.ts index e314c46c79e..d12fdff83fa 100644 --- a/packages/qwik/src/core/shared/serdes/allocate.ts +++ b/packages/qwik/src/core/shared/serdes/allocate.ts @@ -51,11 +51,22 @@ export const allocate = (container: DeserializeContainer, typeId: number, value: case TypeIds.QRL: case TypeIds.PreloadQRL: { if (typeof value === 'string') { - const data = value.split(' ').map(Number); - const chunk = container.$getObjectById$(data[0]) as string; - const symbol = container.$getObjectById$(data[1]) as string; - const captureIds = data.length > 2 ? data.slice(2) : null; - return createQRLWithBackChannel(chunk, symbol, captureIds); + const firstSpace = value.indexOf(' '); + const chunkIdx = Number(value.slice(0, firstSpace)); + const secondSpace = value.indexOf(' ', firstSpace + 1); + const symbolIdx = Number( + secondSpace === -1 + ? value.slice(firstSpace + 1) + : value.slice(firstSpace + 1, secondSpace) + ); + const chunk = container.$getObjectById$(chunkIdx) as string; + const symbol = container.$getObjectById$(symbolIdx) as string; + const qrl = createQRLWithBackChannel(chunk, symbol); + if (secondSpace !== -1) { + // temporarily store the captured references for inflate + qrl.$captureRef$ = value.slice(secondSpace + 1) as unknown as unknown[]; + } + return qrl; } else { return createQRLWithBackChannel('', String(value)); } diff --git a/packages/qwik/src/core/shared/serdes/inflate.ts b/packages/qwik/src/core/shared/serdes/inflate.ts index 39e13458551..f4378335b51 100644 --- a/packages/qwik/src/core/shared/serdes/inflate.ts +++ b/packages/qwik/src/core/shared/serdes/inflate.ts @@ -122,6 +122,7 @@ export const inflate = ( break; } case TypeIds.Signal: { + console.log('INFLATE SIGNAL', target, data); const signal = target as SignalImpl; const d = data as [unknown, ...EffectSubscription[]]; signal.$untrackedValue$ = d[0]; @@ -300,17 +301,17 @@ export const _eagerDeserializeArray = ( return output; }; export function _inflateQRL(container: DeserializeContainer, qrl: QRLInternal) { - if (qrl.$captureRef$) { - // early return if capture references are already set and qrl is already inflated - return qrl; - } - const captureIds = qrl.$capture$; - qrl.$captureRef$ = captureIds ? captureIds.map((id) => container.$getObjectById$(id)) : null; - // clear serialized capture references - qrl.$capture$ = null; if (container.element) { qrl.$setContainer$(container.element); } + if (typeof qrl.$captureRef$ !== 'string') { + // early return if qrl is already inflated by cycle + return qrl; + } + const captureRefs = (qrl.$captureRef$ as unknown as string) + .split(' ') + .map((id) => container.$getObjectById$(Number(id))); + qrl.$captureRef$ = captureRefs; return qrl; } export function deserializeData(container: DeserializeContainer, typeId: number, value: unknown) { diff --git a/packages/qwik/src/core/shared/serdes/qrl-to-string.spec.ts b/packages/qwik/src/core/shared/serdes/qrl-to-string.spec.ts index a5c2de88b3e..fb8de9fc58a 100644 --- a/packages/qwik/src/core/shared/serdes/qrl-to-string.spec.ts +++ b/packages/qwik/src/core/shared/serdes/qrl-to-string.spec.ts @@ -17,28 +17,28 @@ describe('qrlToString', () => { describe('async QRL serialization', () => { it('should serialize a basic async QRL without captures', () => { - const qrl = createQRL('myChunk', 'mySymbol', null, null, null, null) as QRLInternal; + const qrl = createQRL('myChunk', 'mySymbol', null, null, null) as QRLInternal; const result = qrlToString(mockContext, qrl); expect(result).toBe('myChunk#mySymbol'); }); it('should serialize QRL with chunk and symbol', () => { - const qrl = createQRL('path/to/chunk', 'functionName', null, null, null, null) as QRLInternal; + const qrl = createQRL('path/to/chunk', 'functionName', null, null, null) as QRLInternal; const result = qrlToString(mockContext, qrl); expect(result).toBe('path/to/chunk#functionName'); }); it('should remove "./" prefix from chunk', () => { - const qrl = createQRL('./myChunk', 'mySymbol', null, null, null, null) as QRLInternal; + const qrl = createQRL('./myChunk', 'mySymbol', null, null, null) as QRLInternal; const result = qrlToString(mockContext, qrl); expect(result).toBe('myChunk#mySymbol'); }); it('should resolve chunk from context when chunk is missing', () => { - const qrl = createQRL(null, 'mySymbol_abc123', null, null, null, null) as QRLInternal; + const qrl = createQRL(null, 'mySymbol_abc123', null, null, null) as QRLInternal; mockContext.$symbolToChunkResolver$ = vi.fn(() => 'resolved-chunk'); const result = qrlToString(mockContext, qrl); @@ -48,7 +48,7 @@ describe('qrlToString', () => { }); it('should use fallback chunk in dev mode when chunk cannot be resolved', () => { - const qrl = createQRL(null, 'mySymbol_abc123', null, null, null, null) as QRLInternal; + const qrl = createQRL(null, 'mySymbol_abc123', null, null, null) as QRLInternal; mockContext.$symbolToChunkResolver$ = vi.fn(() => '') as any; // In dev mode, it falls back to QRL_RUNTIME_CHUNK instead of throwing @@ -62,7 +62,7 @@ describe('qrlToString', () => { const testFn = function myFunc() { return 42; }; - const qrl = createQRL('', SYNC_QRL, testFn, null, null, null) as SyncQRLInternal; + const qrl = createQRL('', SYNC_QRL, testFn, null, null) as SyncQRLInternal; mockContext.$addSyncFn$ = vi.fn(() => 5); const result = qrlToString(mockContext, qrl); @@ -73,7 +73,7 @@ describe('qrlToString', () => { it('should not include chunk for sync QRL', () => { const testFn = () => 'test'; - const qrl = createQRL('', SYNC_QRL, testFn, null, null, null) as SyncQRLInternal; + const qrl = createQRL('', SYNC_QRL, testFn, null, null) as SyncQRLInternal; mockContext.$addSyncFn$ = vi.fn(() => 99); const result = qrlToString(mockContext, qrl); @@ -85,7 +85,7 @@ describe('qrlToString', () => { describe('capture references', () => { it('should serialize QRL with single capture reference', () => { const captureRef = { value: 'captured' }; - const qrl = createQRL('myChunk', 'mySymbol', null, null, null, [captureRef]) as QRLInternal; + const qrl = createQRL('myChunk', 'mySymbol', null, null, [captureRef]) as QRLInternal; mockContext.$addRoot$ = vi.fn(() => 3) as any; const result = qrlToString(mockContext, qrl); @@ -98,7 +98,7 @@ describe('qrlToString', () => { const capture1 = { value: 'first' }; const capture2 = { value: 'second' }; const capture3 = { value: 'third' }; - const qrl = createQRL('myChunk', 'mySymbol', null, null, null, [ + const qrl = createQRL('myChunk', 'mySymbol', null, null, [ capture1, capture2, capture3, @@ -118,21 +118,15 @@ describe('qrlToString', () => { it('should not mutate the original QRL object', () => { const captureRef = { value: 'captured' }; - const qrl = createQRL('myChunk', 'mySymbol', null, null, null, [captureRef]) as QRLInternal; + const qrl = createQRL('myChunk', 'mySymbol', null, null, [captureRef]) as QRLInternal; mockContext.$addRoot$ = vi.fn(() => 5) as any; - // Ensure the QRL doesn't have $capture$ set initially - expect(qrl.$capture$).toBeNull(); - const result = qrlToString(mockContext, qrl); expect(result).toBe('myChunk#mySymbol[5]'); - - // After serialization, the original QRL should NOT be mutated - expect(qrl.$capture$).toBeNull(); }); it('should handle empty capture references array', () => { - const qrl = createQRL('myChunk', 'mySymbol', null, null, null, []) as QRLInternal; + const qrl = createQRL('myChunk', 'mySymbol', null, null, []) as QRLInternal; const result = qrlToString(mockContext, qrl); @@ -141,7 +135,7 @@ describe('qrlToString', () => { }); it('should handle null capture references', () => { - const qrl = createQRL('myChunk', 'mySymbol', null, null, null, null) as QRLInternal; + const qrl = createQRL('myChunk', 'mySymbol', null, null, null) as QRLInternal; const result = qrlToString(mockContext, qrl); @@ -152,7 +146,7 @@ describe('qrlToString', () => { describe('raw mode', () => { it('should return tuple in raw mode without captures', () => { - const qrl = createQRL('myChunk', 'mySymbol', null, null, null, null) as QRLInternal; + const qrl = createQRL('myChunk', 'mySymbol', null, null, null) as QRLInternal; const result = qrlToString(mockContext, qrl, true); @@ -161,7 +155,7 @@ describe('qrlToString', () => { it('should return tuple in raw mode with captures', () => { const captureRef = { value: 'captured' }; - const qrl = createQRL('myChunk', 'mySymbol', null, null, null, [captureRef]) as QRLInternal; + const qrl = createQRL('myChunk', 'mySymbol', null, null, [captureRef]) as QRLInternal; mockContext.$addRoot$ = vi.fn(() => 7) as any; const result = qrlToString(mockContext, qrl, true); @@ -171,7 +165,7 @@ describe('qrlToString', () => { it('should return tuple in raw mode for sync QRL', () => { const testFn = () => {}; - const qrl = createQRL('', SYNC_QRL, testFn, null, null, null) as SyncQRLInternal; + const qrl = createQRL('', SYNC_QRL, testFn, null, null) as SyncQRLInternal; mockContext.$addSyncFn$ = vi.fn(() => 15); const result = qrlToString(mockContext, qrl, true); @@ -180,7 +174,7 @@ describe('qrlToString', () => { }); it('should return tuple in raw mode with chunk starting with "./"', () => { - const qrl = createQRL('./myChunk', 'mySymbol', null, null, null, null) as QRLInternal; + const qrl = createQRL('./myChunk', 'mySymbol', null, null, null) as QRLInternal; const result = qrlToString(mockContext, qrl, true); diff --git a/packages/qwik/src/core/shared/serdes/qrl-to-string.ts b/packages/qwik/src/core/shared/serdes/qrl-to-string.ts index 3d4a693ff9c..d267b9ca2f0 100644 --- a/packages/qwik/src/core/shared/serdes/qrl-to-string.ts +++ b/packages/qwik/src/core/shared/serdes/qrl-to-string.ts @@ -44,6 +44,12 @@ export function qrlToString( new Map()); // During tests the resolved value is always available backChannel.set(value.$symbol$, value.resolved); + console.log('QRL TO STRING', { + symbol: value.$symbol$, + chunk, + code: value.$symbolRef$?.toString(), + }); + if (!chunk) { chunk = QRL_RUNTIME_CHUNK; } @@ -76,18 +82,14 @@ export function qrlToString( return qrlStringInline; } -export function createQRLWithBackChannel( - chunk: string, - symbol: string, - captureIds?: number[] | null -): QRLInternal { +export function createQRLWithBackChannel(chunk: string, symbol: string): QRLInternal { let qrlRef = null; if (isDev && chunk === QRL_RUNTIME_CHUNK) { const backChannel: Map = (globalThis as any).__qrl_back_channel__; assertDefined(backChannel, 'Missing QRL_RUNTIME_CHUNK'); qrlRef = backChannel.get(symbol); } - return createQRL(chunk, symbol, qrlRef, null, captureIds!, null); + return createQRL(chunk, symbol, qrlRef, null, null); } /** Parses "chunk#hash[...rootRef]" */ @@ -98,15 +100,13 @@ export function parseQRL(qrl: string): QRLInternal { const chunk = hashIdx > -1 ? qrl.slice(0, hashIdx) : qrl.slice(0, captureStart); const symbol = captureStart > -1 ? qrl.slice(hashIdx + 1, captureStart) : qrl.slice(hashIdx + 1); - const captureIds = - captureStart > -1 && captureEnd > -1 - ? qrl - .slice(captureStart + 1, captureEnd) - .split(' ') - .filter((v) => v.length) - .map((s) => parseInt(s, 10)) - : null; - return createQRLWithBackChannel(chunk, symbol, captureIds); + const captureIdsString = + captureStart > -1 && captureEnd > -1 && qrl.slice(captureStart + 1, captureEnd); + const created = createQRLWithBackChannel(chunk, symbol); + if (captureIdsString) { + created.$captureRef$ = captureIdsString as unknown as unknown[]; + } + return created; } export const QRL_RUNTIME_CHUNK = 'mock-chunk'; diff --git a/packages/qwik/src/core/shared/serdes/serdes.unit.ts b/packages/qwik/src/core/shared/serdes/serdes.unit.ts index b1562bff873..5cdb314ade3 100644 --- a/packages/qwik/src/core/shared/serdes/serdes.unit.ts +++ b/packages/qwik/src/core/shared/serdes/serdes.unit.ts @@ -1235,7 +1235,7 @@ describe('shared-serialization', () => { `); }); it('should dedupe function sub-data', async () => { - const objs = await serialize([shared1], createQRL(null, 'foo', 123, null, null, [shared1])); + const objs = await serialize([shared1], createQRL(null, 'foo', 123, null, [shared1])); expect(_dumpState(objs)).toMatchInlineSnapshot(` " 0 Array [ diff --git a/packages/qwik/src/core/ssr/ssr-render-jsx.ts b/packages/qwik/src/core/ssr/ssr-render-jsx.ts index 7f68d3a321e..594bfd12417 100644 --- a/packages/qwik/src/core/ssr/ssr-render-jsx.ts +++ b/packages/qwik/src/core/ssr/ssr-render-jsx.ts @@ -406,7 +406,7 @@ function setEvent( * For internal qrls (starting with `_`) we assume that they do the right thing. */ if (!qrl.$symbol$.startsWith('_') && qrl.$captureRef$?.length) { - qrl = createQRL(null, '_run', _run, null, null, [qrl]); + qrl = createQRL(null, '_run', _run, null, [qrl]); } return qrlToString(serializationCtx, qrl); }; diff --git a/packages/qwik/src/core/tests/container.spec.tsx b/packages/qwik/src/core/tests/container.spec.tsx index fd2c7b35463..d9aff2f88f4 100644 --- a/packages/qwik/src/core/tests/container.spec.tsx +++ b/packages/qwik/src/core/tests/container.spec.tsx @@ -432,7 +432,7 @@ describe('serializer v2', () => { const [dstQrl] = container.$getObjectById$(0)[SERIALIZABLE_STATE]; expect(dstQrl.$hash$).toEqual(srcQrl.$hash$); expect(dstQrl.$captureRef$).toEqual( - srcQrl.$captureRef$.length ? srcQrl.$captureRef$ : null + srcQrl.$captureRef$?.length ? srcQrl.$captureRef$ : null ); expect(dstQrl.resolved).toEqual((srcQrl as any).resolved); }); diff --git a/packages/qwik/src/core/tests/sync-qrl.spec.tsx b/packages/qwik/src/core/tests/sync-qrl.spec.tsx index d73ff967adc..04cd6b7f61c 100644 --- a/packages/qwik/src/core/tests/sync-qrl.spec.tsx +++ b/packages/qwik/src/core/tests/sync-qrl.spec.tsx @@ -15,7 +15,7 @@ describe.each([ { + sync$((e, target) => { if (target.getAttribute('shouldPreventDefault')) { e.preventDefault(); } diff --git a/packages/qwik/src/core/tests/use-serialized.spec.tsx b/packages/qwik/src/core/tests/use-serialized.spec.tsx index f8626405e1b..15446551851 100644 --- a/packages/qwik/src/core/tests/use-serialized.spec.tsx +++ b/packages/qwik/src/core/tests/use-serialized.spec.tsx @@ -10,13 +10,13 @@ import { domRender, ssrRenderToDom, trigger } from '@qwik.dev/core/testing'; import { describe, expect, it } from 'vitest'; import { useSerializer$ } from '../use/use-serializer'; -const debug = false; //true; +const debug = !false; //true; Error.stackTraceLimit = 100; // This is almost the same as useComputed, so we only test the custom serialization describe.each([ { render: ssrRenderToDom }, // - { render: domRender }, // + // { render: domRender }, // ])('$render.name: useSerializer$', ({ render }) => { it('should do custom serialization', async () => { const Counter = component$(() => { @@ -188,7 +188,7 @@ describe.each([ ); }); - it('should not crash when used many times', async () => { + it.only('should not crash when used many times', async () => { // We don't have the Signal type here const MyComponent = component$(({ foo }: { foo: { value: number } }) => { const custom = useSerializer$(() => ({ diff --git a/packages/qwik/src/core/use/use-visible-task.ts b/packages/qwik/src/core/use/use-visible-task.ts index d6d289099fb..ea32b5e07aa 100644 --- a/packages/qwik/src/core/use/use-visible-task.ts +++ b/packages/qwik/src/core/use/use-visible-task.ts @@ -58,5 +58,5 @@ export const useRunTask = (task: Task, eagerness: VisibleTaskStrategy | undefine }; const getTaskHandlerQrl = (task: Task): QRL => { - return createQRL(null, '_task', scheduleTask, null, null, [task]); + return createQRL(null, '_task', scheduleTask, null, [task]); };