Skip to content

Commit e11c4db

Browse files
committed
Support PascalCase instance names for Roblox elements
Updated the transformer to map lowercase tag names to their correct PascalCase Roblox instance names when generating static elements. This ensures that elements like 'textlabel' are emitted as 'TextLabel', improving compatibility and correctness in generated Luau code. Refactored codegen, context, and bridge logic to support this mapping.
1 parent fb1ed07 commit e11c4db

File tree

10 files changed

+54
-37
lines changed

10 files changed

+54
-37
lines changed

demo/out/benchmark/massive-list.benchmark.luau

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ local function OptimizedListItem(_param)
5555
TextColor3 = Color3.fromRGB(200, 200, 200),
5656
BackgroundTransparency = 1,
5757
}))
58-
end, { isActive, name, value, id }, "dynamic_frame_dk9zhdi1f")
58+
end, { isActive, name, value, id }, "dynamic_frame_cv7coskeu")
5959
end
6060
local function MassiveListBenchmark()
6161
local itemCount, setItemCount = useState(1000)
@@ -166,7 +166,7 @@ local function MassiveListBenchmark()
166166
end
167167
-- ▲ ReadonlyArray.map ▲
168168
return React.createElement("scrollingframe", _exp, _exp_1, _newValue)
169-
end, { itemCount, items, ListComponent }, "dynamic_scrollingframe_em72aoow9"))
169+
end, { itemCount, items, ListComponent }, "dynamic_scrollingframe_fnm6x4h2j"))
170170
end
171171
-- Alternative ways to use @undecillion
172172
-- Comment-style for arrow functions

demo/out/benchmark/optimized-list.benchmark.luau

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,23 @@ local React = _react
88
local useState = _react.useState
99
local useEffect = _react.useEffect
1010
-- Static declarations - extracted from render functions for optimal performance
11-
local STATIC_PROPS_TEXTBUTTON_xvu9b2 = {
11+
local STATIC_PROPS_TEXTBUTTON_nhab4l = {
1212
Text = "Toggle First Item",
1313
Size = UDim2.new(0.25, 0, 0, 25),
1414
Position = UDim2.new(0.5, 0, 0, 30),
1515
BackgroundColor3 = Color3.fromRGB(150, 100, 100),
1616
TextColor3 = Color3.fromRGB(255, 255, 255),
1717
}
18-
local STATIC_PROPS_TEXTLABEL_1v96ge = {
18+
local STATIC_PROPS_TEXTLABEL_899va2 = {
1919
Text = "Click individual items to toggle them. Notice how Decillion only updates changed items.",
2020
Size = UDim2.new(1, 0, 0, 50),
2121
Position = UDim2.new(0, 0, 0, 60),
2222
TextColor3 = Color3.fromRGB(200, 200, 200),
2323
BackgroundTransparency = 1,
2424
TextWrapped = true,
2525
}
26-
local STATIC_ELEMENT_TEXTBUTTON_995je9 = createStaticElement("textbutton", STATIC_PROPS_TEXTBUTTON_xvu9b2)
27-
local STATIC_ELEMENT_TEXTLABEL_wu7zte = createStaticElement("textlabel", STATIC_PROPS_TEXTLABEL_1v96ge)
26+
local STATIC_ELEMENT_TEXTBUTTON_5e6h84 = createStaticElement("TextButton", STATIC_PROPS_TEXTBUTTON_nhab4l)
27+
local STATIC_ELEMENT_TEXTLABEL_1x7js1 = createStaticElement("TextLabel", STATIC_PROPS_TEXTLABEL_899va2)
2828
--[[
2929
*
3030
* Advanced List Optimization Benchmark
@@ -59,7 +59,7 @@ local function TraditionalListItem(_param)
5959
end,
6060
},
6161
}))
62-
end, { isActive, name, value }, "dynamic_frame_h97dqb7cy")
62+
end, { isActive, name, value }, "dynamic_frame_gu0vp1zvp")
6363
end
6464
-- Decillion optimized approach
6565
local function OptimizedListItem(_param)
@@ -89,7 +89,7 @@ local function OptimizedListItem(_param)
8989
end,
9090
},
9191
}))
92-
end, { isActive, name, value }, "dynamic_frame_mw530qa01")
92+
end, { isActive, name, value }, "dynamic_frame_fqmtyi0jd")
9393
end
9494
local function OptimizedListBenchmark()
9595
local itemCount, setItemCount = useState(100)
@@ -200,7 +200,7 @@ local function OptimizedListBenchmark()
200200
Event = {
201201
MouseButton1Click = triggerFullRerender,
202202
},
203-
}), STATIC_ELEMENT_TEXTBUTTON_995je9, React.createElement("textbutton", {
203+
}), STATIC_ELEMENT_TEXTBUTTON_5e6h84, React.createElement("textbutton", {
204204
Text = `Items: {itemCount}`,
205205
Size = UDim2.new(0.25, 0, 0, 25),
206206
Position = UDim2.new(0.75, 0, 0, 30),
@@ -211,7 +211,7 @@ local function OptimizedListBenchmark()
211211
return setItemCount(if itemCount == 100 then 500 elseif itemCount == 500 then 1000 else 100)
212212
end,
213213
},
214-
}), STATIC_ELEMENT_TEXTLABEL_wu7zte), useMemoizedBlock(function(itemCount, items, ListComponent)
214+
}), STATIC_ELEMENT_TEXTLABEL_1x7js1), useMemoizedBlock(function(itemCount, items, ListComponent)
215215
local _exp = {
216216
Size = UDim2.new(1, 0, 1, -120),
217217
Position = UDim2.new(0, 0, 0, 120),
@@ -240,7 +240,7 @@ local function OptimizedListBenchmark()
240240
end
241241
-- ▲ ReadonlyArray.map ▲
242242
return React.createElement("scrollingframe", _exp, _exp_1, _newValue)
243-
end, { itemCount, items, ListComponent }, "dynamic_scrollingframe_2t8lkrlmn"))
243+
end, { itemCount, items, ListComponent }, "dynamic_scrollingframe_smspxz21k"))
244244
end
245245
return {
246246
default = OptimizedListBenchmark,

demo/out/benchmark/simple-performance.benchmark.luau

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ local _react = TS.import(script, game:GetService("ReplicatedStorage"), "rbxts_in
77
local React = _react
88
local useState = _react.useState
99
-- Static declarations - extracted from render functions for optimal performance
10-
local STATIC_PROPS_TEXTLABEL_v53xua = {
10+
local STATIC_PROPS_TEXTLABEL_h5h0qv = {
1111
Text = "Check console for render times. Optimized should be significantly faster!",
1212
Size = UDim2.new(1, 0, 0, 20),
1313
Position = UDim2.new(0, 0, 0, 55),
1414
TextColor3 = Color3.fromRGB(200, 200, 200),
1515
BackgroundTransparency = 1,
1616
}
17-
local STATIC_ELEMENT_TEXTLABEL_3bmyd4 = createStaticElement("textlabel", STATIC_PROPS_TEXTLABEL_v53xua)
17+
local STATIC_ELEMENT_TEXTLABEL_zey1ls = createStaticElement("TextLabel", STATIC_PROPS_TEXTLABEL_h5h0qv)
1818
--[[
1919
*
2020
* Simple Performance Test: Shows the clear difference between optimized and unoptimized rendering
@@ -51,7 +51,7 @@ local function OptimizedItem(_param)
5151
TextColor3 = Color3.fromRGB(255, 255, 255),
5252
BackgroundTransparency = 1,
5353
}))
54-
end, { isHighlighted, text }, "dynamic_frame_naa0t64o9")
54+
end, { isHighlighted, text }, "dynamic_frame_exj728egs")
5555
end
5656
local function SimplePerformanceTest()
5757
local itemCount, setItemCount = useState(500)
@@ -134,7 +134,7 @@ local function SimplePerformanceTest()
134134
return setItemCount(if itemCount == 100 then 500 elseif itemCount == 500 then 1000 else 100)
135135
end,
136136
},
137-
}), STATIC_ELEMENT_TEXTLABEL_3bmyd4), useMemoizedBlock(function(itemCount, items, ItemComponent)
137+
}), STATIC_ELEMENT_TEXTLABEL_zey1ls), useMemoizedBlock(function(itemCount, items, ItemComponent)
138138
local _exp = {
139139
Size = UDim2.new(1, 0, 1, -80),
140140
Position = UDim2.new(0, 0, 0, 80),
@@ -161,7 +161,7 @@ local function SimplePerformanceTest()
161161
end
162162
-- ▲ ReadonlyArray.map ▲
163163
return React.createElement("scrollingframe", _exp, _exp_1, _newValue)
164-
end, { itemCount, items, ItemComponent }, "dynamic_scrollingframe_ko3hc3oyg"))
164+
end, { itemCount, items, ItemComponent }, "dynamic_scrollingframe_ofg059k84"))
165165
end
166166
return {
167167
default = SimplePerformanceTest,

demo/out/counter.luau

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,31 @@ local _react = TS.import(script, game:GetService("ReplicatedStorage"), "rbxts_in
55
local React = _react
66
local useState = _react.useState
77
-- Static declarations - extracted from render functions for optimal performance
8-
local STATIC_PROPS_TEXTLABEL_4qbg3s = {
8+
local STATIC_PROPS_TEXTLABEL_mpanhh = {
99
Text = "Optimized Counter App",
1010
TextColor3 = Color3.fromRGB(255, 255, 255),
1111
BackgroundTransparency = 1,
1212
Size = UDim2.new(1, 0, 1, 0),
1313
}
14-
local STATIC_PROPS_TEXTLABEL_5rx6df = {
14+
local STATIC_PROPS_TEXTLABEL_5mqbly = {
1515
Text = "Performance: Decillion Optimized",
1616
TextColor3 = Color3.fromRGB(200, 200, 200),
1717
BackgroundTransparency = 1,
1818
Size = UDim2.new(1, 0, 1, 0),
1919
}
20-
local STATIC_PROPS_FRAME_l7b4n1 = {
20+
local STATIC_PROPS_FRAME_5o6iiy = {
2121
BackgroundColor3 = Color3.fromRGB(50, 50, 50),
2222
Size = UDim2.new(1, 0, 0, 50),
2323
}
24-
local STATIC_PROPS_FRAME_3afnud = {
24+
local STATIC_PROPS_FRAME_7t6bzl = {
2525
Size = UDim2.new(1, 0, 0, 100),
2626
Position = UDim2.new(0, 0, 0, 160),
2727
BackgroundColor3 = Color3.fromRGB(60, 60, 60),
2828
}
29-
local STATIC_ELEMENT_TEXTLABEL_rqbjh5 = createStaticElement("textlabel", STATIC_PROPS_TEXTLABEL_4qbg3s)
30-
local STATIC_ELEMENT_FRAME_i98rh4 = createStaticElement("frame", STATIC_PROPS_FRAME_l7b4n1, STATIC_ELEMENT_TEXTLABEL_rqbjh5)
31-
local STATIC_ELEMENT_TEXTLABEL_mkv9lk = createStaticElement("textlabel", STATIC_PROPS_TEXTLABEL_5rx6df)
32-
local STATIC_ELEMENT_FRAME_pabiud = createStaticElement("frame", STATIC_PROPS_FRAME_3afnud, STATIC_ELEMENT_TEXTLABEL_mkv9lk)
29+
local STATIC_ELEMENT_TEXTLABEL_thasf6 = createStaticElement("TextLabel", STATIC_PROPS_TEXTLABEL_mpanhh)
30+
local STATIC_ELEMENT_FRAME_d22n8y = createStaticElement("Frame", STATIC_PROPS_FRAME_5o6iiy, STATIC_ELEMENT_TEXTLABEL_thasf6)
31+
local STATIC_ELEMENT_TEXTLABEL_xymm1y = createStaticElement("TextLabel", STATIC_PROPS_TEXTLABEL_5mqbly)
32+
local STATIC_ELEMENT_FRAME_djaofb = createStaticElement("Frame", STATIC_PROPS_FRAME_7t6bzl, STATIC_ELEMENT_TEXTLABEL_xymm1y)
3333
local function Counter()
3434
local count, setCount = useState(0)
3535
local increment = function()
@@ -41,7 +41,7 @@ local function Counter()
4141
return React.createElement("frame", {
4242
Size = UDim2.new(1, 0, 1, 0),
4343
BackgroundColor3 = Color3.fromRGB(30, 30, 30),
44-
}, STATIC_ELEMENT_FRAME_i98rh4, React.createElement("textlabel", {
44+
}, STATIC_ELEMENT_FRAME_d22n8y, React.createElement("textlabel", {
4545
Text = `Count: {count}`,
4646
TextColor3 = Color3.fromRGB(255, 255, 255),
4747
BackgroundColor3 = Color3.fromRGB(100, 100, 100),
@@ -65,7 +65,7 @@ local function Counter()
6565
Event = {
6666
MouseButton1Click = decrement,
6767
},
68-
}), STATIC_ELEMENT_FRAME_pabiud)
68+
}), STATIC_ELEMENT_FRAME_djaofb)
6969
end
7070
return {
7171
default = Counter,

demo/out/flamework-class.luau

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ local Service = TS.import(script, game:GetService("ReplicatedStorage"), "rbxts_i
66
local ReactRoblox = TS.import(script, game:GetService("ReplicatedStorage"), "rbxts_include", "node_modules", "@rbxts", "react-roblox")
77
local Workspace = TS.import(script, game:GetService("ReplicatedStorage"), "rbxts_include", "node_modules", "@rbxts", "services").Workspace
88
-- Static declarations - extracted from render functions for optimal performance
9-
local STATIC_PROPS_TEXTLABEL_8c02o3 = {
9+
local STATIC_PROPS_TEXTLABEL_pqpj5q = {
1010
Text = "Static Element",
1111
}
12-
local STATIC_ELEMENT_TEXTLABEL_w1e011 = createStaticElement("textlabel", STATIC_PROPS_TEXTLABEL_8c02o3)
13-
local STATIC_ELEMENT_FRAME_qyud6u = createStaticElement("frame", nil, STATIC_ELEMENT_TEXTLABEL_w1e011)
12+
local STATIC_ELEMENT_TEXTLABEL_4x3z3d = createStaticElement("TextLabel", STATIC_PROPS_TEXTLABEL_pqpj5q)
13+
local STATIC_ELEMENT_FRAME_9k0ofg = createStaticElement("Frame", nil, STATIC_ELEMENT_TEXTLABEL_4x3z3d)
1414
local FlameworkClass
1515
do
1616
FlameworkClass = setmetatable({}, {
@@ -26,7 +26,7 @@ do
2626
function FlameworkClass:constructor()
2727
end
2828
function FlameworkClass:createStaticElement()
29-
return STATIC_ELEMENT_FRAME_qyud6u
29+
return STATIC_ELEMENT_FRAME_9k0ofg
3030
end
3131
function FlameworkClass:onInit()
3232
print("FlameworkClass initialized!")

demo/out/hello-world.story.luau

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ local createStaticElement = TS.import(script, game:GetService("ReplicatedStorage
44
local React = TS.import(script, game:GetService("ReplicatedStorage"), "rbxts_include", "node_modules", "@rbxts", "react")
55
local ReactRoblox = TS.import(script, game:GetService("ReplicatedStorage"), "rbxts_include", "node_modules", "@rbxts", "react-roblox")
66
-- Static declarations - extracted from render functions for optimal performance
7-
local STATIC_PROPS_TEXTLABEL_hn8l67 = {
7+
local STATIC_PROPS_TEXTLABEL_px79uv = {
88
AnchorPoint = Vector2.new(0.5, 0.5),
99
BackgroundColor3 = Color3.new(1, 1, 1),
1010
Position = UDim2.new(0.5, 0, 0.5, 0),
1111
Size = UDim2.new(0, 200, 0, 200),
1212
Text = "Hello, world!",
1313
}
14-
local STATIC_ELEMENT_TEXTLABEL_wi19i3 = createStaticElement("textlabel", STATIC_PROPS_TEXTLABEL_hn8l67)
14+
local STATIC_ELEMENT_TEXTLABEL_0bi77o = createStaticElement("TextLabel", STATIC_PROPS_TEXTLABEL_px79uv)
1515
return {
1616
react = React,
1717
reactRoblox = ReactRoblox,
1818
story = function()
19-
return STATIC_ELEMENT_TEXTLABEL_wi19i3
19+
return STATIC_ELEMENT_TEXTLABEL_0bi77o
2020
end,
2121
}

transformer/src/codegen.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ export function generateBlockId(tagName: string): string {
5252
* - Lowercase tags (frame, textlabel) become string literals
5353
* - PascalCase tags (Counter, MyComponent) become identifiers
5454
*/
55-
function createTagReference(tagName: string): ts.Expression {
55+
function createTagReference(tagName: string, tagToInstanceNameMap: Map<string, string>): ts.Expression {
5656
// Check if tag name starts with uppercase (PascalCase component)
5757
if (tagName[0] && tagName[0] === tagName[0].toUpperCase()) {
5858
// React component - use identifier
5959
return ts.factory.createIdentifier(tagName);
6060
} else {
6161
// HTML-like element - use string literal
62-
return ts.factory.createStringLiteral(tagName);
62+
return ts.factory.createStringLiteral(tagToInstanceNameMap.get(tagName.toLowerCase()) || tagName);
6363
}
6464
}
6565

@@ -70,9 +70,10 @@ export function createStaticElementCall(
7070
tagName: string,
7171
propsArg: ts.Expression,
7272
children: ts.Expression[] = [],
73+
tagToInstanceNameMap: Map<string, string>,
7374
): ts.CallExpression {
7475
return ts.factory.createCallExpression(ts.factory.createIdentifier("createStaticElement"), undefined, [
75-
createTagReference(tagName),
76+
createTagReference(tagName, tagToInstanceNameMap),
7677
propsArg,
7778
...children,
7879
]);

transformer/src/roblox-bridge.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ class RobloxBridge {
7777
this.initialized = true;
7878
}
7979

80+
/**
81+
* Get a mapping of tag names to instance names for Roblox elements
82+
*
83+
* @returns A map of tag names to instance names
84+
*/
85+
getTagToInstanceNameMap() {
86+
const map = new Map<string, string>();
87+
for (const instanceName of this.staticConstructors) {
88+
map.set(instanceName.toLowerCase(), instanceName);
89+
}
90+
return map;
91+
}
92+
8093
private resolveRobloxDTs(program: ts.Program): ts.ResolvedModuleFull | undefined {
8194
// First try the standard TypeScript module resolution
8295
const standardResolve = ts.resolveModuleName(

transformer/src/transformer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export class DecillionTransformer {
124124
blockAnalyzer,
125125
skipTransformFunctions: new Set<string>(),
126126
functionContextStack: [],
127+
tagToInstanceNameMap: robloxStaticDetector.getTagToInstanceNameMap(),
127128
};
128129
}
129130

@@ -194,7 +195,7 @@ function generateStaticElement(
194195
propsArg = ts.factory.createIdentifier("undefined");
195196
}
196197

197-
const elementCall = createStaticElementCall(tagName, propsArg, children);
198+
const elementCall = createStaticElementCall(tagName, propsArg, children, context.tagToInstanceNameMap);
198199

199200
// Check if we should extract the full static element to module level
200201
if (extractFullElement && isCompletelyStatic(node, context)) {

transformer/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,6 @@ export interface OptimizationContext {
5858
skipTransformFunctions: Set<string>;
5959
/** Stack of current function context to track if we're inside a skip function */
6060
functionContextStack: string[];
61+
/** Map of tag names to Roblox instance names */
62+
tagToInstanceNameMap: Map<string, string>;
6163
}

0 commit comments

Comments
 (0)