================= 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: 1. Bundles React and ReactDOM 2. Provides a module registry for component dependencies 3. Transforms ESM bundles for runtime execution 4. Manages component lifecycle (mounting, updating, unmounting) How it works ============ Data attributes --------------- Components are defined using HTML data attributes: .. code-block:: html
Loading...
Required attributes: ``data-wilco-component`` Component name (e.g., ``store:product``, ``counter``) ``data-wilco-props`` JSON-encoded props object Optional attributes: ``data-wilco-api`` Base URL for the component API (default: ``/api``) ``data-wilco-hash`` Content hash for cache busting. When provided, the loader appends it as a query parameter to the bundle URL. Initialization flow ------------------- 1. **DOM Ready**: Loader waits for DOMContentLoaded 2. **Discovery**: Finds all ``[data-wilco-component]`` elements 3. **Fetch**: Loads bundle from ``{api}/bundles/{name}.js`` 4. **Transform**: Converts ESM imports to runtime registry lookups 5. **Compile**: Executes transformed code via ``new Function()`` 6. **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: .. code-block:: javascript window.__MODULES__ = { "react": React, "react/jsx-runtime": ReactJsxRuntime, }; When esbuild bundles a component, it marks React as external: .. code-block:: javascript // 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: .. code-block:: javascript // Transformed for execution const { useState } = window.__MODULES__["react"]; return MyComponent; ESM transformation ================== The ``transformEsmToRuntime`` function converts ESM syntax: **Import statements:** .. code-block:: javascript // 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:** .. code-block:: javascript // 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: .. code-block:: javascript //# sourceURL=components://bundles/store:product.js //# sourceMappingURL=data:application/json;base64,... Global API ========== The loader exposes a ``window.wilco`` API for programmatic control: .. code-block:: javascript window.wilco = { renderComponent, loadComponent, updateComponentProps, }; ``loadComponent(name, apiBase, hash)`` -------------------------------------- Load a component bundle by name. .. code-block:: javascript 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. .. code-block:: javascript 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. .. code-block:: javascript 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: .. code-block:: javascript 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: 1. Check ``window.staticManifest`` for the component name 2. If found, fetch from ``{staticManifestBaseUrl}{file}`` 3. 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 `` How live preview works ---------------------- 1. **Event Listening**: Listens for ``blur`` events on form fields 2. **Debouncing**: Waits 300ms after last field change 3. **Validation**: POSTs form data to ``validate_url`` 4. **Response 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: .. code-block:: bash # 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``: .. code-block:: bash 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: .. code-block:: bash make wheel # Runs build-loader, then uv build Error handling ============== Component load errors --------------------- If a component fails to load, the container displays an error: .. code-block:: html
Failed to load component: store:product
Errors are also logged to the console with full details. Compilation errors ------------------ If the transformed code fails to compile, the error is logged: .. code-block:: javascript 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: 1. View original TypeScript source in the Sources panel 2. Set breakpoints in original code 3. See mapped stack traces in errors The sourceURL comment helps identify component sources: .. code-block:: text components://bundles/store:product.js Performance considerations -------------------------- - **Caching**: Bundles are cached with long ``Cache-Control`` headers - **Deduplication**: 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: 1. Use ``data-wilco-hash`` for cache busting 2. Pre-load critical components using ``window.wilco.loadComponent()`` 3. Consider server-side rendering for above-the-fold content