Skip to content

Commit 162573b

Browse files
committed
Optimize
1 parent e8195c7 commit 162573b

File tree

10 files changed

+186
-142
lines changed

10 files changed

+186
-142
lines changed

src/codegen/Codegen.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,10 @@ export class Codegen {
156156

157157
getComponentsCodes() {
158158
return Array.from(this.components.values()).map(
159-
({ node, code, variants }) =>
160-
[
161-
getComponentName(node),
162-
renderComponent(getComponentName(node), code, variants),
163-
] as const,
159+
({ node, code, variants }) => {
160+
const name = getComponentName(node)
161+
return [name, renderComponent(name, code, variants)] as const
162+
},
164163
)
165164
}
166165

@@ -525,9 +524,11 @@ export class Codegen {
525524
*/
526525
hasViewportVariant(): boolean {
527526
if (this.node.type !== 'COMPONENT_SET') return false
528-
return Object.keys(
529-
(this.node as ComponentSetNode).componentPropertyDefinitions,
530-
).some((key) => key.toLowerCase() === 'viewport')
527+
for (const key in (this.node as ComponentSetNode)
528+
.componentPropertyDefinitions) {
529+
if (key.toLowerCase() === 'viewport') return true
530+
}
531+
return false
531532
}
532533

533534
/**

src/codegen/props/auto-layout.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1+
import type { NodeContext } from '../types'
12
import { addPx } from '../utils/add-px'
23
import { checkAssetNode } from '../utils/check-asset-node'
34

45
export function getAutoLayoutProps(
56
node: SceneNode,
7+
ctx?: NodeContext,
68
): Record<string, boolean | string | number | undefined | null> | undefined {
79
if (
810
!('inferredAutoLayout' in node) ||
911
!node.inferredAutoLayout ||
1012
node.inferredAutoLayout.layoutMode === 'NONE' ||
11-
checkAssetNode(node)
13+
(ctx ? ctx.isAsset !== null : !!checkAssetNode(node))
1214
)
1315
return
1416
const { layoutMode } = node.inferredAutoLayout
1517
if (layoutMode === 'GRID') return getGridProps(node)
16-
const childrenCount = node.children.filter((c) => c.visible).length
18+
let childrenCount = 0
19+
for (const c of node.children) if (c.visible) childrenCount++
1720
return {
1821
display: {
1922
HORIZONTAL: 'flex',

src/codegen/props/index.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import type { NodeContext } from '../types'
2+
import { checkAssetNode } from '../utils/check-asset-node'
3+
import { getPageNode } from '../utils/get-page-node'
4+
import { isPageRoot } from '../utils/is-page-root'
15
import { perfEnd, perfStart } from '../utils/perf'
26
import { getAutoLayoutProps } from './auto-layout'
37
import { getBackgroundProps } from './background'
@@ -12,14 +16,28 @@ import { getMaxLineProps } from './max-line'
1216
import { getObjectFitProps } from './object-fit'
1317
import { getOverflowProps } from './overflow'
1418
import { getPaddingProps } from './padding'
15-
import { getPositionProps } from './position'
19+
import { canBeAbsolute, getPositionProps } from './position'
1620
import { getReactionProps } from './reaction'
1721
import { getTextAlignProps } from './text-align'
1822
import { getTextShadowProps } from './text-shadow'
1923
import { getTextStrokeProps } from './text-stroke'
2024
import { getTransformProps } from './transform'
2125
import { getVisibilityProps } from './visibility'
2226

27+
export function computeNodeContext(node: SceneNode): NodeContext {
28+
const asset = checkAssetNode(node)
29+
const pageNode = getPageNode(
30+
node as BaseNode & ChildrenMixin,
31+
) as SceneNode | null
32+
const pageRoot = isPageRoot(node)
33+
return {
34+
isAsset: asset,
35+
canBeAbsolute: canBeAbsolute(node),
36+
isPageRoot: pageRoot,
37+
pageNode,
38+
}
39+
}
40+
2341
// Cache getProps() results keyed by node.id to avoid redundant computation.
2442
// Figma returns new JS wrapper objects for the same node on each property access,
2543
// so object-reference keys don't work — node.id is the stable identifier.
@@ -56,6 +74,9 @@ export async function getProps(
5674
const promise = (async () => {
5775
const isText = node.type === 'TEXT'
5876

77+
// Compute cross-cutting node context ONCE for all sync getters that need it.
78+
const ctx = computeNodeContext(node)
79+
5980
// PHASE 1: Fire all async prop getters — initiates Figma IPC calls immediately.
6081
// These return Promises that resolve when IPC completes.
6182
const tBorder = perfStart()
@@ -72,9 +93,9 @@ export async function getProps(
7293
// Compute sync results eagerly; they'll be interleaved in the original merge
7394
// order below to preserve "last-key-wins" semantics.
7495
const tSync = perfStart()
75-
const autoLayoutProps = getAutoLayoutProps(node)
96+
const autoLayoutProps = getAutoLayoutProps(node, ctx)
7697
const minMaxProps = getMinMaxProps(node)
77-
const layoutProps = getLayoutProps(node)
98+
const layoutProps = getLayoutProps(node, ctx)
7899
const borderRadiusProps = getBorderRadiusProps(node)
79100
const blendProps = getBlendProps(node)
80101
const paddingProps = getPaddingProps(node)
@@ -85,9 +106,9 @@ export async function getProps(
85106
const tEffect = perfStart()
86107
const effectProps = getEffectProps(node)
87108
perfEnd('getProps.effect', tEffect)
88-
const positionProps = getPositionProps(node)
109+
const positionProps = getPositionProps(node, ctx)
89110
const gridChildProps = getGridChildProps(node)
90-
const transformProps = getTransformProps(node)
111+
const transformProps = getTransformProps(node, ctx)
91112
const overflowProps = getOverflowProps(node)
92113
const tTextShadow = perfStart()
93114
const textShadowProps = isText ? getTextShadowProps(node) : undefined

src/codegen/props/layout.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { NodeContext } from '../types'
12
import { addPx } from '../utils/add-px'
23
import { checkAssetNode } from '../utils/check-asset-node'
34
import { getPageNode } from '../utils/get-page-node'
@@ -17,8 +18,9 @@ export function getMinMaxProps(
1718

1819
export function getLayoutProps(
1920
node: SceneNode,
21+
ctx?: NodeContext,
2022
): Record<string, boolean | string | number | undefined | null> {
21-
const ret = _getLayoutProps(node)
23+
const ret = _getLayoutProps(node, ctx)
2224
if (ret.w && ret.h === ret.w) {
2325
ret.boxSize = ret.w
2426
delete ret.w
@@ -45,15 +47,16 @@ function _getTextLayoutProps(
4547

4648
function _getLayoutProps(
4749
node: SceneNode,
50+
ctx?: NodeContext,
4851
): Record<string, boolean | string | number | undefined | null> {
49-
if (canBeAbsolute(node)) {
52+
if (ctx ? ctx.canBeAbsolute : canBeAbsolute(node)) {
5053
return {
5154
w:
5255
node.type === 'TEXT' ||
5356
(node.parent &&
5457
'width' in node.parent &&
5558
node.parent.width > node.width)
56-
? checkAssetNode(node) ||
59+
? (ctx ? ctx.isAsset !== null : !!checkAssetNode(node)) ||
5760
('children' in node && node.children.length === 0)
5861
? addPx(node.width)
5962
: undefined
@@ -77,7 +80,9 @@ function _getLayoutProps(
7780
}
7881
const aspectRatio =
7982
'targetAspectRatio' in node ? node.targetAspectRatio : undefined
80-
const rootNode = getPageNode(node as BaseNode & ChildrenMixin)
83+
const rootNode = ctx
84+
? ctx.pageNode
85+
: getPageNode(node as BaseNode & ChildrenMixin)
8186

8287
return {
8388
aspectRatio: aspectRatio

src/codegen/props/position.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { NodeContext } from '../types'
12
import { addPx } from '../utils/add-px'
23
import { checkAssetNode } from '../utils/check-asset-node'
34
import { isPageRoot } from '../utils/is-page-root'
@@ -25,8 +26,13 @@ export function canBeAbsolute(node: SceneNode): boolean {
2526

2627
export function getPositionProps(
2728
node: SceneNode,
29+
ctx?: NodeContext,
2830
): Record<string, string | undefined> | undefined {
29-
if ('parent' in node && node.parent && canBeAbsolute(node)) {
31+
if (
32+
'parent' in node &&
33+
node.parent &&
34+
(ctx ? ctx.canBeAbsolute : canBeAbsolute(node))
35+
) {
3036
const constraints =
3137
'constraints' in node
3238
? node.constraints
@@ -105,7 +111,7 @@ export function getPositionProps(
105111
}
106112
if (
107113
'children' in node &&
108-
!checkAssetNode(node) &&
114+
(ctx ? ctx.isAsset === null : !checkAssetNode(node)) &&
109115
(node.children.some(
110116
(child) =>
111117
'layoutPositioning' in child && child.layoutPositioning === 'ABSOLUTE',
@@ -115,7 +121,7 @@ export function getPositionProps(
115121
(child) =>
116122
'layoutPositioning' in child && child.layoutPositioning === 'AUTO',
117123
))) &&
118-
!isPageRoot(node)
124+
!(ctx ? ctx.isPageRoot : isPageRoot(node))
119125
) {
120126
return {
121127
pos: 'relative',

src/codegen/props/transform.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
import type { NodeContext } from '../types'
12
import { fmtPct } from '../utils/fmtPct'
23
import { canBeAbsolute } from './position'
34

45
export function getTransformProps(
56
node: SceneNode,
7+
ctx?: NodeContext,
68
): Record<string, boolean | string | number | undefined | null> | undefined {
79
if ('rotation' in node && Math.abs(node.rotation) > 0.01)
810
return {
911
transform: `rotate(${fmtPct(-node.rotation)}deg)`,
10-
transformOrigin: canBeAbsolute(node) ? 'top left' : undefined,
12+
transformOrigin: (ctx ? ctx.canBeAbsolute : canBeAbsolute(node))
13+
? 'top left'
14+
: undefined,
1115
}
1216
}

src/codegen/render/index.ts

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,48 +25,48 @@ export function renderNode(
2525
): string {
2626
const propsString = propsToString(filterAndTransformProps(component, props))
2727
const hasChildren = childrenCodes.length > 0
28-
const tail = hasChildren ? `${space(deps)}</${component}>` : ''
2928
const multiProps = propsString.includes('\n')
30-
return [
31-
`${space(deps)}<${component}${propsString ? (multiProps ? `\n${paddingLeftMultiline(propsString, deps + 1)}` : ` ${propsString}`) : ''}${
32-
(multiProps ? `\n${space(deps)}` : !hasChildren ? ' ' : '') +
33-
(hasChildren ? '>' : '/>')
34-
}`,
35-
hasChildren
36-
? childrenCodes
37-
.map((child) => paddingLeftMultiline(child, deps + 1))
38-
.join('\n')
39-
: '',
40-
tail,
41-
]
42-
.filter(Boolean)
43-
.join('\n')
29+
let result = `${space(deps)}<${component}${propsString ? (multiProps ? `\n${paddingLeftMultiline(propsString, deps + 1)}` : ` ${propsString}`) : ''}${
30+
(multiProps ? `\n${space(deps)}` : !hasChildren ? ' ' : '') +
31+
(hasChildren ? '>' : '/>')
32+
}`
33+
if (hasChildren) {
34+
const children = childrenCodes
35+
.map((child) => paddingLeftMultiline(child, deps + 1))
36+
.join('\n')
37+
result += `\n${children}\n${space(deps)}</${component}>`
38+
}
39+
return result
4440
}
4541

4642
export function renderComponent(
4743
component: string,
4844
code: string,
4945
variants: Record<string, string>,
5046
) {
51-
// Filter out effect variant (treated as reserved property like viewport)
52-
const filteredVariants = Object.fromEntries(
53-
Object.entries(variants).filter(([key]) => key.toLowerCase() !== 'effect'),
54-
)
55-
const hasVariants = Object.keys(filteredVariants).length > 0
56-
const interfaceCode = hasVariants
57-
? `export interface ${component}Props {
58-
${Object.entries(filteredVariants)
59-
.map(([key, value]) => {
47+
// Single pass: collect variant entries, skipping 'effect' (reserved key)
48+
const variantEntries: [string, string][] = []
49+
for (const key in variants) {
50+
if (key.toLowerCase() !== 'effect')
51+
variantEntries.push([key, variants[key]])
52+
}
53+
if (variantEntries.length === 0) {
54+
return `export function ${component}() {
55+
return ${wrapReturnStatement(code, 1)}
56+
}`
57+
}
58+
const interfaceLines: string[] = []
59+
const keys: string[] = []
60+
for (const [key, value] of variantEntries) {
6061
const optional = value === 'boolean' ? '?' : ''
61-
return ` ${key}${optional}: ${value}`
62-
})
63-
.join('\n')}
64-
}\n\n`
65-
: ''
66-
const propsParam = hasVariants
67-
? `{ ${Object.keys(filteredVariants).join(', ')} }: ${component}Props`
68-
: ''
69-
return `${interfaceCode}export function ${component}(${propsParam}) {
62+
interfaceLines.push(` ${key}${optional}: ${value}`)
63+
keys.push(key)
64+
}
65+
return `export interface ${component}Props {
66+
${interfaceLines.join('\n')}
67+
}
68+
69+
export function ${component}({ ${keys.join(', ')} }: ${component}Props) {
7070
return ${wrapReturnStatement(code, 1)}
7171
}`
7272
}

0 commit comments

Comments
 (0)