1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! Producer SDK for landing-host microfrontends.
//!
//! A microfrontend producer ships a self-registering ESM bundle: it defines a
//! custom element (`customElements.define("mfe-…", …)`) whose `connectedCallback`
//! mounts a Dioxus app into the host node (light DOM). The landing host curates a
//! registry of `{name, tag, kind}` and decides where to render each `<tag>`.
//!
//! This module is the entire Rust side of that contract. A producer crate writes
//! ~5 lines via [`mfe!`] and gets: the custom-element registration, the origin
//! self-derivation (so the bundle works cross-origin with no baked env), the
//! server-fn base override, and the `{name,tag,kind}` manifest — none of which it
//! can get wrong, because the macro owns the naming convention and the JSON shape.
//!
//! wasm-only (see the `cfg` in `lib.rs`): the one cross-origin bundle is always a
//! `wasm32-unknown-unknown` cdylib. manganis/`dx` are unusable here — their asset
//! URLs are root-relative to the *serving* origin, i.e. inherently same-origin —
//! so producers hand-roll `wasm-bindgen --target web` and this SDK stays
//! manganis-free, deriving every URL from the bundle's own origin instead.
use Element;
use *;
// The only JavaScript in the whole stack — the two things Rust can't express:
// subclassing `HTMLElement` (custom elements must extend it), and reading
// `import.meta.url` (a module-syntax form, not a value any Rust binding sees).
//
// `connectedCallback` fires on every (re)attachment, so it is guarded to mount
// once per element instance: a host router that detaches and re-inserts the node
// would otherwise stack a second live app on the first one's DOM. There is no
// paired `disconnectedCallback` because dioxus-web 0.7 exposes no teardown handle
// (`run() -> !` inside a bare `spawn_local`); the app therefore stays alive across
// a detach and is reused — with its DOM and its delegated listeners intact — when
// the element is re-inserted.
extern "C"
/// The bundle's own origin, from `import.meta.url`. Drives every URL the bundle
/// emits (server-fn base, stylesheet, seed images) so nothing is baked at build
/// time and the same artifact works behind any host origin.
/// Define `tag` as a custom element that calls `mount(element)` on its first
/// connect. Idempotent twice over: a no-op if `tag` is already registered, and
/// `mount` runs at most once per element instance, so re-attaching an element
/// (as an SPA host router does on navigate-back) reuses the running app instead
/// of stacking a second one. The `mount` closure must outlive the page — the
/// caller `forget`s it.
/// Launch a Dioxus app rooted at `el` (the custom element instance). Light DOM —
/// the host's fonts/tokens/preflight cascade in, so the bundle must not re-ship them.
/// Define a microfrontend producer bundle. Expands in the producer crate (so its
/// `dioxus` rsx, `wasm-bindgen`, and `web-sys` resolve there), generating the
/// custom-element registration, the `wasm-bindgen(start)` entrypoint, and the
/// `MFE_MANIFEST` the build emits as `mfe.json`.
///
/// ```ignore
/// ev_lib::mfe! {
/// service: "real-estate", name: "overview", kind: component,
/// root: real_estate_allocation::embed::Overview, stylesheet: "mfe.css"
/// }
/// ```
///
/// Naming is enforced: the tag is `mfe-{service}-{name}` and the registry name is
/// `{service}.{name}` — the host can't drift from the producer's identity. One
/// invocation per crate (it owns the single `wasm-bindgen(start)`).