Standalone Loader¶
Overview¶
The standalone loader is a self-contained JavaScript bundle that can render wilco components in any HTML page without requiring a full React application. It’s used by all template-based integrations (Django, Flask, Starlette) for server-side rendered pages.
The loader:
Bundles React and ReactDOM
Provides a module registry for component dependencies
Transforms ESM bundles for runtime execution
Manages component lifecycle (mounting, updating, unmounting)
How it works¶
Data attributes¶
Components are defined using HTML data attributes:
<div
data-wilco-component="store:product"
data-wilco-props='{"name": "Widget", "price": 9.99}'
data-wilco-api="/api"
data-wilco-hash="abc123">
Loading...
</div>
<script src="/static/wilco/loader.js" defer></script>
Required attributes:
data-wilco-componentComponent name (e.g.,
store:product,counter)data-wilco-propsJSON-encoded props object
Optional attributes:
data-wilco-apiBase URL for the component API (default:
/api)data-wilco-hashContent hash for cache busting. When provided, the loader appends it as a query parameter to the bundle URL.
Initialization flow¶
DOM Ready: Loader waits for DOMContentLoaded
Discovery: Finds all
[data-wilco-component]elementsFetch: Loads bundle from
{api}/bundles/{name}.jsTransform: Converts ESM imports to runtime registry lookups
Compile: Executes transformed code via
new Function()Render: Creates React root and renders component
Warning
Content Security Policy (CSP): The loader uses new Function() to compile
component bundles, which is functionally equivalent to eval(). If your application
sets a Content Security Policy, you must include 'unsafe-eval' in the script-src
directive. Applications using django-csp or similar CSP middleware should add this
to their CSP configuration. Always serve bundles over HTTPS to prevent code injection
via man-in-the-middle attacks.
Module registry¶
The loader provides a global module registry that component bundles use:
window.__MODULES__ = {
"react": React,
"react/jsx-runtime": ReactJsxRuntime,
};
When esbuild bundles a component, it marks React as external:
// Original component code
import { useState } from 'react';
// Bundled (ESM with externals)
import { useState } from 'react';
export { MyComponent as default };
The loader transforms this at runtime:
// Transformed for execution
const { useState } = window.__MODULES__["react"];
return MyComponent;
ESM transformation¶
The transformEsmToRuntime function converts ESM syntax:
Import statements:
// Before
import { useState, useEffect } from 'react';
import * as React from 'react';
import Component from './Component';
// After
const { useState, useEffect } = window.__MODULES__["react"];
const React = window.__MODULES__["react"];
const Component = window.__MODULES__["./Component"];
Export statements:
// Before
export { MyComponent as default };
// After
return MyComponent;
Source maps:
The transformation preserves source maps by extracting and reattaching
the //# sourceMappingURL comment. It also adds a //# sourceURL
for debugging:
//# sourceURL=components://bundles/store:product.js
//# sourceMappingURL=data:application/json;base64,...
Global API¶
The loader exposes a window.wilco API for programmatic control:
window.wilco = {
renderComponent,
loadComponent,
updateComponentProps,
};
loadComponent(name, apiBase, hash)¶
Load a component bundle by name.
const Component = await window.wilco.loadComponent(
"store:product",
"/api",
"abc123"
);
Returns a Promise that resolves to the React component function.
Components are cached by name or name?hash if a hash is provided.
The promise itself is cached to prevent duplicate fetches when multiple
containers request the same component simultaneously.
renderComponent(container, name, props, apiBase, hash)¶
Render a component into a container element.
const container = document.getElementById("my-component");
await window.wilco.renderComponent(
container,
"store:product",
{ name: "Widget", price: 9.99 },
"/api",
"abc123"
);
The container element is enhanced with internal references:
_wilcoRoot: React root instance_wilcoComponent: Loaded component function_wilcoProps: Current props
updateComponentProps(container, newProps)¶
Update props on an already-rendered component.
window.wilco.updateComponentProps(container, {
name: "New Name",
price: 19.99,
});
Returns true if successful, false if component not yet loaded.
Static mode (pre-built bundles)¶
When components are pre-compiled with wilco build, the loader can fetch
bundles from static file URLs instead of the API.
How static mode activates¶
The loader checks for window.staticManifest at initialization. If present,
it contains a mapping of component names to their hashed bundle files:
window.staticManifest = {
"store:product": {
"file": "bundles/store--product.a1b2c3.js",
"hash": "a1b2c3"
}
};
window.staticManifestBaseUrl = "/static/wilco/";
These are typically set by the server-rendered HTML (e.g., Django’s
{% wilco_loader_script %} tag).
Bundle resolution in static mode¶
When loading a component:
Check
window.staticManifestfor the component nameIf found, fetch from
{staticManifestBaseUrl}{file}If not found, fall back to the API endpoint (
{apiBase}/bundles/{name}.js)
This fallback enables incremental adoption: pre-build some components while others are still bundled at runtime.
Persistence across duplicate loads¶
Both staticManifest and staticManifestBaseUrl are stored on the
window object rather than as module-level variables. This ensures the
manifest state survives when multiple <script> tags load the same
loader.js file (e.g., one from a widget, one from a template), since
each IIFE execution would otherwise reset module-level variables.
Live preview extension¶
The live-loader.js script extends the standalone loader with live
preview functionality for Django admin forms.
Enabling live preview¶
Add additional data attributes:
<div
data-wilco-component="store:product"
data-wilco-props='{"name": "Widget"}'
data-wilco-live="true"
data-wilco-validate-url="/admin/store/product/123/validate_preview/">
</div>
<script src="/static/wilco/loader.js" defer></script>
<script src="/static/wilco/live-loader.js" defer></script>
How live preview works¶
Event Listening: Listens for
blurevents on form fieldsDebouncing: Waits 300ms after last field change
Validation: POSTs form data to
validate_urlResponse Handling:
Success: Updates component with new props
Failure: Shows validation errors above preview
Extended API¶
Live preview adds functions to window.wilco:
validateAndUpdate(container)Trigger validation and update for a container.
showValidationError(container, errors)Display validation errors above the preview.
clearValidationError(container)Remove validation error display.
Building the loader¶
The standalone loader is built using esbuild:
# Via Makefile
make build-loader
# Or directly
cd src/wilcojs/react
pnpm build:loader
This compiles src/loader/standalone.ts directly to
src/wilco/bridges/django/static/wilco/loader.js.
The build command in package.json:
esbuild src/loader/standalone.ts \
--bundle \
--minify \
--format=iife \
--outfile=../../wilco/bridges/django/static/wilco/loader.js
The IIFE format ensures the loader is self-contained and doesn’t pollute
the global namespace (except for window.__MODULES__ and window.wilco).
When building the Python wheel, the loader is automatically rebuilt:
make wheel # Runs build-loader, then uv build
Error handling¶
Component load errors¶
If a component fails to load, the container displays an error:
<div style="color: red; padding: 1rem;">
Failed to load component: store:product
</div>
Errors are also logged to the console with full details.
Compilation errors¶
If the transformed code fails to compile, the error is logged:
console.error("Failed to compile component 'store:product':", error);
Invalid props JSON¶
If data-wilco-props contains invalid JSON, the error is logged and
the component renders with empty props.
Debugging¶
Source maps¶
Component bundles include inline source maps. In browser DevTools, you can:
View original TypeScript source in the Sources panel
Set breakpoints in original code
See mapped stack traces in errors
The sourceURL comment helps identify component sources:
components://bundles/store:product.js
Performance considerations¶
Caching: Bundles are cached with long
Cache-ControlheadersDeduplication: Concurrent requests for the same component share one fetch
Lazy Loading: Components load on-demand when containers appear
React Reuse: Single React instance shared across all components
For optimal performance:
Use
data-wilco-hashfor cache bustingPre-load critical components using
window.wilco.loadComponent()Consider server-side rendering for above-the-fold content