Skip to content

Commit

Permalink
(fix): use node-lookups for mapping generated components if used mult…
Browse files Browse the repository at this point in the history
…iple times in html (#932)

* (fix): use node-lookups for mapping generated components if used multiple times in html

* fix bugs with re-using components with html components
  • Loading branch information
JayaKrishnaNamburu authored Sep 4, 2024
1 parent 396edb4 commit c7b2148
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 116 deletions.
15 changes: 6 additions & 9 deletions packages/teleport-plugin-common/src/builders/style-builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,16 @@ export const generateStylesFromStyleSetDefinitions = (

export const setPropValueForCompStyle = (params: {
attrs: Record<string, UIDLAttributeValue>
key: string
jsxNodesLookup: Record<string, types.JSXElement | HastNode>
root: types.JSXElement | HastNode
templateStyle?: 'jsx' | 'html'
getClassName: (str: string) => string
}) => {
const { attrs, jsxNodesLookup, key, templateStyle = 'jsx', getClassName } = params
const { attrs, root, templateStyle = 'jsx', getClassName } = params
Object.keys(attrs)
.filter((attr) => attrs[attr].type === 'comp-style')
.forEach((attr) => {
const compInstanceNode = jsxNodesLookup[key]

if (templateStyle === 'jsx' && isJSXElement(compInstanceNode)) {
compInstanceNode.openingElement?.attributes.forEach((attribute: types.JSXAttribute) => {
if (templateStyle === 'jsx' && isJSXElement(root)) {
root.openingElement?.attributes.forEach((attribute: types.JSXAttribute) => {
if (
attribute.value.type === 'StringLiteral' &&
attribute.value?.value &&
Expand All @@ -230,8 +227,8 @@ export const setPropValueForCompStyle = (params: {
})
}

if (templateStyle === 'html' && isHastElement(compInstanceNode)) {
compInstanceNode.properties[attr] = getClassName(String(compInstanceNode.properties[attr]))
if (templateStyle === 'html' && isHastElement(root)) {
root.properties[attr] = getClassName(String(root.properties[attr]))
}
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createCSSModulesPlugin } from '../src'
import { staticNode, elementNode, component } from '@teleporthq/teleport-uidl-builders'
import { ComponentStructure } from '@teleporthq/teleport-types'
import { createComponentChunk } from './mocks'
import { JSXElement } from '@babel/types'

describe('Component Scoped Styles', () => {
const uidl = component('MYComponent', elementNode('container', {}, [], null, {}), {}, {})
Expand Down Expand Up @@ -85,8 +86,8 @@ describe('Component Scoped Styles', () => {

const { chunks } = await plugin(structure)
const jsxComponent = chunks.find((chunk) => chunk.name === 'jsx-component')
const jsxExpressions =
jsxComponent.meta.nodesLookup.container.openingElement.attributes[0].value.expression
const jsxExpressions = (jsxComponent?.meta?.nodesLookup?.container as unknown as JSXElement)
.openingElement.attributes[0]?.value.expression

expect(jsxExpressions.quasis.length).toBe(4)
expect(jsxExpressions.expressions[0]?.value).toBe('md-8')
Expand Down
6 changes: 3 additions & 3 deletions packages/teleport-plugin-css-modules/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,12 @@ describe('plugin-css-modules', () => {
const jsxChunk = result.chunks.find((chunk) => chunk.fileType === 'js')
const cssFile = result.chunks.find((file) => file.fileType === 'css')

const nodeReference = jsxChunk.meta.nodesLookup.container
const styleAttr = nodeReference.openingElement.attributes[0]
const nodeReference = jsxChunk?.meta?.nodesLookup?.container
const styleAttr = nodeReference?.openingElement?.attributes?.[0]

expect(cssFile).toBeDefined()
expect(jsxChunk).toBeDefined()
expect(nodeReference.openingElement.attributes.length).toBe(1)
expect(nodeReference?.openingElement?.attributes?.length).toBe(1)
expect(styleAttr.value.expression.quasis.length).toBe(3)
expect(styleAttr.value.expression.expressions.length).toBe(2)
})
Expand Down
9 changes: 7 additions & 2 deletions packages/teleport-plugin-css-modules/__tests__/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@ export const createComponentChunk = (elementKey: string = 'container') => {
meta: {
nodesLookup: {
[elementKey]: {
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
name: {
type: 'JSXIdentifier',
name: '',
},
selfClosing: false,
attributes: [],
},
children: [],
},
},
dynamicRefPrefix: {
Expand All @@ -36,12 +41,12 @@ export const createComponentChunk = (elementKey: string = 'container') => {

export const setupPluginStructure = (
elementKey: string = 'container',
styleDefinition: UIDLStyleDefinitions = null
styleDefinition?: UIDLStyleDefinitions
) => {
const style = styleDefinition || {
height: staticNode('100px'),
}
const element = elementNode('container', {}, [], null, style)
const element = elementNode('container', {}, [], undefined, style)
element.content.key = elementKey
const uidlSample = component('CSSModules', element)

Expand Down
14 changes: 7 additions & 7 deletions packages/teleport-plugin-css-modules/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from '@teleporthq/teleport-types'
import { createStyleSheetPlugin } from './style-sheet'
import { generateStyledFromStyleContent } from './utils'
import { isJSXElement } from '@teleporthq/teleport-plugin-common/dist/cjs/utils/ast-utils'

interface CSSModulesConfig {
componentChunkName?: string
Expand Down Expand Up @@ -108,25 +109,24 @@ export const createCSSModulesPlugin: ComponentPluginFactory<CSSModulesConfig> =

const generateStylesForElementNode = (element: UIDLElement) => {
const { style, key, referencedStyles, dependency, attrs = {}, elementType } = element
const jsxTag = astNodesLookup[key] as types.JSXElement
const jsxTag = astNodesLookup[key]
const classNamesToAppend: Set<
types.MemberExpression | types.Identifier | types.StringLiteral
> = new Set()

if (!jsxTag || !isJSXElement(jsxTag)) {
return
}

if (dependency?.type === 'local') {
StyleBuilders.setPropValueForCompStyle({
attrs,
key,
jsxNodesLookup: astNodesLookup,
root: jsxTag,
getClassName: (styleName: string) =>
StringUtils.camelCaseToDashCase(elementType + styleName),
})
}

if (!jsxTag) {
return
}

if (!style && !referencedStyles) {
return
}
Expand Down
19 changes: 6 additions & 13 deletions packages/teleport-plugin-css/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,16 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
dependency,
} = element

const root = jsxNodesLookup[key]
if (!root) {
return
}

// Refer to line 323 all component scoped styles are appended with component name by default
if (dependency?.type === 'local') {
StyleBuilders.setPropValueForCompStyle({
attrs,
key,
jsxNodesLookup,
root,
templateStyle,
// elementType is used here in-order to target the component name that the class is actually defined.
// Here we are appendigng to the node where the component is being called.
Expand All @@ -120,17 +124,6 @@ const createCSSPlugin: ComponentPluginFactory<CSSPluginConfig> = (config) => {
return
}

const root = jsxNodesLookup[key]
if (!root) {
throw new PluginCSS(
`Element \n ${JSON.stringify(
element,
null,
2
)} \n with key ${key} is missing from the template chunk of component ${uidl.name}`
)
}

const className = StringUtils.camelCaseToDashCase(key)

const { staticStyles, dynamicStyles, tokenStyles } =
Expand Down
49 changes: 17 additions & 32 deletions packages/teleport-plugin-html-base-component/src/node-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const isValidURL = (url: string) => {
}

const addNodeToLookup = (
key: string,
node: UIDLElementNode,
tag: HastNode | HastText,
nodesLoookup: Record<string, HastNode | HastText>,
Expand All @@ -50,18 +51,18 @@ const addNodeToLookup = (
// In html code-generation we combine the nodes of the component that is being consumed with the current component.
// As html can't load the component at runtime like react or any other frameworks. So, we merge the component as a standalone
// component in the current component.
if (nodesLoookup[node.content.key]) {
if (nodesLoookup[key]) {
throw new HTMLComponentGeneratorError(
`\n${hierarchy.join(' -> ')} \n
Duplicate key found in nodesLookup: ${node.content.key} \n
A node with the same key already exists\n
Received \n\n ${JSON.stringify(tag)}\n ${JSON.stringify(node)}
Existing \n\n ${JSON.stringify(nodesLoookup[node.content.key])} \n\n`
Existing \n\n ${JSON.stringify(nodesLoookup[key])} \n\n`
)
}

nodesLoookup[node.content.key] = tag
nodesLoookup[key] = tag
}

type NodeToHTML<NodeType, ReturnType> = (
Expand Down Expand Up @@ -126,6 +127,9 @@ export const generateHtmlSyntax: NodeToHTML<UIDLNode, Promise<HastNode | HastTex
)
return dynamicNode

case 'conditional':
return HASTBuilders.createComment('Conditional nodes are not supported in HTML')

default:
throw new HTMLComponentGeneratorError(
`generateHtmlSyntax encountered a node of unsupported type: ${JSON.stringify(
Expand Down Expand Up @@ -155,15 +159,7 @@ const generateElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTe
dependency,
} = node.content
const { dependencies } = structure
if (dependency && (dependency as UIDLDependency)?.type !== 'local') {
dependencies[dependency.path] = dependency
}

if (dependency && (dependency as UIDLDependency)?.type === 'local') {
if (nodesLookup[node.content.key]) {
return nodesLookup[node.content.key]
}

const compTag = await generateComponentContent(
node,
compName,
Expand All @@ -181,6 +177,10 @@ const generateElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTe
return compTag
}

if (dependency && (dependency as UIDLDependency)?.type !== 'local') {
dependencies[dependency.path] = dependency
}

const elementNode = HASTBuilders.createHTMLNode(elementType)

if (children) {
Expand Down Expand Up @@ -226,7 +226,7 @@ const generateElementNode: NodeToHTML<UIDLElementNode, Promise<HastNode | HastTe
structure.outputOptions
)

addNodeToLookup(node, elementNode, nodesLookup, [compName])
addNodeToLookup(node.content.key, node, elementNode, nodesLookup, [compName])
return elementNode
}

Expand Down Expand Up @@ -274,9 +274,7 @@ const generateComponentContent = async (

const componentClone = UIDLUtils.cloneObject<ComponentUIDL>(component)

let compHasSlots: boolean = false
if (children.length) {
compHasSlots = true
UIDLUtils.traverseNodes(componentClone.node, (childNode, parentNode) => {
if (childNode.type === 'slot' && parentNode.type === 'element') {
const nonSlotNodes = parentNode.content?.children?.filter((n) => n.type !== 'slot')
Expand Down Expand Up @@ -485,26 +483,13 @@ const generateComponentContent = async (
Promise.resolve(initialStructure)
)

if (compHasSlots) {
result.chunks.forEach((chunk) => {
if (chunk.fileType === FileType.CSS) {
chunks.push(chunk)
}
})
} else {
const chunk = chunks.find((item) => item.name === componentClone.name)
if (!chunk) {
const styleChunk = result.chunks.find(
(item: ChunkDefinition) => item.fileType === FileType.CSS
)
if (!styleChunk) {
return
}
chunks.push(styleChunk)
result.chunks.forEach((chunk) => {
if (chunk.fileType === FileType.CSS) {
chunks.push(chunk)
}
}
})

addNodeToLookup(node, compTag, nodesLookup, [compName, component.name])
addNodeToLookup(node.content.key, node, compTag, nodesLookup, [compName, component.name])
return compTag
}

Expand Down
66 changes: 38 additions & 28 deletions packages/teleport-plugin-react-jss/__tests__/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
import { ChunkDefinition, FileType, ChunkType } from '@teleporthq/teleport-types'
import * as types from '@babel/types'

export const createComponentChunk = (): ChunkDefinition => ({
name: 'jsx-component',
meta: {
nodesLookup: {
container: {
openingElement: {
name: {
name: '',
},
attributes: [],
},
export const createComponentChunk = (): ChunkDefinition => {
const jsxElement: types.JSXElement = {
type: 'JSXElement',
openingElement: {
type: 'JSXOpeningElement',
name: {
type: 'JSXIdentifier',
name: '',
},
selfClosing: false,
attributes: [],
},
dynamicRefPrefix: {
prop: 'props',
children: [],
}

return {
name: 'jsx-component',
meta: {
nodesLookup: {
container: jsxElement as unknown as Record<string, unknown>,
},
dynamicRefPrefix: {
prop: 'props',
},
},
},
type: ChunkType.AST,
fileType: FileType.JS,
linkAfter: ['import-local'],
content: {
declarations: [
{
init: {
params: [],
body: {
body: [],
type: ChunkType.AST,
fileType: FileType.JS,
linkAfter: ['import-local'],
content: {
declarations: [
{
init: {
params: [],
body: {
body: [],
},
},
},
},
],
},
})
],
},
}
}
Loading

0 comments on commit c7b2148

Please sign in to comment.