Created
August 31, 2025 14:46
-
-
Save ankushKun/21e317679a19c0cdcbfc39ef5d3ab0ca to your computer and use it in GitHub Desktop.
serving react like webapps from ao processes using LuaX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- This app can be found on https://workshop.forward.computer/8miMYZuKvlFrVR-GIOpSybDT59PFK04gmKGIFUY8ZYI/now/app | |
| local luax = require("luax") | |
| -- LuaX: https://apm_betteridea.ar.io/pkg?id=@apm/luax | |
| -- Reactive useState function that auto-rerenders when state changes | |
| local function useState(initialValue, varName, Component) | |
| local state = { initialValue } | |
| local function getValue() | |
| return state[1] | |
| end | |
| local function setValue(newValue) | |
| -- Update the state | |
| if type(newValue) == "function" then | |
| state[1] = newValue(state[1]) | |
| else | |
| state[1] = newValue | |
| end | |
| -- Re-render the component and update the global variable | |
| if Component and varName then | |
| _G[varName] = luax.render(Component()) | |
| print("Re-rendered " .. varName .. " with new value:", state[1]) | |
| end | |
| end | |
| return getValue, setValue | |
| end | |
| -- Example usage | |
| num, setNum = useState(num and num() or 0, "app", function() return App() end) | |
| -- JavaScript functions for frontend interaction | |
| local jsScript = script {} { [[ | |
| // Function to disable all buttons | |
| function disableAllButtons() { | |
| const buttons = document.querySelectorAll('button'); | |
| buttons.forEach(button => { | |
| button.disabled = true; | |
| button.style.opacity = '0.6'; | |
| button.style.cursor = 'not-allowed'; | |
| }); | |
| } | |
| // Function to show loading state | |
| function showLoadingState(buttonElement, originalText) { | |
| buttonElement.textContent = 'Loading...'; | |
| buttonElement.style.backgroundColor = '#6c757d'; | |
| } | |
| async function makeRequest(action, buttonElement) { | |
| const processId = '8miMYZuKvlFrVR-GIOpSybDT59PFK04gmKGIFUY8ZYI'; | |
| const url = `https://workshop.forward.computer/${processId}[email protected]/push&action=${action}&!`; | |
| // Get original button text and show loading state | |
| const originalText = buttonElement.textContent; | |
| showLoadingState(buttonElement, originalText); | |
| // Disable all buttons immediately | |
| disableAllButtons(); | |
| try { | |
| await fetch(url, { method: 'GET', mode: 'no-cors' }); | |
| console.log(`${action} action sent successfully`); | |
| // Show success feedback | |
| buttonElement.textContent = 'Success!'; | |
| buttonElement.style.backgroundColor = '#28a745'; | |
| // Reload page after a short delay | |
| setTimeout(() => location.reload(), 500); | |
| } catch (error) { | |
| console.error(`Failed to send ${action} action:`, error); | |
| // Show error feedback | |
| buttonElement.textContent = 'Error!'; | |
| buttonElement.style.backgroundColor = '#dc3545'; | |
| // Still reload after error (in case the request actually went through) | |
| setTimeout(() => location.reload(), 1000); | |
| } | |
| } | |
| function incrementNumber() { | |
| const button = event.target; | |
| makeRequest('increment', button); | |
| } | |
| function decrementNumber() { | |
| const button = event.target; | |
| makeRequest('decrement', button); | |
| } | |
| function resetNumber() { | |
| const button = event.target; | |
| makeRequest('reset', button); | |
| } | |
| ]] } | |
| local styles = style {} { [[ | |
| button { | |
| padding: 10px 20px; | |
| margin: 5px; | |
| border: none; | |
| border-radius: 4px; | |
| background-color: #007bff; | |
| color: white; | |
| font-size: 16px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| button:hover:not(:disabled) { | |
| background-color: #0056b3; | |
| transform: translateY(-1px); | |
| } | |
| button:disabled { | |
| cursor: not-allowed !important; | |
| transform: none !important; | |
| } | |
| h1 { | |
| color: #333; | |
| text-align: center; | |
| } | |
| div { | |
| text-align: center; | |
| font-family: Arial, sans-serif; | |
| margin: 10px 0; | |
| } | |
| .counter-display { | |
| font-size: 24px; | |
| font-weight: bold; | |
| margin: 20px 0; | |
| color: #007bff; | |
| } | |
| ]] } | |
| function App() | |
| print("Rendering App with num:", num()) | |
| return div {} { | |
| styles, | |
| h1 {} { "Reactive Counter App" }, | |
| div { class = "counter-display" } { "Current Number: " .. tostring(num()) }, | |
| div {} { | |
| button { onclick = "incrementNumber()" } { "Increment (+1)" }, | |
| button { onclick = "decrementNumber()" } { "Decrement (-1)" }, | |
| button { onclick = "resetNumber()" } { "Reset (0)" } | |
| }, | |
| div {style="margin-top:10px;"} { | |
| p{}{"The counter variable is stored in the hyper aos process, and gets served with the HTML of this page when you refresh it. SSR!?"}, | |
| p{}{"When you click any of the above buttons, a message gets sent to the process, which updates the variable and re-renders the HTML"}, | |
| p{}{"The frontend then refreshes to load the latest HTML from the process."}, | |
| }, | |
| jsScript | |
| } | |
| end | |
| -- Message handlers that trigger state changes and auto re-render | |
| Handlers.add("increment", function(msg) | |
| setNum(num() + 1) | |
| end) | |
| Handlers.add("decrement", function(msg) | |
| setNum(function(prev) return math.max(prev - 1, 0) end) | |
| end) | |
| Handlers.add("reset", function(msg) | |
| setNum(0) | |
| end) | |
| -- Initial render | |
| app = luax.render(App()) | |
| print("Initial app rendered with num =", num()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- LuaX Module | |
| local luax = {} | |
| luax.elements = {} | |
| -- Safe element creation with error handling | |
| local function safeCreateElement(name, attrs, children) | |
| local success, result = pcall(function() | |
| return { | |
| type = "html", | |
| name = name, | |
| atts = attrs or {}, | |
| children = children or {} | |
| } | |
| end) | |
| if not success then | |
| error("Failed to create element '" .. tostring(name) .. "': " .. tostring(result)) | |
| end | |
| return result | |
| end | |
| -- Create HTML elements with a more elegant syntax | |
| function luax.element(name, attrs, children) | |
| if type(name) ~= "string" then | |
| error("Element name must be a string, got " .. type(name)) | |
| end | |
| return safeCreateElement(name, attrs, children) | |
| end | |
| -- Create text nodes with error handling | |
| function luax.text(text) | |
| local success, result = pcall(function() | |
| return { | |
| type = "text", | |
| text = tostring(text or "") | |
| } | |
| end) | |
| if not success then | |
| error("Failed to create text node: " .. tostring(result)) | |
| end | |
| return result | |
| end | |
| -- Create a component with error handling | |
| function luax.component(render) | |
| if type(render) ~= "function" then | |
| error("Component render must be a function, got " .. type(render)) | |
| end | |
| return function(props, children) | |
| local success, result = pcall(function() | |
| -- If first argument is not a table, it's children | |
| if type(props) ~= "table" or props.type then | |
| children = props | |
| props = {} | |
| end | |
| -- Call the render function with props and children | |
| return render(props, children) | |
| end) | |
| if not success then | |
| error("Component render failed: " .. tostring(result)) | |
| end | |
| return result | |
| end | |
| end | |
| -- Helper function to create elements with the new syntax | |
| function luax.create(name) | |
| if type(name) ~= "string" then | |
| error("Element name must be a string, got " .. type(name)) | |
| end | |
| -- Return a function that handles both attributes and children | |
| return function(attrs) | |
| -- If attrs is a table of elements (children), treat it as children | |
| if type(attrs) == "table" then | |
| local isChildren = false | |
| for _, v in pairs(attrs) do | |
| if type(v) == "table" and v.type then | |
| isChildren = true | |
| break | |
| end | |
| end | |
| if isChildren then | |
| return safeCreateElement(name, {}, attrs) | |
| end | |
| end | |
| -- Return a function that takes children | |
| return function(children) | |
| if type(children) == "table" then | |
| -- If children is a table of elements, use it directly | |
| local isElementList = true | |
| for _, v in pairs(children) do | |
| if type(v) ~= "table" or not v.type then | |
| isElementList = false | |
| break | |
| end | |
| end | |
| if isElementList then | |
| return safeCreateElement(name, attrs, children) | |
| end | |
| -- If children is a table of strings, convert to text nodes | |
| local textChildren = {} | |
| for _, child in pairs(children) do | |
| if type(child) == "string" then | |
| table.insert(textChildren, luax.text(child)) | |
| else | |
| table.insert(textChildren, child) | |
| end | |
| end | |
| return safeCreateElement(name, attrs, textChildren) | |
| end | |
| -- If children is a string, convert to text node | |
| return safeCreateElement(name, attrs, { luax.text(children) }) | |
| end | |
| end | |
| end | |
| -- Function to render HTML elements to string with error handling | |
| function luax.render(element) | |
| local success, result = pcall(function() | |
| if type(element) ~= "table" then | |
| return tostring(element or "") | |
| end | |
| if element.type == "text" then | |
| return tostring(element.text or "") | |
| end | |
| if not element.name then | |
| return "" | |
| end | |
| local attrs = "" | |
| if element.atts then | |
| for k, v in pairs(element.atts) do | |
| if v ~= nil then | |
| attrs = attrs .. string.format(' %s="%s"', k, tostring(v)) | |
| end | |
| end | |
| end | |
| if not element.children or #element.children == 0 then | |
| return string.format("<%s%s/>", element.name, attrs) | |
| end | |
| local children = "" | |
| for _, child in ipairs(element.children) do | |
| if child then | |
| children = children .. luax.render(child) | |
| end | |
| end | |
| return string.format("<%s%s>%s</%s>", element.name, attrs, children, element.name) | |
| end) | |
| if not success then | |
| error("Failed to render element: " .. tostring(result)) | |
| end | |
| return result | |
| end | |
| -- Initialize global element functions with a more elegant syntax | |
| function luax.init() | |
| local elements = { | |
| "div", "span", "p", "h1", "h2", "h3", "h4", "h5", "h6", | |
| "a", "img", "input", "button", "form", "label", | |
| "option", "textarea", "ul", "ol", "li", "tr", | |
| "td", "th", "thead", "tbody", "header", "footer", "nav", | |
| "main", "section", "strong", "em", | |
| "br", "hr", "meta", "link", "script", "style", | |
| "iframe", "summary", "details", "article", "time", "aside", "dialog" | |
| } | |
| local reserved_names = { "table", "select" } | |
| -- Create elements with error handling | |
| for _, name in ipairs(elements) do | |
| local success, result = pcall(function() | |
| local element_func = luax.create(name) | |
| luax.elements[name] = element_func | |
| if not _G[name] then | |
| _G[name] = element_func | |
| else | |
| -- print(name .. " already exists in global scope") | |
| end | |
| end) | |
| if not success then | |
| error("Failed to create element '" .. name .. "': " .. tostring(result)) | |
| end | |
| end | |
| -- Handle reserved names separately | |
| for _, name in ipairs(reserved_names) do | |
| local success, result = pcall(function() | |
| local element_func = luax.create(name) | |
| luax.elements["html_" .. name] = element_func | |
| if not _G["html_" .. name] then | |
| _G["html_" .. name] = element_func | |
| else | |
| -- print("html_" .. name .. " already exists in global scope") | |
| end | |
| end) | |
| if not success then | |
| error("Failed to create element '" .. name .. "': " .. tostring(result)) | |
| end | |
| end | |
| -- luax.elements = elements | |
| end | |
| luax.init() | |
| _G.package.loaded["luax"] = luax |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment