Single-File Components All
WildflowerJS does not have a custom file format like .vue or .svelte. It does not need one. Two patterns built entirely from standard HTML, CSS, and JavaScript deliver the same colocation and ergonomics, with no compiler.
.js file with all the component's logic, or an .html fragment file containing the markup, styles, and registration script that you load with a tiny helper.
Pattern 1: JS File With Use-Site Markup
The most common pattern. A single .js file holds the component definition. The HTML markup the component binds to lives where the component is used, inside the data-component element. Loaded via a normal <script src> tag.
The .js file is the single-file component: it owns the state, methods, computed properties, and lifecycle. Styles can also live here via injection on first init(), or sit in a separate stylesheet, whichever fits the project. Markup is content the host page provides, the same way <input> takes attributes from the page that uses it.
Every component used by the documentation site you are reading follows this pattern: www/js/components/*.js. Most of them lean on global stylesheets; one (code-example.js) injects styles in init() as the example below does. Both variants are valid Pattern 1.
The component file. State, methods, lifecycle, and (optionally) component-scoped styles all live here.
// counter.js: the single-file component
wildflower.component('counter', {
state: { count: 0 },
increment() { this.state.count++; },
decrement() { this.state.count--; },
init() {
// Inject component styles once. Idempotent across multiple instances.
if (document.getElementById('counter-styles')) return;
const style = document.createElement('style');
style.id = 'counter-styles';
style.textContent = `
.counter { display: flex; gap: 12px; align-items: center; }
.counter button { padding: 8px 16px; }
`;
document.head.appendChild(style);
}
});
The page that uses it. Loads the component file via a normal <script src>, then declares a data-component element wherever the component should appear.
<script src="counter.js"></script>
<div class="counter" data-component="counter">
<button data-action="decrement">-</button>
<span data-bind="count"></span>
<button data-action="increment">+</button>
</div>
What you get: state, methods, lifecycle, and styles all in one .js file. Browser executes the script tag with no special handling. Multiple instances cost nothing extra and the style injection is idempotent; drop more data-component="counter" elements wherever you need them.
When to reach for it: default for almost everything. If you only need one of the two patterns, this is the one.
Trade-off: the markup lives separately from the JS, which some teams prefer (designers can edit HTML without touching JS) and some prefer to colocate. If you want template + script + style in one file, see Pattern 2.
Pattern 2: HTML Fragment File With Helper
Template, styles, and registration script colocated in a single .html file. A small helper fetches the file, hoists its styles into <head>, re-executes its script (so the component registers), and inserts the markup wherever you want the component to appear. Familiar to anyone arriving from Vue or Svelte.
The .html fragment looks like this.
<!-- counter-fragment.html -->
<style>
.sfc-frag-counter { display: flex; gap: 12px; align-items: center; }
.sfc-frag-counter button { padding: 8px 16px; }
</style>
<div class="sfc-frag-counter" data-component="sfc-frag-counter">
<button data-action="decrement">-</button>
<span class="count" data-bind="count"></span>
<button data-action="increment">+</button>
</div>
<script>
wildflower.component('sfc-frag-counter', {
state: { count: 0 },
increment() { this.state.count++; },
decrement() { this.state.count--; }
});
</script>
The reusable helper. Copy into your project once.
async function mountComponent(url, target) {
const html = await fetch(url).then(r => r.text());
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
// Move <style> tags into <head>
for (const style of wrapper.querySelectorAll('style')) {
document.head.appendChild(style);
}
// Re-create <script> elements so the browser executes them.
// Scripts inserted via innerHTML are inert; this is the standard fix.
for (const oldScript of [...wrapper.querySelectorAll('script')]) {
oldScript.remove();
const newScript = document.createElement('script');
for (const attr of oldScript.attributes) {
newScript.setAttribute(attr.name, attr.value);
}
newScript.textContent = oldScript.textContent;
document.head.appendChild(newScript);
}
// Move remaining markup into the target and scan
target.innerHTML = '';
target.append(...wrapper.children);
wildflower.scan(target);
}
// Usage
await mountComponent('/components/counter.html', document.getElementById('mount'));
What you get: one file per component. Template, style, and script side by side. Familiar layout for users coming from Vue or Svelte. Lazy-loadable: the file is fetched only when needed.
When to reach for it: you want the visual layout of a .vue file, you want components shipped as standalone HTML fragments without a build step, or you want code-split components fetched on demand.
Trade-off: the helper is twenty lines you maintain. The component instantiates only after a network round-trip. CSS scoping is by class convention, not enforced.
Choosing Between Them
| Pattern | File format | Loading | Best for |
|---|---|---|---|
| 1. JS file with use-site markup | .js |
Plain <script src> |
Default. Apps you control end-to-end. |
| 2. HTML fragment with helper | .html |
Helper (fetch + parse + script re-create + scan) |
.vue-style file layout, or lazy-loaded components. |
Neither is "the WildflowerJS way." Both are first-class. Use the one that matches your project's needs. You can mix them within a single app without friction.
What You Don't Get
Honest list of what other frameworks' SFC formats provide that these patterns don't:
- Compile-time CSS scoping with auto-generated attribute selectors. Vue and Svelte rewrite selectors at build time. Standards-only patterns rely on class conventions or attribute prefixes; nothing the browser enforces.
- Hot module replacement for SFCs. WildflowerJS does not run a dev server. Editing a component file requires a page reload.
- TypeScript or Sass inside the file. Pre-processors require a compile step. If you want them, run them as a separate step that outputs plain JS and CSS, then load that output normally.
- A unique editor mode for the framework's file format. The patterns above are valid HTML, CSS, or JavaScript, so any editor with standard syntax highlighting works without a plugin.
The trade is real and worth understanding. Standards-only means no compiler dependency, no version skew between the framework and your build tools, and no weeks lost to debugging your toolchain. It also means you opt into pre-processing yourself when you want it, instead of getting it free.