Transitioning from Marko's server-side template system to Juris's universal JavaScript islands with integrated server-side rendering
Performance Analysis: Real-World Load Testing Results
Juris's universal SSR implementation demonstrates superior performance metrics compared to traditional template-based frameworks:
Load Testing Specifications:
Configuration: 2000 virtual users, 10 connections per user (20,000 total requests) Test Duration: 11 seconds 🎯 PERFORMANCE METRICS: • Request Rate: 1,988 requests/second • Median Response Time: 5ms • P95 Response Time: 7.9ms • P99 Response Time: 10.9ms • Success Rate: 100% (zero failures) • Data Throughput: 26.78MB processed 🔥 STANDOUT RESULTS: • Sub-5ms median response times • 2000+ requests/second sustained performance • Zero request failures under peak load • Consistent performance across all percentiles
Framework Performance Comparison:
Framework | Median Response | P95 Response | Throughput | Build Step Required |
---|---|---|---|---|
Juris Universal | 5ms | 7.9ms | 1,988 req/s | ❌ None |
Marko SSR | ~15-25ms | ~45-60ms | ~800-1200 req/s | ✅ Required |
Next.js SSR | ~20-35ms | ~80-120ms | ~600-900 req/s | ✅ Required |
Nuxt.js SSR | ~25-40ms | ~90-150ms | ~500-800 req/s | ✅ Required |
Performance Optimization Factors:
1. Elimination of Build Step Overhead
- Direct JavaScript execution without compilation
- No template processing delays
- Zero bundling latency impact
2. Singleton Architecture Optimization
// Single application instance shared across requests const app = createApp(); function resetAppForRequest() { app.stateManager.reset([]); app.stateManager.state = INITIAL_STATE; return app; }
3. Pre-optimized HTML Templates
// Template parts compiled once at startup, not per-request const HTML_HEAD = `<!DOCTYPE html>...`; const HTML_TAIL = `</div><script>window.__hydration_data = `; const HTML_FOOTER = `;</script></body></html>`;
4. Streamlined String Rendering
- Direct string concatenation approach
- Minimal virtual DOM overhead
- Optimized memory allocation patterns
Resource Efficiency Metrics:
- Memory Footprint: Single application instance approach
- Garbage Collection: Efficient string-based rendering
- Memory Leaks: Prevented through proper state reset mechanisms
Universal Debugging Architecture: Debug Anywhere Capabilities
Juris's most innovative feature is Debug Anywhere - enabling identical debugging interfaces whether components execute server-side during SSR or client-side during hydration and interaction.
Universal Debugging Interface Implementation
// components/todo-list.js - Universal debugging support function createTodoList(juris) { const component = { // Debugging interface that works across environments debug: { // Functions identically on server and client getState: () => ({ todos: juris.getState('todos.items', []), newTodo: juris.getState('todos.newTodo', ''), environment: typeof window !== 'undefined' ? 'client' : 'server', timestamp: new Date().toISOString() }), logAction: (action, data) => { const env = typeof window !== 'undefined' ? 'CLIENT' : 'SERVER'; console.log(`[${env}] TodoList.${action}:`, data); }, logRender: (context, meta) => { const env = typeof window !== 'undefined' ? 'CLIENT' : 'SERVER'; console.log(`[${env}] TodoList.render(${context}):`, meta); }, // Environment-adaptive inspection inspect: () => { if (typeof window !== 'undefined') { // Client-side debugging capabilities return { environment: 'client', domElements: document.querySelectorAll('[data-debug-component="todo-list"]').length, enhancements: juris.getEnhancementStats?.() || 'Not available', state: component.debug.getState() }; } else { // Server-side debugging capabilities return { environment: 'server', renderMode: 'ssr', state: component.debug.getState() }; } } }, // Component methods with integrated debug hooks addTodo: (text) => { const todos = juris.getState('todos.items', []); const newTodo = { id: Date.now(), text, done: false }; juris.setState('todos.items', [...todos, newTodo]); // Universal debug hook component.debug.logAction('addTodo', { text, newTodo }); }, toggleTodo: (id) => { const todos = juris.getState('todos.items', []); const updated = todos.map(todo => todo.id === id ? { ...todo, done: !todo.done } : todo ); juris.setState('todos.items', updated); // Universal debug hook component.debug.logAction('toggleTodo', { id, affected: updated.filter(t => t.id === id) }); }, // SERVER-SIDE: String rendering with debug information renderToString: () => { const renderStart = Date.now(); const todos = juris.getState('todos.items', []); // Debug during server-side rendering component.debug.logRender('server', { todoCount: todos.length, renderTime: Date.now() - renderStart }); const html = ` <div class="todo-list" data-debug-component="todo-list" data-debug-env="server"> <h3>Todos (${todos.length})</h3> <ul class="todo-items"> ${todos.map(todo => ` <li data-todo-id="${todo.id}" class="${todo.done ? 'done' : ''}"> <span>${todo.text}</span> <button class="toggle-btn">${todo.done ? 'Undo' : 'Done'}</button> <button class="delete-btn">Delete</button> </li> `).join('')} </ul> <div class="add-todo"> <input class="new-todo-input" value="${juris.getState('todos.newTodo', '')}" placeholder="Add todo"> <button class="add-todo-btn">Add</button> </div> </div> `; // Debug the final rendered output component.debug.logRender('server-complete', { htmlLength: html.length }); return html; }, // CLIENT-SIDE: Enhancement with debug information enhance: () => { const enhanceStart = Date.now(); // Debug enhancement initialization component.debug.logRender('client-enhance-start', { enhanceStart, existingElements: document.querySelectorAll('[data-debug-component="todo-list"]').length }); // Input field enhancement juris.enhance('.new-todo-input', { value: () => juris.getState('todos.newTodo', ''), oninput: (e) => { juris.setState('todos.newTodo', e.target.value); component.debug.logAction('input-change', { value: e.target.value }); }, onkeyup: (e, { todoManager }) => { if (e.key === 'Enter') { component.addTodo(e.target.value); } } }); // Add button enhancement juris.enhance('.add-todo-btn', { onclick: () => { const newTodo = juris.getState('todos.newTodo', ''); component.addTodo(newTodo); } }); // Dynamic list enhancement juris.enhance('.todo-items', { children: () => { const todos = juris.getState('todos.items', []); component.debug.logRender('client-list-update', { todoCount: todos.length }); return todos.map(todo => ({ li: { key: todo.id, className: todo.done ? 'done' : '', 'data-todo-id': todo.id, children: [ { span: { text: todo.text } }, { button: { className: 'toggle-btn', text: todo.done ? 'Undo' : 'Done', onclick: () => component.toggleTodo(todo.id) } }, { button: { className: 'delete-btn', text: 'Delete', onclick: () => { const todos = juris.getState('todos.items', []); juris.setState('todos.items', todos.filter(t => t.id !== todo.id)); component.debug.logAction('deleteTodo', { id: todo.id }); } } } ] } })); } }); // Debug enhancement completion component.debug.logRender('client-enhance-complete', { enhanceTime: Date.now() - enhanceStart }); } }; return component; }
Debugging Usage Examples
Server-Side Debugging During SSR:
// server.js - Server-side debug integration fastify.get('*', async (request, reply) => { const serverApp = resetAppForRequest(); // Instantiate todo component const todoList = createTodoList(serverApp); // Server-side debugging console.log('SSR Debug:', todoList.debug.inspect()); // Output: { environment: 'server', renderMode: 'ssr', state: {...} } // Render with debug tracking const content = todoList.renderToString(); // Console: [SERVER] TodoList.render(server): { todoCount: 2, renderTime: 1 } return htmlTemplate(content, serverApp.stateManager.state); });
Client-Side Debugging During Hydration:
// Client-side debug integration const clientApp = createApp(); const todoList = createTodoList(clientApp); // Client-side debugging console.log('Client Debug:', todoList.debug.inspect()); // Output: { environment: 'client', domElements: 1, enhancements: {...}, state: {...} } // Enhancement with debug tracking todoList.enhance(); // Console: [CLIENT] TodoList.render(client-enhance-start): { enhanceStart: 1234567890 } // Debug component interactions todoList.addTodo('Debug this functionality!'); // Console: [CLIENT] TodoList.addTodo: { text: 'Debug this functionality!', newTodo: {...} }
Universal Debug Console Implementation
// Global debug console for all environments window.debugTodoList = () => { const todoList = createTodoList(window.clientApp || serverApp); return { // Universal debugging methods inspect: () => todoList.debug.inspect(), getState: () => todoList.debug.getState(), // Component testing methods addTestTodo: () => todoList.addTodo('Debug test todo'), toggleFirst: () => { const todos = todoList.debug.getState().todos; if (todos.length > 0) { todoList.toggleTodo(todos[0].id); } }, // Environment detection whereAmI: () => { return typeof window !== 'undefined' ? 'CLIENT' : 'SERVER'; } }; }; // Browser console usage examples: // debugTodoList().inspect() // debugTodoList().addTestTodo() // debugTodoList().whereAmI()
Debug Anywhere vs Marko Debugging Comparison
Traditional Marko Debugging:
<!-- components/todo-list/index.marko --> <div class="todo-list"> <!-- Limited template debugging --> <div if(process.env.NODE_ENV === 'development')> Debug: ${JSON.stringify(state)} </div> </div> <script> // Environment-specific debugging export default { onCreate() { if (process.env.NODE_ENV === 'development') { console.log('Marko component created'); } } // Separate debugging approaches for server vs client }; </script>
Juris Universal Debugging:
// Unified debugging interface across environments const todoList = createTodoList(juris); // Identical API for server-side rendering console.log('SSR State:', todoList.debug.getState()); // Identical API for client-side hydration console.log('Client State:', todoList.debug.getState()); // Universal debugging methods work everywhere todoList.debug.inspect(); // Environment-aware inspection
Debug Anywhere Architecture Benefits:
- Unified Debug Interface: Identical debugging API across server and client environments
- Environment Intelligence: Debugging automatically adapts to execution context
- State Transparency: Component state accessible in all environments
- Action Tracking: Monitor component behavior across environment boundaries
- Performance Monitoring: Measure SSR vs client enhancement performance metrics
- Production Safety: Debug functionality can be conditionally enabled
This Debug Anywhere Architecture eliminates the traditional debugging friction between server-side rendering and client-side enhancement, providing a seamless debugging experience throughout the entire application lifecycle.
Migration Rationale: Why Choose Juris Over Marko?
While Marko excels at server-side rendering through its template-based architecture, Juris provides a more cohesive and maintainable development approach:
Marko Framework Limitations:
- Template-Logic Division - Business logic fragmented between template files and JavaScript
- Build Tool Complexity - Dependencies on Marko compiler and associated build infrastructure
- Dual Syntax Requirements - Developers must master both template syntax and JavaScript patterns
- Client-Side Constraints - Template-centric approach restricts dynamic client behavior flexibility
- Vendor Lock-in - Marko-specific template syntax creates migration barriers
Juris Framework Advantages:
- Universal JavaScript Approach - Identical codebase executes on server and client
- Zero Build Dependencies - Direct compatibility with Node.js and browser environments
- Authentic SSR + Hydration - Server renders initial content, client enhances progressively
- Islands Architecture - Progressive enhancement through isolated component systems
- Pure JavaScript Foundation - No proprietary template syntax requirements
Architectural Pattern Comparison
Marko Processing Flow:
Server: .marko templates → Marko Compiler → Compiled JavaScript → HTML Output Client: Marko Hydrated Components
Juris Processing Flow:
Server: Juris Application → String Renderer → HTML + State Output Client: Same Juris Application → State Hydration with Enhancement
Phase 1: Server-Side Rendering Implementation Differences
Marko SSR Configuration:
// server.js (Marko implementation) const express = require('express'); const markoExpress = require('@marko/express'); const homeTemplate = require('./views/home.marko'); app.use(markoExpress()); app.get('/', (req, res) => { res.marko(homeTemplate, { title: 'Home Page', todos: [ { id: 1, text: 'Learn Marko Framework', done: false } ] }); });
Juris SSR Configuration:
// server.js (Juris with Universal Components) const fastify = require('fastify')({ logger: false }); const Juris = require('./juris/juris.js'); const { createApp } = require('./source/app.js'); // Server-side DOM environment setup if (!global.document) { global.document = { createElement: () => ({}), querySelector: () => null, querySelectorAll: () => [] }; } // Application singleton creation const app = createApp(); const stringRenderer = app.getHeadlessComponent('StringRenderer').api; stringRenderer.enableStringRenderer(); // HTML template generation function function htmlTemplate(content, state) { return `<!DOCTYPE html> <html> <head> <title>Juris Universal Application</title> <script src="https://unpkg.com/juris@0.5.2/juris.js"></script> </head> <body> <div id="app">${content}</div> <script> window.__hydration_data = ${JSON.stringify(state)}; // Automatic client-side hydration const clientApp = createApp(); clientApp.render('#app'); </script> </body> </html>`; } // Universal request handler fastify.get('*', async (request, reply) => { try { // Request-specific app state reset app.stateManager.reset([]); app.stateManager.state = { todos: [ { id: 1, text: 'Learn Juris Framework', done: false } ] }; // Route configuration const router = app.getHeadlessComponent('Router').api; router.setRoute(request.url); // String rendering execution const content = stringRenderer.renderToString(); const state = app.stateManager.state; reply.type('text/html'); return htmlTemplate(content, state); } catch (error) { reply.code(500); return `<h1>Error</h1><p>${error.message}</p>`; } });
Phase 2: Component Migration Strategies
2.1 Basic Interactive Components
Marko Counter Template:
<!-- components/counter/index.marko --> <div class="counter"> <h2>Counter: ${state.count}</h2> <button on-click('increment')>Increment</button> <button on-click('decrement')>Decrement</button> </div> <script> export default { onCreate() { this.state = { count: 0 }; }, increment() { this.state.count++; }, decrement() { this.state.count--; } }; </script>
Juris Universal Counter Component:
// components/counter.js (Universal execution) function createCounter(juris) { return { // Server-side string rendering capability renderToString: () => { const count = juris.getState('counter.count', 0); return ` <div class="counter"> <h2>Counter: ${count}</h2> <button class="increment-btn">Increment</button> <button class="decrement-btn">Decrement</button> </div> `; }, // Client-side progressive enhancement enhance: () => { juris.enhance('.increment-btn', { onclick: () => { const current = juris.getState('counter.count', 0); juris.setState('counter.count', current + 1); } }); juris.enhance('.decrement-btn', { onclick: () => { const current = juris.getState('counter.count', 0); juris.setState('counter.count', current - 1); } }); // Reactive display updates juris.enhance('.counter h2', { text: () => `Counter: ${juris.getState('counter.count', 0)}` }); } }; } module.exports = { createCounter };
2.2 Dynamic List Components with Server Rendering
Marko Todo List Template:
<!-- components/todo-list/index.marko --> <div class="todo-list"> <h3>Todos (${state.todos.length})</h3> <ul> <li for(todo in state.todos) key=todo.id class=todo.done ? 'done' : ''> <span>${todo.text}</span> <button on-click('toggleTodo', todo.id)> ${todo.done ? 'Undo' : 'Done'} </button> <button on-click('deleteTodo', todo.id)>Delete</button> </li> </ul> <div class="add-todo"> <input key="newTodo" bind-value="newTodo" placeholder="Add todo"> <button on-click('addTodo')>Add</button> </div> </div> <script> export default { onCreate() { this.state = { todos: this.input.todos || [], newTodo: '' }; }, toggleTodo(id) { const todo = this.state.todos.find(t => t.id === id); todo.done = !todo.done; }, deleteTodo(id) { this.state.todos = this.state.todos.filter(t => t.id !== id); }, addTodo() { if (this.state.newTodo.trim()) { this.state.todos.push({ id: Date.now(), text: this.state.newTodo, done: false }); this.state.newTodo = ''; } } }; </script>
Juris Universal Todo List Component:
// components/todo-list.js function createTodoList(juris) { const services = { todoManager: { addTodo: () => { const newTodo = juris.getState('todos.newTodo', ''); if (newTodo.trim()) { const todos = juris.getState('todos.items', []); juris.setState('todos.items', [ ...todos, { id: Date.now(), text: newTodo, done: false } ]); juris.setState('todos.newTodo', ''); } }, toggleTodo: (id) => { const todos = juris.getState('todos.items', []); const updated = todos.map(todo => todo.id === id ? { ...todo, done: !todo.done } : todo ); juris.setState('todos.items', updated); }, deleteTodo: (id) => { const todos = juris.getState('todos.items', []); juris.setState('todos.items', todos.filter(t => t.id !== id)); } } }; return { // Service registration registerServices: () => { Object.assign(juris.services, services); }, // Server-side rendering implementation renderToString: () => { const todos = juris.getState('todos.items', []); const newTodo = juris.getState('todos.newTodo', ''); const todoItems = todos.map(todo => ` <li class="${todo.done ? 'done' : ''}" data-todo-id="${todo.id}"> <span>${todo.text}</span> <button class="toggle-btn">${todo.done ? 'Undo' : 'Done'}</button> <button class="delete-btn">Delete</button> </li> `).join(''); return ` <div class="todo-list"> <h3>Todos (${todos.length})</h3> <ul class="todo-items">${todoItems}</ul> <div class="add-todo"> <input class="new-todo-input" value="${newTodo}" placeholder="Add todo"> <button class="add-todo-btn">Add</button> </div> </div> `; }, // Client-side enhancement implementation enhance: () => { // Input field enhancement juris.enhance('.new-todo-input', { value: () => juris.getState('todos.newTodo', ''), oninput: (e) => juris.setState('todos.newTodo', e.target.value), onkeyup: (e, { todoManager }) => { if (e.key === 'Enter') { todoManager.addTodo(); } } }); // Add button enhancement juris.enhance('.add-todo-btn', { onclick: ({ todoManager }) => todoManager.addTodo() }); // Dynamic list rendering juris.enhance('.todo-items', ({ todoManager }) => ({ children: () => { const todos = juris.getState('todos.items', []); return todos.map(todo => ({ li: { key: todo.id, className: todo.done ? 'done' : '', children: [ { span: { text: todo.text } }, { button: { className: 'toggle-btn', text: todo.done ? 'Undo' : 'Done', onclick: () => todoManager.toggleTodo(todo.id) } }, { button: { className: 'delete-btn', text: 'Delete', onclick: () => todoManager.deleteTodo(todo.id) } } ] } })); } })); // Counter updates juris.enhance('.todo-list h3', { text: () => { const todos = juris.getState('todos.items', []); return `Todos (${todos.length})`; } }); } }; } module.exports = { createTodoList };
2.3 Advanced Form Components with Validation
Marko Contact Form:
<!-- components/contact-form/index.marko --> <form class="contact-form" on-submit('handleSubmit')> <div class="field"> <label>Name</label> <input bind-value="name" class=state.errors.name ? 'error' : ''> <span if(state.errors.name) class="error">${state.errors.name}</span> </div> <div class="field"> <label>Email</label> <input type="email" bind-value="email" class=state.errors.email ? 'error' : ''> <span if(state.errors.email) class="error">${state.errors.email}</span> </div> <button type="submit" disabled=!state.isValid> ${state.isSubmitting ? 'Sending...' : 'Send Message'} </button> </form> <script> export default { onCreate() { this.state = { name: '', email: '', errors: {}, isSubmitting: false, isValid: false }; }, validate() { const errors = {}; if (!this.state.name.trim()) errors.name = 'Name required'; if (!this.state.email.includes('@')) errors.email = 'Valid email required'; this.state.errors = errors; this.state.isValid = Object.keys(errors).length === 0; }, async handleSubmit(event) { event.preventDefault(); this.validate(); if (this.state.isValid) { this.state.isSubmitting = true; await this.submitForm(); this.state.isSubmitting = false; } } }; </script>
Juris Universal Contact Form:
// components/contact-form.js function createContactForm(juris) { const services = { formValidator: { validateField: (field, value) => { let error = null; if (field === 'name' && !value.trim()) { error = 'Name required'; } else if (field === 'email' && !value.includes('@')) { error = 'Valid email required'; } juris.setState(`contactForm.errors.${field}`, error); return !error; }, isFormValid: () => { const errors = juris.getState('contactForm.errors', {}); const name = juris.getState('contactForm.name', ''); const email = juris.getState('contactForm.email', ''); return !errors.name && !errors.email && name && email; } }, formHandler: { submit: async () => { const { formValidator } = juris.services; const name = juris.getState('contactForm.name', ''); const email = juris.getState('contactForm.email', ''); // Complete field validation const nameValid = formValidator.validateField('name', name); const emailValid = formValidator.validateField('email', email); if (nameValid && emailValid) { juris.setState('contactForm.isSubmitting', true); try { // API submission simulation await new Promise(resolve => setTimeout(resolve, 1000)); console.log('Form submitted:', { name, email }); // Form state reset juris.setState('contactForm.name', ''); juris.setState('contactForm.email', ''); juris.setState('contactForm.errors', {}); } catch (error) { console.error('Submit error:', error); } finally { juris.setState('contactForm.isSubmitting', false); } } } } }; return { registerServices: () => { Object.assign(juris.services, services); }, renderToString: () => { const name = juris.getState('contactForm.name', ''); const email = juris.getState('contactForm.email', ''); const errors = juris.getState('contactForm.errors', {}); const isSubmitting = juris.getState('contactForm.isSubmitting', false); return ` <form class="contact-form"> <div class="field"> <label>Name</label> <input name="name" value="${name}" class="${errors.name ? 'error' : ''}"> <span class="error-message" data-field="name" style="display: ${errors.name ? 'block' : 'none'}">${errors.name || ''}</span> </div> <div class="field"> <label>Email</label> <input name="email" type="email" value="${email}" class="${errors.email ? 'error' : ''}"> <span class="error-message" data-field="email" style="display: ${errors.email ? 'block' : 'none'}">${errors.email || ''}</span> </div> <button type="submit" class="submit-btn"> ${isSubmitting ? 'Sending...' : 'Send Message'} </button> </form> `; }, enhance: () => { // Name input enhancement juris.enhance('input[name="name"]', { value: () => juris.getState('contactForm.name', ''), oninput: (e) => juris.setState('contactForm.name', e.target.value), onblur: ({ formValidator }) => { const value = juris.getState('contactForm.name', ''); formValidator.validateField('name', value); }, className: () => juris.getState('contactForm.errors.name') ? 'error' : '' }); // Email input enhancement juris.enhance('input[name="email"]', { value: () => juris.getState('contactForm.email', ''), oninput: (e) => juris.setState('contactForm.email', e.target.value), onblur: ({ formValidator }) => { const value = juris.getState('contactForm.email', ''); formValidator.validateField('email', value); }, className: () => juris.getState('contactForm.errors.email') ? 'error' : '' }); // Error message handling juris.enhance('.error-message', (ctx) => { const field = ctx.element.dataset.field; return { text: () => juris.getState(`contactForm.errors.${field}`, ''), style: () => ({ display: juris.getState(`contactForm.errors.${field}`) ? 'block' : 'none' }) }; }); // Submit button enhancement juris.enhance('.submit-btn', { disabled: ({ formValidator }) => !formValidator.isFormValid(), text: () => { const isSubmitting = juris.getState('contactForm.isSubmitting', false); return isSubmitting ? 'Sending...' : 'Send Message'; }, onclick: (e, { formHandler }) => { e.preventDefault(); formHandler.submit(); } }); } }; } module.exports = { createContactForm };
Phase 3: Application Architecture Migration
3.1 Marko Application Structure:
marko-application/ ├── components/ │ ├── header/ │ │ ├── index.marko │ │ └── style.css │ ├── footer/ │ │ ├── index.marko │ │ └── style.css │ └── todo-list/ │ ├── index.marko │ └── component.js ├── views/ │ ├── home.marko │ ├── about.marko │ └── layout.marko ├── server.js └── package.json
3.2 Juris Universal Application Structure:
juris-application/ ├── components/ │ ├── header.js │ ├── footer.js │ ├── todo-list.js │ └── contact-form.js ├── source/ │ ├── app.js (universal application) │ └── router.js ├── public/ │ ├── css/ │ │ └── styles.css │ └── js/ │ └── juris-app.js (client bundle) ├── server.js (SSR server) └── package.json
3.3 Universal Application Configuration:
source/app.js (Universal Execution):
// source/app.js - Compatible with both server and client environments const Juris = require('../juris/juris.js'); // Node.js environment // const Juris = window.Juris; // Browser environment (conditional loading) const { createCounter } = require('../components/counter.js'); const { createTodoList } = require('../components/todo-list.js'); const { createContactForm } = require('../components/contact-form.js'); function createApp() { const juris = new Juris({ states: { counter: { count: 0 }, todos: { items: [], newTodo: '' }, contactForm: { name: '', email: '', errors: {}, isSubmitting: false }, router: { currentRoute: '/' } }, // Headless components for universal functionality headlessComponents: { // Server-side string rendering capability StringRenderer: (props, ctx) => ({ api: { enableStringRenderer: () => { ctx.juris.isServerSide = true; }, renderToString: () => { const route = ctx.getState('router.currentRoute', '/'); switch (route) { case '/': return createHomePage(ctx.juris); case '/about': return createAboutPage(ctx.juris); case '/todos': return createTodosPage(ctx.juris); case '/contact': return createContactPage(ctx.juris); default: return '<h1>404 - Page Not Found</h1>'; } } } }), // Universal routing system Router: (props, ctx) => ({ api: { setRoute: (path) => { ctx.setState('router.currentRoute', path); }, navigate: (path) => { ctx.setState('router.currentRoute', path); if (typeof window !== 'undefined') { window.history.pushState({}, '', path); } } } }) } }); // Component service registration const counter = createCounter(juris); const todoList = createTodoList(juris); const contactForm = createContactForm(juris); todoList.registerServices(); contactForm.registerServices(); // Client-side specific enhancements if (typeof window !== 'undefined') { // State hydration from server if (window.__hydration_data) { juris.stateManager.state = window.__hydration_data; } // Client-side routing setup const router = juris.getHeadlessComponent('Router').api; window.addEventListener('popstate', () => { router.setRoute(window.location.pathname); }); // Component enhancement activation counter.enhance(); todoList.enhance(); contactForm.enhance(); // Route-based content rendering juris.enhance('#app', { children: () => { const route = juris.getState('router.currentRoute', '/'); switch (route) { case '/': return [{ div: { innerHTML: createHomePage(juris) } }]; case '/about': return [{ div: { innerHTML: createAboutPage(juris) } }]; case '/todos': return [{ div: { innerHTML: createTodosPage(juris) } }]; case '/contact': return [{ div: { innerHTML: createContactPage(juris) } }]; default: return [{ h1: { text: '404 - Page Not Found' } }]; } } }); } return juris; } // Universal page generation functions function createHomePage(juris) { const counter = createCounter(juris); return ` <div class="home-page"> <h1>Welcome to Juris Universal Application</h1> <p>This page was rendered ${juris.isServerSide ? 'on the server' : 'on the client'}!</p> ${counter.renderToString()} <nav> <a href="/about">About</a> | <a href="/todos">Todos</a> | <a href="/contact">Contact</a> </nav> </div> `; } function createAboutPage(juris) { return ` <div class="about-page"> <h1>About This Application</h1> <p>This is a universal Juris application demonstrating server-side rendering capabilities.</p> <nav> <a href="/">Home</a> | <a href="/todos">Todos</a> | <a href="/contact">Contact</a> </nav> </div> `; } function createTodosPage(juris) { const todoList = createTodoList(juris); return ` <div class="todos-page"> <h1>Todo Management</h1> ${todoList.renderToString()} <nav> <a href="/">Home</a> | <a href="/about">About</a> | <a href="/contact">Contact</a> </nav> </div> `; } function createContactPage(juris) { const contactForm = createContactForm(juris); return ` <div class="contact-page"> <h1>Contact Information</h1> ${contactForm.renderToString()} <nav> <a href="/">Home</a> | <a href="/about">About</a> | <a href="/todos">Todos</a> </nav> </div> `; } // Multi-environment export compatibility if (typeof module !== 'undefined' && module.exports) { module.exports = { createApp }; } else { window.createApp = createApp; }
Phase 4: Optimized Server-Side Rendering Implementation
4.1 Production-Ready Fastify Server:
server.js (Performance-Optimized SSR Server):
// server.js - Production-optimized Fastify Server for Juris SSR const fastify = require('fastify')({ logger: false, disableRequestLogging: true, keepAliveTimeout: 30000, connectionTimeout: 60000, bodyLimit: 1048576, maxParamLength: 100, ignoreTrailingSlash: true, caseSensitive: false }); const path = require('path'); // Server-side DOM environment simulation if (!global.document) { global.document = { createElement: () => ({}), querySelector: () => null, querySelectorAll: () => [], addEventListener: () => {} }; } if (!global.window) { global.window = { addEventListener: () => {}, history: null, location: { pathname: '/', search: '' } }; } // Application dependencies const Juris = require('./juris/juris.js'); const { createApp } = require('./source/app.js'); const PORT = process.env.PORT || 3000; // Pre-compiled HTML template segments for performance const HTML_HEAD = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Juris Universal Application</title> <link rel="stylesheet" href="/public/css/styles.css"> <script src="https://unpkg.com/juris@0.5.2/juris.js"></script> </head> <body> <div id="app">`; const HTML_TAIL = `</div> <script> window.__hydration_data = `; const HTML_FOOTER = `; // Client-side application initialization const clientApp = createApp(); clientApp.render('#app'); </script> <script src="/public/js/juris-app.js"></script> </body> </html>`; // Static file serving with performance caching const staticOptions = { maxAge: '1d', immutable: true, etag: true, lastModified: true }; fastify.register(require('@fastify/static'), { root: path.join(__dirname, 'public'), prefix: '/public/', ...staticOptions }); // Singleton application instance creation const app = createApp(); const stringRenderer = app.getHeadlessComponent('StringRenderer').api; stringRenderer.enableStringRenderer(); // Default SSR state configuration const INITIAL_STATE = { counter: { count: 42 }, todos: { items: [ { id: 1, text: 'Server-rendered todo item', done: false }, { id: 2, text: 'Another example todo', done: true } ], newTodo: '' }, contactForm: { name: '', email: '', errors: {}, isSubmitting: false }, router: { currentRoute: '/' } }; // Optimized application reset mechanism function resetAppForRequest() { app.stateManager.reset([]); app.stateManager.state = { ...INITIAL_STATE }; return app; } // Efficient HTML template generation function htmlTemplate(content, state) { return HTML_HEAD + content + HTML_TAIL + JSON.stringify(state) + HTML_FOOTER; } // Route performance schema const routeSchema = { response: { 200: { type: 'string' }, 404: { type: 'string' }, 500: { type: 'string' } } }; // Static file detection pattern const STATIC_FILE_REGEX = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)$/; // Universal request handler with optimization fastify.get('*', { schema: routeSchema }, async (request, reply) => { const url = request.url; // Performance fast-path for static file requests if (STATIC_FILE_REGEX.test(url)) { reply.code(404); return 'Not Found'; } try { // Request-scoped application reset const serverApp = resetAppForRequest(); // Component API access const router = serverApp.getHeadlessComponent('Router').api; const stringRenderer = serverApp.getHeadlessComponent('StringRenderer').api; // Server rendering configuration stringRenderer.enableStringRenderer(); router.setRoute(url); // String rendering execution const content = stringRenderer.renderToString(); // State extraction for client hydration const state = serverApp.stateManager.state; // Response optimization reply.type('text/html; charset=utf-8'); reply.header('Cache-Control', 'no-cache, no-store, must-revalidate'); return htmlTemplate(content, state); } catch (error) { console.error('SSR Processing Error:', error); reply.code(500); return `<h1>Server Error</h1><p>${error.message}</p>`; } }); // Response compression integration fastify.register(require('@fastify/compress'), { global: true, threshold: 1024, encodings: ['gzip', 'deflate'] }); // Server initialization const start = async () => { try { await fastify.listen({ port: PORT, host: '0.0.0.0', backlog: 1024, exclusive: false }); console.log(`🚀 Juris Universal Server running on http://localhost:${PORT}`); console.log('Enabled Features:'); console.log(' ✓ Server-side rendering capability'); console.log(' ✓ Client-side state hydration'); console.log(' ✓ Universal JavaScript components'); console.log(' ✓ Islands architecture implementation'); console.log(' ✓ Performance optimization suite'); } catch (err) { fastify.log.error(err); process.exit(1); } }; start();
4.2 Client-Side Application Bundle:
public/js/juris-app.js:
// public/js/juris-app.js - Client-side application bundle // Production version would be generated from source/app.js via bundling // This enables universal app functionality in browser environment // Generated through build tools like esbuild, rollup, or webpack in production
Phase 5: Strategic Migration Implementation
5.1 Incremental Migration Approach:
Phase A: Parallel Framework Setup
// Maintain existing Marko functionality app.get('/legacy/*', (req, res) => { res.marko(legacyTemplate, data); }); // Introduce new Juris-powered routes app.get('/new/*', jurisHandler);
Phase B: Component-Level Migration Strategy
- Begin with simple interactive components (counters, toggles, basic forms)
- Progress to dynamic lists and complex state management
- Migrate sophisticated page layouts and navigation
- Complete with routing system migration
Phase C: Framework Consolidation
- Remove Marko framework dependencies
- Update build and deployment scripts
- Convert remaining templates to universal components
5.2 Migration Implementation Checklist:
- [ ] Juris server setup with DOM environment simulation
- [ ] Universal application architecture design
- [ ] Simple component migration and testing
- [ ] Server-side rendering integration
- [ ] Client-side hydration implementation
- [ ] SSR and client enhancement validation
- [ ] Complex component migration
- [ ] Routing system upgrade
- [ ] Performance optimization implementation
- [ ] Marko framework dependency removal
Phase 6: Testing and Quality Assurance
6.1 Universal Component Testing Framework:
// test/components.test.js const { createApp } = require('../source/app.js'); describe('Universal Component Testing', () => { let app; beforeEach(() => { // DOM environment simulation for testing global.document = { createElement: () => ({}), querySelector: () => null, querySelectorAll: () => [] }; app = createApp(); app.getHeadlessComponent('StringRenderer').api.enableStringRenderer(); }); test('Counter component server rendering', () => { app.setState('counter.count', 5); app.getHeadlessComponent('Router').api.setRoute('/'); const html = app.getHeadlessComponent('StringRenderer').api.renderToString(); expect(html).toContain('Counter: 5'); }); test('Todo list component with data rendering', () => { app.setState('todos.items', [ { id: 1, text: 'Test todo item', done: false } ]); app.getHeadlessComponent('Router').api.setRoute('/todos'); const html = app.getHeadlessComponent('StringRenderer').api.renderToString(); expect(html).toContain('Test todo item'); expect(html).toContain('Todos (1)'); }); });
6.2 Server-Side Rendering Testing:
// test/ssr.test.js const fastify = require('fastify')(); const app = require('../server.js'); describe('Server-Side Rendering Validation', () => { test('Home page rendering with counter component', async () => { const response = await app.inject({ method: 'GET', url: '/' }); expect(response.statusCode).toBe(200); expect(response.body).toContain('Counter: 42'); expect(response.body).toContain('window.__hydration_data'); }); test('Todos page rendering with initial data', async () => { const response = await app.inject({ method: 'GET', url: '/todos' }); expect(response.statusCode).toBe(200); expect(response.body).toContain('Server-rendered todo item'); expect(response.body).toContain('Todo Management'); }); });
Migration Benefits Analysis
Performance Advantages:
- Build Pipeline Elimination - Direct Node.js execution without compilation overhead
- Reduced Bundle Sizes - No Marko compiler dependencies in production
- Faster Server Initialization - Elimination of template compilation step
- Optimized Hydration Process - Minimal client-side JavaScript requirements
Developer Experience Improvements:
- Universal JavaScript Development - Identical codebase for server and client
- Enhanced Debugging Capabilities - Standard JavaScript debugging tools compatibility
- Simplified Deployment Process - No build artifacts management required
- TypeScript Integration Ready - Straightforward static typing implementation
Architectural Enhancements:
- Islands Architecture Pattern - Progressive enhancement through isolated components
- Authentic SSR + Hydration - Server renders initial content, client progressively enhances
- Component Reusability - Universal components across environments
- Maintainable Codebase - Elimination of template/logic separation complexity
Implementation Summary
The migration from Marko to Juris represents a transition to universal JavaScript applications featuring authentic server-side rendering and seamless client-side hydration. Primary benefits include:
- Universal Component Architecture: Identical code execution across server and client environments
- Build Pipeline Elimination: Direct execution in Node.js and browser environments
- True Islands Implementation: Progressive enhancement with isolated component functionality
- Performance Optimization: Optimized SSR with efficient state hydration
- Maintainability Enhancement: Pure JavaScript approach eliminates template complexity
This migration eliminates the maintenance overhead of separate template and JavaScript logic while delivering superior performance and developer experience. Juris's universal architecture provides server-side rendering benefits with client-side interactivity flexibility, all implemented in pure JavaScript without proprietary template syntax dependencies.
Top comments (0)