@builder.io/sdk-svelte 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/--package.json +10 -5
- package/package/CHANGELOG.md +17 -0
- package/package/blocks/columns/columns.svelte +1 -1
- package/package/blocks/symbol/symbol.svelte +1 -1
- package/package/components/render-block/block-styles.svelte +3 -1
- package/package/components/render-block/render-block.helpers.d.ts +0 -1
- package/package/components/render-block/render-block.helpers.js +8 -20
- package/package/components/render-block/render-block.svelte +25 -19
- package/package/components/render-block/render-repeated-block.svelte +3 -2
- package/package/components/render-content/components/render-styles.svelte +1 -1
- package/package/components/render-content/render-content.svelte +99 -10
- package/package/components/render-content/render-content.svelte.d.ts +5 -0
- package/package/components/render-content/render-content.types.d.ts +11 -2
- package/package/components/render-content/wrap-component-ref.d.ts +6 -0
- package/package/components/render-content/wrap-component-ref.js +6 -0
- package/package/components/render-content-variants/helpers.d.ts +17 -0
- package/package/components/render-content-variants/helpers.js +182 -0
- package/package/components/render-content-variants/render-content-variants.svelte +70 -0
- package/package/components/render-content-variants/render-content-variants.svelte.d.ts +23 -0
- package/package/components/render-inlined-styles.svelte +3 -15
- package/package/components/render-inlined-styles.svelte.d.ts +2 -0
- package/package/constants/sdk-version.d.ts +1 -0
- package/package/constants/sdk-version.js +1 -0
- package/package/context/builder.context.d.ts +3 -2
- package/package/context/builder.context.js +3 -2
- package/package/context/types.d.ts +17 -2
- package/package/functions/evaluate.d.ts +4 -3
- package/package/functions/evaluate.js +23 -2
- package/package/functions/evaluate.test.d.ts +1 -0
- package/package/functions/evaluate.test.js +17 -0
- package/package/functions/get-block-actions-handler.d.ts +1 -1
- package/package/functions/get-block-actions-handler.js +3 -1
- package/package/functions/get-block-actions.d.ts +1 -1
- package/package/functions/get-content/generate-content-url.js +2 -2
- package/package/functions/get-content/generate-content-url.test.js +15 -0
- package/package/functions/get-content/index.d.ts +7 -2
- package/package/functions/get-content/index.js +43 -20
- package/package/functions/get-content/types.d.ts +6 -0
- package/package/functions/get-processed-block.d.ts +2 -2
- package/package/functions/get-processed-block.js +16 -4
- package/package/functions/get-processed-block.test.js +3 -1
- package/package/helpers/ab-tests.d.ts +8 -7
- package/package/helpers/ab-tests.js +103 -4
- package/package/helpers/canTrack.d.ts +1 -0
- package/package/helpers/canTrack.js +2 -0
- package/package/helpers/cookie.d.ts +7 -3
- package/package/helpers/cookie.js +9 -6
- package/package/helpers/logger.d.ts +1 -0
- package/package/helpers/logger.js +1 -0
- package/package/index-helpers/blocks-exports.d.ts +1 -1
- package/package/index-helpers/blocks-exports.js +1 -1
- package/package/index.d.ts +8 -7
- package/package/index.js +6 -7
- package/package/scripts/init-editing.d.ts +1 -0
- package/package/scripts/init-editing.js +2 -0
- package/package/types/builder-content.d.ts +1 -3
- package/package.json +4 -8
- package/src/blocks/columns/columns.svelte +1 -5
- package/src/blocks/symbol/symbol.svelte +1 -1
- package/src/components/render-block/block-styles.svelte +3 -1
- package/src/components/render-block/render-block.helpers.ts +9 -31
- package/src/components/render-block/render-block.svelte +24 -19
- package/src/components/render-block/render-repeated-block.svelte +3 -2
- package/src/components/render-content/components/render-styles.svelte +1 -1
- package/src/components/render-content/render-content.svelte +101 -10
- package/src/components/render-content/render-content.types.ts +11 -2
- package/src/components/render-content/wrap-component-ref.ts +6 -0
- package/src/components/render-content-variants/helpers.ts +245 -0
- package/src/components/render-content-variants/render-content-variants.svelte +83 -0
- package/src/components/render-inlined-styles.svelte +3 -16
- package/src/constants/sdk-version.ts +1 -0
- package/src/context/builder.context.ts +3 -2
- package/src/context/types.ts +17 -2
- package/src/functions/evaluate.test.ts +21 -0
- package/src/functions/evaluate.ts +46 -4
- package/src/functions/get-block-actions-handler.ts +7 -2
- package/src/functions/get-block-actions.ts +4 -1
- package/src/functions/get-content/generate-content-url.test.ts +17 -0
- package/src/functions/get-content/generate-content-url.ts +2 -1
- package/src/functions/get-content/index.ts +57 -26
- package/src/functions/get-content/types.ts +7 -0
- package/src/functions/get-processed-block.test.ts +3 -1
- package/src/functions/get-processed-block.ts +28 -6
- package/src/helpers/ab-tests.ts +164 -11
- package/src/helpers/canTrack.ts +4 -0
- package/src/helpers/cookie.ts +16 -11
- package/src/helpers/logger.ts +1 -0
- package/src/index-helpers/blocks-exports.ts +1 -1
- package/src/index.ts +17 -8
- package/src/scripts/init-editing.ts +3 -0
- package/src/types/builder-content.ts +1 -3
- package/package/functions/get-content/ab-testing.d.ts +0 -5
- package/package/functions/get-content/ab-testing.js +0 -78
- package/src/functions/get-content/ab-testing.ts +0 -132
|
@@ -42,6 +42,37 @@
|
|
|
42
42
|
} from "./render-content.helpers.js";
|
|
43
43
|
import { TARGET } from "../../constants/target.js";
|
|
44
44
|
import { logger } from "../../helpers/logger.js";
|
|
45
|
+
import { getRenderContentScriptString } from "../render-content-variants/helpers.js";
|
|
46
|
+
import { wrapComponentRef } from "./wrap-component-ref.js";
|
|
47
|
+
|
|
48
|
+
const isEvent = (attr) => attr.startsWith("on:");
|
|
49
|
+
const isNonEvent = (attr) => !attr.startsWith("on:");
|
|
50
|
+
const filterAttrs = (attrs = {}, filter) => {
|
|
51
|
+
const validAttr = {};
|
|
52
|
+
Object.keys(attrs).forEach((attr) => {
|
|
53
|
+
if (filter(attr)) {
|
|
54
|
+
validAttr[attr] = attrs[attr];
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return validAttr;
|
|
58
|
+
};
|
|
59
|
+
const setAttrs = (node, attrs = {}) => {
|
|
60
|
+
const attrKeys = Object.keys(attrs);
|
|
61
|
+
const setup = (attr) => node.addEventListener(attr.substr(3), attrs[attr]);
|
|
62
|
+
const teardown = (attr) =>
|
|
63
|
+
node.removeEventListener(attr.substr(3), attrs[attr]);
|
|
64
|
+
attrKeys.map(setup);
|
|
65
|
+
return {
|
|
66
|
+
update(attrs = {}) {
|
|
67
|
+
const attrKeys = Object.keys(attrs);
|
|
68
|
+
attrKeys.map(teardown);
|
|
69
|
+
attrKeys.map(setup);
|
|
70
|
+
},
|
|
71
|
+
destroy() {
|
|
72
|
+
attrKeys.map(teardown);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
45
76
|
|
|
46
77
|
export let content: RenderContentProps["content"];
|
|
47
78
|
export let data: RenderContentProps["data"];
|
|
@@ -51,7 +82,12 @@
|
|
|
51
82
|
export let model: RenderContentProps["model"];
|
|
52
83
|
export let context: RenderContentProps["context"];
|
|
53
84
|
export let apiKey: RenderContentProps["apiKey"];
|
|
85
|
+
export let parentContentId: RenderContentProps["parentContentId"];
|
|
86
|
+
export let hideContent: RenderContentProps["hideContent"];
|
|
87
|
+
export let classNameProp: RenderContentProps["classNameProp"];
|
|
88
|
+
export let isSsrAbTest: RenderContentProps["isSsrAbTest"];
|
|
54
89
|
export let includeRefs: RenderContentProps["includeRefs"];
|
|
90
|
+
export let enrich: RenderContentProps["enrich"];
|
|
55
91
|
export let apiVersion: RenderContentProps["apiVersion"];
|
|
56
92
|
|
|
57
93
|
function mergeNewContent(newContent: BuilderContent) {
|
|
@@ -79,8 +115,8 @@
|
|
|
79
115
|
},
|
|
80
116
|
};
|
|
81
117
|
}
|
|
82
|
-
function
|
|
83
|
-
contentState =
|
|
118
|
+
function contentSetState(newRootState: BuilderRenderState) {
|
|
119
|
+
contentState = newRootState;
|
|
84
120
|
}
|
|
85
121
|
function processMessage(event: MessageEvent) {
|
|
86
122
|
const { data } = event;
|
|
@@ -127,7 +163,9 @@
|
|
|
127
163
|
evaluate({
|
|
128
164
|
code: jsCode,
|
|
129
165
|
context: context || {},
|
|
130
|
-
|
|
166
|
+
localState: undefined,
|
|
167
|
+
rootState: contentState,
|
|
168
|
+
rootSetState: contentSetState,
|
|
131
169
|
});
|
|
132
170
|
}
|
|
133
171
|
}
|
|
@@ -154,7 +192,9 @@
|
|
|
154
192
|
evaluate({
|
|
155
193
|
code: group,
|
|
156
194
|
context: context || {},
|
|
157
|
-
|
|
195
|
+
localState: undefined,
|
|
196
|
+
rootState: contentState,
|
|
197
|
+
rootSetState: contentSetState,
|
|
158
198
|
})
|
|
159
199
|
);
|
|
160
200
|
}
|
|
@@ -166,7 +206,7 @@
|
|
|
166
206
|
...contentState,
|
|
167
207
|
[key]: json,
|
|
168
208
|
};
|
|
169
|
-
|
|
209
|
+
contentSetState(newState);
|
|
170
210
|
})
|
|
171
211
|
.catch((err) => {
|
|
172
212
|
console.error("error fetching dynamic data", url, err);
|
|
@@ -229,14 +269,22 @@
|
|
|
229
269
|
...components,
|
|
230
270
|
...(customComponents || []),
|
|
231
271
|
].reduce(
|
|
232
|
-
(acc, curr) => ({
|
|
272
|
+
(acc, { component, ...curr }) => ({
|
|
233
273
|
...acc,
|
|
234
|
-
[curr.name]:
|
|
274
|
+
[curr.name]: {
|
|
275
|
+
component: TARGET === "vue3" ? wrapComponentRef(component) : component,
|
|
276
|
+
...curr,
|
|
277
|
+
},
|
|
235
278
|
}),
|
|
236
279
|
{} as RegisteredComponents
|
|
237
280
|
);
|
|
238
281
|
let httpReqsData = {};
|
|
239
282
|
let clicked = false;
|
|
283
|
+
let scriptStr = getRenderContentScriptString({
|
|
284
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
|
|
285
|
+
contentId: content?.id!,
|
|
286
|
+
parentContentId: parentContentId!,
|
|
287
|
+
});
|
|
240
288
|
|
|
241
289
|
onMount(() => {
|
|
242
290
|
if (!apiKey) {
|
|
@@ -259,6 +307,11 @@
|
|
|
259
307
|
includeRefs: includeRefs,
|
|
260
308
|
}
|
|
261
309
|
: {}),
|
|
310
|
+
...(enrich
|
|
311
|
+
? {
|
|
312
|
+
enrich: enrich,
|
|
313
|
+
}
|
|
314
|
+
: {}),
|
|
262
315
|
});
|
|
263
316
|
Object.values<RegisteredComponent>(allRegisteredComponents).forEach(
|
|
264
317
|
(registeredComponent) => {
|
|
@@ -348,11 +401,14 @@
|
|
|
348
401
|
get content() {
|
|
349
402
|
return useContent;
|
|
350
403
|
},
|
|
351
|
-
get
|
|
404
|
+
get localState() {
|
|
405
|
+
return undefined;
|
|
406
|
+
},
|
|
407
|
+
get rootState() {
|
|
352
408
|
return contentState;
|
|
353
409
|
},
|
|
354
|
-
get
|
|
355
|
-
return
|
|
410
|
+
get rootSetState() {
|
|
411
|
+
return TARGET === "qwik" ? undefined : contentSetState;
|
|
356
412
|
},
|
|
357
413
|
get context() {
|
|
358
414
|
return context || {};
|
|
@@ -390,7 +446,42 @@
|
|
|
390
446
|
}}
|
|
391
447
|
builder-content-id={useContent?.id}
|
|
392
448
|
builder-model={model}
|
|
449
|
+
{...filterAttrs(
|
|
450
|
+
TARGET === "reactNative"
|
|
451
|
+
? {
|
|
452
|
+
dataSet: {
|
|
453
|
+
// currently, we can't set the actual ID here.
|
|
454
|
+
// we don't need it right now, we just need to identify content divs for testing.
|
|
455
|
+
"builder-content-id": "",
|
|
456
|
+
},
|
|
457
|
+
}
|
|
458
|
+
: {},
|
|
459
|
+
isNonEvent
|
|
460
|
+
)}
|
|
461
|
+
{...hideContent
|
|
462
|
+
? {
|
|
463
|
+
hidden: true,
|
|
464
|
+
"aria-hidden": true,
|
|
465
|
+
}
|
|
466
|
+
: {}}
|
|
467
|
+
class={classNameProp}
|
|
468
|
+
use:setAttrs={filterAttrs(
|
|
469
|
+
TARGET === "reactNative"
|
|
470
|
+
? {
|
|
471
|
+
dataSet: {
|
|
472
|
+
// currently, we can't set the actual ID here.
|
|
473
|
+
// we don't need it right now, we just need to identify content divs for testing.
|
|
474
|
+
"builder-content-id": "",
|
|
475
|
+
},
|
|
476
|
+
}
|
|
477
|
+
: {},
|
|
478
|
+
isEvent
|
|
479
|
+
)}
|
|
393
480
|
>
|
|
481
|
+
{#if isSsrAbTest}
|
|
482
|
+
<svelte:element this={"script"}>{@html scriptStr}</svelte:element>
|
|
483
|
+
{/if}
|
|
484
|
+
|
|
394
485
|
{#if TARGET !== "reactNative"}
|
|
395
486
|
<RenderContentStyles
|
|
396
487
|
contentId={useContent?.id}
|
|
@@ -7,7 +7,7 @@ import type { BuilderContent } from '../../types/builder-content';
|
|
|
7
7
|
import type { Nullable } from '../../types/typescript';
|
|
8
8
|
import type { ApiVersion } from '../../types/api-version';
|
|
9
9
|
|
|
10
|
-
export
|
|
10
|
+
export interface RenderContentProps {
|
|
11
11
|
content?: Nullable<BuilderContent>;
|
|
12
12
|
model?: string;
|
|
13
13
|
data?: { [key: string]: any };
|
|
@@ -17,8 +17,17 @@ export type RenderContentProps = {
|
|
|
17
17
|
customComponents?: RegisteredComponent[];
|
|
18
18
|
canTrack?: boolean;
|
|
19
19
|
locale?: string;
|
|
20
|
+
/** @deprecated use `enrich` instead **/
|
|
20
21
|
includeRefs?: boolean;
|
|
21
|
-
|
|
22
|
+
enrich?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* TO-DO: improve qwik generator to not remap this name for non-HTML tags, then name it `className`
|
|
25
|
+
*/
|
|
26
|
+
classNameProp?: string;
|
|
27
|
+
hideContent?: boolean;
|
|
28
|
+
parentContentId?: string;
|
|
29
|
+
isSsrAbTest?: boolean;
|
|
30
|
+
}
|
|
22
31
|
|
|
23
32
|
export interface BuilderComponentStateChange {
|
|
24
33
|
state: BuilderRenderState;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { isBrowser } from '../../functions/is-browser';
|
|
2
|
+
import type { Nullable } from '../../helpers/nullable';
|
|
3
|
+
import type { BuilderContent } from '../../types/builder-content';
|
|
4
|
+
import type { Target } from '../../types/targets';
|
|
5
|
+
|
|
6
|
+
export const getVariants = (content: Nullable<BuilderContent>) =>
|
|
7
|
+
Object.values(content?.variations || {});
|
|
8
|
+
|
|
9
|
+
export const checkShouldRunVariants = ({
|
|
10
|
+
canTrack,
|
|
11
|
+
content,
|
|
12
|
+
}: {
|
|
13
|
+
canTrack: Nullable<boolean>;
|
|
14
|
+
content: Nullable<BuilderContent>;
|
|
15
|
+
}) => {
|
|
16
|
+
const hasVariants = getVariants(content).length > 0;
|
|
17
|
+
|
|
18
|
+
if (!hasVariants) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!canTrack) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (isBrowser()) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type VariantData = {
|
|
34
|
+
id: string;
|
|
35
|
+
testRatio?: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* NOTE: when this function is stringified, single-line comments can cause weird issues when compiled by Sveltekit.
|
|
40
|
+
* Make sure to write multi-line comments only.
|
|
41
|
+
*/
|
|
42
|
+
function bldrAbTest(
|
|
43
|
+
contentId: string,
|
|
44
|
+
variants: VariantData[],
|
|
45
|
+
isHydrationTarget: boolean
|
|
46
|
+
) {
|
|
47
|
+
function getAndSetVariantId(): string {
|
|
48
|
+
function setCookie(name: string, value: string, days?: number) {
|
|
49
|
+
let expires = '';
|
|
50
|
+
if (days) {
|
|
51
|
+
const date = new Date();
|
|
52
|
+
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
53
|
+
expires = '; expires=' + date.toUTCString();
|
|
54
|
+
}
|
|
55
|
+
document.cookie =
|
|
56
|
+
name +
|
|
57
|
+
'=' +
|
|
58
|
+
(value || '') +
|
|
59
|
+
expires +
|
|
60
|
+
'; path=/' +
|
|
61
|
+
'; Secure; SameSite=None';
|
|
62
|
+
}
|
|
63
|
+
function getCookie(name: string) {
|
|
64
|
+
const nameEQ = name + '=';
|
|
65
|
+
const ca = document.cookie.split(';');
|
|
66
|
+
for (let i = 0; i < ca.length; i++) {
|
|
67
|
+
let c = ca[i];
|
|
68
|
+
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
|
69
|
+
if (c.indexOf(nameEQ) === 0)
|
|
70
|
+
return c.substring(nameEQ.length, c.length);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const cookieName = `builder.tests.${contentId}`;
|
|
75
|
+
const variantInCookie = getCookie(cookieName);
|
|
76
|
+
const availableIDs = variants.map((vr) => vr.id).concat(contentId);
|
|
77
|
+
/**
|
|
78
|
+
* cookie already exists
|
|
79
|
+
*/
|
|
80
|
+
if (variantInCookie && availableIDs.includes(variantInCookie)) {
|
|
81
|
+
return variantInCookie;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* no cookie exists, find variant
|
|
86
|
+
*/
|
|
87
|
+
let n = 0;
|
|
88
|
+
const random = Math.random();
|
|
89
|
+
for (let i = 0; i < variants.length; i++) {
|
|
90
|
+
const variant = variants[i];
|
|
91
|
+
const testRatio = variant.testRatio;
|
|
92
|
+
n += testRatio!;
|
|
93
|
+
if (random < n) {
|
|
94
|
+
setCookie(cookieName, variant.id);
|
|
95
|
+
return variant.id;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* no variant found, assign default content
|
|
101
|
+
*/
|
|
102
|
+
setCookie(cookieName, contentId);
|
|
103
|
+
|
|
104
|
+
return contentId;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const winningVariantId = getAndSetVariantId();
|
|
108
|
+
|
|
109
|
+
const styleEl = document.getElementById(
|
|
110
|
+
`variants-styles-${contentId}`
|
|
111
|
+
) as HTMLStyleElement;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* For React to work, we need hydration to match SSR, so we completely remove this node and the styles tag.
|
|
115
|
+
*/
|
|
116
|
+
if (isHydrationTarget) {
|
|
117
|
+
styleEl.remove();
|
|
118
|
+
const thisScriptEl = document.getElementById(
|
|
119
|
+
`variants-script-${contentId}`
|
|
120
|
+
);
|
|
121
|
+
thisScriptEl?.remove();
|
|
122
|
+
} else {
|
|
123
|
+
/* update styles to hide all variants except the winning variant */
|
|
124
|
+
const newStyleStr = variants
|
|
125
|
+
.concat({ id: contentId })
|
|
126
|
+
.filter((variant) => variant.id !== winningVariantId)
|
|
127
|
+
.map((value) => {
|
|
128
|
+
return `.variant-${value.id} { display: none; }
|
|
129
|
+
`;
|
|
130
|
+
})
|
|
131
|
+
.join('');
|
|
132
|
+
|
|
133
|
+
/* TO-DO: check if this actually updates the style */
|
|
134
|
+
styleEl.innerHTML = newStyleStr;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* NOTE: when this function is stringified, single-line comments can cause weird issues when compiled by Sveltekit.
|
|
140
|
+
* Make sure to write multi-line comments only.
|
|
141
|
+
*/
|
|
142
|
+
function bldrCntntScrpt(
|
|
143
|
+
variantContentId: string,
|
|
144
|
+
defaultContentId: string,
|
|
145
|
+
isHydrationTarget: boolean
|
|
146
|
+
) {
|
|
147
|
+
if (!navigator.cookieEnabled) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getCookie(name: string) {
|
|
152
|
+
const nameEQ = name + '=';
|
|
153
|
+
const ca = document.cookie.split(';');
|
|
154
|
+
for (let i = 0; i < ca.length; i++) {
|
|
155
|
+
let c = ca[i];
|
|
156
|
+
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
|
|
157
|
+
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const cookieName = `builder.tests.${defaultContentId}`;
|
|
162
|
+
const variantId = getCookie(cookieName);
|
|
163
|
+
|
|
164
|
+
/** get parent div by searching on `builder-content-id` attr */
|
|
165
|
+
const parentDiv = document.querySelector(
|
|
166
|
+
`[builder-content-id="${variantContentId}"]`
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const variantIsDefaultContent = variantContentId === defaultContentId;
|
|
170
|
+
|
|
171
|
+
if (variantId === variantContentId) {
|
|
172
|
+
if (variantIsDefaultContent) {
|
|
173
|
+
/** the default content is already visible, no need to do anything */
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** this is the winning variant and not already visible: remove `hidden` and `aria-hidden` attr */
|
|
178
|
+
|
|
179
|
+
parentDiv?.removeAttribute('hidden');
|
|
180
|
+
parentDiv?.removeAttribute('aria-hidden');
|
|
181
|
+
} else {
|
|
182
|
+
if (variantIsDefaultContent) {
|
|
183
|
+
if (isHydrationTarget) {
|
|
184
|
+
/**
|
|
185
|
+
* For React to work, we need to support hydration, in which case the first CSR will have none of the hidden variants.
|
|
186
|
+
* So we completely remove that node.
|
|
187
|
+
*/
|
|
188
|
+
parentDiv?.remove();
|
|
189
|
+
} else {
|
|
190
|
+
/** this is not the winning variant, add `hidden` attr */
|
|
191
|
+
parentDiv?.setAttribute('hidden', 'true');
|
|
192
|
+
parentDiv?.setAttribute('aria-hidden', 'true');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** This is not the winning variant, and it's not the default content.
|
|
197
|
+
* There's no need to hide it, because it's already hidden.
|
|
198
|
+
*/
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const isHydrationTarget = (target: Target) =>
|
|
206
|
+
target === 'react' ||
|
|
207
|
+
target === 'reactNative' ||
|
|
208
|
+
target === 'vue3' ||
|
|
209
|
+
target === 'vue2';
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* We hardcode explicit function names here, because the `.toString()` of a function can change depending on the bundler.
|
|
213
|
+
* Some bundlers will minify the fn name, etc.
|
|
214
|
+
*
|
|
215
|
+
* So we hardcode the function names here, and then use those names in the script string to make sure the function names are consistent.
|
|
216
|
+
*/
|
|
217
|
+
const AB_TEST_FN_NAME = 'bldrAbTest';
|
|
218
|
+
const CONTENT_FN_NAME = 'bldrCntntScrpt';
|
|
219
|
+
|
|
220
|
+
export const getVariantsScriptString = (
|
|
221
|
+
variants: VariantData[],
|
|
222
|
+
contentId: string
|
|
223
|
+
) => {
|
|
224
|
+
const fnStr = bldrAbTest.toString().replace(/\s+/g, ' ');
|
|
225
|
+
const fnStr2 = bldrCntntScrpt.toString().replace(/\s+/g, ' ');
|
|
226
|
+
|
|
227
|
+
return `
|
|
228
|
+
const ${AB_TEST_FN_NAME} = ${fnStr}
|
|
229
|
+
const ${CONTENT_FN_NAME} = ${fnStr2}
|
|
230
|
+
${AB_TEST_FN_NAME}("${contentId}", ${JSON.stringify(
|
|
231
|
+
variants
|
|
232
|
+
)}, ${isHydrationTarget})
|
|
233
|
+
`;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const getRenderContentScriptString = ({
|
|
237
|
+
parentContentId,
|
|
238
|
+
contentId,
|
|
239
|
+
}: {
|
|
240
|
+
contentId: string;
|
|
241
|
+
parentContentId: string;
|
|
242
|
+
}) => {
|
|
243
|
+
return `
|
|
244
|
+
${CONTENT_FN_NAME}("${contentId}", "${parentContentId}", ${isHydrationTarget})`;
|
|
245
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
type VariantsProviderProps = RenderContentProps;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import {
|
|
7
|
+
checkShouldRunVariants,
|
|
8
|
+
getVariants,
|
|
9
|
+
getVariantsScriptString,
|
|
10
|
+
} from "./helpers";
|
|
11
|
+
import RenderContent from "../render-content/render-content.svelte";
|
|
12
|
+
import type { RenderContentProps } from "../render-content/render-content.types";
|
|
13
|
+
import { getDefaultCanTrack } from "../../helpers/canTrack";
|
|
14
|
+
import RenderInlinedStyles from "../render-inlined-styles.svelte";
|
|
15
|
+
import { handleABTestingSync } from "../../helpers/ab-tests";
|
|
16
|
+
|
|
17
|
+
export let content: VariantsProviderProps["content"];
|
|
18
|
+
export let canTrack: VariantsProviderProps["canTrack"];
|
|
19
|
+
export let apiKey: VariantsProviderProps["apiKey"];
|
|
20
|
+
export let apiVersion: VariantsProviderProps["apiVersion"];
|
|
21
|
+
export let customComponents: VariantsProviderProps["customComponents"];
|
|
22
|
+
export let model: VariantsProviderProps["model"];
|
|
23
|
+
|
|
24
|
+
let variantScriptStr = getVariantsScriptString(
|
|
25
|
+
getVariants(content).map((value) => ({
|
|
26
|
+
id: value.id!,
|
|
27
|
+
testRatio: value.testRatio,
|
|
28
|
+
})),
|
|
29
|
+
content?.id || ""
|
|
30
|
+
);
|
|
31
|
+
let shouldRenderVariants = checkShouldRunVariants({
|
|
32
|
+
canTrack: getDefaultCanTrack(canTrack),
|
|
33
|
+
content: content,
|
|
34
|
+
});
|
|
35
|
+
let hideVariantsStyleString = getVariants(content)
|
|
36
|
+
.map((value) => `.variant-${value.id} { display: none; } `)
|
|
37
|
+
.join("");
|
|
38
|
+
let contentToRender = checkShouldRunVariants({
|
|
39
|
+
canTrack: getDefaultCanTrack(canTrack),
|
|
40
|
+
content: content,
|
|
41
|
+
})
|
|
42
|
+
? content
|
|
43
|
+
: handleABTestingSync({
|
|
44
|
+
item: content,
|
|
45
|
+
canTrack: getDefaultCanTrack(canTrack),
|
|
46
|
+
});
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
{#if shouldRenderVariants}
|
|
50
|
+
<RenderInlinedStyles
|
|
51
|
+
id={`variants-styles-${content?.id}`}
|
|
52
|
+
styles={hideVariantsStyleString}
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
<svelte:element this={"script"} id={`variants-script-${content?.id}`}
|
|
56
|
+
>{@html variantScriptStr}</svelte:element
|
|
57
|
+
>
|
|
58
|
+
|
|
59
|
+
{#each getVariants(content) as variant (variant.id)}
|
|
60
|
+
<RenderContent
|
|
61
|
+
content={variant}
|
|
62
|
+
{apiKey}
|
|
63
|
+
{apiVersion}
|
|
64
|
+
{canTrack}
|
|
65
|
+
{customComponents}
|
|
66
|
+
hideContent={true}
|
|
67
|
+
parentContentId={content?.id}
|
|
68
|
+
isSsrAbTest={shouldRenderVariants}
|
|
69
|
+
/>
|
|
70
|
+
{/each}
|
|
71
|
+
{/if}
|
|
72
|
+
|
|
73
|
+
<RenderContent
|
|
74
|
+
{model}
|
|
75
|
+
content={contentToRender}
|
|
76
|
+
{apiKey}
|
|
77
|
+
{apiVersion}
|
|
78
|
+
{canTrack}
|
|
79
|
+
{customComponents}
|
|
80
|
+
classNameProp={`variant-${content?.id}`}
|
|
81
|
+
parentContentId={content?.id}
|
|
82
|
+
isSsrAbTest={shouldRenderVariants}
|
|
83
|
+
/>
|
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
<script context="module" lang="ts">
|
|
2
2
|
interface Props {
|
|
3
3
|
styles: string;
|
|
4
|
+
id?: string;
|
|
4
5
|
}
|
|
5
6
|
</script>
|
|
6
7
|
|
|
7
8
|
<script lang="ts">
|
|
8
|
-
import { TARGET } from "../constants/target.js";
|
|
9
|
-
|
|
10
9
|
export let styles: Props["styles"];
|
|
11
|
-
|
|
12
|
-
$: tag = () => {
|
|
13
|
-
// NOTE: we have to obfusctate the name of the tag due to a limitation in the svelte-preprocessor plugin.
|
|
14
|
-
// https://github.com/sveltejs/vite-plugin-svelte/issues/315#issuecomment-1109000027
|
|
15
|
-
return "sty" + "le";
|
|
16
|
-
};
|
|
17
|
-
$: injectedStyleScript = () => {
|
|
18
|
-
return `<${tag()}>${styles}</${tag()}>`;
|
|
19
|
-
};
|
|
10
|
+
export let id: Props["id"];
|
|
20
11
|
</script>
|
|
21
12
|
|
|
22
|
-
{
|
|
23
|
-
{@html injectedStyleScript()}
|
|
24
|
-
{:else}
|
|
25
|
-
<svelte:element this={tag()}>{styles}</svelte:element>
|
|
26
|
-
{/if}
|
|
13
|
+
<svelte:element this={"sty" + "le"} {id}>{@html styles}</svelte:element>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SDK_VERSION = "0.4.0"
|
package/src/context/types.ts
CHANGED
|
@@ -16,8 +16,23 @@ export type BuilderRenderContext = Record<string, unknown>;
|
|
|
16
16
|
export interface BuilderContextInterface {
|
|
17
17
|
content: Nullable<BuilderContent>;
|
|
18
18
|
context: BuilderRenderContext;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The state of the application.
|
|
21
|
+
*
|
|
22
|
+
* NOTE: see `localState` below to understand how it is different from `rootState`.
|
|
23
|
+
*/
|
|
24
|
+
rootState: BuilderRenderState;
|
|
25
|
+
/**
|
|
26
|
+
* Some frameworks have a `setState` function which needs to be invoked to notify
|
|
27
|
+
* the framework of state change. (other frameworks don't in which case it is `undefined')
|
|
28
|
+
*/
|
|
29
|
+
rootSetState: ((rootState: BuilderRenderState) => void) | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* The local state of the current component. This is different from `rootState` in that
|
|
32
|
+
* it can be a child state created by a repeater containing local state.
|
|
33
|
+
* The `rootState` is where all of the state mutations are actually stored.
|
|
34
|
+
*/
|
|
35
|
+
localState: BuilderRenderState | undefined;
|
|
21
36
|
apiKey: string | null;
|
|
22
37
|
apiVersion: ApiVersion | undefined;
|
|
23
38
|
registeredComponents: RegisteredComponents;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { flattenState } from './evaluate';
|
|
2
|
+
|
|
3
|
+
describe('flatten state', () => {
|
|
4
|
+
it('should behave normally when no PROTO_STATE', () => {
|
|
5
|
+
const localState = {};
|
|
6
|
+
const rootState = { foo: 'bar' };
|
|
7
|
+
const flattened = flattenState(rootState, localState, undefined);
|
|
8
|
+
expect(flattened.foo).toEqual('bar');
|
|
9
|
+
flattened.foo = 'baz';
|
|
10
|
+
expect(rootState.foo).toEqual('baz');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should shadow write ', () => {
|
|
14
|
+
const rootState = { foo: 'foo' };
|
|
15
|
+
const localState = { foo: 'baz' };
|
|
16
|
+
const flattened = flattenState(rootState, localState, undefined);
|
|
17
|
+
expect(() => (flattened.foo = 'bar')).toThrow(
|
|
18
|
+
'Writing to local state is not allowed as it is read-only.'
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
});
|