Skip to content

Commit 764908e

Browse files
committed
feat(core): serializer support
FEAT: `NoSerializeSymbol`: objects that have this defined will not be serialized FEAT: `SerializerSymbol`: objects that have this defined as a function will get it called with the object as a parameter during serialization. The function should return the data that should be serialized. Use this to remove cached data, consolidate things etc.
1 parent 89947db commit 764908e

File tree

8 files changed

+152
-6
lines changed

8 files changed

+152
-6
lines changed

.changeset/tricky-peaches-buy.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@qwik.dev/core': minor
3+
---
4+
5+
FEAT: `NoSerializeSymbol`: objects that have this defined will not be serialized
6+
7+
FEAT: `SerializerSymbol`: objects that have this defined as a function will get it called with the object as a parameter during serialization. The function should return the data that should be serialized.
8+
Use this to remove cached data, consolidate things etc.

packages/docs/src/routes/api/qwik/api.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,20 @@
841841
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts",
842842
"mdFile": "core.noserialize.md"
843843
},
844+
{
845+
"name": "NoSerializeSymbol",
846+
"id": "noserializesymbol",
847+
"hierarchy": [
848+
{
849+
"name": "NoSerializeSymbol",
850+
"id": "noserializesymbol"
851+
}
852+
],
853+
"kind": "Variable",
854+
"content": "If an object has this property, it will not be serialized\n\n\n```typescript\nNoSerializeSymbol: unique symbol\n```",
855+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts",
856+
"mdFile": "core.noserializesymbol.md"
857+
},
844858
{
845859
"name": "OnRenderFn",
846860
"id": "onrenderfn",
@@ -1541,6 +1555,20 @@
15411555
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-resource.ts",
15421556
"mdFile": "core.resourcereturn.md"
15431557
},
1558+
{
1559+
"name": "SerializerSymbol",
1560+
"id": "serializersymbol",
1561+
"hierarchy": [
1562+
{
1563+
"name": "SerializerSymbol",
1564+
"id": "serializersymbol"
1565+
}
1566+
],
1567+
"kind": "Variable",
1568+
"content": "If an object has this property as a function, it will be called with the object and should return a serializable value.\n\nThis can be used to clean up etc.\n\n\n```typescript\nSerializerSymbol: unique symbol\n```",
1569+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts",
1570+
"mdFile": "core.serializersymbol.md"
1571+
},
15441572
{
15451573
"name": "setPlatform",
15461574
"id": "setplatform",

packages/docs/src/routes/api/qwik/index.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,16 @@ export type NoSerialize<T> =
18551855
18561856
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts)
18571857
1858+
## NoSerializeSymbol
1859+
1860+
If an object has this property, it will not be serialized
1861+
1862+
```typescript
1863+
NoSerializeSymbol: unique symbol
1864+
```
1865+
1866+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts)
1867+
18581868
## OnRenderFn
18591869
18601870
```typescript
@@ -3549,6 +3559,18 @@ export type ResourceReturn<T> =
35493559
35503560
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-resource.ts)
35513561
3562+
## SerializerSymbol
3563+
3564+
If an object has this property as a function, it will be called with the object and should return a serializable value.
3565+
3566+
This can be used to clean up etc.
3567+
3568+
```typescript
3569+
SerializerSymbol: unique symbol
3570+
```
3571+
3572+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/shared/utils/serialize-utils.ts)
3573+
35523574
## setPlatform
35533575
35543576
Sets the `CorePlatform`.

packages/qwik/src/core/api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,9 @@ export type NoSerialize<T> = (T & {
498498
// @public
499499
export const noSerialize: <T extends object | undefined>(input: T) => NoSerialize<T>;
500500

501+
// @public
502+
export const NoSerializeSymbol: unique symbol;
503+
501504
// @public (undocumented)
502505
export type OnRenderFn<PROPS> = (props: PROPS) => JSXOutput;
503506

@@ -811,6 +814,9 @@ export const _restProps: (props: Record<string, any>, omit: string[], target?: {
811814
// @internal
812815
export function _serialize(data: unknown[]): Promise<string>;
813816

817+
// @public
818+
export const SerializerSymbol: unique symbol;
819+
814820
// @public
815821
export const setPlatform: (plt: CorePlatform) => CorePlatform;
816822

packages/qwik/src/core/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ export { EffectPropData as _EffectData } from './signal/signal';
139139
// Developer Low-Level API
140140
//////////////////////////////////////////////////////////////////////////////////////////
141141
export type { ValueOrPromise } from './shared/utils/types';
142-
export { type NoSerialize } from './shared/utils/serialize-utils';
142+
export {
143+
NoSerializeSymbol,
144+
SerializerSymbol,
145+
type NoSerialize,
146+
} from './shared/utils/serialize-utils';
143147
export { noSerialize } from './shared/utils/serialize-utils';
144148
export { version } from './version';
145149

packages/qwik/src/core/shared/shared-serialization.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import { isElement, isNode } from './utils/element';
4848
import { EMPTY_ARRAY, EMPTY_OBJ } from './utils/flyweight';
4949
import { ELEMENT_ID, ELEMENT_KEY } from './utils/markers';
5050
import { isPromise } from './utils/promises';
51-
import { fastSkipSerialize } from './utils/serialize-utils';
51+
import { SerializerSymbol, fastSkipSerialize } from './utils/serialize-utils';
5252
import { type ValueOrPromise } from './utils/types';
5353

5454
const deserializedProxyMap = new WeakMap<object, unknown[]>();
@@ -864,8 +864,6 @@ export const createSerializationContext = (
864864
discoveredValues.push(obj.$ssrNode$.id);
865865
} else if (isJSXNode(obj)) {
866866
discoveredValues.push(obj.type, obj.props, obj.constProps, obj.children);
867-
} else if (Array.isArray(obj)) {
868-
discoveredValues.push(...obj);
869867
} else if (isQrl(obj)) {
870868
obj.$captureRef$ && obj.$captureRef$.length && discoveredValues.push(...obj.$captureRef$);
871869
} else if (isPropsProxy(obj)) {
@@ -884,6 +882,12 @@ export const createSerializationContext = (
884882
promises.push(obj);
885883
} else if (obj instanceof EffectPropData) {
886884
discoveredValues.push(obj.data);
885+
} else if (Array.isArray(obj)) {
886+
discoveredValues.push(...obj);
887+
} else if (SerializerSymbol in obj && typeof obj[SerializerSymbol] === 'function') {
888+
const result = obj[SerializerSymbol](obj);
889+
serializationResults.set(obj, result);
890+
discoveredValues.push(result);
887891
} else if (isObjectLiteral(obj)) {
888892
Object.entries(obj).forEach(([key, value]) => {
889893
discoveredValues.push(key, value);
@@ -940,6 +944,7 @@ const discoverValuesForVNodeData = (vnodeData: VNodeData, discoveredValues: unkn
940944
};
941945

942946
const promiseResults = new WeakMap<Promise<any>, [boolean, unknown]>();
947+
const serializationResults = new WeakMap<object, unknown>();
943948

944949
/**
945950
* Format:
@@ -1134,6 +1139,11 @@ function serialize(serializationContext: SerializationContext): void {
11341139
}
11351140
output(Array.isArray(storeTarget) ? TypeIds.StoreArray : TypeIds.Store, out);
11361141
}
1142+
} else if (SerializerSymbol in value && typeof value[SerializerSymbol] === 'function') {
1143+
const result = serializationResults.get(value);
1144+
depth--;
1145+
writeValue(result, idx);
1146+
depth++;
11371147
} else if (isObjectLiteral(value)) {
11381148
if (Array.isArray(value)) {
11391149
output(TypeIds.Array, value);

packages/qwik/src/core/shared/shared-serialization.unit.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { $, component$ } from '@qwik.dev/core';
1+
import { $, component$, noSerialize } from '@qwik.dev/core';
22
import { describe, expect, it } from 'vitest';
33
import { _fnSignal, _wrapProp } from '../internal';
44
import { EffectPropData, type Signal } from '../signal/signal';
@@ -17,6 +17,7 @@ import {
1717
dumpState,
1818
} from './shared-serialization';
1919
import { EMPTY_ARRAY, EMPTY_OBJ } from './utils/flyweight';
20+
import { NoSerializeSymbol, SerializerSymbol } from './utils/serialize-utils';
2021

2122
const DEBUG = false;
2223

@@ -780,6 +781,57 @@ describe('shared-serialization', () => {
780781
expect((obj as any).shared).toBe(newValue);
781782
});
782783
});
784+
785+
describe('custom serialization', () => {
786+
it('should ignore noSerialize', async () => {
787+
const obj = { hi: true };
788+
const state = await serialize(noSerialize(obj));
789+
expect(dumpState(state)).toMatchInlineSnapshot(`
790+
"
791+
0 Constant undefined
792+
(5 chars)"
793+
`);
794+
});
795+
it('should ignore NoSerializeSymbol', async () => {
796+
const obj = { hi: true, [NoSerializeSymbol]: true };
797+
const state = await serialize(obj);
798+
expect(dumpState(state)).toMatchInlineSnapshot(`
799+
"
800+
0 Constant undefined
801+
(5 chars)"
802+
`);
803+
});
804+
it('should use SerializerSymbol', async () => {
805+
const obj = { hi: 'obj', [SerializerSymbol]: (o: any) => o.hi };
806+
class Foo {
807+
hi = 'class';
808+
[SerializerSymbol]() {
809+
return this.hi;
810+
}
811+
}
812+
const state = await serialize([obj, new Foo()]);
813+
expect(dumpState(state)).toMatchInlineSnapshot(`
814+
"
815+
0 Array [
816+
String "obj"
817+
String "class"
818+
]
819+
(23 chars)"
820+
`);
821+
});
822+
it('should not use SerializeSymbol if not function', async () => {
823+
const obj = { hi: 'orig', [SerializerSymbol]: 'hey' };
824+
const state = await serialize(obj);
825+
expect(dumpState(state)).toMatchInlineSnapshot(`
826+
"
827+
0 Object [
828+
String "hi"
829+
String "orig"
830+
]
831+
(22 chars)"
832+
`);
833+
});
834+
});
783835
});
784836

785837
async function serialize(...roots: any[]): Promise<any[]> {

packages/qwik/src/core/shared/utils/serialize-utils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export const shouldSerialize = (obj: unknown): boolean => {
9393
};
9494

9595
export const fastSkipSerialize = (obj: object): boolean => {
96-
return noSerializeSet.has(obj);
96+
return typeof obj === 'object' && obj && (NoSerializeSymbol in obj || noSerializeSet.has(obj));
9797
};
9898

9999
export const fastWeakSerialize = (obj: object): boolean => {
@@ -140,3 +140,19 @@ export const _weakSerialize = <T extends object>(input: T): Partial<T> => {
140140
weakSerializeSet.add(input);
141141
return input as any;
142142
};
143+
144+
/**
145+
* If an object has this property, it will not be serialized
146+
*
147+
* @public
148+
*/
149+
export const NoSerializeSymbol = Symbol('noSerialize');
150+
/**
151+
* If an object has this property as a function, it will be called with the object and should return
152+
* a serializable value.
153+
*
154+
* This can be used to clean up etc.
155+
*
156+
* @public
157+
*/
158+
export const SerializerSymbol = Symbol('serialize');

0 commit comments

Comments
 (0)