WU / DOCSUNIVERSAL MICROFRONTENDS
EN

Astro, React, Vue, and adapters

Polyglot composition with an Astro client script, real adapters, and a manual bridge that preserves the scoped facade.

An Astro shell with independent apps

Astro can serve editorial content and slots while WU loads applications built separately with Vite. The runtime import that mounts DOM must be in an Astro-processed <script>, not executed as a mount in server frontmatter.

---
const title = 'Laboratorio políglota';
---
<html lang="es">
  <head><meta charset="utf-8" /><title>{title}</title></head>
  <body>
    <main>
      <div id="react-slot"></div>
      <div id="vue-slot"></div>
    </main>
    <script>
      import { wu } from 'wu-framework';
      await wu.init({
        apps: [
          { name: 'react-demo', url: '/apps/react-demo', framework: 'react' },
          { name: 'vue-demo', url: '/apps/vue-demo', framework: 'vue' },
        ],
      });
      await Promise.all([
        wu.mount('react-demo', '#react-slot'),
        wu.mount('vue-demo', '#vue-slot'),
      ]);
    </script>
  </body>
</html>

Both public directories must contain their manifests and compiled assets. For scoped state/events, add requests to each manifest and corresponding grants to both host configurations.

React entry with an adapter

import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { register } from 'wu-framework/adapters/react';
import App from './App.jsx';

const registered = await register('react-demo', App, {
  runtime: { React, ReactDOM },
  standalone: false,
  strictMode: true,
  props: { label: 'React' },
});
if (!registered) throw new Error('No se pudo registrar react-demo');

runtime injection lets the adapter use the same instances as your application. Import ReactDOM from react-dom/client: mounting requires createRoot, even though the React peer range is broader. For hydration, the runtime must also provide hydrateRoot.

The adapter keeps roots by name, implements update by merging props, and provides hydration through hydrateRoot when available. standalone defaults to true and looks for #root when it cannot find WU; disable it to prevent an integrated microfrontend from accidentally mounting elsewhere.

Vue entry with an adapter

import { register, wuVuePlugin } from 'wu-framework/adapters/vue';
import * as Vue from 'vue';
import App from './App.vue';

const registered = await register('vue-demo', App, {
  standalone: false,
  props: { label: 'Vue' },
  setup(app) {
    app.use(wuVuePlugin, { Vue });
  },
});
if (!registered) throw new Error('No se pudo registrar vue-demo');

Vue register resolves its runtime through the adapter mechanism; it does not have React’s runtime option. setup receives the app before mounting to register plugins or a router. The plugin accepts the Vue namespace to resolve helpers in contexts without bare imports or window.Vue; passing the namespace in setup does not itself replace the initial register resolution.

Updates use reactive root props when Vue provides reactive. Hydration support is advertised when createSSRApp exists. standaloneContainer defaults to #app.

Preserve scoped authority

In 4.0, React/Vue adapters pass the facade to the component tree, and their event/store hooks enforce the microapp’s grants. Declare manifest requests and shell grants. The following manual pattern remains useful for a custom renderer or an explicit appWu dependency.

For a custom renderer, register an explicit lifecycle and pass the facade as a dependency:

import { wu } from 'wu-framework';
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';

let root;
let appWu;
let props = {};
const render = () => root.render(React.createElement(App, { ...props, appWu }));

wu.define('react-demo', {
  mount(container, context) {
    appWu = context.wu;
    root = createRoot(container);
    render();
  },
  update(_container, nextProps) {
    props = { ...props, ...nextProps };
    render();
  },
  unmount() {
    root?.unmount();
    root = undefined;
    appWu = undefined;
  },
});

In Vue, the same pattern uses createApp(App, { appWu: context.wu }), app.provide('appWu', context.wu), and app.unmount(). If you need live props, keep a reactive object and update it in the update slot.

Integrations included in the package

The following subpaths are part of the package:

Import Function
wu-framework/integrations/astro Factory for astro.config.mjs
wu-framework/integrations/astro/WuShell.astro Initializes shell apps
wu-framework/integrations/astro/WuApp.astro Mount slot, optionally lazy
wu-framework/integrations/astro/WuAppSSR.astro Server rendering and client mounting/hydration
wu-framework/integrations/next Next integration and package components

Import the integration and its components from wu-framework/integrations/astro. Astro wrapper types expose less configuration than WuCore; explicit client bootstrapping lets you control grants and startup order when the shell needs that additional configuration.

Separate adapters exist for React, Vue, Angular, Svelte, Preact, Solid, Lit, Vanilla, Alpine, Qwik, Stencil, HTMX, and Stimulus. Select the one for your application’s renderer and check its slots: mounting, live props, hydration, and context forwarding have specific contracts in each adapter.

Implementation references

Exports: package.json; React: src/adapters/react/index.js and src/adapters/react/index.js; Vue: src/adapters/vue/index.js and src/adapters/vue/index.js; Astro: integrations/astro/index.js, integrations/astro/WuShell.astro, integrations/astro/WuApp.astro. Tests: tests/unit/react-runtime-injection.test.js, tests/unit/adapter-conformance.test.js, tests/unit/adapter-slots.test.js, tests/unit/vue-slot-mount-target.test.js.

BUILD WU WITH US

Found a bug? Have an idea?

Your experience improves WU. Tell us what went wrong or help support its development.