The JavaScript landscape evolves with new paradigms. Juris, with its "Object-First" architecture, presents a different approach to reactive development compared to Vue.js. This comparison examines both frameworks across architecture, performance, and use cases.
Framework Overview
Vue.js: The Progressive Framework
Vue.js combines React's component architecture with Angular's templates, offering gentle learning curves and excellent developer experience. Vue 3's Composition API brings improved TypeScript support and better logic reuse.
Juris: The Object-First Revolutionary
At 2,616 lines of code, Juris transforms web development through object-first architecture where reactivity is intentional, not automatic. It delivers true progressive enhancement without build tools.
Core Architecture
Vue.js: Template-Based Reactivity
- HTML-like templates with directives
- Component-based encapsulation
- Automatic reactive data binding
- Virtual DOM with proxy-based reactivity
Juris: Object-First Intentional Reactivity
- Pure JavaScript objects (no compilation)
- Functions define reactive properties explicitly
- Branch-aware dependency tracking
- Dual rendering modes (fine-grained/batch)
// Vue 3 Component
<template>
<div :class="{ completed: todo.completed }">
<input v-model="todo.completed" @change="toggleTodo" />
<span>{{ todo.text }}</span>
<button @click="deleteTodo">×</button>
</div>
</template>
<script setup>
const props = defineProps(['todo'])
const emit = defineEmits(['toggle', 'delete'])
const toggleTodo = () => emit('toggle', props.todo.id)
const deleteTodo = () => emit('delete', props.todo.id)
</script>
// Juris Component - Pure JavaScript Objects
const TodoItem = (props, context) => ({
div: {
className: () => props.todo.completed ? 'completed' : '', // Reactive function
children: [
{ input: { type: 'checkbox', checked: () => props.todo.completed, onchange: () => props.onToggle(props.todo.id) } },
{ span: { text: () => props.todo.text } }, // Reactive
{ button: { text: '×', onclick: () => props.onDelete(props.todo.id) } } // Static
]
}
});
Performance & Bundle Size
Framework | Bundle Size | Build Tools | Key Features |
---|---|---|---|
Vue.js | ~34KB | Required | VDOM, proxy reactivity, compilation |
Juris | ~47KB | None | Dual rendering, temporal independence, 2,616 LOC |
Vue.js Performance:
- Efficient virtual DOM with automatic dependency tracking
- Template compilation adds build-time overhead
- Can cause cascade updates in complex apps
Juris Performance Innovations:
- Temporal Independence: Path-based state prevents cascade re-renders
- Dual Rendering: Fine-grained (compatibility) + batch (performance) modes
- Element Recycling: VDOM optimizations with automatic fallback
- Branch-Aware Tracking: Only tracks dependencies in executed code paths
Setup & Development Experience
Vue.js Setup:
npm create vue@latest my-project
cd my-project && npm install && npm run dev
# Requires: Vite/Webpack, build tools, compilation
Juris Setup:
<script src="https://unpkg.com/juris@0.4.3/juris.js"></script>
<script>
const app = new Juris({
states: { count: 0 },
layout: {
div: {
children: [
{ h1: { text: () => `Count: ${app.getState('count')}` } },
{ button: { text: 'Increment', onclick: () => app.setState('count', app.getState('count') + 1) } }
]
}
}
});
app.render('#app');
</script>
Component Systems
Vue.js: Standard Components
// Lifecycle hooks
onMounted(() => console.log('mounted'))
onUnmounted(() => console.log('unmounted'))
Juris: Dual Architecture (UI + Headless)
// UI Component
const Counter = (props, context) => ({
div: {
children: [/* UI definition */]
}
});
// Headless Component (logic only)
const CounterLogic = (props, context) => ({
api: {
increment: () => context.setState('count', context.getState('count', 0) + 1),
reset: () => context.setState('count', 0)
},
hooks: {
onRegister: () => console.log('Counter logic ready')
}
});
// Any UI can use the headless logic
app.registerHeadlessComponent('CounterLogic', CounterLogic, { autoInit: true });
State Management Revolution
Vue.js: Automatic Reactivity
import { ref, computed } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
// Changing count triggers all dependent updates
Juris: Path-Based Temporal Independence
// Domain-based paths prevent cascade updates
context.setState('user.profile.name', 'Jane'); // Only affects user.profile subscribers
context.setState('user.preferences.theme', 'dark'); // Only affects user.preferences subscribers
context.setState('cart.items.0.quantity', 3); // Only affects specific cart item
// Components subscribe only to needed paths
const UserProfile = (props, context) => ({
div: {
text: () => context.getState('user.profile.name'), // Only updates when this path changes
style: () => ({
color: context.getState('user.preferences.theme') === 'dark' ? '#fff' : '#000'
})
}
});
Progressive Enhancement
Vue.js: Hydration (Replacement)
// Server renders HTML, client replaces with reactive version
app.mount('#app') // Takes over existing HTML
Juris: True Enhancement (No Replacement)
// Enhance existing HTML without replacement
app.enhance('.checkout-button', {
style: () => ({ opacity: context.getState('cart.items.length') > 0 ? 1 : 0.5 }),
onclick: () => context.setState('checkout.step', 'payment')
});
// Advanced nested enhancement
app.enhance('.data-table', (ctx) => ({
'th[data-column]': {
className: (el) => () => {
const column = el.dataset.column;
const sort = ctx.getState('sort.column');
return sort === column ? 'sortable active' : 'sortable';
},
onclick: (el) => () => ctx.setState('sort.column', el.dataset.column)
},
'.status': {
style: (el) => () => ({
backgroundColor: el.textContent === 'active' ? '#28a745' : '#dc3545'
})
}
}));
Testing Complexity
Vue.js Testing:
// Requires Vue Test Utils, mounting, and mock setup
import { mount } from '@vue/test-utils'
import TodoItem from '@/components/TodoItem.vue'
describe('TodoItem', () => {
test('toggles completion', async () => {
const todo = { id: 1, text: 'Test', completed: false }
const wrapper = mount(TodoItem, {
props: { todo },
emits: ['toggle']
})
await wrapper.find('input[type="checkbox"]').trigger('change')
expect(wrapper.emitted('toggle')).toBeTruthy()
})
})
Juris Testing:
// Pure JavaScript functions - easy to test directly
import { TodoItem } from './components.js'
describe('TodoItem', () => {
test('creates correct structure', () => {
const props = { todo: { id: 1, text: 'Test', completed: false } }
const mockContext = {
getState: jest.fn(),
setState: jest.fn()
}
const result = TodoItem(props, mockContext)
// Test pure function output
expect(result.div.children).toHaveLength(3)
expect(result.div.children[1].span.text()).toBe('Test')
// Test reactive functions
const checkbox = result.div.children[0].input
expect(checkbox.checked()).toBe(false)
// Test event handlers
checkbox.onchange()
expect(props.onToggle).toHaveBeenCalledWith(1)
})
})
When to Choose Each
Aspect | Vue.js | Juris |
---|---|---|
Setup Complexity | High - requires mounting, mocking | Low - pure functions |
Test Dependencies | Vue Test Utils, jsdom | Standard Jest/testing library |
Component Testing | Template + logic integration | Direct function testing |
State Testing | Mock store/composables | Mock context object |
Event Testing | Trigger DOM events | Call functions directly |
Learning Curve | Vue-specific testing patterns | Standard JavaScript testing |
Choose Vue.js For:
- Production applications requiring proven stability
- Large teams with existing Vue expertise
- Complex applications needing mature ecosystem
- TypeScript projects requiring strong integration
- SEO-critical apps with Nuxt.js SSR/SSG
Choose Juris For:
- Legacy modernization requiring true progressive enhancement
- Performance-critical apps with complex state management
- Rapid prototyping without build tool setup
- AI-assisted development with pure JavaScript objects
- Educational projects exploring reactive programming
Quick Example Comparison
Vue Todo App:
<template>
<div>
<input v-model="newTodo" @keyup.enter="addTodo">
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" v-model="todo.completed">
{{ todo.text }}
</li>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const newTodo = ref('')
const todos = reactive([])
const addTodo = () => {
if (newTodo.value.trim()) {
todos.push({ id: Date.now(), text: newTodo.value, completed: false })
newTodo.value = ''
}
}
</script>
Juris Todo App:
const TodoApp = (props, context) => ({
div: {
children: [
{
input: {
value: () => context.getState('newTodo', ''),
oninput: (e) => context.setState('newTodo', e.target.value),
onkeyup: (e) => {
if (e.key === 'Enter') {
const text = context.getState('newTodo', '').trim();
if (text) {
const todos = context.getState('todos', []);
context.setState('todos', [...todos, {
id: Date.now(),
text,
completed: false
}]);
context.setState('newTodo', '');
}
}
}
}
},
{
ul: {
children: () => context.getState('todos', []).map(todo => ({
li: {
key: todo.id,
children: [
{
input: {
type: 'checkbox',
checked: () => todo.completed,
onchange: () => {
const todos = context.getState('todos', []);
const updated = todos.map(t =>
t.id === todo.id ? { ...t, completed: !t.completed } : t
);
context.setState('todos', updated);
}
}
},
{
span: {
text: todo.text
}
}
]
}
}))
}
}
]
}
});
new Juris({
states: { todos: [], newTodo: '' },
components: { TodoApp },
layout: { TodoApp: {} }
}).render('#app');
Conclusion
Vue.js and Juris represent different philosophies: Vue.js offers mature, proven development with excellent tooling, while Juris introduces revolutionary concepts that solve fundamental problems affecting almost all web development.
Vue.js is ideal for traditional applications, large teams, and risk-averse environments requiring proven stability and extensive ecosystem support.
Juris excels at legacy modernization, performance-critical applications, and scenarios requiring true progressive enhancement without build complexity.
The choice depends on your specific needs: Vue.js for stability and ecosystem, Juris for innovation and progressive enhancement capabilities.
Top comments (0)