meno-core 1.0.46 → 1.0.48
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/build-astro.ts +13 -77
- package/dist/build-static.js +7 -7
- package/dist/chunks/{chunk-FED5MME6.js → chunk-3FHJUHAS.js} +5 -4
- package/dist/chunks/chunk-3FHJUHAS.js.map +7 -0
- package/dist/chunks/{chunk-XSWR3QLI.js → chunk-B2RTLDXY.js} +130 -130
- package/dist/chunks/chunk-B2RTLDXY.js.map +7 -0
- package/dist/chunks/{chunk-ZTKHJQ2Z.js → chunk-BJRKEPMP.js} +2 -2
- package/dist/chunks/{chunk-ORN7S4AP.js → chunk-D5E3OKSL.js} +5 -5
- package/dist/chunks/{chunk-KITQJYZV.js → chunk-EK4KESLU.js} +33 -3
- package/dist/chunks/chunk-EK4KESLU.js.map +7 -0
- package/dist/chunks/{chunk-C6U5T5S5.js → chunk-NKUV77SR.js} +43 -10
- package/dist/chunks/chunk-NKUV77SR.js.map +7 -0
- package/dist/chunks/{chunk-ZWYDT3QJ.js → chunk-NP76N4HQ.js} +4 -4
- package/dist/chunks/{chunk-ZWYDT3QJ.js.map → chunk-NP76N4HQ.js.map} +2 -2
- package/dist/chunks/{chunk-IF3RATBY.js → chunk-RQSTH2BS.js} +2 -2
- package/dist/chunks/{chunk-77ZB6353.js → chunk-TPQ7APVQ.js} +42 -86
- package/dist/chunks/chunk-TPQ7APVQ.js.map +7 -0
- package/dist/chunks/{configService-DYCUEURL.js → configService-IGJEC3MC.js} +3 -3
- package/dist/entries/server-router.js +8 -8
- package/dist/lib/client/index.js +16 -4
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +1803 -383
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +8 -4
- package/dist/lib/shared/index.js.map +1 -1
- package/lib/client/core/ComponentBuilder.test.ts +38 -0
- package/lib/client/core/ComponentBuilder.ts +25 -1
- package/lib/server/astro/astroEmitHelpers.ts +5 -0
- package/lib/server/astro/cmsPageEmitter.ts +15 -2
- package/lib/server/astro/componentEmitter.ts +13 -5
- package/lib/server/astro/nodeToAstro.ts +23 -9
- package/lib/server/astro/pageEmitter.ts +15 -2
- package/lib/server/index.ts +1 -1
- package/lib/server/jsonLoader.test.ts +0 -17
- package/lib/server/jsonLoader.ts +0 -81
- package/lib/server/routes/api/variables.ts +4 -2
- package/lib/server/ssr/htmlGenerator.test.ts +4 -4
- package/lib/server/ssr/htmlGenerator.ts +7 -2
- package/lib/server/ssr/imageMetadata.ts +15 -9
- package/lib/server/ssr/ssrRenderer.test.ts +103 -1
- package/lib/server/ssr/ssrRenderer.ts +41 -35
- package/lib/server/utils/jsonLineMapper.test.ts +53 -1
- package/lib/server/utils/jsonLineMapper.ts +43 -3
- package/lib/server/webflow/buildWebflow.ts +343 -123
- package/lib/server/webflow/index.ts +1 -0
- package/lib/server/webflow/nodeToWebflow.test.ts +3170 -0
- package/lib/server/webflow/nodeToWebflow.ts +2141 -129
- package/lib/server/webflow/styleMapper.test.ts +389 -0
- package/lib/server/webflow/styleMapper.ts +517 -63
- package/lib/server/webflow/templateWrapper.ts +49 -0
- package/lib/server/webflow/types.ts +218 -18
- package/lib/shared/elementClassName.test.ts +15 -0
- package/lib/shared/elementClassName.ts +7 -3
- package/lib/shared/registry/nodeTypes/ListNodeType.ts +1 -1
- package/lib/shared/styleNodeUtils.test.ts +47 -1
- package/lib/shared/styleNodeUtils.ts +7 -7
- package/lib/shared/types/index.ts +1 -0
- package/lib/shared/types/variables.ts +37 -0
- package/package.json +1 -1
- package/dist/chunks/chunk-77ZB6353.js.map +0 -7
- package/dist/chunks/chunk-C6U5T5S5.js.map +0 -7
- package/dist/chunks/chunk-FED5MME6.js.map +0 -7
- package/dist/chunks/chunk-KITQJYZV.js.map +0 -7
- package/dist/chunks/chunk-XSWR3QLI.js.map +0 -7
- /package/dist/chunks/{chunk-ZTKHJQ2Z.js.map → chunk-BJRKEPMP.js.map} +0 -0
- /package/dist/chunks/{chunk-ORN7S4AP.js.map → chunk-D5E3OKSL.js.map} +0 -0
- /package/dist/chunks/{chunk-IF3RATBY.js.map → chunk-RQSTH2BS.js.map} +0 -0
- /package/dist/chunks/{configService-DYCUEURL.js.map → configService-IGJEC3MC.js.map} +0 -0
|
@@ -20,6 +20,7 @@ import { isComponentNode, isHtmlNode, isLinkNode, isEmbedNode, isLocaleListNode,
|
|
|
20
20
|
import type { ListNode } from '../../shared/registry/nodeTypes/ListNodeType';
|
|
21
21
|
import type { CMSService } from '../services/cmsService';
|
|
22
22
|
import { extractAttributesFromNode, skipEmptyTemplateAttributes } from '../../shared/attributeNodeUtils';
|
|
23
|
+
import { mergeNodeStyles } from '../../shared/styleNodeUtils';
|
|
23
24
|
import { SSRRegistry } from '../../shared/registry/SSRRegistry';
|
|
24
25
|
import { mergeResponsiveStyles } from '../../shared/responsiveStyleUtils';
|
|
25
26
|
// Lazy-loaded: jsdom (used by isomorphic-dompurify on the server) can't be bundled by esbuild
|
|
@@ -242,6 +243,14 @@ function evaluateIfCondition(node: ComponentNode, ctx: SSRContext): boolean {
|
|
|
242
243
|
resolved = processCMSTemplate(resolved, ctx.cmsContext.cms, ctx.locale, ctx.i18nConfig);
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
// Unresolved template (e.g. {{prop}} where the prop is undefined and
|
|
247
|
+
// processStructure preserved the original template per evaluateTemplate's
|
|
248
|
+
// backward-compat fallback) → treat as falsy so missing context means
|
|
249
|
+
// "don't render", matching `if: false` and unset BooleanMapping values.
|
|
250
|
+
if (resolved.includes('{{') && resolved.includes('}}')) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
245
254
|
// Evaluate truthiness - false, 0, empty string are falsy
|
|
246
255
|
return Boolean(resolved) && resolved !== 'false' && resolved !== '0' && resolved !== '';
|
|
247
256
|
}
|
|
@@ -1112,13 +1121,25 @@ async function renderNode(
|
|
|
1112
1121
|
// Convert styles to utility classes instead of inline styles
|
|
1113
1122
|
let utilityClasses: string[] = [];
|
|
1114
1123
|
let resolvedStyle: StyleObject = {};
|
|
1124
|
+
// For custom component instances, defer style→class conversion: forward the
|
|
1125
|
+
// style object so renderComponent can deep-merge it into the structure root,
|
|
1126
|
+
// then let the recursive renderNode call generate classes from the merged
|
|
1127
|
+
// result. This matches the client (ComponentBuilder.mergeNodeStyles) so that
|
|
1128
|
+
// an instance override of e.g. marginBottom actually replaces the structure's
|
|
1129
|
+
// marginBottom instead of fighting it as a same-specificity utility class.
|
|
1130
|
+
const isCustomComponentNode = nodeType === NODE_TYPE.COMPONENT && isComponentNode(node) && ssrComponentRegistry.has(node.component);
|
|
1131
|
+
let deferredComponentStyle: StyleObject | ResponsiveStyleObject | undefined;
|
|
1115
1132
|
|
|
1116
1133
|
if (nodeStyle) {
|
|
1117
|
-
|
|
1118
|
-
|
|
1134
|
+
if (isCustomComponentNode) {
|
|
1135
|
+
deferredComponentStyle = nodeStyle;
|
|
1136
|
+
} else {
|
|
1137
|
+
// Validate that all styles can generate utility classes (build-time warnings)
|
|
1138
|
+
validateStyleCoverage(nodeStyle, `Node: ${nodeType || 'unknown'}`);
|
|
1119
1139
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1140
|
+
// Convert style object to utility class names (process templates in style values)
|
|
1141
|
+
utilityClasses = processStyleToClasses(nodeStyle, ctx);
|
|
1142
|
+
}
|
|
1122
1143
|
} else if (nodeProps.style) {
|
|
1123
1144
|
// If no node.style but props have style, keep it for backward compatibility
|
|
1124
1145
|
if (isResponsiveStyle(nodeProps.style) && breakpoints && viewportWidth) {
|
|
@@ -1164,7 +1185,9 @@ async function renderNode(
|
|
|
1164
1185
|
...nodeProps,
|
|
1165
1186
|
...nodeAttributesWithoutClass,
|
|
1166
1187
|
...(mergedClassName ? { className: mergedClassName } : {}),
|
|
1167
|
-
...(
|
|
1188
|
+
...(deferredComponentStyle
|
|
1189
|
+
? { style: deferredComponentStyle }
|
|
1190
|
+
: Object.keys(resolvedStyle).length > 0 ? { style: resolvedStyle } : {})
|
|
1168
1191
|
};
|
|
1169
1192
|
|
|
1170
1193
|
// Check if this is a custom component
|
|
@@ -1243,42 +1266,25 @@ async function renderComponent(
|
|
|
1243
1266
|
|
|
1244
1267
|
// Merge instance style overrides, className, and attributes
|
|
1245
1268
|
// processedStructure is typically an HTML node (the component's root element)
|
|
1246
|
-
// Handle
|
|
1269
|
+
// Handle component nodes, HTML nodes, and Link nodes (Link and HTML share top-level style/attributes)
|
|
1247
1270
|
const rootNode = processedStructure as ComponentNode & { props?: Record<string, unknown>; style?: any; attributes?: Record<string, string | number | boolean> };
|
|
1248
|
-
if (isComponentNode(rootNode) || isHtmlNode(rootNode)) {
|
|
1271
|
+
if (isComponentNode(rootNode) || isHtmlNode(rootNode) || isLinkNode(rootNode)) {
|
|
1249
1272
|
if (!rootNode.props) {
|
|
1250
1273
|
rootNode.props = {};
|
|
1251
1274
|
}
|
|
1252
1275
|
|
|
1253
|
-
//
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
...(existingStyle as Record<string, any>),
|
|
1260
|
-
...(propsWithStyleAndAttrs.style as Record<string, any>)
|
|
1261
|
-
};
|
|
1262
|
-
} else {
|
|
1263
|
-
rootNode.style = propsWithStyleAndAttrs.style as StyleObject | ResponsiveStyleObject;
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
} else {
|
|
1267
|
-
// For component root nodes, merge into props.style
|
|
1268
|
-
if (rootNode.props.style && typeof rootNode.props.style === 'object') {
|
|
1269
|
-
rootNode.props.style = {
|
|
1270
|
-
...(rootNode.props.style as Record<string, unknown>),
|
|
1271
|
-
...(propsWithStyleAndAttrs.style as Record<string, unknown> || {})
|
|
1272
|
-
};
|
|
1273
|
-
} else if (propsWithStyleAndAttrs.style) {
|
|
1274
|
-
rootNode.props.style = propsWithStyleAndAttrs.style;
|
|
1275
|
-
}
|
|
1276
|
+
// Deep-merge instance styles into the structure root (per-breakpoint for
|
|
1277
|
+
// responsive styles), so an instance override of a property actually
|
|
1278
|
+
// replaces the structure's value for that property instead of producing a
|
|
1279
|
+
// competing utility class on the same element.
|
|
1280
|
+
if (propsWithStyleAndAttrs.style) {
|
|
1281
|
+
mergeNodeStyles(rootNode, propsWithStyleAndAttrs.style as StyleObject | ResponsiveStyleObject);
|
|
1276
1282
|
}
|
|
1277
1283
|
|
|
1278
1284
|
// Merge className from component instance (includes responsive utility classes)
|
|
1279
1285
|
if (propsWithStyleAndAttrs.className) {
|
|
1280
|
-
if (isHtmlNode(rootNode)) {
|
|
1281
|
-
// For HTML root nodes, merge into attributes where the renderer reads them
|
|
1286
|
+
if (isHtmlNode(rootNode) || isLinkNode(rootNode)) {
|
|
1287
|
+
// For HTML and Link root nodes, merge into attributes where the renderer reads them
|
|
1282
1288
|
if (!rootNode.attributes) rootNode.attributes = {};
|
|
1283
1289
|
const existingClass = (rootNode.attributes.class || '') as string;
|
|
1284
1290
|
rootNode.attributes.class = existingClass
|
|
@@ -1293,11 +1299,11 @@ async function renderComponent(
|
|
|
1293
1299
|
}
|
|
1294
1300
|
|
|
1295
1301
|
// Merge attributes into props
|
|
1296
|
-
Object.assign(rootNode.props, nodeAttributes);
|
|
1302
|
+
Object.assign(rootNode.props as Record<string, unknown>, nodeAttributes);
|
|
1297
1303
|
|
|
1298
|
-
// Forward nodeAttributes to rootNode.attributes for HTML root nodes
|
|
1304
|
+
// Forward nodeAttributes to rootNode.attributes for HTML/Link root nodes
|
|
1299
1305
|
// so attributes from outer components (e.g. data-component) reach the final HTML element
|
|
1300
|
-
if (isHtmlNode(rootNode) && Object.keys(nodeAttributes).length > 0) {
|
|
1306
|
+
if ((isHtmlNode(rootNode) || isLinkNode(rootNode)) && Object.keys(nodeAttributes).length > 0) {
|
|
1301
1307
|
if (!rootNode.attributes) {
|
|
1302
1308
|
rootNode.attributes = {};
|
|
1303
1309
|
}
|
|
@@ -70,7 +70,27 @@ describe("jsonLineMapper", () => {
|
|
|
70
70
|
}`;
|
|
71
71
|
|
|
72
72
|
const lineMap = buildLineMap(json);
|
|
73
|
-
|
|
73
|
+
// Only the root entry (empty-string key) is recorded; no children.
|
|
74
|
+
expect(lineMap.size).toBe(1);
|
|
75
|
+
expect(lineMap.get("")).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("records the root's own line range under the empty-string key", () => {
|
|
79
|
+
const json = `{
|
|
80
|
+
"root": {
|
|
81
|
+
"type": "node",
|
|
82
|
+
"children": [
|
|
83
|
+
{
|
|
84
|
+
"type": "node",
|
|
85
|
+
"tag": "div"
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
}`;
|
|
90
|
+
|
|
91
|
+
const lineMap = buildLineMap(json);
|
|
92
|
+
// Root value starts at line 2 and closes at line 10.
|
|
93
|
+
expect(lineMap.get("")).toEqual({ startLine: 2, endLine: 10 });
|
|
74
94
|
});
|
|
75
95
|
|
|
76
96
|
test("should handle JSON without root", () => {
|
|
@@ -83,6 +103,38 @@ describe("jsonLineMapper", () => {
|
|
|
83
103
|
const lineMap = buildLineMap(json);
|
|
84
104
|
expect(lineMap.size).toBe(0);
|
|
85
105
|
});
|
|
106
|
+
|
|
107
|
+
test("tracks component format via component.structure", () => {
|
|
108
|
+
const json = `{
|
|
109
|
+
"component": {
|
|
110
|
+
"interface": {
|
|
111
|
+
"size": { "type": "text" }
|
|
112
|
+
},
|
|
113
|
+
"structure": {
|
|
114
|
+
"type": "node",
|
|
115
|
+
"tag": "h1",
|
|
116
|
+
"children": [
|
|
117
|
+
{
|
|
118
|
+
"type": "node",
|
|
119
|
+
"tag": "span"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"type": "component",
|
|
123
|
+
"component": "Icon"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}`;
|
|
129
|
+
|
|
130
|
+
const lineMap = buildLineMap(json);
|
|
131
|
+
// Structure spans from its opening `{` (line 6) to its closing `}` (line 19).
|
|
132
|
+
expect(lineMap.get("")).toEqual({ startLine: 6, endLine: 19 });
|
|
133
|
+
// First child: the <span>.
|
|
134
|
+
expect(lineMap.get("0")?.startLine).toBe(10);
|
|
135
|
+
// Second child: the Icon component instance.
|
|
136
|
+
expect(lineMap.get("1")?.startLine).toBe(14);
|
|
137
|
+
});
|
|
86
138
|
});
|
|
87
139
|
|
|
88
140
|
describe("lineMapToObject", () => {
|
|
@@ -86,6 +86,16 @@ export function buildLineMap(jsonText: string): LineMap {
|
|
|
86
86
|
return { start: startPos, end: pos };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function recordTrackedRoot(path: number[]): void {
|
|
90
|
+
// Parse the root element of the tracked structure and record its own line range
|
|
91
|
+
// under the empty-string key, so callers can resolve the root selection itself.
|
|
92
|
+
const { start, end } = parseValue(path, true);
|
|
93
|
+
lineMap.set('', {
|
|
94
|
+
startLine: charToLine[start],
|
|
95
|
+
endLine: charToLine[end - 1] || charToLine[start],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
function parseObject(path: number[], trackChildren: boolean): void {
|
|
90
100
|
pos++; // skip {
|
|
91
101
|
skipWhitespace();
|
|
@@ -98,10 +108,13 @@ export function buildLineMap(jsonText: string): LineMap {
|
|
|
98
108
|
skipWhitespace();
|
|
99
109
|
|
|
100
110
|
if (key === 'root' && path.length === 0) {
|
|
101
|
-
//
|
|
102
|
-
|
|
111
|
+
// Page format: track from `root`.
|
|
112
|
+
recordTrackedRoot(path);
|
|
113
|
+
} else if (key === 'component' && path.length === 0) {
|
|
114
|
+
// Component format: descend into the component object looking for `structure`.
|
|
115
|
+
parseComponentWrapper(path);
|
|
103
116
|
} else if (key === 'children' && trackChildren) {
|
|
104
|
-
//
|
|
117
|
+
// Children array — track each element with indices
|
|
105
118
|
parseValue(path, true);
|
|
106
119
|
} else {
|
|
107
120
|
// Regular property - don't track individual items
|
|
@@ -115,6 +128,33 @@ export function buildLineMap(jsonText: string): LineMap {
|
|
|
115
128
|
pos++; // skip }
|
|
116
129
|
}
|
|
117
130
|
|
|
131
|
+
function parseComponentWrapper(path: number[]): void {
|
|
132
|
+
// We expect an object value for the `component` key. If it isn't one, just skip it.
|
|
133
|
+
if (jsonText[pos] !== '{') {
|
|
134
|
+
parseValue(path, false);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
pos++; // skip {
|
|
138
|
+
skipWhitespace();
|
|
139
|
+
while (pos < jsonText.length && jsonText[pos] !== '}') {
|
|
140
|
+
skipWhitespace();
|
|
141
|
+
const key = parseString();
|
|
142
|
+
skipWhitespace();
|
|
143
|
+
pos++; // skip :
|
|
144
|
+
skipWhitespace();
|
|
145
|
+
if (key === 'structure') {
|
|
146
|
+
// The component's structure is the root of what we track — same as `root` for pages.
|
|
147
|
+
recordTrackedRoot(path);
|
|
148
|
+
} else {
|
|
149
|
+
parseValue(path, false);
|
|
150
|
+
}
|
|
151
|
+
skipWhitespace();
|
|
152
|
+
if (jsonText[pos] === ',') pos++;
|
|
153
|
+
skipWhitespace();
|
|
154
|
+
}
|
|
155
|
+
pos++; // skip }
|
|
156
|
+
}
|
|
157
|
+
|
|
118
158
|
function parseArray(path: number[], trackChildren: boolean): void {
|
|
119
159
|
pos++; // skip [
|
|
120
160
|
skipWhitespace();
|