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)