@teleporthq/teleport-plugin-html-base-component 0.36.0-alpha.0 → 0.36.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 +5 -4
- package/dist/cjs/node-handlers.d.ts.map +1 -1
- package/dist/cjs/node-handlers.js +290 -121
- 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 +5 -4
- package/dist/esm/node-handlers.d.ts.map +1 -1
- package/dist/esm/node-handlers.js +288 -119
- 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 +374 -140
package/src/node-handlers.ts
CHANGED
|
@@ -19,37 +19,76 @@ import {
|
|
|
19
19
|
UIDLRouteDefinitions,
|
|
20
20
|
ComponentPlugin,
|
|
21
21
|
ComponentStructure,
|
|
22
|
+
UIDLComponentOutputOptions,
|
|
23
|
+
UIDLElement,
|
|
24
|
+
ElementsLookup,
|
|
22
25
|
} from '@teleporthq/teleport-types'
|
|
26
|
+
import { join, relative } from 'path'
|
|
23
27
|
import { HASTBuilders, HASTUtils } from '@teleporthq/teleport-plugin-common'
|
|
24
28
|
import { StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
|
|
25
29
|
import { staticNode } from '@teleporthq/teleport-uidl-builders'
|
|
26
30
|
import { createCSSPlugin } from '@teleporthq/teleport-plugin-css'
|
|
31
|
+
import { generateUniqueKeys, createNodesLookup } from '@teleporthq/teleport-uidl-resolver'
|
|
27
32
|
import { DEFAULT_COMPONENT_CHUNK_NAME } from './constants'
|
|
28
33
|
|
|
34
|
+
const isValidURL = (url: string) => {
|
|
35
|
+
try {
|
|
36
|
+
/* tslint:disable:no-unused-expression */
|
|
37
|
+
new URL(url)
|
|
38
|
+
return true
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const addNodeToLookup = (
|
|
45
|
+
node: UIDLElementNode,
|
|
46
|
+
tag: HastNode | HastText,
|
|
47
|
+
nodesLoookup: Record<string, HastNode | HastText>,
|
|
48
|
+
hierarchy: string[] = []
|
|
49
|
+
) => {
|
|
50
|
+
// In html code-generation we combine the nodes of the component that is being consumed with the current component.
|
|
51
|
+
// As html can't load the component at runtime like react or any other frameworks. So, we merge the component as a standalone
|
|
52
|
+
// component in the current component.
|
|
53
|
+
if (nodesLoookup[node.content.key]) {
|
|
54
|
+
throw new HTMLComponentGeneratorError(
|
|
55
|
+
`\n${hierarchy.join(' -> ')} \n
|
|
56
|
+
Duplicate key found in nodesLookup: ${node.content.key} \n
|
|
57
|
+
|
|
58
|
+
A node with the same key already exists\n
|
|
59
|
+
Received \n\n ${JSON.stringify(tag)}\n ${JSON.stringify(node)}
|
|
60
|
+
Existing \n\n ${JSON.stringify(nodesLoookup[node.content.key])} \n\n`
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
nodesLoookup[node.content.key] = tag
|
|
65
|
+
}
|
|
66
|
+
|
|
29
67
|
type NodeToHTML<NodeType, ReturnType> = (
|
|
30
68
|
node: NodeType,
|
|
31
|
-
|
|
69
|
+
componentName: string,
|
|
70
|
+
nodesLookup: Record<string, HastNode | HastText>,
|
|
32
71
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
33
72
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
34
73
|
subComponentOptions: {
|
|
35
74
|
externals: Record<string, ComponentUIDL>
|
|
36
75
|
plugins: ComponentPlugin[]
|
|
37
76
|
},
|
|
38
|
-
routeDefinitions: UIDLRouteDefinitions,
|
|
39
77
|
structure: {
|
|
40
78
|
chunks: ChunkDefinition[]
|
|
41
79
|
dependencies: Record<string, UIDLDependency>
|
|
42
80
|
options: GeneratorOptions
|
|
81
|
+
outputOptions: UIDLComponentOutputOptions
|
|
43
82
|
}
|
|
44
83
|
) => ReturnType
|
|
45
84
|
|
|
46
|
-
export const
|
|
85
|
+
export const generateHtmlSyntax: NodeToHTML<UIDLNode, Promise<HastNode | HastText>> = async (
|
|
47
86
|
node,
|
|
48
|
-
|
|
87
|
+
compName,
|
|
88
|
+
nodesLookup,
|
|
49
89
|
propDefinitions,
|
|
50
90
|
stateDefinitions,
|
|
51
91
|
subComponentOptions,
|
|
52
|
-
routeDefinitions,
|
|
53
92
|
structure
|
|
54
93
|
) => {
|
|
55
94
|
switch (node.type) {
|
|
@@ -64,26 +103,28 @@ export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
|
|
|
64
103
|
return HASTBuilders.createHTMLNode(node.type)
|
|
65
104
|
|
|
66
105
|
case 'element':
|
|
67
|
-
|
|
106
|
+
const elementNode = await generateElementNode(
|
|
68
107
|
node,
|
|
69
|
-
|
|
108
|
+
compName,
|
|
109
|
+
nodesLookup,
|
|
70
110
|
propDefinitions,
|
|
71
111
|
stateDefinitions,
|
|
72
112
|
subComponentOptions,
|
|
73
|
-
routeDefinitions,
|
|
74
113
|
structure
|
|
75
114
|
)
|
|
115
|
+
return elementNode
|
|
76
116
|
|
|
77
117
|
case 'dynamic':
|
|
78
|
-
|
|
118
|
+
const dynamicNode = await generateDynamicNode(
|
|
79
119
|
node,
|
|
80
|
-
|
|
120
|
+
compName,
|
|
121
|
+
nodesLookup,
|
|
81
122
|
propDefinitions,
|
|
82
123
|
stateDefinitions,
|
|
83
124
|
subComponentOptions,
|
|
84
|
-
routeDefinitions,
|
|
85
125
|
structure
|
|
86
126
|
)
|
|
127
|
+
return dynamicNode
|
|
87
128
|
|
|
88
129
|
default:
|
|
89
130
|
throw new HTMLComponentGeneratorError(
|
|
@@ -96,13 +137,13 @@ export const generateHtmlSynatx: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
|
|
|
96
137
|
}
|
|
97
138
|
}
|
|
98
139
|
|
|
99
|
-
const
|
|
140
|
+
const generateElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastText>> = async (
|
|
100
141
|
node,
|
|
101
|
-
|
|
142
|
+
compName,
|
|
143
|
+
nodesLookup,
|
|
102
144
|
propDefinitions,
|
|
103
145
|
stateDefinitions,
|
|
104
146
|
subComponentOptions,
|
|
105
|
-
routeDefinitions,
|
|
106
147
|
structure
|
|
107
148
|
) => {
|
|
108
149
|
const {
|
|
@@ -112,45 +153,48 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
|
|
|
112
153
|
style = {},
|
|
113
154
|
referencedStyles = {},
|
|
114
155
|
dependency,
|
|
115
|
-
key,
|
|
116
156
|
} = node.content
|
|
117
|
-
|
|
118
|
-
const elementNode = HASTBuilders.createHTMLNode(elementType)
|
|
119
|
-
templatesLookUp[key] = elementNode
|
|
120
|
-
|
|
121
157
|
const { dependencies } = structure
|
|
122
158
|
if (dependency && (dependency as UIDLDependency)?.type !== 'local') {
|
|
123
159
|
dependencies[dependency.path] = dependency
|
|
124
160
|
}
|
|
125
161
|
|
|
126
162
|
if (dependency && (dependency as UIDLDependency)?.type === 'local') {
|
|
163
|
+
if (nodesLookup[node.content.key]) {
|
|
164
|
+
return nodesLookup[node.content.key]
|
|
165
|
+
}
|
|
166
|
+
|
|
127
167
|
const compTag = await generateComponentContent(
|
|
128
168
|
node,
|
|
169
|
+
compName,
|
|
170
|
+
nodesLookup,
|
|
129
171
|
propDefinitions,
|
|
130
172
|
stateDefinitions,
|
|
131
173
|
subComponentOptions,
|
|
132
|
-
routeDefinitions,
|
|
133
174
|
structure
|
|
134
175
|
)
|
|
176
|
+
|
|
177
|
+
if ('tagName' in compTag) {
|
|
178
|
+
compTag.children.unshift(HASTBuilders.createComment(`${node.content.semanticType} component`))
|
|
179
|
+
}
|
|
180
|
+
|
|
135
181
|
return compTag
|
|
136
182
|
}
|
|
137
183
|
|
|
184
|
+
const elementNode = HASTBuilders.createHTMLNode(elementType)
|
|
185
|
+
|
|
138
186
|
if (children) {
|
|
139
187
|
for (const child of children) {
|
|
140
|
-
const childTag = await
|
|
188
|
+
const childTag = await generateHtmlSyntax(
|
|
141
189
|
child,
|
|
142
|
-
|
|
190
|
+
compName,
|
|
191
|
+
nodesLookup,
|
|
143
192
|
propDefinitions,
|
|
144
193
|
stateDefinitions,
|
|
145
194
|
subComponentOptions,
|
|
146
|
-
routeDefinitions,
|
|
147
195
|
structure
|
|
148
196
|
)
|
|
149
197
|
|
|
150
|
-
if (!childTag) {
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
|
|
154
198
|
if (typeof childTag === 'string') {
|
|
155
199
|
HASTUtils.addTextNode(elementNode, childTag)
|
|
156
200
|
} else {
|
|
@@ -164,7 +208,6 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
|
|
|
164
208
|
const refStyle = referencedStyles[styleRef]
|
|
165
209
|
if (refStyle.content.mapType === 'inlined') {
|
|
166
210
|
handleStyles(node, refStyle.content.styles, propDefinitions, stateDefinitions)
|
|
167
|
-
return
|
|
168
211
|
}
|
|
169
212
|
})
|
|
170
213
|
}
|
|
@@ -173,43 +216,68 @@ const generatElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTex
|
|
|
173
216
|
handleStyles(node, style, propDefinitions, stateDefinitions)
|
|
174
217
|
}
|
|
175
218
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
219
|
+
handleAttributes(
|
|
220
|
+
elementType,
|
|
221
|
+
elementNode,
|
|
222
|
+
attrs,
|
|
223
|
+
propDefinitions,
|
|
224
|
+
stateDefinitions,
|
|
225
|
+
structure.options.projectRouteDefinition,
|
|
226
|
+
structure.outputOptions
|
|
227
|
+
)
|
|
179
228
|
|
|
229
|
+
addNodeToLookup(node, elementNode, nodesLookup, [compName])
|
|
180
230
|
return elementNode
|
|
181
231
|
}
|
|
182
232
|
|
|
233
|
+
const createLookupTable = (
|
|
234
|
+
component: ComponentUIDL,
|
|
235
|
+
nodesLookup: Record<string, HastNode | HastText>
|
|
236
|
+
): ElementsLookup => {
|
|
237
|
+
const lookup: ElementsLookup = {}
|
|
238
|
+
for (const node of Object.keys(nodesLookup)) {
|
|
239
|
+
lookup[node] = {
|
|
240
|
+
count: 1,
|
|
241
|
+
nextKey: '1',
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
createNodesLookup(component, lookup)
|
|
245
|
+
return lookup
|
|
246
|
+
}
|
|
247
|
+
|
|
183
248
|
const generateComponentContent = async (
|
|
184
249
|
node: UIDLElementNode,
|
|
250
|
+
compName: string,
|
|
251
|
+
nodesLookup: Record<string, HastNode | HastText>,
|
|
185
252
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
186
253
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
187
254
|
subComponentOptions: {
|
|
188
255
|
externals: Record<string, ComponentUIDL>
|
|
189
256
|
plugins: ComponentPlugin[]
|
|
190
257
|
},
|
|
191
|
-
routeDefinitions: UIDLRouteDefinitions,
|
|
192
258
|
structure: {
|
|
193
259
|
chunks: ChunkDefinition[]
|
|
194
260
|
dependencies: Record<string, UIDLDependency>
|
|
195
261
|
options: GeneratorOptions
|
|
262
|
+
outputOptions: UIDLComponentOutputOptions
|
|
196
263
|
}
|
|
197
264
|
) => {
|
|
198
265
|
const { externals, plugins } = subComponentOptions
|
|
199
|
-
const { elementType, attrs = {},
|
|
266
|
+
const { elementType, attrs = {}, children = [] } = node.content
|
|
200
267
|
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)}`)
|
|
268
|
+
// "Component" will not exist when generating a component because the resolver checks for illegal class names
|
|
269
|
+
const componentName = elementType === 'Component' ? 'AppComponent' : elementType
|
|
270
|
+
const component = externals[componentName]
|
|
271
|
+
if (component === undefined) {
|
|
272
|
+
throw new HTMLComponentGeneratorError(`${componentName} is missing from externals object`)
|
|
208
273
|
}
|
|
209
274
|
|
|
275
|
+
const componentClone = UIDLUtils.cloneObject<ComponentUIDL>(component)
|
|
276
|
+
|
|
277
|
+
let compHasSlots: boolean = false
|
|
210
278
|
if (children.length) {
|
|
211
279
|
compHasSlots = true
|
|
212
|
-
UIDLUtils.traverseNodes(
|
|
280
|
+
UIDLUtils.traverseNodes(componentClone.node, (childNode, parentNode) => {
|
|
213
281
|
if (childNode.type === 'slot' && parentNode.type === 'element') {
|
|
214
282
|
const nonSlotNodes = parentNode.content?.children?.filter((n) => n.type !== 'slot')
|
|
215
283
|
parentNode.content.children = [
|
|
@@ -219,6 +287,7 @@ const generateComponentContent = async (
|
|
|
219
287
|
content: {
|
|
220
288
|
key: 'custom-slot',
|
|
221
289
|
elementType: 'slot',
|
|
290
|
+
name: componentClone.name + 'slot',
|
|
222
291
|
style: {
|
|
223
292
|
display: {
|
|
224
293
|
type: 'static',
|
|
@@ -238,25 +307,38 @@ const generateComponentContent = async (
|
|
|
238
307
|
node.content.children = []
|
|
239
308
|
}
|
|
240
309
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
310
|
+
// In UIDL, we define only the link between a component and a page.
|
|
311
|
+
// We define this link using the UIDLLocalDependency approach.
|
|
312
|
+
// So, during the page resolution step, where we ideally generate the unique keys for the components.
|
|
313
|
+
// We can't generate the unique keys for the components because we don't have the full UIDL of the component.
|
|
314
|
+
// When we are using components in a page, the `addExternalComponents` step of the
|
|
315
|
+
// html-component-generator will add the full UIDL of the component to the externals object after resolving them.
|
|
316
|
+
// But when a component is used multiple number of times, we are basically using the same nodes again and again.
|
|
317
|
+
// Which indivates duplication. So, we create a lookup table of all the nodes present with us in the page
|
|
318
|
+
// And then pass it to the component to avoid any coilissions.
|
|
319
|
+
const lookupTableForCurrentPage = createLookupTable(componentClone, nodesLookup)
|
|
320
|
+
generateUniqueKeys(componentClone, lookupTableForCurrentPage)
|
|
321
|
+
|
|
322
|
+
// We are combining props of the current component
|
|
323
|
+
// with props of the component that we need to generate.
|
|
324
|
+
// Refer to line 309, for element props. We either pick from the attr of the current instance of component
|
|
325
|
+
// or from the propDefinitions of the component that we are generating.
|
|
326
|
+
// We don't need to keep passing the props of the current component to the child component and so on
|
|
327
|
+
// for the case of element nodes in attributes or propDefinitions.
|
|
328
|
+
const combinedProps: Record<string, UIDLPropDefinition> = {
|
|
329
|
+
...Object.keys(propDefinitions).reduce<Record<string, UIDLPropDefinition>>(
|
|
330
|
+
(acc: Record<string, UIDLPropDefinition>, propKey) => {
|
|
331
|
+
if (propDefinitions[propKey]?.type === 'element') {
|
|
332
|
+
return acc
|
|
249
333
|
}
|
|
250
|
-
|
|
251
|
-
acc
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
const combinedStates = { ...stateDefinitions, ...(comp?.stateDefinitions || {}) }
|
|
334
|
+
acc[propKey] = propDefinitions[propKey]
|
|
335
|
+
return acc
|
|
336
|
+
},
|
|
337
|
+
{}
|
|
338
|
+
),
|
|
339
|
+
...(componentClone?.propDefinitions || {}),
|
|
340
|
+
}
|
|
341
|
+
const combinedStates = { ...stateDefinitions, ...(componentClone?.stateDefinitions || {}) }
|
|
260
342
|
const statesForInstance = Object.keys(combinedStates).reduce(
|
|
261
343
|
(acc: Record<string, UIDLStateDefinition>, propKey) => {
|
|
262
344
|
if (attrs[propKey]) {
|
|
@@ -273,43 +355,109 @@ const generateComponentContent = async (
|
|
|
273
355
|
{}
|
|
274
356
|
)
|
|
275
357
|
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
358
|
+
const propsForInstance: Record<string, UIDLPropDefinition> = {}
|
|
359
|
+
// this is where we check if the component we are conusming is actually passing any props to the instance.
|
|
360
|
+
// 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
|
|
361
|
+
// the component instance that we are using here.
|
|
362
|
+
for (const propKey of Object.keys(combinedProps)) {
|
|
363
|
+
const prop = attrs[propKey]
|
|
364
|
+
if (prop?.type === 'element') {
|
|
365
|
+
propsForInstance[propKey] = {
|
|
366
|
+
...combinedProps[propKey],
|
|
367
|
+
defaultValue: attrs[propKey],
|
|
368
|
+
}
|
|
369
|
+
await generateHtmlSyntax(
|
|
370
|
+
attrs[propKey] as UIDLElementNode,
|
|
371
|
+
component.name,
|
|
372
|
+
nodesLookup,
|
|
373
|
+
propDefinitions,
|
|
374
|
+
stateDefinitions,
|
|
375
|
+
subComponentOptions,
|
|
376
|
+
structure
|
|
377
|
+
)
|
|
378
|
+
} else if (prop?.type === 'dynamic') {
|
|
379
|
+
// When we are using a component instance in a component and the attribute
|
|
380
|
+
// that is passed to the component is of dynamic reference.
|
|
381
|
+
// If means, the component is redirecting the prop that is received to the prop of the component that it is consuming.
|
|
382
|
+
// 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.
|
|
383
|
+
// And similary we do the same for the states.
|
|
384
|
+
switch (prop.content.referenceType) {
|
|
385
|
+
case 'prop':
|
|
386
|
+
propsForInstance[propKey] = combinedProps[propKey]
|
|
387
|
+
break
|
|
388
|
+
case 'state':
|
|
389
|
+
propsForInstance[propKey] = combinedStates[propKey]
|
|
390
|
+
break
|
|
391
|
+
default:
|
|
392
|
+
throw new Error(
|
|
393
|
+
`ReferenceType ${prop.content.referenceType} is not supported in HTML Export.`
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
} else if (prop) {
|
|
397
|
+
propsForInstance[propKey] = {
|
|
398
|
+
...combinedProps[propKey],
|
|
399
|
+
defaultValue: attrs[propKey]?.content || combinedProps[propKey]?.defaultValue,
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
const propFromCurrentComponent = combinedProps[propKey]
|
|
403
|
+
if (propFromCurrentComponent.type === 'element' && propFromCurrentComponent.defaultValue) {
|
|
404
|
+
await generateHtmlSyntax(
|
|
405
|
+
propFromCurrentComponent.defaultValue as UIDLElementNode,
|
|
406
|
+
component.name,
|
|
407
|
+
nodesLookup,
|
|
408
|
+
propDefinitions,
|
|
409
|
+
stateDefinitions,
|
|
410
|
+
subComponentOptions,
|
|
411
|
+
structure
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
propsForInstance[propKey] = propFromCurrentComponent
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
let componentWrapper = StringUtils.camelCaseToDashCase(`${componentName}-wrapper`)
|
|
419
|
+
const isExistingNode = nodesLookup[componentWrapper]
|
|
420
|
+
if (isExistingNode !== undefined) {
|
|
421
|
+
componentWrapper = `${componentWrapper}-${StringUtils.generateRandomString()}`
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const componentInstanceToGenerate: UIDLElementNode = {
|
|
425
|
+
type: 'element',
|
|
426
|
+
content: {
|
|
427
|
+
elementType: componentWrapper,
|
|
428
|
+
key: componentWrapper,
|
|
429
|
+
children: [componentClone.node],
|
|
430
|
+
style: {
|
|
431
|
+
display: {
|
|
432
|
+
type: 'static',
|
|
433
|
+
content: 'contents',
|
|
290
434
|
},
|
|
291
435
|
},
|
|
292
436
|
},
|
|
293
|
-
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const compTag = await generateHtmlSyntax(
|
|
440
|
+
componentInstanceToGenerate,
|
|
441
|
+
component.name,
|
|
442
|
+
nodesLookup,
|
|
294
443
|
propsForInstance,
|
|
295
444
|
statesForInstance,
|
|
296
445
|
subComponentOptions,
|
|
297
|
-
routeDefinitions,
|
|
298
446
|
structure
|
|
299
|
-
)
|
|
447
|
+
)
|
|
300
448
|
|
|
301
449
|
const cssPlugin = createCSSPlugin({
|
|
302
450
|
templateStyle: 'html',
|
|
303
451
|
templateChunkName: DEFAULT_COMPONENT_CHUNK_NAME,
|
|
304
452
|
declareDependency: 'import',
|
|
305
|
-
|
|
306
|
-
chunkName: comp.name,
|
|
453
|
+
chunkName: componentClone.name,
|
|
307
454
|
staticPropReferences: true,
|
|
308
455
|
})
|
|
309
456
|
|
|
310
457
|
const initialStructure: ComponentStructure = {
|
|
311
458
|
uidl: {
|
|
312
|
-
...
|
|
459
|
+
...componentClone,
|
|
460
|
+
node: componentInstanceToGenerate,
|
|
313
461
|
propDefinitions: propsForInstance,
|
|
314
462
|
stateDefinitions: statesForInstance,
|
|
315
463
|
},
|
|
@@ -321,7 +469,7 @@ const generateComponentContent = async (
|
|
|
321
469
|
linkAfter: [],
|
|
322
470
|
content: compTag,
|
|
323
471
|
meta: {
|
|
324
|
-
nodesLookup
|
|
472
|
+
nodesLookup,
|
|
325
473
|
},
|
|
326
474
|
},
|
|
327
475
|
],
|
|
@@ -344,7 +492,7 @@ const generateComponentContent = async (
|
|
|
344
492
|
}
|
|
345
493
|
})
|
|
346
494
|
} else {
|
|
347
|
-
const chunk = chunks.find((item) => item.name ===
|
|
495
|
+
const chunk = chunks.find((item) => item.name === componentClone.name)
|
|
348
496
|
if (!chunk) {
|
|
349
497
|
const styleChunk = result.chunks.find(
|
|
350
498
|
(item: ChunkDefinition) => item.fileType === FileType.CSS
|
|
@@ -356,22 +504,45 @@ const generateComponentContent = async (
|
|
|
356
504
|
}
|
|
357
505
|
}
|
|
358
506
|
|
|
507
|
+
addNodeToLookup(node, compTag, nodesLookup, [compName, component.name])
|
|
359
508
|
return compTag
|
|
360
509
|
}
|
|
361
510
|
|
|
362
|
-
const generateDynamicNode: NodeToHTML<UIDLDynamicReference, HastNode
|
|
511
|
+
const generateDynamicNode: NodeToHTML<UIDLDynamicReference, Promise<HastNode | HastText>> = async (
|
|
363
512
|
node,
|
|
364
|
-
|
|
513
|
+
/* tslint:disable variable-name */
|
|
514
|
+
_compName,
|
|
515
|
+
nodesLookup,
|
|
365
516
|
propDefinitions,
|
|
366
517
|
stateDefinitions
|
|
367
|
-
) => {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
node.content.referenceType === 'prop'
|
|
371
|
-
|
|
372
|
-
|
|
518
|
+
): Promise<HastNode | HastText> => {
|
|
519
|
+
const usedReferenceValue = getValueFromReference(
|
|
520
|
+
node.content.id,
|
|
521
|
+
node.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
if (usedReferenceValue.type === 'element' && usedReferenceValue.defaultValue) {
|
|
525
|
+
const elementNode = usedReferenceValue.defaultValue as UIDLElementNode
|
|
373
526
|
|
|
374
|
-
|
|
527
|
+
if (elementNode.content.key in nodesLookup) {
|
|
528
|
+
return nodesLookup[elementNode.content.key]
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const spanTagWrapper = HASTBuilders.createHTMLNode('span')
|
|
532
|
+
const commentNode = HASTBuilders.createComment(`Content for slot ${node.content.id}`)
|
|
533
|
+
HASTUtils.addChildNode(spanTagWrapper, commentNode)
|
|
534
|
+
return spanTagWrapper
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (usedReferenceValue.type === 'element' && usedReferenceValue.defaultValue === undefined) {
|
|
538
|
+
const spanTagWrapper = HASTBuilders.createHTMLNode('span')
|
|
539
|
+
const commentNode = HASTBuilders.createComment(`Content for slot ${node.content.id}`)
|
|
540
|
+
HASTUtils.addChildNode(spanTagWrapper, commentNode)
|
|
541
|
+
return spanTagWrapper
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const spanTag = HASTBuilders.createHTMLNode('span')
|
|
545
|
+
HASTUtils.addTextNode(spanTag, String(usedReferenceValue.defaultValue))
|
|
375
546
|
return spanTag
|
|
376
547
|
}
|
|
377
548
|
|
|
@@ -384,10 +555,12 @@ const handleStyles = (
|
|
|
384
555
|
Object.keys(styles).forEach((styleKey) => {
|
|
385
556
|
let style: string | UIDLStyleValue = styles[styleKey]
|
|
386
557
|
if (style.type === 'dynamic' && style.content?.referenceType !== 'token') {
|
|
387
|
-
|
|
388
|
-
style
|
|
389
|
-
|
|
390
|
-
|
|
558
|
+
const referencedValue = getValueFromReference(
|
|
559
|
+
style.content.id,
|
|
560
|
+
style.content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
561
|
+
)
|
|
562
|
+
if (referencedValue.type === 'string' || referencedValue.type === 'number') {
|
|
563
|
+
style = String(referencedValue.defaultValue)
|
|
391
564
|
}
|
|
392
565
|
node.content.style[styleKey] = typeof style === 'string' ? staticNode(style) : style
|
|
393
566
|
}
|
|
@@ -395,61 +568,119 @@ const handleStyles = (
|
|
|
395
568
|
}
|
|
396
569
|
|
|
397
570
|
const handleAttributes = (
|
|
571
|
+
elementType: UIDLElement['elementType'],
|
|
398
572
|
htmlNode: HastNode,
|
|
399
573
|
attrs: Record<string, UIDLAttributeValue>,
|
|
400
574
|
propDefinitions: Record<string, UIDLPropDefinition>,
|
|
401
575
|
stateDefinitions: Record<string, UIDLStateDefinition>,
|
|
402
|
-
routeDefinitions: UIDLRouteDefinitions
|
|
576
|
+
routeDefinitions: UIDLRouteDefinitions,
|
|
577
|
+
outputOptions: UIDLComponentOutputOptions
|
|
403
578
|
) => {
|
|
404
|
-
Object.keys(attrs)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
579
|
+
for (const attrKey of Object.keys(attrs)) {
|
|
580
|
+
const attrValue = attrs[attrKey]
|
|
581
|
+
const { type, content } = attrValue
|
|
582
|
+
|
|
583
|
+
switch (type) {
|
|
584
|
+
case 'static': {
|
|
585
|
+
if (attrKey === 'href' && typeof content === 'string' && content.startsWith('/')) {
|
|
586
|
+
let targetLink
|
|
587
|
+
|
|
588
|
+
const targetRoute = (routeDefinitions?.values || []).find(
|
|
589
|
+
(route) => route.pageOptions.navLink === content
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
if (targetRoute) {
|
|
593
|
+
targetLink = targetRoute.pageOptions.navLink
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (!targetRoute && content === '/home') {
|
|
597
|
+
targetLink = '/'
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (!targetLink && !targetRoute) {
|
|
601
|
+
targetLink = content
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const currentPageRoute = join(...(outputOptions?.folderPath || []), './')
|
|
605
|
+
const localPrefix = relative(
|
|
606
|
+
`/${currentPageRoute}`,
|
|
607
|
+
`/${targetLink === '/' ? 'index' : targetLink}`
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, `${localPrefix}.html`)
|
|
611
|
+
break
|
|
612
|
+
}
|
|
424
613
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
attrValue.content
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
614
|
+
if (typeof content === 'boolean') {
|
|
615
|
+
htmlNode.properties[attrKey] = content === true ? 'true' : 'false'
|
|
616
|
+
} else if (typeof content === 'string' || typeof attrValue.content === 'number') {
|
|
617
|
+
let value = StringUtils.encode(String(attrValue.content))
|
|
618
|
+
|
|
619
|
+
/*
|
|
620
|
+
elementType of image is always mapped to img.
|
|
621
|
+
For reference, check `html-mapping` file.
|
|
622
|
+
*/
|
|
623
|
+
if (elementType === 'img' && attrKey === 'src' && !isValidURL(value)) {
|
|
624
|
+
/*
|
|
625
|
+
By default we just prefix all the asset paths with just the
|
|
626
|
+
assetPrefix that is configured in the project. But for `html` generators
|
|
627
|
+
we need to prefix that with the current file location.
|
|
628
|
+
|
|
629
|
+
Because, all the other frameworks have a build setup. which serves all the
|
|
630
|
+
assets from the `public` folder. But in the case of `html` here is how it works
|
|
631
|
+
|
|
632
|
+
We load a file from `index.html` the request for the image goes from
|
|
633
|
+
'...url.../public/...image...'
|
|
634
|
+
If it's a nested url, then the request goes from
|
|
635
|
+
'...url/nested/public/...image..'
|
|
636
|
+
|
|
637
|
+
But the nested folder is available only on the root. With this
|
|
638
|
+
The url changes prefixes to
|
|
639
|
+
|
|
640
|
+
../public/playground_assets/..image.. etc depending on the dept the file is in.
|
|
641
|
+
*/
|
|
642
|
+
value = join(relative(join(...outputOptions.folderPath), './'), value)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, value)
|
|
646
|
+
}
|
|
433
647
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
648
|
+
break
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
case 'dynamic': {
|
|
652
|
+
const value = getValueFromReference(
|
|
653
|
+
content.id,
|
|
654
|
+
content.referenceType === 'prop' ? propDefinitions : stateDefinitions
|
|
655
|
+
)
|
|
438
656
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
657
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, String(value.defaultValue))
|
|
658
|
+
break
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
case 'raw': {
|
|
662
|
+
HASTUtils.addAttributeToNode(htmlNode, attrKey, content)
|
|
663
|
+
break
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
case 'element':
|
|
667
|
+
case 'import':
|
|
668
|
+
case 'expr':
|
|
669
|
+
break
|
|
670
|
+
|
|
671
|
+
default: {
|
|
672
|
+
throw new HTMLComponentGeneratorError(
|
|
673
|
+
`Received ${JSON.stringify(attrValue, null, 2)} \n in handleAttributes for html`
|
|
674
|
+
)
|
|
675
|
+
}
|
|
445
676
|
}
|
|
446
|
-
}
|
|
677
|
+
}
|
|
447
678
|
}
|
|
448
679
|
|
|
449
680
|
const getValueFromReference = (
|
|
450
681
|
key: string,
|
|
451
682
|
definitions: Record<string, UIDLPropDefinition>
|
|
452
|
-
):
|
|
683
|
+
): UIDLPropDefinition | undefined => {
|
|
453
684
|
const usedReferenceValue = definitions[key.includes('.') ? key.split('.')[0] : key]
|
|
454
685
|
|
|
455
686
|
if (!usedReferenceValue) {
|
|
@@ -458,9 +689,9 @@ const getValueFromReference = (
|
|
|
458
689
|
)
|
|
459
690
|
}
|
|
460
691
|
|
|
461
|
-
if (
|
|
692
|
+
if (['string', 'number', 'object', 'element'].includes(usedReferenceValue?.type) === false) {
|
|
462
693
|
throw new HTMLComponentGeneratorError(
|
|
463
|
-
`
|
|
694
|
+
`Attribute is using dynamic value, but received of type ${JSON.stringify(
|
|
464
695
|
usedReferenceValue,
|
|
465
696
|
null,
|
|
466
697
|
2
|
|
@@ -468,9 +699,12 @@ const getValueFromReference = (
|
|
|
468
699
|
)
|
|
469
700
|
}
|
|
470
701
|
|
|
471
|
-
if (
|
|
702
|
+
if (
|
|
703
|
+
usedReferenceValue.type !== 'element' &&
|
|
704
|
+
usedReferenceValue.hasOwnProperty('defaultValue') === false
|
|
705
|
+
) {
|
|
472
706
|
throw new HTMLComponentGeneratorError(
|
|
473
|
-
`
|
|
707
|
+
`Default value is missing from dynamic reference - ${JSON.stringify(
|
|
474
708
|
usedReferenceValue,
|
|
475
709
|
null,
|
|
476
710
|
2
|
|
@@ -478,5 +712,5 @@ const getValueFromReference = (
|
|
|
478
712
|
)
|
|
479
713
|
}
|
|
480
714
|
|
|
481
|
-
return
|
|
715
|
+
return usedReferenceValue
|
|
482
716
|
}
|