@teleporthq/teleport-plugin-html-base-component 0.40.0-alpha.0 → 0.40.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/__tests__/index.ts +26 -20
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +40 -17
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/node-handlers.d.ts +12 -4
- package/dist/cjs/node-handlers.d.ts.map +1 -1
- package/dist/cjs/node-handlers.js +519 -143
- package/dist/cjs/node-handlers.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +41 -18
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/node-handlers.d.ts +12 -4
- package/dist/esm/node-handlers.d.ts.map +1 -1
- package/dist/esm/node-handlers.js +519 -143
- package/dist/esm/node-handlers.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/index.ts +55 -21
- package/src/node-handlers.ts +747 -170
package/src/node-handlers.ts
CHANGED
|
@@ -19,38 +19,90 @@ import {
|
|
|
19
19
|
UIDLRouteDefinitions,
|
|
20
20
|
ComponentPlugin,
|
|
21
21
|
ComponentStructure,
|
|
22
|
+
UIDLComponentOutputOptions,
|
|
23
|
+
UIDLElement,
|
|
24
|
+
ElementsLookup,
|
|
25
|
+
UIDLConditionalNode,
|
|
26
|
+
PropDefaultValueTypes,
|
|
27
|
+
UIDLCMSListRepeaterNode,
|
|
22
28
|
} from '@teleporthq/teleport-types'
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
29
|
+
import { join, relative } from 'path'
|
|
30
|
+
import { HASTBuilders, HASTUtils, ASTUtils } from '@teleporthq/teleport-plugin-common'
|
|
31
|
+
import { GenericUtils, StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
|
|
25
32
|
import { staticNode } from '@teleporthq/teleport-uidl-builders'
|
|
26
33
|
import { createCSSPlugin } from '@teleporthq/teleport-plugin-css'
|
|
34
|
+
import { generateUniqueKeys, createNodesLookup } from '@teleporthq/teleport-uidl-resolver'
|
|
27
35
|
import { DEFAULT_COMPONENT_CHUNK_NAME } from './constants'
|
|
28
36
|
|
|
37
|
+
const isValidURL = (url: string) => {
|
|
38
|
+
try {
|
|
39
|
+
/* tslint:disable:no-unused-expression */
|
|
40
|
+
new URL(url)
|
|
41
|
+
return true
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const addNodeToLookup = (
|
|
48
|
+
key: string,
|
|
49
|
+
tag: HastNode | HastText | Array<HastNode | HastText>,
|
|
50
|
+
nodesLoookup: Record<string, HastNode | HastText | Array<HastNode | HastText>>
|
|
51
|
+
) => {
|
|
52
|
+
// In html code-generation we combine the nodes of the component that is being consumed with the current component.
|
|
53
|
+
// As html can't load the component at runtime like react or any other frameworks. So, we merge the component as a standalone
|
|
54
|
+
// component in the current component.
|
|
55
|
+
const currentLookup = nodesLoookup[key]
|
|
56
|
+
if (currentLookup) {
|
|
57
|
+
if (Array.isArray(currentLookup)) {
|
|
58
|
+
Array.isArray(tag) ? currentLookup.push(...tag) : currentLookup.push(tag)
|
|
59
|
+
} else {
|
|
60
|
+
nodesLoookup[key] = Array.isArray(tag) ? [currentLookup, ...tag] : [currentLookup, tag]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
nodesLoookup[key] = tag
|
|
67
|
+
}
|
|
68
|
+
|
|
29
69
|
type NodeToHTML<NodeType, ReturnType> = (
|
|
30
70
|
node: NodeType,
|
|
31
|
-
|
|
71
|
+
componentName: string,
|
|
72
|
+
nodesLookup: Record<string, HastNode | HastText | Array<HastNode | HastText>>,
|
|
32
73
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
33
74
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
34
75
|
subComponentOptions: {
|
|
35
76
|
externals: Record<string, ComponentUIDL>
|
|
36
77
|
plugins: ComponentPlugin[]
|
|
37
78
|
},
|
|
38
|
-
routeDefinitions: UIDLRouteDefinitions,
|
|
39
79
|
structure: {
|
|
40
80
|
chunks: ChunkDefinition[]
|
|
41
81
|
dependencies: Record<string, UIDLDependency>
|
|
42
82
|
options: GeneratorOptions
|
|
83
|
+
outputOptions: UIDLComponentOutputOptions
|
|
84
|
+
},
|
|
85
|
+
/**
|
|
86
|
+
* This param is just to be able to handle CMS array mappers/Repeater nodes. A bit hacky, better support should be implemented
|
|
87
|
+
*/
|
|
88
|
+
resolvedExpressions?: {
|
|
89
|
+
expressions: Record<string, UIDLPropDefinition>
|
|
90
|
+
currentIndex: number
|
|
43
91
|
}
|
|
44
92
|
) => ReturnType
|
|
45
93
|
|
|
46
|
-
export const
|
|
94
|
+
export const generateHtmlSyntax: NodeToHTML<
|
|
95
|
+
UIDLNode,
|
|
96
|
+
Promise<HastNode | HastText | Array<HastNode | HastText>>
|
|
97
|
+
> = async (
|
|
47
98
|
node,
|
|
48
|
-
|
|
99
|
+
compName,
|
|
100
|
+
nodesLookup,
|
|
49
101
|
propDefinitions,
|
|
50
102
|
stateDefinitions,
|
|
51
103
|
subComponentOptions,
|
|
52
|
-
|
|
53
|
-
|
|
104
|
+
structure,
|
|
105
|
+
resolvedExpressions
|
|
54
106
|
) => {
|
|
55
107
|
switch (node.type) {
|
|
56
108
|
case 'inject':
|
|
@@ -64,27 +116,133 @@ export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
|
|
|
64
116
|
return HASTBuilders.createHTMLNode(node.type)
|
|
65
117
|
|
|
66
118
|
case 'element':
|
|
67
|
-
|
|
119
|
+
const elementNode = await generateElementNode(
|
|
68
120
|
node,
|
|
69
|
-
|
|
121
|
+
compName,
|
|
122
|
+
nodesLookup,
|
|
70
123
|
propDefinitions,
|
|
71
124
|
stateDefinitions,
|
|
72
125
|
subComponentOptions,
|
|
73
|
-
|
|
74
|
-
|
|
126
|
+
structure,
|
|
127
|
+
resolvedExpressions
|
|
75
128
|
)
|
|
129
|
+
return elementNode
|
|
76
130
|
|
|
77
131
|
case 'dynamic':
|
|
78
|
-
|
|
132
|
+
const dynamicNode = await generateDynamicNode(
|
|
79
133
|
node,
|
|
80
|
-
|
|
134
|
+
compName,
|
|
135
|
+
nodesLookup,
|
|
81
136
|
propDefinitions,
|
|
82
137
|
stateDefinitions,
|
|
83
138
|
subComponentOptions,
|
|
84
|
-
routeDefinitions,
|
|
85
139
|
structure
|
|
86
140
|
)
|
|
141
|
+
return dynamicNode
|
|
142
|
+
|
|
143
|
+
case 'conditional':
|
|
144
|
+
const conditionalNodeComment = HASTBuilders.createTextNode('')
|
|
145
|
+
const {
|
|
146
|
+
value: staticValue,
|
|
147
|
+
reference,
|
|
148
|
+
condition: { conditions, matchingCriteria },
|
|
149
|
+
} = node.content
|
|
150
|
+
|
|
151
|
+
if (reference.type !== 'dynamic') {
|
|
152
|
+
return conditionalNodeComment
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const {
|
|
156
|
+
content: { referenceType, id, refPath = [] },
|
|
157
|
+
} = reference
|
|
158
|
+
|
|
159
|
+
switch (referenceType) {
|
|
160
|
+
case 'prop': {
|
|
161
|
+
const usedProp = propDefinitions[id]
|
|
162
|
+
if (usedProp === undefined || usedProp.defaultValue === undefined) {
|
|
163
|
+
return conditionalNodeComment
|
|
164
|
+
}
|
|
165
|
+
let defaultValue = usedProp.defaultValue
|
|
166
|
+
for (const path of refPath) {
|
|
167
|
+
defaultValue = (defaultValue as Record<string, unknown[]>)?.[path]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If defaultValue is undefined or null after path traversal, use original default
|
|
171
|
+
defaultValue = defaultValue ?? usedProp.defaultValue
|
|
172
|
+
|
|
173
|
+
// Since we know the operand and the default value from the prop.
|
|
174
|
+
// We can try building the condition and check if the condition is true or false.
|
|
175
|
+
// @todo: You can only use a 'value' in UIDL or 'conditions' but not both.
|
|
176
|
+
// UIDL validations need to be improved on this aspect.
|
|
177
|
+
const dynamicConditions = createConditionalStatement(
|
|
178
|
+
staticValue !== undefined ? [{ operand: staticValue, operation: '===' }] : conditions,
|
|
179
|
+
defaultValue
|
|
180
|
+
)
|
|
181
|
+
const matchCondition = matchingCriteria && matchingCriteria === 'all' ? '&&' : '||'
|
|
182
|
+
const conditionString = dynamicConditions.join(` ${matchCondition} `)
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// tslint:disable-next-line function-constructor
|
|
186
|
+
const isConditionPassing = new Function(`return ${conditionString}`)()
|
|
187
|
+
if (isConditionPassing) {
|
|
188
|
+
return generateHtmlSyntax(
|
|
189
|
+
node.content.node,
|
|
190
|
+
compName,
|
|
191
|
+
nodesLookup,
|
|
192
|
+
propDefinitions,
|
|
193
|
+
stateDefinitions,
|
|
194
|
+
subComponentOptions,
|
|
195
|
+
structure,
|
|
196
|
+
resolvedExpressions
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return conditionalNodeComment
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return conditionalNodeComment
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'state':
|
|
207
|
+
default:
|
|
208
|
+
return conditionalNodeComment
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
case 'expr':
|
|
212
|
+
const content = node.content.split('?.')
|
|
87
213
|
|
|
214
|
+
if (resolvedExpressions && resolvedExpressions.expressions?.[content[0] || '']) {
|
|
215
|
+
const uidlDynamicRef: UIDLDynamicReference = {
|
|
216
|
+
type: 'dynamic',
|
|
217
|
+
content: {
|
|
218
|
+
referenceType: 'prop',
|
|
219
|
+
refPath: [resolvedExpressions.currentIndex.toString(), ...content.slice(1)],
|
|
220
|
+
id: content[0],
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
const generatedNode = await generateDynamicNode(
|
|
224
|
+
uidlDynamicRef,
|
|
225
|
+
compName,
|
|
226
|
+
nodesLookup,
|
|
227
|
+
resolvedExpressions.expressions,
|
|
228
|
+
stateDefinitions,
|
|
229
|
+
subComponentOptions,
|
|
230
|
+
structure
|
|
231
|
+
)
|
|
232
|
+
return generatedNode
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return HASTBuilders.createComment('Expressions are not supported in HTML')
|
|
236
|
+
case 'cms-list-repeater':
|
|
237
|
+
return generateRepeaterNode(
|
|
238
|
+
node,
|
|
239
|
+
compName,
|
|
240
|
+
nodesLookup,
|
|
241
|
+
propDefinitions,
|
|
242
|
+
stateDefinitions,
|
|
243
|
+
subComponentOptions,
|
|
244
|
+
structure
|
|
245
|
+
)
|
|
88
246
|
default:
|
|
89
247
|
throw new HTMLComponentGeneratorError(
|
|
90
248
|
`generateHtmlSyntax encountered a node of unsupported type: ${JSON.stringify(
|
|
@@ -96,14 +254,155 @@ export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
|
|
|
96
254
|
}
|
|
97
255
|
}
|
|
98
256
|
|
|
99
|
-
const
|
|
257
|
+
const createConditionalStatement = (
|
|
258
|
+
conditions: UIDLConditionalNode['content']['condition']['conditions'],
|
|
259
|
+
leftOperand: UIDLPropDefinition['defaultValue']
|
|
260
|
+
) => {
|
|
261
|
+
return conditions.map((condition) => {
|
|
262
|
+
const { operation, operand } = condition
|
|
263
|
+
|
|
264
|
+
if (operand === undefined) {
|
|
265
|
+
return `${ASTUtils.convertToUnaryOperator(operation)}${getValueType(operand)}`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return `${getValueType(leftOperand)} ${ASTUtils.convertToBinaryOperator(
|
|
269
|
+
operation
|
|
270
|
+
)} ${getValueType(operand)}`
|
|
271
|
+
})
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const getValueType = (value: UIDLPropDefinition['defaultValue']) => {
|
|
275
|
+
const valueType = typeof value
|
|
276
|
+
switch (valueType) {
|
|
277
|
+
case 'string':
|
|
278
|
+
return `"${value}"`
|
|
279
|
+
case 'number':
|
|
280
|
+
return value
|
|
281
|
+
case 'boolean':
|
|
282
|
+
return value
|
|
283
|
+
default:
|
|
284
|
+
throw new HTMLComponentGeneratorError(
|
|
285
|
+
`Conditional node received an operand of type ${valueType} \n
|
|
286
|
+
Received ${JSON.stringify(value)}`
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const generateRepeaterNode: NodeToHTML<
|
|
292
|
+
UIDLCMSListRepeaterNode,
|
|
293
|
+
Promise<HastNode | HastText>
|
|
294
|
+
> = async (
|
|
100
295
|
node,
|
|
101
|
-
|
|
296
|
+
compName,
|
|
297
|
+
nodesLookup,
|
|
102
298
|
propDefinitions,
|
|
103
299
|
stateDefinitions,
|
|
104
300
|
subComponentOptions,
|
|
105
|
-
routeDefinitions,
|
|
106
301
|
structure
|
|
302
|
+
) => {
|
|
303
|
+
const { nodes } = node.content
|
|
304
|
+
|
|
305
|
+
const contextId = node.content.renderPropIdentifier
|
|
306
|
+
const sourceValue = node.content.source
|
|
307
|
+
let propDef =
|
|
308
|
+
propDefinitions[
|
|
309
|
+
Object.keys(propDefinitions).find((propKey) => sourceValue.includes(propKey)) || ''
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
if (!propDef || !Array.isArray(propDef.defaultValue)) {
|
|
313
|
+
// If no prop is found we might have a static source value
|
|
314
|
+
try {
|
|
315
|
+
const parsedSource = JSON.parse(sourceValue)
|
|
316
|
+
propDef = {
|
|
317
|
+
defaultValue: parsedSource,
|
|
318
|
+
id: contextId,
|
|
319
|
+
type: 'array',
|
|
320
|
+
}
|
|
321
|
+
} catch {
|
|
322
|
+
// Silent fail
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// We do the check again to keep typescript happy, otherwise this could be in catch
|
|
327
|
+
if (!propDef || !Array.isArray(propDef.defaultValue)) {
|
|
328
|
+
return HASTBuilders.createComment(
|
|
329
|
+
'CMS Array Mapper/Repeater not supported in HTML without a prop source'
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
propDefinitions[contextId] = propDef
|
|
333
|
+
|
|
334
|
+
const elementNode = HASTBuilders.createHTMLNode('div')
|
|
335
|
+
node.content.nodes.list.content.style = { display: { type: 'static', content: 'contents' } }
|
|
336
|
+
if (node.content.nodes.empty) {
|
|
337
|
+
node.content.nodes.empty.content.style = { display: { type: 'static', content: 'contents' } }
|
|
338
|
+
}
|
|
339
|
+
// Empty case
|
|
340
|
+
if (propDef.defaultValue.length === 0) {
|
|
341
|
+
const emptyChildren = nodes.empty.content.children
|
|
342
|
+
if (emptyChildren) {
|
|
343
|
+
for (const child of emptyChildren) {
|
|
344
|
+
const childTag = await generateHtmlSyntax(
|
|
345
|
+
child,
|
|
346
|
+
compName,
|
|
347
|
+
nodesLookup,
|
|
348
|
+
propDefinitions,
|
|
349
|
+
stateDefinitions,
|
|
350
|
+
subComponentOptions,
|
|
351
|
+
structure
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if (typeof childTag === 'string') {
|
|
355
|
+
HASTUtils.addTextNode(elementNode, childTag)
|
|
356
|
+
} else {
|
|
357
|
+
HASTUtils.addChildNode(elementNode, childTag as HastNode)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
addNodeToLookup(`${node.content.nodes.empty.content.key}`, elementNode, nodesLookup)
|
|
363
|
+
return elementNode
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const listChildren = nodes.list.content.children
|
|
367
|
+
if (listChildren) {
|
|
368
|
+
for (let index = 0; index < propDef.defaultValue.length; index++) {
|
|
369
|
+
for (const child of listChildren) {
|
|
370
|
+
const childTag = await generateHtmlSyntax(
|
|
371
|
+
child,
|
|
372
|
+
compName,
|
|
373
|
+
nodesLookup,
|
|
374
|
+
propDefinitions,
|
|
375
|
+
stateDefinitions,
|
|
376
|
+
subComponentOptions,
|
|
377
|
+
structure,
|
|
378
|
+
{ currentIndex: index, expressions: { [contextId]: propDef } }
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if (typeof childTag === 'string') {
|
|
382
|
+
HASTUtils.addTextNode(elementNode, childTag)
|
|
383
|
+
} else {
|
|
384
|
+
HASTUtils.addChildNode(elementNode, childTag as HastNode)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
addNodeToLookup(`${node.content.nodes.list.content.key}`, elementNode, nodesLookup)
|
|
391
|
+
return elementNode
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const generateElementNode: NodeToHTML<
|
|
395
|
+
UIDLElementNode,
|
|
396
|
+
Promise<HastNode | HastText | Array<HastNode | HastText>>
|
|
397
|
+
> = async (
|
|
398
|
+
node,
|
|
399
|
+
compName,
|
|
400
|
+
nodesLookup,
|
|
401
|
+
propDefinitions,
|
|
402
|
+
stateDefinitions,
|
|
403
|
+
subComponentOptions,
|
|
404
|
+
structure,
|
|
405
|
+
resolvedExpressions
|
|
107
406
|
) => {
|
|
108
407
|
const {
|
|
109
408
|
elementType,
|
|
@@ -112,45 +411,45 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
|
|
|
112
411
|
style = {},
|
|
113
412
|
referencedStyles = {},
|
|
114
413
|
dependency,
|
|
115
|
-
key,
|
|
116
414
|
} = node.content
|
|
117
|
-
|
|
118
|
-
const elementNode = HASTBuilders.createHTMLNode(elementType)
|
|
119
|
-
templatesLookUp[key] = elementNode
|
|
120
|
-
|
|
121
415
|
const { dependencies } = structure
|
|
122
|
-
if (dependency && (dependency as UIDLDependency)?.type !== 'local') {
|
|
123
|
-
dependencies[dependency.path] = dependency
|
|
124
|
-
}
|
|
125
|
-
|
|
126
416
|
if (dependency && (dependency as UIDLDependency)?.type === 'local') {
|
|
127
417
|
const compTag = await generateComponentContent(
|
|
128
418
|
node,
|
|
419
|
+
nodesLookup,
|
|
129
420
|
propDefinitions,
|
|
130
421
|
stateDefinitions,
|
|
131
422
|
subComponentOptions,
|
|
132
|
-
|
|
133
|
-
|
|
423
|
+
structure,
|
|
424
|
+
resolvedExpressions
|
|
134
425
|
)
|
|
426
|
+
|
|
427
|
+
if ('tagName' in compTag) {
|
|
428
|
+
compTag.children.unshift(HASTBuilders.createComment(`${node.content.semanticType} component`))
|
|
429
|
+
}
|
|
430
|
+
|
|
135
431
|
return compTag
|
|
136
432
|
}
|
|
137
433
|
|
|
434
|
+
if (dependency && (dependency as UIDLDependency)?.type !== 'local') {
|
|
435
|
+
dependencies[dependency.path] = dependency
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const elementNode = HASTBuilders.createHTMLNode(elementType)
|
|
439
|
+
|
|
138
440
|
if (children) {
|
|
139
441
|
for (const child of children) {
|
|
140
|
-
const childTag = await
|
|
442
|
+
const childTag = await generateHtmlSyntax(
|
|
141
443
|
child,
|
|
142
|
-
|
|
444
|
+
compName,
|
|
445
|
+
nodesLookup,
|
|
143
446
|
propDefinitions,
|
|
144
447
|
stateDefinitions,
|
|
145
448
|
subComponentOptions,
|
|
146
|
-
|
|
147
|
-
|
|
449
|
+
structure,
|
|
450
|
+
resolvedExpressions
|
|
148
451
|
)
|
|
149
452
|
|
|
150
|
-
if (!childTag) {
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
|
|
154
453
|
if (typeof childTag === 'string') {
|
|
155
454
|
HASTUtils.addTextNode(elementNode, childTag)
|
|
156
455
|
} else {
|
|
@@ -164,7 +463,6 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
|
|
|
164
463
|
const refStyle = referencedStyles[styleRef]
|
|
165
464
|
if (refStyle.content.mapType === 'inlined') {
|
|
166
465
|
handleStyles(node, refStyle.content.styles, propDefinitions, stateDefinitions)
|
|
167
|
-
return
|
|
168
466
|
}
|
|
169
467
|
})
|
|
170
468
|
}
|
|
@@ -173,43 +471,70 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
|
|
|
173
471
|
handleStyles(node, style, propDefinitions, stateDefinitions)
|
|
174
472
|
}
|
|
175
473
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
474
|
+
handleAttributes(
|
|
475
|
+
elementType,
|
|
476
|
+
elementNode,
|
|
477
|
+
attrs,
|
|
478
|
+
propDefinitions,
|
|
479
|
+
stateDefinitions,
|
|
480
|
+
structure.options.projectRouteDefinition,
|
|
481
|
+
structure.outputOptions,
|
|
482
|
+
resolvedExpressions?.currentIndex
|
|
483
|
+
)
|
|
179
484
|
|
|
485
|
+
addNodeToLookup(node.content.key, elementNode, nodesLookup)
|
|
180
486
|
return elementNode
|
|
181
487
|
}
|
|
182
488
|
|
|
489
|
+
const createLookupTable = (
|
|
490
|
+
component: ComponentUIDL,
|
|
491
|
+
nodesLookup: Record<string, HastNode | HastText | Array<HastNode | HastText>>
|
|
492
|
+
): ElementsLookup => {
|
|
493
|
+
const lookup: ElementsLookup = {}
|
|
494
|
+
for (const node of Object.keys(nodesLookup)) {
|
|
495
|
+
lookup[node] = {
|
|
496
|
+
count: 1,
|
|
497
|
+
nextKey: '1',
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
createNodesLookup(component, lookup)
|
|
501
|
+
return lookup
|
|
502
|
+
}
|
|
503
|
+
|
|
183
504
|
const generateComponentContent = async (
|
|
184
505
|
node: UIDLElementNode,
|
|
506
|
+
nodesLookup: Record<string, HastNode | HastText | Array<HastNode | HastText>>,
|
|
185
507
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
186
508
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
187
509
|
subComponentOptions: {
|
|
188
510
|
externals: Record<string, ComponentUIDL>
|
|
189
511
|
plugins: ComponentPlugin[]
|
|
190
512
|
},
|
|
191
|
-
routeDefinitions: UIDLRouteDefinitions,
|
|
192
513
|
structure: {
|
|
193
514
|
chunks: ChunkDefinition[]
|
|
194
515
|
dependencies: Record<string, UIDLDependency>
|
|
195
516
|
options: GeneratorOptions
|
|
517
|
+
outputOptions: UIDLComponentOutputOptions
|
|
518
|
+
},
|
|
519
|
+
resolvedExpressions?: {
|
|
520
|
+
expressions: Record<string, UIDLPropDefinition>
|
|
521
|
+
currentIndex: number
|
|
196
522
|
}
|
|
197
523
|
) => {
|
|
198
524
|
const { externals, plugins } = subComponentOptions
|
|
199
|
-
const { elementType, attrs = {},
|
|
525
|
+
const { elementType, attrs = {}, children = [] } = node.content
|
|
200
526
|
const { dependencies, chunks = [], options } = structure
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
throw new HTMLComponentGeneratorError(`${elementType} is not found from the externals. \n
|
|
207
|
-
Received ${JSON.stringify(Object.keys(externals), null, 2)}`)
|
|
527
|
+
// "Component" will not exist when generating a component because the resolver checks for illegal class names
|
|
528
|
+
const componentName = elementType === 'Component' ? 'AppComponent' : elementType
|
|
529
|
+
const component = externals[componentName]
|
|
530
|
+
if (component === undefined) {
|
|
531
|
+
throw new HTMLComponentGeneratorError(`${componentName} is missing from externals object`)
|
|
208
532
|
}
|
|
209
533
|
|
|
534
|
+
const componentClone = UIDLUtils.cloneObject<ComponentUIDL>(component)
|
|
535
|
+
|
|
210
536
|
if (children.length) {
|
|
211
|
-
|
|
212
|
-
UIDLUtils.traverseNodes(comp.node, (childNode, parentNode) => {
|
|
537
|
+
UIDLUtils.traverseNodes(componentClone.node, (childNode, parentNode) => {
|
|
213
538
|
if (childNode.type === 'slot' && parentNode.type === 'element') {
|
|
214
539
|
const nonSlotNodes = parentNode.content?.children?.filter((n) => n.type !== 'slot')
|
|
215
540
|
parentNode.content.children = [
|
|
@@ -219,6 +544,7 @@ const generateComponentContent = async (
|
|
|
219
544
|
content: {
|
|
220
545
|
key: 'custom-slot',
|
|
221
546
|
elementType: 'slot',
|
|
547
|
+
name: componentClone.name + 'slot',
|
|
222
548
|
style: {
|
|
223
549
|
display: {
|
|
224
550
|
type: 'static',
|
|
@@ -238,31 +564,47 @@ const generateComponentContent = async (
|
|
|
238
564
|
node.content.children = []
|
|
239
565
|
}
|
|
240
566
|
|
|
241
|
-
|
|
567
|
+
// In UIDL, we define only the link between a component and a page.
|
|
568
|
+
// We define this link using the UIDLLocalDependency approach.
|
|
569
|
+
// So, during the page resolution step, where we ideally generate the unique keys for the components.
|
|
570
|
+
// We can't generate the unique keys for the components because we don't have the full UIDL of the component.
|
|
571
|
+
// When we are using components in a page, the `addExternalComponents` step of the
|
|
572
|
+
// html-component-generator will add the full UIDL of the component to the externals object after resolving them.
|
|
573
|
+
// But when a component is used multiple number of times, we are basically using the same nodes again and again.
|
|
574
|
+
// Which indivates duplication. So, we create a lookup table of all the nodes present with us in the page
|
|
575
|
+
// And then pass it to the component to avoid any coilissions.
|
|
576
|
+
const lookupTableForCurrentPage = createLookupTable(componentClone, nodesLookup)
|
|
577
|
+
generateUniqueKeys(componentClone, lookupTableForCurrentPage)
|
|
242
578
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const combinedStates = { ...stateDefinitions, ...(
|
|
579
|
+
// We are combining props of the current component
|
|
580
|
+
// with props of the component that we need to generate.
|
|
581
|
+
// Refer to line 309, for element props. We either pick from the attr of the current instance of component
|
|
582
|
+
// or from the propDefinitions of the component that we are generating.
|
|
583
|
+
// We NEED to keep passing the props of the current component to the child component and so on,
|
|
584
|
+
// so that all props are forwarded.
|
|
585
|
+
const combinedProps: Record<string, UIDLPropDefinition> = {
|
|
586
|
+
...Object.keys(propDefinitions).reduce<Record<string, UIDLPropDefinition>>(
|
|
587
|
+
(acc: Record<string, UIDLPropDefinition>, propKey) => {
|
|
588
|
+
acc[propKey] = propDefinitions[propKey]
|
|
589
|
+
return acc
|
|
590
|
+
},
|
|
591
|
+
{}
|
|
592
|
+
),
|
|
593
|
+
...(componentClone?.propDefinitions || {}),
|
|
594
|
+
}
|
|
595
|
+
const combinedStates = { ...stateDefinitions, ...(componentClone?.stateDefinitions || {}) }
|
|
260
596
|
const statesForInstance = Object.keys(combinedStates).reduce(
|
|
261
597
|
(acc: Record<string, UIDLStateDefinition>, propKey) => {
|
|
262
|
-
|
|
598
|
+
const attr = attrs[propKey]
|
|
599
|
+
|
|
600
|
+
if (attr?.type === 'object') {
|
|
601
|
+
throw new Error(`Object attributes are not supported in html exports`)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (attr) {
|
|
263
605
|
acc[propKey] = {
|
|
264
606
|
...combinedStates[propKey],
|
|
265
|
-
defaultValue:
|
|
607
|
+
defaultValue: attr?.content ?? combinedStates[propKey]?.defaultValue,
|
|
266
608
|
}
|
|
267
609
|
} else {
|
|
268
610
|
acc[propKey] = combinedStates[propKey]
|
|
@@ -273,43 +615,130 @@ const generateComponentContent = async (
|
|
|
273
615
|
{}
|
|
274
616
|
)
|
|
275
617
|
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
618
|
+
const propsForInstance: Record<string, UIDLPropDefinition> = {}
|
|
619
|
+
// this is where we check if the component we are conusming is actually passing any props to the instance.
|
|
620
|
+
// We check if we are passing any props and pick the value from the atrrs, if not we pick the value from the propDefinitions of
|
|
621
|
+
// the component instance that we are using here.
|
|
622
|
+
for (const propKey of Object.keys(combinedProps)) {
|
|
623
|
+
const attribute = attrs[propKey]
|
|
624
|
+
|
|
625
|
+
if (attribute?.type === 'element') {
|
|
626
|
+
propsForInstance[propKey] = {
|
|
627
|
+
...combinedProps[propKey],
|
|
628
|
+
defaultValue: attrs[propKey],
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (attribute?.type === 'dynamic') {
|
|
633
|
+
// When we are using a component instance in a component and the attribute
|
|
634
|
+
// that is passed to the component is of dynamic reference.
|
|
635
|
+
// If means, the component is redirecting the prop that is received to the prop of the component that it is consuming.
|
|
636
|
+
// In this case, we need to pass the value of the prop that is received to the prop of the component that it is consuming.
|
|
637
|
+
// And similary we do the same for the states.
|
|
638
|
+
switch (attribute.content.referenceType) {
|
|
639
|
+
case 'prop':
|
|
640
|
+
propsForInstance[propKey] = combinedProps[attribute.content.id]
|
|
641
|
+
break
|
|
642
|
+
case 'state':
|
|
643
|
+
propsForInstance[propKey] = combinedStates[attribute.content.id]
|
|
644
|
+
break
|
|
645
|
+
case 'expr':
|
|
646
|
+
// Ignore expr type attributes in html comp instances for the time being.
|
|
647
|
+
break
|
|
648
|
+
default:
|
|
649
|
+
throw new Error(
|
|
650
|
+
`ReferenceType ${attribute.content.referenceType} is not supported in HTML Export.`
|
|
651
|
+
)
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (attribute?.type === 'object') {
|
|
656
|
+
propsForInstance[propKey] = {
|
|
657
|
+
...combinedProps[propKey],
|
|
658
|
+
defaultValue: (attribute?.content as object) || combinedProps[propKey]?.defaultValue,
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (attribute?.type === 'expr') {
|
|
663
|
+
const [ctxId, ...refPath] = attribute.content.split('?.')
|
|
664
|
+
const propKeyFromAttr = Object.keys(combinedProps).find((key) => key === ctxId)
|
|
665
|
+
|
|
666
|
+
const resolvedValue = combinedProps[propKeyFromAttr]
|
|
667
|
+
propsForInstance[propKey] = Array.isArray(resolvedValue)
|
|
668
|
+
? resolvedValue
|
|
669
|
+
: {
|
|
670
|
+
...resolvedValue,
|
|
671
|
+
defaultValue:
|
|
672
|
+
refPath.length > 0 && resolvedValue?.defaultValue
|
|
673
|
+
? extractDefaultValueFromRefPath(resolvedValue.defaultValue, [
|
|
674
|
+
resolvedExpressions.currentIndex.toString(),
|
|
675
|
+
...refPath,
|
|
676
|
+
])
|
|
677
|
+
: null,
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (
|
|
682
|
+
attribute?.type !== 'dynamic' &&
|
|
683
|
+
attribute?.type !== 'element' &&
|
|
684
|
+
attribute?.type !== 'object' &&
|
|
685
|
+
attribute?.type !== 'expr'
|
|
686
|
+
) {
|
|
687
|
+
propsForInstance[propKey] = {
|
|
688
|
+
...combinedProps[propKey],
|
|
689
|
+
defaultValue: attribute?.content ?? combinedProps[propKey]?.defaultValue,
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (attribute === undefined) {
|
|
694
|
+
const propFromCurrentComponent = combinedProps[propKey]
|
|
695
|
+
propsForInstance[propKey] = propFromCurrentComponent
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
let componentWrapper = StringUtils.camelCaseToDashCase(`${componentName}-wrapper`)
|
|
700
|
+
const isExistingNode = nodesLookup[componentWrapper]
|
|
701
|
+
if (isExistingNode !== undefined) {
|
|
702
|
+
componentWrapper = `${componentWrapper}-${StringUtils.generateRandomString()}`
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const componentInstanceToGenerate: UIDLElementNode = {
|
|
706
|
+
type: 'element',
|
|
707
|
+
content: {
|
|
708
|
+
elementType: componentWrapper,
|
|
709
|
+
key: componentWrapper,
|
|
710
|
+
children: [componentClone.node],
|
|
711
|
+
style: {
|
|
712
|
+
display: {
|
|
713
|
+
type: 'static',
|
|
714
|
+
content: 'contents',
|
|
290
715
|
},
|
|
291
716
|
},
|
|
292
717
|
},
|
|
293
|
-
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const compTag = await generateHtmlSyntax(
|
|
721
|
+
componentInstanceToGenerate,
|
|
722
|
+
component.name,
|
|
723
|
+
nodesLookup,
|
|
294
724
|
propsForInstance,
|
|
295
725
|
statesForInstance,
|
|
296
726
|
subComponentOptions,
|
|
297
|
-
routeDefinitions,
|
|
298
727
|
structure
|
|
299
|
-
)
|
|
728
|
+
)
|
|
300
729
|
|
|
301
730
|
const cssPlugin = createCSSPlugin({
|
|
302
731
|
templateStyle: 'html',
|
|
303
732
|
templateChunkName: DEFAULT_COMPONENT_CHUNK_NAME,
|
|
304
733
|
declareDependency: 'import',
|
|
305
|
-
|
|
306
|
-
chunkName: comp.name,
|
|
734
|
+
chunkName: componentClone.name,
|
|
307
735
|
staticPropReferences: true,
|
|
308
736
|
})
|
|
309
737
|
|
|
310
738
|
const initialStructure: ComponentStructure = {
|
|
311
739
|
uidl: {
|
|
312
|
-
...
|
|
740
|
+
...componentClone,
|
|
741
|
+
node: componentInstanceToGenerate,
|
|
313
742
|
propDefinitions: propsForInstance,
|
|
314
743
|
stateDefinitions: statesForInstance,
|
|
315
744
|
},
|
|
@@ -321,7 +750,7 @@ const generateComponentContent = async (
|
|
|
321
750
|
linkAfter: [],
|
|
322
751
|
content: compTag,
|
|
323
752
|
meta: {
|
|
324
|
-
nodesLookup
|
|
753
|
+
nodesLookup,
|
|
325
754
|
},
|
|
326
755
|
},
|
|
327
756
|
],
|
|
@@ -337,41 +766,84 @@ const generateComponentContent = async (
|
|
|
337
766
|
Promise.resolve(initialStructure)
|
|
338
767
|
)
|
|
339
768
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
chunks.push(chunk)
|
|
344
|
-
}
|
|
345
|
-
})
|
|
346
|
-
} else {
|
|
347
|
-
const chunk = chunks.find((item) => item.name === comp.name)
|
|
348
|
-
if (!chunk) {
|
|
349
|
-
const styleChunk = result.chunks.find(
|
|
350
|
-
(item: ChunkDefinition) => item.fileType === FileType.CSS
|
|
351
|
-
)
|
|
352
|
-
if (!styleChunk) {
|
|
353
|
-
return
|
|
354
|
-
}
|
|
355
|
-
chunks.push(styleChunk)
|
|
769
|
+
result.chunks.forEach((chunk) => {
|
|
770
|
+
if (chunk.fileType === FileType.CSS) {
|
|
771
|
+
chunks.push(chunk)
|
|
356
772
|
}
|
|
357
|
-
}
|
|
773
|
+
})
|
|
358
774
|
|
|
775
|
+
addNodeToLookup(node.content.key, compTag, nodesLookup)
|
|
359
776
|
return compTag
|
|
360
777
|
}
|
|
361
778
|
|
|
362
|
-
const generateDynamicNode: NodeToHTML<
|
|
779
|
+
const generateDynamicNode: NodeToHTML<
|
|
780
|
+
UIDLDynamicReference,
|
|
781
|
+
Promise<HastNode | HastText | Array<HastNode | HastText>>
|
|
782
|
+
> = async (
|
|
363
783
|
node,
|
|
364
|
-
|
|
784
|
+
compName,
|
|
785
|
+
nodesLookup,
|
|
365
786
|
propDefinitions,
|
|
366
|
-
stateDefinitions
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
787
|
+
stateDefinitions,
|
|
788
|
+
subComponentOptions,
|
|
789
|
+
structure
|
|
790
|
+
): Promise<HastNode | HastText | Array<HastNode | HastText>> => {
|
|
791
|
+
if (node.content.referenceType === 'locale') {
|
|
792
|
+
const localeTag = HASTBuilders.createHTMLNode('span')
|
|
793
|
+
const commentNode = HASTBuilders.createComment(`Content for locale ${node.content.id}`)
|
|
794
|
+
HASTUtils.addChildNode(localeTag, commentNode)
|
|
795
|
+
return localeTag
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const usedReferenceValue = getValueFromReference(
|
|
799
|
+
node.content.id,
|
|
800
|
+
node.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
801
|
+
)
|
|
373
802
|
|
|
374
|
-
|
|
803
|
+
if (
|
|
804
|
+
(usedReferenceValue.type === 'object' || usedReferenceValue.type === 'array') &&
|
|
805
|
+
usedReferenceValue.defaultValue
|
|
806
|
+
) {
|
|
807
|
+
// Let's say users are biding the prop to a node using something like this "fields.Title"
|
|
808
|
+
// But the fields in the object is the value where the object is defined either in propDefinitions
|
|
809
|
+
// or on the attrs. So, we just need to parsed the rest of the object path and get the value from the object.
|
|
810
|
+
return HASTBuilders.createTextNode(
|
|
811
|
+
String(
|
|
812
|
+
extractDefaultValueFromRefPath(
|
|
813
|
+
usedReferenceValue.defaultValue as Record<string, UIDLPropDefinition>,
|
|
814
|
+
node.content.refPath
|
|
815
|
+
)
|
|
816
|
+
)
|
|
817
|
+
)
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (usedReferenceValue.type === 'element') {
|
|
821
|
+
const elementNode = usedReferenceValue.defaultValue as UIDLElementNode
|
|
822
|
+
if (elementNode) {
|
|
823
|
+
if (elementNode.content.key in nodesLookup) {
|
|
824
|
+
return nodesLookup[elementNode.content.key]
|
|
825
|
+
} else {
|
|
826
|
+
const elementTag = await generateHtmlSyntax(
|
|
827
|
+
elementNode,
|
|
828
|
+
compName,
|
|
829
|
+
nodesLookup,
|
|
830
|
+
propDefinitions,
|
|
831
|
+
stateDefinitions,
|
|
832
|
+
subComponentOptions,
|
|
833
|
+
structure
|
|
834
|
+
)
|
|
835
|
+
return elementTag
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const spanTagWrapper = HASTBuilders.createHTMLNode('span')
|
|
840
|
+
const commentNode = HASTBuilders.createComment(`Content for slot ${node.content.id}`)
|
|
841
|
+
HASTUtils.addChildNode(spanTagWrapper, commentNode)
|
|
842
|
+
return spanTagWrapper
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const spanTag = HASTBuilders.createHTMLNode('span')
|
|
846
|
+
HASTUtils.addTextNode(spanTag, String(usedReferenceValue.defaultValue))
|
|
375
847
|
return spanTag
|
|
376
848
|
}
|
|
377
849
|
|
|
@@ -384,10 +856,14 @@ const handleStyles = (
|
|
|
384
856
|
Object.keys(styles).forEach((styleKey) => {
|
|
385
857
|
let style: string | UIDLStyleValue = styles[styleKey]
|
|
386
858
|
if (style.type === 'dynamic' && style.content?.referenceType !== 'token') {
|
|
387
|
-
|
|
388
|
-
style
|
|
389
|
-
|
|
390
|
-
|
|
859
|
+
const referencedValue = getValueFromReference(
|
|
860
|
+
style.content.id,
|
|
861
|
+
style.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
862
|
+
)
|
|
863
|
+
if (referencedValue.type === 'string' || referencedValue.type === 'number') {
|
|
864
|
+
style = String(
|
|
865
|
+
extractDefaultValueFromRefPath(referencedValue.defaultValue, style?.content?.refPath)
|
|
866
|
+
)
|
|
391
867
|
}
|
|
392
868
|
node.content.style[styleKey] = typeof style === 'string' ? staticNode(style) : style
|
|
393
869
|
}
|
|
@@ -395,62 +871,145 @@ const handleStyles = (
|
|
|
395
871
|
}
|
|
396
872
|
|
|
397
873
|
const handleAttributes = (
|
|
874
|
+
elementType: UIDLElement['elementType'],
|
|
398
875
|
htmlNode: HastNode,
|
|
399
876
|
attrs: Record<string, UIDLAttributeValue>,
|
|
400
877
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
401
878
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
402
|
-
routeDefinitions: UIDLRouteDefinitions
|
|
879
|
+
routeDefinitions: UIDLRouteDefinitions,
|
|
880
|
+
outputOptions: UIDLComponentOutputOptions,
|
|
881
|
+
currentIndex?: number
|
|
403
882
|
) => {
|
|
404
|
-
Object.keys(attrs)
|
|
405
|
-
|
|
883
|
+
for (const attrKey of Object.keys(attrs)) {
|
|
884
|
+
const attrValue = attrs[attrKey]
|
|
885
|
+
const { type, content } = attrValue
|
|
406
886
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
attrValue.content.startsWith('/')
|
|
412
|
-
) {
|
|
413
|
-
attrValue =
|
|
414
|
-
attrValue.content === '/' ||
|
|
415
|
-
attrValue.content ===
|
|
416
|
-
`/${StringUtils.camelCaseToDashCase(
|
|
417
|
-
StringUtils.removeIllegalCharacters(routeDefinitions?.defaultValue || '')
|
|
418
|
-
)}`
|
|
419
|
-
? staticNode('index.html')
|
|
420
|
-
: staticNode(`${attrValue.content.split('/').pop()}.html`)
|
|
421
|
-
HASTUtils.addAttributeToNode(htmlNode, attrKey, String(attrValue.content))
|
|
422
|
-
return
|
|
423
|
-
}
|
|
887
|
+
switch (type) {
|
|
888
|
+
case 'static': {
|
|
889
|
+
if (attrKey === 'href' && typeof content === 'string' && content.startsWith('/')) {
|
|
890
|
+
let targetLink
|
|
424
891
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
? getValueFromReference(attrValue.content.id, propDefinitions)
|
|
429
|
-
: getValueFromReference(attrValue.content.id, stateDefinitions)
|
|
430
|
-
HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value))
|
|
431
|
-
return
|
|
432
|
-
}
|
|
892
|
+
const targetRoute = (routeDefinitions?.values || []).find(
|
|
893
|
+
(route) => route.pageOptions.navLink === content
|
|
894
|
+
)
|
|
433
895
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
896
|
+
if (targetRoute) {
|
|
897
|
+
targetLink = targetRoute.pageOptions.navLink
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (!targetRoute && content === '/home') {
|
|
901
|
+
targetLink = '/'
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (!targetLink && !targetRoute) {
|
|
905
|
+
targetLink = content
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const currentPageRoute = join(...(outputOptions?.folderPath || []), './')
|
|
909
|
+
const localPrefix = relative(
|
|
910
|
+
`/${currentPageRoute}`,
|
|
911
|
+
`/${targetLink === '/' ? 'index' : targetLink}`
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, `${localPrefix}.html`)
|
|
915
|
+
break
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (typeof content === 'boolean') {
|
|
919
|
+
htmlNode.properties[attrKey] = content === true ? 'true' : 'false'
|
|
920
|
+
} else if (typeof content === 'string' || typeof attrValue.content === 'number') {
|
|
921
|
+
let value = StringUtils.encode(String(attrValue.content))
|
|
922
|
+
|
|
923
|
+
/*
|
|
924
|
+
elementType of image is always mapped to img.
|
|
925
|
+
For reference, check `html-mapping` file.
|
|
926
|
+
*/
|
|
927
|
+
if (elementType === 'img' && attrKey === 'src' && !isValidURL(value)) {
|
|
928
|
+
/*
|
|
929
|
+
By default we just prefix all the asset paths with just the
|
|
930
|
+
assetPrefix that is configured in the project. But for `html` generators
|
|
931
|
+
we need to prefix that with the current file location.
|
|
932
|
+
|
|
933
|
+
Because, all the other frameworks have a build setup. which serves all the
|
|
934
|
+
assets from the `public` folder. But in the case of `html` here is how it works
|
|
935
|
+
|
|
936
|
+
We load a file from `index.html` the request for the image goes from
|
|
937
|
+
'...url.../public/...image...'
|
|
938
|
+
If it's a nested url, then the request goes from
|
|
939
|
+
'...url/nested/public/...image..'
|
|
940
|
+
|
|
941
|
+
But the nested folder is available only on the root. With this
|
|
942
|
+
The url changes prefixes to
|
|
943
|
+
|
|
944
|
+
../public/playground_assets/..image.. etc depending on the dept the file is in.
|
|
945
|
+
*/
|
|
946
|
+
value = join(relative(join(...outputOptions.folderPath), './'), value)
|
|
947
|
+
}
|
|
438
948
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
949
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, value)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
break
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
case 'dynamic': {
|
|
956
|
+
const value = getValueFromReference(
|
|
957
|
+
content.id,
|
|
958
|
+
content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
HASTUtils.addAttributeToNode(
|
|
962
|
+
htmlNode,
|
|
963
|
+
attrKey,
|
|
964
|
+
String(extractDefaultValueFromRefPath(value.defaultValue, content.refPath))
|
|
965
|
+
)
|
|
966
|
+
break
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
case 'raw': {
|
|
970
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, content)
|
|
971
|
+
break
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
case 'expr': {
|
|
975
|
+
const fullPath = content.split('?.')
|
|
976
|
+
const prop = propDefinitions[fullPath?.[0] || '']
|
|
977
|
+
|
|
978
|
+
if (!prop) {
|
|
979
|
+
break
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const path =
|
|
983
|
+
typeof currentIndex === 'number'
|
|
984
|
+
? [currentIndex.toString(), ...fullPath.slice(1)]
|
|
985
|
+
: fullPath.slice(1)
|
|
986
|
+
const value = extractDefaultValueFromRefPath(prop.defaultValue, path)
|
|
987
|
+
if (!value) {
|
|
988
|
+
break
|
|
989
|
+
}
|
|
990
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value))
|
|
991
|
+
break
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
case 'element':
|
|
995
|
+
case 'import':
|
|
996
|
+
case 'object':
|
|
997
|
+
break
|
|
998
|
+
|
|
999
|
+
default: {
|
|
1000
|
+
throw new HTMLComponentGeneratorError(
|
|
1001
|
+
`Received ${JSON.stringify(attrValue, null, 2)} \n in handleAttributes for html`
|
|
1002
|
+
)
|
|
1003
|
+
}
|
|
445
1004
|
}
|
|
446
|
-
}
|
|
1005
|
+
}
|
|
447
1006
|
}
|
|
448
1007
|
|
|
449
1008
|
const getValueFromReference = (
|
|
450
1009
|
key: string,
|
|
451
1010
|
definitions: Record<string, UIDLPropDefinition>
|
|
452
|
-
):
|
|
453
|
-
const usedReferenceValue = definitions[key.includes('
|
|
1011
|
+
): UIDLPropDefinition | undefined => {
|
|
1012
|
+
const usedReferenceValue = definitions[key.includes('?.') ? key.split('?.')[0] : key]
|
|
454
1013
|
|
|
455
1014
|
if (!usedReferenceValue) {
|
|
456
1015
|
throw new HTMLComponentGeneratorError(
|
|
@@ -458,9 +1017,13 @@ const getValueFromReference = (
|
|
|
458
1017
|
)
|
|
459
1018
|
}
|
|
460
1019
|
|
|
461
|
-
if (
|
|
1020
|
+
if (
|
|
1021
|
+
['string', 'number', 'object', 'element', 'array', 'boolean'].includes(
|
|
1022
|
+
usedReferenceValue?.type
|
|
1023
|
+
) === false
|
|
1024
|
+
) {
|
|
462
1025
|
throw new HTMLComponentGeneratorError(
|
|
463
|
-
`
|
|
1026
|
+
`Attribute is using dynamic value, but received of type ${JSON.stringify(
|
|
464
1027
|
usedReferenceValue,
|
|
465
1028
|
null,
|
|
466
1029
|
2
|
|
@@ -468,9 +1031,12 @@ const getValueFromReference = (
|
|
|
468
1031
|
)
|
|
469
1032
|
}
|
|
470
1033
|
|
|
471
|
-
if (
|
|
1034
|
+
if (
|
|
1035
|
+
usedReferenceValue.type !== 'element' &&
|
|
1036
|
+
usedReferenceValue.hasOwnProperty('defaultValue') === false
|
|
1037
|
+
) {
|
|
472
1038
|
throw new HTMLComponentGeneratorError(
|
|
473
|
-
`
|
|
1039
|
+
`Default value is missing from dynamic reference - ${JSON.stringify(
|
|
474
1040
|
usedReferenceValue,
|
|
475
1041
|
null,
|
|
476
1042
|
2
|
|
@@ -478,5 +1044,16 @@ const getValueFromReference = (
|
|
|
478
1044
|
)
|
|
479
1045
|
}
|
|
480
1046
|
|
|
481
|
-
return
|
|
1047
|
+
return usedReferenceValue
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const extractDefaultValueFromRefPath = (
|
|
1051
|
+
propDefaultValue: PropDefaultValueTypes,
|
|
1052
|
+
refPath?: string[]
|
|
1053
|
+
) => {
|
|
1054
|
+
if (typeof propDefaultValue !== 'object' || !refPath?.length) {
|
|
1055
|
+
return propDefaultValue
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return GenericUtils.getValueFromPath(refPath.join('.'), propDefaultValue) as PropDefaultValueTypes
|
|
482
1059
|
}
|