4.1 KiB
ParenScript Conversion Experiment
- Overview
- Goals
- Current JavaScript Files
- Implementation Plan
- Benefits
- ParenScript Resources
- Lessons Learned
- Notes
Overview
This branch experiments with converting all JavaScript files to ParenScript, allowing us to write client-side code in Common Lisp that compiles to JavaScript.
Goals
- Replace all
.jsfiles with ParenScript equivalents - Maintain same functionality
- Improve code maintainability by using one language (Lisp) for both frontend and backend
- Take advantage of Lisp macros for client-side code generation
Current JavaScript Files
static/js/admin.js- Admin dashboard functionalitystatic/js/auth-ui.js- Authentication UIstatic/js/front-page.js- Front page interactionsstatic/js/player.js- Audio player controlsstatic/js/profile.js- User profile pagestatic/js/users.js- User management
Implementation Plan
Phase 1: Setup [DONE]
- Add ParenScript dependency to
asteroid.asd - Create
parenscript-utils.lispwith helper functions - Create experimental branch
Phase 2: Convert Simple Files First
- Convert
auth-ui.js(smallest, simplest) - COMPLETE ✅ - Convert
profile.js - Convert
users.js
Phase 3: Convert Complex Files
- Convert
player.js(audio player logic) - Convert
front-page.js(pop-out player, frameset mode) - Convert
admin.js(queue management, track controls)
Phase 4: Testing & Refinement
- Test all functionality
- Optimize generated JavaScript
- Document ParenScript patterns used
Benefits
Code Reuse
- Share utility functions between frontend and backend
- Use same data structures and validation logic
Macros
- Create domain-specific macros for common UI patterns
- Generate repetitive JavaScript code programmatically
Type Safety
- Catch more errors at compile time
- Better IDE support with Lisp tooling
Maintainability
- Single language for entire stack
- Easier refactoring across frontend/backend boundary
ParenScript Resources
Lessons Learned
auth-ui.js Conversion (2025-11-06)
Challenge 1: Route Precedence
Problem: Radiance routes are matched in load order, not definition order. The general static file route (/static/(.*)) was intercepting our specific ParenScript route.
Solution: Intercept the static file route and check if path is js/auth-ui.js. If yes, serve ParenScript; otherwise serve regular file.
Challenge 2: Async/Await Syntax
Problem: ParenScript doesn't support async/await syntax. Using (async lambda ...) generated invalid JavaScript.
Solution: Use promise chains with .then() instead of async/await.
Challenge 3: Compile Time vs Runtime
Problem: ParenScript compiler (ps:ps*) isn't available in saved binary at runtime.
Solution: Compile JavaScript at load time and store in a parameter. The function just returns the pre-compiled string.
Success Metrics
- JavaScript compiles correctly (1386 characters)
- No browser console errors
- Auth UI works perfectly (show/hide elements based on login status)
- Generated code is readable and maintainable
Key Patterns
Compile at load time:
(defparameter *my-js* (ps:ps* '(progn ...))) (defun generate-my-js () *my-js*)
Promise chains instead of async/await:
(ps:chain (fetch url)
(then (lambda (response) (ps:chain response (json))))
(then (lambda (data) (process data)))
(catch (lambda (error) (handle error))))
Intercept static route:
(define-page static #@"/static/(.*)" (:uri-groups (path))
(if (string= path "js/my-file.js")
(serve-parenscript)
(serve-static-file)))
Notes
This is an EXPERIMENTAL branch. The goal is to evaluate ParenScript for this project, not to immediately replace all JavaScript.
If successful, we can merge incrementally, one file at a time.
First Conversion Complete!
auth-ui.js successfully converted to ParenScript on 2025-11-06. All functionality working, no errors.