Performance Optimization
Best practices for building fast, efficient WildflowerJS applications.
Page Setup for Optimal Performance
The way you structure your HTML page has a significant impact on Lighthouse scores and perceived performance. Following these guidelines can improve your Total Blocking Time (TBT) by 50% or more.
Script Loading Best Practices
Place all scripts in <head> with the defer attribute:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App</title>
<link rel="stylesheet" href="styles.css">
<!-- All scripts in head with defer -->
<script defer src="https://unpkg.com/wildflowerjs@1/dist/wildflower.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script defer src="app.js"></script>
</head>
<body>
<div data-component="my-app">
<!-- Your app HTML -->
</div>
</body>
</html>
Why This Works
| Approach | Behavior | Performance Impact |
|---|---|---|
<script defer> in head |
Downloads in parallel with HTML parsing, executes after DOM ready | Best - parallel download, non-blocking |
<script> at end of body |
Downloads after HTML parsed, blocks until complete | Good - but sequential, not parallel |
<script> in head (no defer) |
Blocks HTML parsing until downloaded and executed | Worst - blocks everything |
Key Points
- Parallel downloads - With
defer, the browser downloads scripts while parsing HTML - Preserved order - Defer scripts execute in document order (framework loads before app.js)
- DOM ready guarantee - Defer scripts run after HTML is parsed but before
DOMContentLoaded - No wrapper needed - Unlike inline scripts, external
deferscripts don't needDOMContentLoadedwrappers
--no-inline flag generates a separate app.js file with proper defer attributes.
Preventing Flash of Unstyled Content (FOUC)
When using defer scripts, the HTML renders before JavaScript executes. This can cause modals and other initially-hidden elements to briefly flash. Add this CSS to prevent the flash:
/* Hide cloaked elements until framework processes them */
[data-cloak] { display: none; }
Add the data-cloak attribute to elements that should be hidden until the framework initializes (e.g., data-show elements starting false, data-portal elements). Place this CSS at the top of your stylesheet or in a <style> tag in the <head>. The framework removes data-cloak after initialization.
data-cloak inside <template> elements (i.e., inside data-list templates). Template content is inert and invisible until the framework clones it, so there is no FOUC risk. Using data-cloak inside templates causes bugs: when new list items are created dynamically, the attribute may not be removed, causing data-show toggling to silently fail.
x-cloak) and petite-vue (v-cloak).
Inline Scripts with Deferred Dependencies
If you have inline scripts that depend on deferred external libraries, wrap them in DOMContentLoaded. Deferred scripts execute after HTML parsing but before DOMContentLoaded fires, so this ensures your dependencies are loaded:
<head>
<!-- External library loaded with defer -->
<script defer src="https://cdn.example.com/library.js"></script>
</head>
<body>
<!-- Inline script that uses the library -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Library is now available
library.init();
});
</script>
</body>
DOMContentLoaded.
Lighthouse Results Comparison
Using defer scripts in <head> vs scripts at end of <body>:
| Metric | Scripts at End of Body | Defer Scripts in Head |
|---|---|---|
| First Contentful Paint (FCP) | ~3.5s | ~2.3s |
| Total Blocking Time (TBT) | ~450ms | ~290ms |
| Lighthouse Score | ~65 | ~80 |
Understanding the Reactivity Model
WildflowerJS tracks dependencies at the binding level, not the component level. When state changes:
- Only bindings that reference the changed property update
- DOM updates happen directly (no virtual DOM diff)
- Sibling elements are unaffected
This means a component with 1000 bindings where only one property changes will only update that one DOM node.
List Rendering Performance
Keyed Rendering
WildflowerJS automatically uses keyed rendering when your list items have an id property. This enables efficient updates for:
- Reordering - Items are moved, not recreated
- Insertions - Only new items are rendered
- Deletions - Only removed items are destroyed
// Keyed rendering (automatic when items have 'id')
state: {
items: [
{ id: 1, name: 'First' },
{ id: 2, name: 'Second' },
{ id: 3, name: 'Third' }
]
}
Without an id property, the framework falls back to index-based rendering, which is less efficient for reordering operations.
Optimized Array Operations
The framework detects common array operations and optimizes DOM updates:
| Operation | Detection | Optimization |
|---|---|---|
push() |
Append detection | Only creates new elements at end |
unshift() |
Prepend detection | Only creates new element at start |
splice() |
Insert/remove detection | Surgical DOM updates at splice point |
| Swap two items | Swap detection | DOM node swap (no recreation) |
| Sort/reverse | Reorder detection | Moves existing nodes (with keys) |
Large List Best Practices
// For very large lists, consider pagination or windowing
wildflower.component('paginated-list', {
state: {
allItems: [], // Full dataset
page: 0,
pageSize: 50
},
computed: {
// Only render current page
visibleItems() {
const start = this.page * this.pageSize;
return this.allItems.slice(start, start + this.pageSize);
},
totalPages() {
return Math.ceil(this.allItems.length / this.pageSize);
}
}
});
data-show vs data-render
Choose the right conditional rendering approach based on your use case:
| Attribute | Performance Characteristics | Best For |
|---|---|---|
data-show |
Fast toggle (CSS only), keeps DOM/state | Frequently toggled content, tabs, dropdowns |
data-render |
Slower toggle (DOM insert/remove), frees memory | Expensive widgets, modals, rarely-shown content |
<!-- Use data-show for frequently toggled UI -->
<div class="dropdown-menu" data-show="isDropdownOpen">
...menu items...
</div>
<!-- Use data-render for expensive, rarely-shown content -->
<div data-render="showAnalyticsDashboard">
<div data-component="heavy-charts">
...complex visualization...
</div>
</div>
Computed Property Optimization
Automatic Caching
Computed properties are cached and only re-evaluate when their dependencies change:
computed: {
// Only recalculates when items array changes
expensiveCalculation() {
return this.items
.filter(item => item.active)
.map(item => item.value * 2)
.reduce((sum, val) => sum + val, 0);
}
}
Avoid Expensive Operations in Templates
// BAD: Recalculates on every access
// <span data-bind="items.filter(i => i.active).length">
// GOOD: Use computed property (cached)
computed: {
activeCount() {
return this.items.filter(i => i.active).length;
}
}
// <span data-bind="activeCount">
Component Architecture
State Separation
Separate unrelated state into different components to prevent unnecessary update propagation:
// BAD: Mixed concerns cause unnecessary updates
wildflower.component('app', {
state: {
theme: 'light', // Theme changes trigger...
currentPage: 'home', // ...navigation re-renders
userData: {} // ...and user display updates
}
});
// GOOD: Separated concerns
wildflower.component('theme-manager', {
state: { theme: 'light' }
});
wildflower.component('navigation-manager', {
state: { currentPage: 'home' }
});
wildflower.component('user-manager', {
state: { userData: {} }
});
Preserve Components During Content Updates
Use data-external to preserve components when parent HTML is replaced:
<div data-component="content-area">
<!-- This component survives innerHTML updates -->
<div data-external data-component="persistent-widget">
...maintains state across content changes...
</div>
<!-- This content can be replaced -->
<div class="dynamic-content" data-bind-html="pageContent"></div>
</div>
Event Handling Optimization
Debouncing and Throttling
Use built-in debounce and throttle for high-frequency events:
<!-- Debounce: Wait for pause in input (good for search) -->
<input
data-model="searchQuery"
data-action="input:onSearch"
data-event-debounce="300">
<!-- Throttle: Limit frequency (good for scroll/resize) -->
<div
data-action="scroll:handleScroll"
data-event-throttle="100">
Event Delegation in Lists
WildflowerJS uses event delegation internally for list items, so you don't need to worry about attaching thousands of event handlers:
<!-- Framework handles delegation automatically -->
<ul data-list="items">
<template>
<li>
<!-- Not 1000 handlers, just 1 delegated handler -->
<button data-action="handleClick">Click</button>
</li>
</template>
</ul>
Debugging Performance
Enable Debug Mode
Debug mode logs reactivity information to help identify unnecessary updates:
<!-- Via script tag -->
<script src="wildflower.min.js" data-debug="true"></script>
<!-- Or via JavaScript -->
<script>
window.WILDFLOWER_DEBUG = true;
</script>
Browser DevTools
Use the Performance tab in browser DevTools to profile your application:
- Open DevTools → Performance tab
- Click Record and perform the slow action
- Look for long-running JavaScript or excessive DOM operations
- Check the "Bottom-Up" view for hot functions
Performance Checklist
Quick Reference:
- Use
idproperties on list items for keyed rendering - Use
data-showfor frequently toggled content - Use
data-renderfor expensive, rarely-shown widgets - Move expensive calculations to computed properties
- Separate unrelated state into different components
- Use debounce/throttle for high-frequency events
- Choose the smallest bundle that meets your needs
- Use
data-externalto preserve components during HTML updates