@builder.io/sdk-svelte 5.2.16 → 5.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/README.md +4 -0
- package/lib/browser/blocks/symbol/symbol.helpers.d.ts +1 -0
- package/lib/browser/blocks/symbol/symbol.helpers.js +6 -0
- package/lib/browser/blocks/symbol/symbol.svelte +5 -1
- package/lib/browser/components/block/block.helpers.d.ts +1 -3
- package/lib/browser/components/block/block.helpers.js +31 -4
- package/lib/browser/components/block/block.svelte +14 -1
- package/lib/browser/components/builder-scripts.svelte +39 -0
- package/lib/browser/components/builder-scripts.svelte.d.ts +22 -0
- package/lib/browser/components/content/components/styles.helpers.d.ts +1 -1
- package/lib/browser/components/content/components/styles.helpers.js +1 -1
- package/lib/browser/components/content/content.helpers.d.ts +5 -1
- package/lib/browser/components/content/content.helpers.js +39 -2
- package/lib/browser/components/content/content.svelte +4 -17
- package/lib/browser/components/content-variants/content-variants.svelte +15 -8
- package/lib/browser/components/content-variants/content-variants.svelte.d.ts +5 -0
- package/lib/browser/components/content-variants/helpers.d.ts +6 -0
- package/lib/browser/components/content-variants/helpers.js +5 -0
- package/lib/browser/constants/builder-registered-components.d.ts +1 -0
- package/lib/browser/constants/builder-registered-components.js +40 -33
- package/lib/browser/constants/device-sizes.js +14 -2
- package/lib/browser/constants/sdk-version.d.ts +1 -1
- package/lib/browser/constants/sdk-version.js +1 -1
- package/lib/browser/context/builder-scripts.context.d.ts +7 -0
- package/lib/browser/context/builder-scripts.context.js +5 -0
- package/lib/browser/functions/evaluate/browser-runtime/browser.js +13 -1
- package/lib/browser/functions/evaluate/evaluate.js +21 -5
- package/lib/browser/functions/evaluate/node-runtime/node-runtime.js +114 -23
- package/lib/browser/functions/evaluate/safe-state-expression.d.ts +33 -0
- package/lib/browser/functions/evaluate/safe-state-expression.js +236 -0
- package/lib/browser/functions/evaluate/should-force-browser-runtime-in-node.js +3 -1
- package/lib/browser/functions/extract-localized-values.d.ts +4 -0
- package/lib/browser/functions/extract-localized-values.js +65 -26
- package/lib/browser/functions/get-block-properties.js +2 -1
- package/lib/browser/functions/get-processed-block.d.ts +0 -1
- package/lib/browser/functions/get-processed-block.js +45 -57
- package/lib/browser/functions/is-editing-or-previewing.d.ts +1 -0
- package/lib/browser/functions/is-editing-or-previewing.js +3 -0
- package/lib/browser/functions/set.d.ts +29 -1
- package/lib/browser/functions/set.js +85 -4
- package/lib/browser/helpers/bounded-cache.d.ts +4 -0
- package/lib/browser/helpers/bounded-cache.js +12 -0
- package/lib/browser/helpers/copy-plain-data.d.ts +6 -0
- package/lib/browser/helpers/copy-plain-data.js +40 -0
- package/lib/browser/index-helpers/blocks-exports.d.ts +1 -0
- package/lib/browser/index-helpers/blocks-exports.js +1 -0
- package/lib/edge/blocks/symbol/symbol.helpers.d.ts +1 -0
- package/lib/edge/blocks/symbol/symbol.helpers.js +6 -0
- package/lib/edge/blocks/symbol/symbol.svelte +5 -1
- package/lib/edge/components/block/block.helpers.d.ts +1 -3
- package/lib/edge/components/block/block.helpers.js +31 -4
- package/lib/edge/components/block/block.svelte +14 -1
- package/lib/edge/components/builder-scripts.svelte +39 -0
- package/lib/edge/components/builder-scripts.svelte.d.ts +22 -0
- package/lib/edge/components/content/components/styles.helpers.d.ts +1 -1
- package/lib/edge/components/content/components/styles.helpers.js +1 -1
- package/lib/edge/components/content/content.helpers.d.ts +5 -1
- package/lib/edge/components/content/content.helpers.js +39 -2
- package/lib/edge/components/content/content.svelte +4 -17
- package/lib/edge/components/content-variants/content-variants.svelte +15 -8
- package/lib/edge/components/content-variants/content-variants.svelte.d.ts +5 -0
- package/lib/edge/components/content-variants/helpers.d.ts +6 -0
- package/lib/edge/components/content-variants/helpers.js +5 -0
- package/lib/edge/constants/builder-registered-components.d.ts +1 -0
- package/lib/edge/constants/builder-registered-components.js +40 -33
- package/lib/edge/constants/device-sizes.js +14 -2
- package/lib/edge/constants/sdk-version.d.ts +1 -1
- package/lib/edge/constants/sdk-version.js +1 -1
- package/lib/edge/context/builder-scripts.context.d.ts +7 -0
- package/lib/edge/context/builder-scripts.context.js +5 -0
- package/lib/edge/functions/evaluate/browser-runtime/browser.js +13 -1
- package/lib/edge/functions/evaluate/evaluate.js +21 -5
- package/lib/edge/functions/evaluate/node-runtime/node-runtime.js +114 -23
- package/lib/edge/functions/evaluate/safe-state-expression.d.ts +33 -0
- package/lib/edge/functions/evaluate/safe-state-expression.js +236 -0
- package/lib/edge/functions/evaluate/should-force-browser-runtime-in-node.js +3 -1
- package/lib/edge/functions/extract-localized-values.d.ts +4 -0
- package/lib/edge/functions/extract-localized-values.js +65 -26
- package/lib/edge/functions/get-block-properties.js +2 -1
- package/lib/edge/functions/get-processed-block.d.ts +0 -1
- package/lib/edge/functions/get-processed-block.js +45 -57
- package/lib/edge/functions/is-editing-or-previewing.d.ts +1 -0
- package/lib/edge/functions/is-editing-or-previewing.js +3 -0
- package/lib/edge/functions/set.d.ts +29 -1
- package/lib/edge/functions/set.js +85 -4
- package/lib/edge/helpers/bounded-cache.d.ts +4 -0
- package/lib/edge/helpers/bounded-cache.js +12 -0
- package/lib/edge/helpers/copy-plain-data.d.ts +6 -0
- package/lib/edge/helpers/copy-plain-data.js +40 -0
- package/lib/edge/index-helpers/blocks-exports.d.ts +1 -0
- package/lib/edge/index-helpers/blocks-exports.js +1 -0
- package/lib/node/blocks/symbol/symbol.helpers.d.ts +1 -0
- package/lib/node/blocks/symbol/symbol.helpers.js +6 -0
- package/lib/node/blocks/symbol/symbol.svelte +5 -1
- package/lib/node/components/block/block.helpers.d.ts +1 -3
- package/lib/node/components/block/block.helpers.js +31 -4
- package/lib/node/components/block/block.svelte +14 -1
- package/lib/node/components/builder-scripts.svelte +39 -0
- package/lib/node/components/builder-scripts.svelte.d.ts +22 -0
- package/lib/node/components/content/components/styles.helpers.d.ts +1 -1
- package/lib/node/components/content/components/styles.helpers.js +1 -1
- package/lib/node/components/content/content.helpers.d.ts +5 -1
- package/lib/node/components/content/content.helpers.js +39 -2
- package/lib/node/components/content/content.svelte +4 -17
- package/lib/node/components/content-variants/content-variants.svelte +15 -8
- package/lib/node/components/content-variants/content-variants.svelte.d.ts +5 -0
- package/lib/node/components/content-variants/helpers.d.ts +6 -0
- package/lib/node/components/content-variants/helpers.js +5 -0
- package/lib/node/constants/builder-registered-components.d.ts +1 -0
- package/lib/node/constants/builder-registered-components.js +40 -33
- package/lib/node/constants/device-sizes.js +14 -2
- package/lib/node/constants/sdk-version.d.ts +1 -1
- package/lib/node/constants/sdk-version.js +1 -1
- package/lib/node/context/builder-scripts.context.d.ts +7 -0
- package/lib/node/context/builder-scripts.context.js +5 -0
- package/lib/node/functions/evaluate/browser-runtime/browser.js +13 -1
- package/lib/node/functions/evaluate/evaluate.js +21 -5
- package/lib/node/functions/evaluate/node-runtime/node-runtime.js +114 -23
- package/lib/node/functions/evaluate/safe-state-expression.d.ts +33 -0
- package/lib/node/functions/evaluate/safe-state-expression.js +236 -0
- package/lib/node/functions/evaluate/should-force-browser-runtime-in-node.js +3 -1
- package/lib/node/functions/extract-localized-values.d.ts +4 -0
- package/lib/node/functions/extract-localized-values.js +65 -26
- package/lib/node/functions/get-block-properties.js +2 -1
- package/lib/node/functions/get-processed-block.d.ts +0 -1
- package/lib/node/functions/get-processed-block.js +45 -57
- package/lib/node/functions/is-editing-or-previewing.d.ts +1 -0
- package/lib/node/functions/is-editing-or-previewing.js +3 -0
- package/lib/node/functions/set.d.ts +29 -1
- package/lib/node/functions/set.js +85 -4
- package/lib/node/helpers/bounded-cache.d.ts +4 -0
- package/lib/node/helpers/bounded-cache.js +12 -0
- package/lib/node/helpers/copy-plain-data.d.ts +6 -0
- package/lib/node/helpers/copy-plain-data.js +40 -0
- package/lib/node/index-helpers/blocks-exports.d.ts +1 -0
- package/lib/node/index-helpers/blocks-exports.js +1 -0
- package/package.json +1 -1
- package/src/blocks/symbol/symbol.helpers.ts +7 -0
- package/src/blocks/symbol/symbol.svelte +8 -1
- package/src/components/block/block.helpers.ts +38 -5
- package/src/components/block/block.svelte +14 -1
- package/src/components/builder-scripts.svelte +57 -0
- package/src/components/content/components/styles.helpers.ts +1 -1
- package/src/components/content/content.helpers.ts +45 -4
- package/src/components/content/content.svelte +7 -24
- package/src/components/content-variants/content-variants.svelte +21 -8
- package/src/components/content-variants/helpers.ts +6 -0
- package/src/constants/builder-registered-components.ts +40 -34
- package/src/constants/device-sizes.ts +14 -3
- package/src/constants/sdk-version.ts +1 -1
- package/src/context/builder-scripts.context.ts +6 -0
- package/src/functions/evaluate/browser-runtime/browser.ts +14 -1
- package/src/functions/evaluate/evaluate.ts +22 -5
- package/src/functions/evaluate/node-runtime/node-runtime.ts +116 -25
- package/src/functions/evaluate/safe-state-expression.ts +224 -0
- package/src/functions/evaluate/should-force-browser-runtime-in-node.ts +3 -1
- package/src/functions/extract-localized-values.ts +61 -27
- package/src/functions/get-block-properties.ts +5 -1
- package/src/functions/get-processed-block.ts +46 -57
- package/src/functions/is-editing-or-previewing.ts +3 -0
- package/src/functions/set.ts +89 -4
- package/src/helpers/bounded-cache.ts +12 -0
- package/src/helpers/copy-plain-data.ts +36 -0
- package/src/index-helpers/blocks-exports.ts +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createBoundedCache } from '../../helpers/bounded-cache';
|
|
1
2
|
import { logger } from '../../helpers/logger';
|
|
2
3
|
import { get } from '../get';
|
|
3
4
|
import { chooseBrowserOrServerEval } from './choose-eval';
|
|
@@ -16,6 +17,24 @@ const VIRTUAL_INDEX_REGEX = /(\s)*var(\s)+_virtual_index(\s)*=(\s)*state(?<getPa
|
|
|
16
17
|
export const getSimpleExpressionGetPath = (code) => {
|
|
17
18
|
return STATE_GETTER_REGEX.exec(code.trim())?.groups?.getPath?.slice(1) || VIRTUAL_INDEX_REGEX.exec(code.trim())?.groups?.getPath?.slice(1);
|
|
18
19
|
};
|
|
20
|
+
const MAX_CACHED_CODE_STRINGS = 5000;
|
|
21
|
+
const SIMPLE_GET_PATH_CACHE = createBoundedCache(MAX_CACHED_CODE_STRINGS);
|
|
22
|
+
const getCachedSimpleExpressionGetPath = (code) => {
|
|
23
|
+
let getPath = SIMPLE_GET_PATH_CACHE.get(code);
|
|
24
|
+
if (getPath === undefined) {
|
|
25
|
+
getPath = getSimpleExpressionGetPath(code) || null;
|
|
26
|
+
SIMPLE_GET_PATH_CACHE.set(code, getPath);
|
|
27
|
+
}
|
|
28
|
+
return getPath;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Same result as `get({ ...rootState, ...localState }, getPath)`, without copying every state key.
|
|
32
|
+
*/
|
|
33
|
+
const getSimpleStateValue = (rootState, localState, getPath) => {
|
|
34
|
+
const firstKey = getPath.split('.')[0];
|
|
35
|
+
const source = localState && Object.prototype.hasOwnProperty.call(localState, firstKey) ? localState : rootState;
|
|
36
|
+
return get(source, getPath);
|
|
37
|
+
};
|
|
19
38
|
export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true, trackingContext }) {
|
|
20
39
|
if (code.trim() === '') {
|
|
21
40
|
return undefined;
|
|
@@ -26,12 +45,9 @@ export function evaluate({ code, context, localState, rootState, rootSetState, e
|
|
|
26
45
|
* We try not to take many risks with this optimizations, so we only do it for
|
|
27
46
|
* `state.{path}` expressions.
|
|
28
47
|
*/
|
|
29
|
-
const getPath =
|
|
48
|
+
const getPath = getCachedSimpleExpressionGetPath(code.trim());
|
|
30
49
|
if (getPath) {
|
|
31
|
-
return
|
|
32
|
-
...rootState,
|
|
33
|
-
...localState
|
|
34
|
-
}, getPath);
|
|
50
|
+
return getSimpleStateValue(rootState, localState, getPath);
|
|
35
51
|
}
|
|
36
52
|
const args = {
|
|
37
53
|
code: parseCode(code, {
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { SDK_NAME } from '../../../constants/sdk-name';
|
|
2
2
|
import { MSG_PREFIX, logger } from '../../../helpers/logger';
|
|
3
3
|
import { fastClone } from '../../fast-clone';
|
|
4
|
-
import { set } from '../../set';
|
|
4
|
+
import { set, unset } from '../../set';
|
|
5
5
|
import { getFunctionArguments } from '../helpers';
|
|
6
|
+
import { NOT_EVALUATED, evaluateSafeStateExpression } from '../safe-state-expression';
|
|
6
7
|
import { safeDynamicRequire } from './safeDynamicRequire';
|
|
7
8
|
const getSyncValName = (key) => `bldr_${key}_sync`;
|
|
8
9
|
const BUILDER_SET_STATE_NAME = 'BUILDER_SET_STATE';
|
|
10
|
+
const BUILDER_DELETE_STATE_NAME = 'BUILDER_DELETE_STATE';
|
|
9
11
|
const INJECTED_IVM_GLOBAL = 'BUILDER_IVM';
|
|
10
12
|
// Convert all argument references to proxies, and pass `copySync` method to target object, to return a copy of the original JS object
|
|
13
|
+
// Only the `state` proxy carries a `path`, so only writes and deletes on `state` reach the host's root state.
|
|
11
14
|
// https://github.com/laverdet/isolated-vm#referencecopysync
|
|
12
15
|
const REF_TO_PROXY_FN = `
|
|
13
|
-
var refToProxy = (obj) => {
|
|
16
|
+
var refToProxy = (obj, path) => {
|
|
14
17
|
if (typeof obj !== 'object' || obj === null) {
|
|
15
18
|
return obj;
|
|
16
19
|
}
|
|
@@ -21,25 +24,33 @@ var refToProxy = (obj) => {
|
|
|
21
24
|
}
|
|
22
25
|
const val = obj.getSync(key);
|
|
23
26
|
if (typeof val?.getSync === 'function') {
|
|
24
|
-
return refToProxy(val);
|
|
27
|
+
return refToProxy(val, path && path.concat(key));
|
|
25
28
|
}
|
|
26
29
|
return val;
|
|
27
30
|
},
|
|
28
31
|
set(target, key, value) {
|
|
32
|
+
// Functions cannot cross the isolate boundary, and server renders never call them.
|
|
33
|
+
if (typeof value === 'function') {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
29
36
|
const v = typeof value === 'object' ? new ${INJECTED_IVM_GLOBAL}.Reference(value) : value;
|
|
30
37
|
obj.setSync(key, v);
|
|
31
|
-
${BUILDER_SET_STATE_NAME}(key, value)
|
|
38
|
+
if (path) ${BUILDER_SET_STATE_NAME}(path.concat(key), value);
|
|
39
|
+
return true;
|
|
32
40
|
},
|
|
33
41
|
deleteProperty(target, key) {
|
|
34
42
|
obj.deleteSync(key);
|
|
43
|
+
if (path) ${BUILDER_DELETE_STATE_NAME}(path.concat(key));
|
|
44
|
+
return true;
|
|
35
45
|
}
|
|
36
46
|
})
|
|
37
47
|
}
|
|
38
48
|
`;
|
|
39
49
|
const processCode = ({ code, args }) => {
|
|
40
|
-
const fnArgs = args.map(([name]) => `var ${name} = refToProxy(${getSyncValName(name)}); `).join('');
|
|
50
|
+
const fnArgs = args.map(([name]) => `var ${name} = refToProxy(${getSyncValName(name)}${name === 'state' ? ', []' : ''}); `).join('');
|
|
41
51
|
// the output is stringified and parsed back to the parent isolate if needed (when it's an `object`)
|
|
42
|
-
|
|
52
|
+
// Wrapped in an IIFE so it compiles as a reusable script whose completion value is the result.
|
|
53
|
+
return `(function () {
|
|
43
54
|
${REF_TO_PROXY_FN}
|
|
44
55
|
${fnArgs}
|
|
45
56
|
function theFunction() {
|
|
@@ -53,13 +64,57 @@ if (typeof output === 'object' && output !== null) {
|
|
|
53
64
|
} else {
|
|
54
65
|
return output;
|
|
55
66
|
}
|
|
56
|
-
`;
|
|
67
|
+
})()`;
|
|
57
68
|
};
|
|
58
69
|
let IVM_INSTANCE = null;
|
|
59
|
-
// Create fresh isolates per request to prevent memory leaks
|
|
60
70
|
let IVM_OPTIONS = {
|
|
61
71
|
memoryLimit: 128
|
|
62
72
|
};
|
|
73
|
+
/**
|
|
74
|
+
* Evaluations share one isolate, each in a fresh context. The isolate is disposed at the end of the
|
|
75
|
+
* current task (or after a fixed number of evaluations), so a render's bindings share it without it
|
|
76
|
+
* outliving that work: a single long-lived isolate leaked memory (#4210), and one still alive at
|
|
77
|
+
* worker-thread teardown crashes `isolated-vm` on Linux.
|
|
78
|
+
*/
|
|
79
|
+
const MAX_EVALUATIONS_PER_ISOLATE = 1000;
|
|
80
|
+
let POOLED = null;
|
|
81
|
+
let DISPOSE_TIMER = null;
|
|
82
|
+
let IS_EXIT_HANDLER_REGISTERED = false;
|
|
83
|
+
const disposePooledIsolate = () => {
|
|
84
|
+
if (DISPOSE_TIMER) {
|
|
85
|
+
clearTimeout(DISPOSE_TIMER);
|
|
86
|
+
DISPOSE_TIMER = null;
|
|
87
|
+
}
|
|
88
|
+
if (POOLED && !POOLED.isolate.isDisposed) {
|
|
89
|
+
try {
|
|
90
|
+
POOLED.isolate.dispose();
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
// Ignore disposal errors
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
POOLED = null;
|
|
97
|
+
};
|
|
98
|
+
const getPooledIsolate = (ivm) => {
|
|
99
|
+
if (!POOLED || POOLED.isolate.isDisposed || POOLED.evaluations >= MAX_EVALUATIONS_PER_ISOLATE) {
|
|
100
|
+
disposePooledIsolate();
|
|
101
|
+
POOLED = {
|
|
102
|
+
isolate: new ivm.Isolate(IVM_OPTIONS),
|
|
103
|
+
evaluations: 0,
|
|
104
|
+
scripts: new Map()
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (!DISPOSE_TIMER) {
|
|
108
|
+
DISPOSE_TIMER = setTimeout(disposePooledIsolate, 0);
|
|
109
|
+
DISPOSE_TIMER.unref?.();
|
|
110
|
+
}
|
|
111
|
+
if (!IS_EXIT_HANDLER_REGISTERED && typeof process !== 'undefined' && typeof process.once === 'function') {
|
|
112
|
+
IS_EXIT_HANDLER_REGISTERED = true;
|
|
113
|
+
process.once('exit', disposePooledIsolate);
|
|
114
|
+
}
|
|
115
|
+
POOLED.evaluations++;
|
|
116
|
+
return POOLED;
|
|
117
|
+
};
|
|
63
118
|
/**
|
|
64
119
|
* Set the `isolated-vm` instance to be used by the node runtime.
|
|
65
120
|
* This is useful for environments that are not able to rely on our
|
|
@@ -97,13 +152,34 @@ const getIvm = () => {
|
|
|
97
152
|
For more information, visit https://builder.io/c/docs/integration-tips#enabling-data-bindings-in-node-environments`;
|
|
98
153
|
throw new Error(ERROR_MESSAGE);
|
|
99
154
|
};
|
|
155
|
+
let IS_PROXY;
|
|
156
|
+
/**
|
|
157
|
+
* Node's `util.types.isProxy`, which detects a Proxy without running its traps.
|
|
158
|
+
* `null` when unavailable, in which case the pure-expression fast path is skipped.
|
|
159
|
+
*/
|
|
160
|
+
const getIsProxy = () => {
|
|
161
|
+
if (IS_PROXY === undefined) {
|
|
162
|
+
try {
|
|
163
|
+
IS_PROXY = safeDynamicRequire('node:util')?.types?.isProxy ?? null;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
IS_PROXY = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return IS_PROXY;
|
|
170
|
+
};
|
|
100
171
|
export const runInNode = ({ code, builder, context, event, localState, rootSetState, rootState }) => {
|
|
172
|
+
const isProxy = getIsProxy();
|
|
173
|
+
if (isProxy) {
|
|
174
|
+
const safeValue = evaluateSafeStateExpression(code, rootState, localState, isProxy);
|
|
175
|
+
if (safeValue !== NOT_EVALUATED)
|
|
176
|
+
return safeValue;
|
|
177
|
+
}
|
|
101
178
|
const ivm = getIvm();
|
|
102
|
-
|
|
103
|
-
|
|
179
|
+
const pooled = getPooledIsolate(ivm);
|
|
180
|
+
const isolateContext = pooled.isolate.createContextSync();
|
|
181
|
+
const references = [];
|
|
104
182
|
try {
|
|
105
|
-
isolate = new ivm.Isolate(IVM_OPTIONS);
|
|
106
|
-
const isolateContext = isolate.createContextSync();
|
|
107
183
|
const jail = isolateContext.global;
|
|
108
184
|
// Setup the isolate
|
|
109
185
|
jail.setSync('global', jail.derefInto());
|
|
@@ -124,13 +200,17 @@ export const runInNode = ({ code, builder, context, event, localState, rootSetSt
|
|
|
124
200
|
/**
|
|
125
201
|
* Propagate state changes back to the reactive root state.
|
|
126
202
|
*/
|
|
127
|
-
jail.setSync(BUILDER_SET_STATE_NAME, function (
|
|
203
|
+
jail.setSync(BUILDER_SET_STATE_NAME, function (path, value) {
|
|
128
204
|
// mutate the `rootState` object itself. Important for cases where we do not have `rootSetState`
|
|
129
205
|
// like Qwik.
|
|
130
|
-
set(rootState,
|
|
206
|
+
set(rootState, path, value);
|
|
131
207
|
// call the `rootSetState` function if it exists
|
|
132
208
|
rootSetState?.(rootState);
|
|
133
209
|
});
|
|
210
|
+
jail.setSync(BUILDER_DELETE_STATE_NAME, function (path) {
|
|
211
|
+
unset(rootState, path);
|
|
212
|
+
rootSetState?.(rootState);
|
|
213
|
+
});
|
|
134
214
|
args.forEach(([key, arg]) => {
|
|
135
215
|
const val = typeof arg === 'object' ? new ivm.Reference(
|
|
136
216
|
// workaround: methods with default values for arguments is not being cloned over
|
|
@@ -138,13 +218,19 @@ export const runInNode = ({ code, builder, context, event, localState, rootSetSt
|
|
|
138
218
|
...arg,
|
|
139
219
|
getUserAttributes: () => arg.getUserAttributes()
|
|
140
220
|
} : arg) : null;
|
|
221
|
+
if (val)
|
|
222
|
+
references.push(val);
|
|
141
223
|
jail.setSync(getSyncValName(key), val);
|
|
142
224
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
225
|
+
let script = pooled.scripts.get(code);
|
|
226
|
+
if (!script) {
|
|
227
|
+
script = pooled.isolate.compileScriptSync(processCode({
|
|
228
|
+
code,
|
|
229
|
+
args
|
|
230
|
+
}));
|
|
231
|
+
pooled.scripts.set(code, script);
|
|
232
|
+
}
|
|
233
|
+
const resultStr = script.runSync(isolateContext);
|
|
148
234
|
try {
|
|
149
235
|
// returning objects throw errors in isolated vm, so we stringify it and parse it back
|
|
150
236
|
const res = JSON.parse(resultStr);
|
|
@@ -155,14 +241,19 @@ export const runInNode = ({ code, builder, context, event, localState, rootSetSt
|
|
|
155
241
|
}
|
|
156
242
|
}
|
|
157
243
|
finally {
|
|
158
|
-
|
|
159
|
-
if (isolate) {
|
|
244
|
+
references.forEach(reference => {
|
|
160
245
|
try {
|
|
161
|
-
|
|
246
|
+
reference.release();
|
|
162
247
|
}
|
|
163
248
|
catch (e) {
|
|
164
|
-
// Ignore
|
|
249
|
+
// Ignore release errors
|
|
165
250
|
}
|
|
251
|
+
});
|
|
252
|
+
try {
|
|
253
|
+
isolateContext.release();
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
// Ignore release errors (e.g. the isolate was disposed after hitting its memory limit)
|
|
166
257
|
}
|
|
167
258
|
}
|
|
168
259
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `expression` when it only uses the allowed grammar, otherwise `null`.
|
|
3
|
+
*/
|
|
4
|
+
export declare const getSafeStateExpression: (expression: string) => string | null;
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the expression from code produced by `parseCode` (`return (expr);`) or
|
|
7
|
+
* from the editor's transpiled form (`var _virtual_index=expr;` followed by a return of it).
|
|
8
|
+
* (Mitosis treats any file containing a literal "return" + " _virtual_index" as a function body.)
|
|
9
|
+
*/
|
|
10
|
+
export declare const getSafeStateExpressionFromCode: (code: string) => string | null;
|
|
11
|
+
type SafeExpressionFn = (state: Record<string, any>) => unknown;
|
|
12
|
+
/**
|
|
13
|
+
* The compiled expression for `code`, or `null` when `code` is not a safe state expression.
|
|
14
|
+
*/
|
|
15
|
+
export declare const getSafeStateExpressionFn: (code: string) => SafeExpressionFn | null;
|
|
16
|
+
/**
|
|
17
|
+
* A read-only view of local state over root state, without copying either.
|
|
18
|
+
*
|
|
19
|
+
* Plain objects and arrays are wrapped so every nested read is checked: accessors
|
|
20
|
+
* anywhere on the prototype chain, class instances and functions (which could run
|
|
21
|
+
* getters or custom `valueOf`/`toString`) abort the read. Each source object maps to
|
|
22
|
+
* one wrapper for the life of the view, so `===` still compares the underlying objects.
|
|
23
|
+
* Wrappers target a fresh empty container rather than the object itself, because proxy
|
|
24
|
+
* invariants forbid returning a wrapper for a frozen (non-configurable) property.
|
|
25
|
+
*/
|
|
26
|
+
export declare const getReadOnlyStateView: (rootState: Record<string | symbol, any>, localState: Record<string | symbol, any> | undefined, isProxy: (value: unknown) => boolean) => {};
|
|
27
|
+
export declare const NOT_EVALUATED: unique symbol;
|
|
28
|
+
/**
|
|
29
|
+
* Evaluates `code` outside the sandbox when it is a safe state expression over plain
|
|
30
|
+
* data and produces a primitive. Returns `NOT_EVALUATED` when the sandbox must run it.
|
|
31
|
+
*/
|
|
32
|
+
export declare const evaluateSafeStateExpression: (code: string, rootState: Record<string | symbol, any>, localState: Record<string | symbol, any> | undefined, isProxy: (value: unknown) => boolean) => unknown;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createBoundedCache } from '../../helpers/bounded-cache';
|
|
2
|
+
/**
|
|
3
|
+
* Recognizes binding expressions that only read `state` and combine the values
|
|
4
|
+
* with operators and literals, e.g. `!state.open`, `state.$index + 1` or
|
|
5
|
+
* `state.plan === "pro" ? "Upgrade" : "Manage"`. Such an expression cannot call
|
|
6
|
+
* functions, assign, loop or reach any global, so it can run as a plain function
|
|
7
|
+
* instead of in a sandbox. Anything else returns `null` and is sandboxed as usual.
|
|
8
|
+
*/
|
|
9
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*/;
|
|
10
|
+
const NUMBER = /^(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/;
|
|
11
|
+
/**
|
|
12
|
+
* The length of a single- or double-quoted string literal at the start of `code`
|
|
13
|
+
* (no escapes or line breaks), or 0 when there is none.
|
|
14
|
+
*/
|
|
15
|
+
const getStringLiteralLength = (code) => {
|
|
16
|
+
const quote = code[0];
|
|
17
|
+
if (quote !== '"' && quote !== "'")
|
|
18
|
+
return 0;
|
|
19
|
+
for (let i = 1; i < code.length; i++) {
|
|
20
|
+
const char = code[i];
|
|
21
|
+
if (char === quote)
|
|
22
|
+
return i + 1;
|
|
23
|
+
if (char === '\\' || char === '\n' || char === '\r')
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
};
|
|
28
|
+
const OPERATORS = ['===', '!==', '==', '!=', '<=', '>=', '&&', '||', '??', '<', '>', '!', '+', '-', '*', '/', '%', '?', ':'];
|
|
29
|
+
const LITERAL_KEYWORDS = new Set(['true', 'false', 'null', 'undefined']);
|
|
30
|
+
// Reading these is harmless on data, but they lead to functions and prototypes.
|
|
31
|
+
const BLOCKED_PROPERTIES = new Set(['constructor', '__proto__', 'prototype', '__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__']);
|
|
32
|
+
const MAX_EXPRESSION_LENGTH = 500;
|
|
33
|
+
/**
|
|
34
|
+
* Returns `expression` when it only uses the allowed grammar, otherwise `null`.
|
|
35
|
+
*/
|
|
36
|
+
export const getSafeStateExpression = (expression) => {
|
|
37
|
+
if (expression.length > MAX_EXPRESSION_LENGTH)
|
|
38
|
+
return null;
|
|
39
|
+
let rest = expression.trim();
|
|
40
|
+
if (!rest)
|
|
41
|
+
return null;
|
|
42
|
+
let depth = 0;
|
|
43
|
+
// A value (state path, literal or closing paren) was just read: `(` here would be a call.
|
|
44
|
+
let afterValue = false;
|
|
45
|
+
let readsState = false;
|
|
46
|
+
while (rest) {
|
|
47
|
+
const whitespace = /^\s+/.exec(rest);
|
|
48
|
+
if (whitespace) {
|
|
49
|
+
rest = rest.slice(whitespace[0].length);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const identifier = IDENTIFIER.exec(rest);
|
|
53
|
+
if (identifier) {
|
|
54
|
+
if (afterValue)
|
|
55
|
+
return null;
|
|
56
|
+
const word = identifier[0];
|
|
57
|
+
rest = rest.slice(word.length);
|
|
58
|
+
if (LITERAL_KEYWORDS.has(word)) {
|
|
59
|
+
afterValue = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (word !== 'state')
|
|
63
|
+
return null;
|
|
64
|
+
let hasProperty = false;
|
|
65
|
+
let property = /^\s*\.\s*([A-Za-z_$][\w$]*)/.exec(rest);
|
|
66
|
+
while (property) {
|
|
67
|
+
if (BLOCKED_PROPERTIES.has(property[1]))
|
|
68
|
+
return null;
|
|
69
|
+
hasProperty = true;
|
|
70
|
+
rest = rest.slice(property[0].length);
|
|
71
|
+
property = /^\s*\.\s*([A-Za-z_$][\w$]*)/.exec(rest);
|
|
72
|
+
}
|
|
73
|
+
if (!hasProperty)
|
|
74
|
+
return null;
|
|
75
|
+
readsState = true;
|
|
76
|
+
afterValue = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const literalLength = NUMBER.exec(rest)?.[0].length || getStringLiteralLength(rest);
|
|
80
|
+
if (literalLength) {
|
|
81
|
+
if (afterValue)
|
|
82
|
+
return null;
|
|
83
|
+
rest = rest.slice(literalLength);
|
|
84
|
+
afterValue = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (rest[0] === '(') {
|
|
88
|
+
if (afterValue)
|
|
89
|
+
return null;
|
|
90
|
+
depth++;
|
|
91
|
+
rest = rest.slice(1);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (rest[0] === ')') {
|
|
95
|
+
if (depth === 0)
|
|
96
|
+
return null;
|
|
97
|
+
depth--;
|
|
98
|
+
rest = rest.slice(1);
|
|
99
|
+
afterValue = true;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const operator = OPERATORS.find(op => rest.startsWith(op));
|
|
103
|
+
if (!operator)
|
|
104
|
+
return null;
|
|
105
|
+
// `?.` is optional chaining (a property access we do not handle)
|
|
106
|
+
if (operator === '?' && rest[1] === '.')
|
|
107
|
+
return null;
|
|
108
|
+
// `++` / `--` would write to state
|
|
109
|
+
if ((operator === '+' || operator === '-') && rest[1] === operator) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
rest = rest.slice(operator.length);
|
|
113
|
+
afterValue = false;
|
|
114
|
+
}
|
|
115
|
+
if (depth !== 0 || !readsState || !afterValue)
|
|
116
|
+
return null;
|
|
117
|
+
return expression.trim();
|
|
118
|
+
};
|
|
119
|
+
const RETURN_EXPRESSION = /^\s*return\s*\(([\s\S]*)\);\s*$/;
|
|
120
|
+
const VIRTUAL_INDEX_EXPRESSION = /^\s*var\s+_virtual_index\s*=\s*([\s\S]*?);?\s*return\s+_virtual_index\s*;?\s*$/;
|
|
121
|
+
/**
|
|
122
|
+
* Extracts the expression from code produced by `parseCode` (`return (expr);`) or
|
|
123
|
+
* from the editor's transpiled form (`var _virtual_index=expr;` followed by a return of it).
|
|
124
|
+
* (Mitosis treats any file containing a literal "return" + " _virtual_index" as a function body.)
|
|
125
|
+
*/
|
|
126
|
+
export const getSafeStateExpressionFromCode = (code) => {
|
|
127
|
+
const match = RETURN_EXPRESSION.exec(code) || VIRTUAL_INDEX_EXPRESSION.exec(code);
|
|
128
|
+
return match ? getSafeStateExpression(match[1]) : null;
|
|
129
|
+
};
|
|
130
|
+
const isPrimitiveValue = (value) => value === null || typeof value !== 'object' && typeof value !== 'function';
|
|
131
|
+
const MAX_CACHED_EXPRESSIONS = 5000;
|
|
132
|
+
const COMPILED_EXPRESSIONS = createBoundedCache(MAX_CACHED_EXPRESSIONS);
|
|
133
|
+
/**
|
|
134
|
+
* The compiled expression for `code`, or `null` when `code` is not a safe state expression.
|
|
135
|
+
*/
|
|
136
|
+
export const getSafeStateExpressionFn = (code) => {
|
|
137
|
+
let fn = COMPILED_EXPRESSIONS.get(code);
|
|
138
|
+
if (fn === undefined) {
|
|
139
|
+
const expression = getSafeStateExpressionFromCode(code);
|
|
140
|
+
fn = null;
|
|
141
|
+
if (expression) {
|
|
142
|
+
try {
|
|
143
|
+
fn = new Function('state', `return (${expression});`);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
fn = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
COMPILED_EXPRESSIONS.set(code, fn);
|
|
150
|
+
}
|
|
151
|
+
return fn;
|
|
152
|
+
};
|
|
153
|
+
const UNSAFE_STATE_READ = Symbol('unsafe state read');
|
|
154
|
+
const isPlainContainer = (value) => {
|
|
155
|
+
const prototype = Object.getPrototypeOf(value);
|
|
156
|
+
return prototype === Object.prototype || prototype === Array.prototype || prototype === null;
|
|
157
|
+
};
|
|
158
|
+
const READ_ONLY_TRAPS = {
|
|
159
|
+
set: () => false,
|
|
160
|
+
deleteProperty: () => false
|
|
161
|
+
};
|
|
162
|
+
const findDescriptor = (target, prop) => {
|
|
163
|
+
for (let o = target; o; o = Object.getPrototypeOf(o)) {
|
|
164
|
+
const descriptor = Object.getOwnPropertyDescriptor(o, prop);
|
|
165
|
+
if (descriptor)
|
|
166
|
+
return descriptor;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* A read-only view of local state over root state, without copying either.
|
|
172
|
+
*
|
|
173
|
+
* Plain objects and arrays are wrapped so every nested read is checked: accessors
|
|
174
|
+
* anywhere on the prototype chain, class instances and functions (which could run
|
|
175
|
+
* getters or custom `valueOf`/`toString`) abort the read. Each source object maps to
|
|
176
|
+
* one wrapper for the life of the view, so `===` still compares the underlying objects.
|
|
177
|
+
* Wrappers target a fresh empty container rather than the object itself, because proxy
|
|
178
|
+
* invariants forbid returning a wrapper for a frozen (non-configurable) property.
|
|
179
|
+
*/
|
|
180
|
+
export const getReadOnlyStateView = (rootState, localState, isProxy) => {
|
|
181
|
+
// Reflecting on a Proxy (even `Object.getPrototypeOf`) runs its traps.
|
|
182
|
+
const isInspectable = (value) => !isProxy(value) && isPlainContainer(value);
|
|
183
|
+
if (!isInspectable(rootState) || localState && isProxy(localState)) {
|
|
184
|
+
throw UNSAFE_STATE_READ;
|
|
185
|
+
}
|
|
186
|
+
const wrappers = new WeakMap();
|
|
187
|
+
const guardStateValue = (value) => {
|
|
188
|
+
if (value === null || typeof value !== 'object') {
|
|
189
|
+
if (typeof value === 'function')
|
|
190
|
+
throw UNSAFE_STATE_READ;
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
if (!isInspectable(value))
|
|
194
|
+
throw UNSAFE_STATE_READ;
|
|
195
|
+
let wrapper = wrappers.get(value);
|
|
196
|
+
if (!wrapper) {
|
|
197
|
+
wrapper = new Proxy(Array.isArray(value) ? [] : {}, {
|
|
198
|
+
get: (_, prop) => readDataProperty(value, prop),
|
|
199
|
+
...READ_ONLY_TRAPS
|
|
200
|
+
});
|
|
201
|
+
wrappers.set(value, wrapper);
|
|
202
|
+
}
|
|
203
|
+
return wrapper;
|
|
204
|
+
};
|
|
205
|
+
const readDataProperty = (target, prop) => {
|
|
206
|
+
const descriptor = findDescriptor(target, prop);
|
|
207
|
+
if (descriptor && (descriptor.get || descriptor.set)) {
|
|
208
|
+
throw UNSAFE_STATE_READ;
|
|
209
|
+
}
|
|
210
|
+
return guardStateValue(descriptor?.value);
|
|
211
|
+
};
|
|
212
|
+
return new Proxy({}, {
|
|
213
|
+
get: (_, prop) => localState && Object.prototype.hasOwnProperty.call(localState, prop) ? readDataProperty(localState, prop) : readDataProperty(rootState, prop),
|
|
214
|
+
...READ_ONLY_TRAPS
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
export const NOT_EVALUATED = Symbol('not evaluated');
|
|
218
|
+
/**
|
|
219
|
+
* Evaluates `code` outside the sandbox when it is a safe state expression over plain
|
|
220
|
+
* data and produces a primitive. Returns `NOT_EVALUATED` when the sandbox must run it.
|
|
221
|
+
*/
|
|
222
|
+
export const evaluateSafeStateExpression = (code, rootState, localState, isProxy) => {
|
|
223
|
+
const fn = getSafeStateExpressionFn(code);
|
|
224
|
+
if (!fn)
|
|
225
|
+
return NOT_EVALUATED;
|
|
226
|
+
let value;
|
|
227
|
+
try {
|
|
228
|
+
value = fn(getReadOnlyStateView(rootState, localState, isProxy));
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Unsafe reads and ordinary errors (e.g. a null-prototype object in `+`) both
|
|
232
|
+
// go to the regular evaluator, so behavior matches evaluation without the fast path.
|
|
233
|
+
return NOT_EVALUATED;
|
|
234
|
+
}
|
|
235
|
+
return isPrimitiveValue(value) ? value : NOT_EVALUATED;
|
|
236
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '../../helpers/logger';
|
|
2
2
|
import { isNodeRuntime } from '../is-node-runtime';
|
|
3
|
+
let hasLoggedWarning = false;
|
|
3
4
|
export const shouldForceBrowserRuntimeInNode = ({ shouldLogWarning }) => {
|
|
4
5
|
if (!isNodeRuntime())
|
|
5
6
|
return false;
|
|
@@ -7,7 +8,8 @@ export const shouldForceBrowserRuntimeInNode = ({ shouldLogWarning }) => {
|
|
|
7
8
|
const isNode20 = process.version.startsWith('v20');
|
|
8
9
|
const hasNoNodeSnapshotNodeOption = process.env.NODE_OPTIONS?.includes('--no-node-snapshot');
|
|
9
10
|
if (isArm64 && isNode20 && !hasNoNodeSnapshotNodeOption) {
|
|
10
|
-
if (shouldLogWarning) {
|
|
11
|
+
if (shouldLogWarning && !hasLoggedWarning) {
|
|
12
|
+
hasLoggedWarning = true;
|
|
11
13
|
logger.log(`Skipping usage of \`isolated-vm\` to avoid crashes in Node v20 on an arm64 machine.
|
|
12
14
|
If you would like to use the \`isolated-vm\` package on this machine, please provide the \`NODE_OPTIONS=--no-node-snapshot\` config to your Node process.
|
|
13
15
|
See https://github.com/BuilderIO/builder/blob/main/packages/sdks/README.md#node-v20--m1-macs-apple-silicon-support for more information.
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { BuilderBlock } from '../types/builder-block';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the block with localized values in `component.options` resolved.
|
|
4
|
+
* The block is returned unchanged (same object) when it has none.
|
|
5
|
+
*/
|
|
2
6
|
export declare function resolveLocalizedValues(block: BuilderBlock, locale: string | undefined): BuilderBlock;
|
|
@@ -1,37 +1,76 @@
|
|
|
1
|
-
import { traverse } from './traverse';
|
|
2
1
|
function isLocalizedField(value) {
|
|
3
2
|
return value && typeof value === 'object' && value['@type'] === '@builder.io/core:LocalizedValue';
|
|
4
3
|
}
|
|
5
|
-
|
|
6
|
-
if (
|
|
7
|
-
return
|
|
4
|
+
const isPlainObjectOrArray = (value) => {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return true;
|
|
7
|
+
const proto = Object.getPrototypeOf(value);
|
|
8
|
+
return proto === Object.prototype || proto === null;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Returns `value` with every localized value replaced by its `locale` entry.
|
|
12
|
+
* Only the objects and arrays on the way to a replaced value are copied: the
|
|
13
|
+
* input is never mutated, and a value with nothing localized is returned as-is.
|
|
14
|
+
*/
|
|
15
|
+
function resolveLocalized(value, locale, seen) {
|
|
16
|
+
if (value === null || typeof value !== 'object')
|
|
17
|
+
return value;
|
|
18
|
+
if (isLocalizedField(value)) {
|
|
19
|
+
return resolveLocalized(value[locale] ?? undefined, locale, seen);
|
|
8
20
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
if (!isPlainObjectOrArray(value))
|
|
22
|
+
return value;
|
|
23
|
+
if (seen.has(value))
|
|
24
|
+
return seen.get(value);
|
|
25
|
+
seen.set(value, value);
|
|
26
|
+
let copy = null;
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
for (let i = 0; i < value.length; i++) {
|
|
29
|
+
const next = resolveLocalized(value[i], locale, seen);
|
|
30
|
+
if (next !== value[i]) {
|
|
31
|
+
if (!copy)
|
|
32
|
+
copy = value.slice();
|
|
33
|
+
copy[i] = next;
|
|
34
|
+
}
|
|
14
35
|
}
|
|
15
|
-
});
|
|
16
|
-
return hasLocalizedValues;
|
|
17
|
-
}
|
|
18
|
-
function extractLocalizedValues(data, locale) {
|
|
19
|
-
if (!data || !Object.getOwnPropertyNames(data).length) {
|
|
20
|
-
return {};
|
|
21
36
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
else {
|
|
38
|
+
for (const key of Object.keys(value)) {
|
|
39
|
+
const next = resolveLocalized(value[key], locale, seen);
|
|
40
|
+
if (next !== value[key]) {
|
|
41
|
+
if (!copy)
|
|
42
|
+
copy = {
|
|
43
|
+
...value
|
|
44
|
+
};
|
|
45
|
+
copy[key] = next;
|
|
46
|
+
}
|
|
25
47
|
}
|
|
26
|
-
}
|
|
27
|
-
|
|
48
|
+
}
|
|
49
|
+
const result = copy ?? value;
|
|
50
|
+
seen.set(value, result);
|
|
51
|
+
return result;
|
|
28
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Returns the block with localized values in `component.options` resolved.
|
|
55
|
+
* The block is returned unchanged (same object) when it has none.
|
|
56
|
+
*/
|
|
29
57
|
export function resolveLocalizedValues(block, locale) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
block.component.options = extractLocalizedValues(block.component.options, locale ?? 'Default');
|
|
58
|
+
const options = block.component?.options;
|
|
59
|
+
if (!options || typeof options !== 'object' || isLocalizedField(options)) {
|
|
60
|
+
return block;
|
|
35
61
|
}
|
|
36
|
-
|
|
62
|
+
const resolvedOptions = resolveLocalized(options, locale ?? 'Default', new WeakMap());
|
|
63
|
+
if (resolvedOptions === options) {
|
|
64
|
+
return block;
|
|
65
|
+
}
|
|
66
|
+
if (!locale) {
|
|
67
|
+
console.warn('[Builder.io] In order to use localized fields in Builder, you must pass a locale prop to the BuilderComponent or to options object while fetching the content to resolve localized fields. Learn more: https://www.builder.io/c/docs/localization-inline#targeting-and-inline-localization');
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
...block,
|
|
71
|
+
component: {
|
|
72
|
+
...block.component,
|
|
73
|
+
options: resolvedOptions
|
|
74
|
+
}
|
|
75
|
+
};
|
|
37
76
|
}
|