From b55fd507a5ca43e5cbad93ef8af199eddcfbfda9 Mon Sep 17 00:00:00 2001 From: Mario Serrano Date: Mon, 20 Jul 2026 13:49:23 -0500 Subject: [PATCH 1/9] feat: new microfrontend component --- .../tools/dynamia/zk/ui/MicroFrontend.java | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 platform/ui/zk/src/main/java/tools/dynamia/zk/ui/MicroFrontend.java diff --git a/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/MicroFrontend.java b/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/MicroFrontend.java new file mode 100644 index 00000000..145c47a5 --- /dev/null +++ b/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/MicroFrontend.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2023 Dynamia Soluciones IT S.A.S - NIT 900302344-1 + * Colombia / South America + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package tools.dynamia.zk.ui; + +import org.zkoss.zk.ui.Page; +import org.zkoss.zk.ui.WrongValueException; +import org.zkoss.zk.ui.ext.AfterCompose; +import org.zkoss.zk.ui.ext.DynamicPropertied; +import org.zkoss.zk.ui.util.Clients; +import org.zkoss.zul.Div; +import tools.dynamia.commons.StringPojoParser; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * ZK component that embeds an external JavaScript bundle ("microfrontend") built with any + * framework (Vue, React, Svelte, plain JS, etc.) inside a ZUL page. + *

+ * The bundle is loaded once per {@code src} (loads are cached/shared client-side across every + * {@code MicroFrontend} instance on the page) and mounted using one of two strategies, selected + * with {@link #setMode(String)}: + *

+ * Any extra ZUL attribute not matching a bean property (via {@link DynamicPropertied}) is + * forwarded as a prop to the mounted microfrontend, e.g. {@code userId="${user.id}"}. + * + * Example: + *
{@code
+ * 
+ * }
+ * + * @author Mario A. Serrano Leones + */ +public class MicroFrontend extends Div implements DynamicPropertied, AfterCompose { + + /** Mounts the bundle's custom element (default mode). */ + public static final String MODE_CUSTOM_ELEMENT = "custom-element"; + /** Mounts the bundle through global {@code window[mountFn]/window[unmountFn]} functions. */ + public static final String MODE_MOUNT_FN = "mount-fn"; + + private String src; + private String type = "module"; + private String mode = MODE_CUSTOM_ELEMENT; + private String tag; + private String mountFn; + private String unmountFn; + private final Map props = new LinkedHashMap<>(); + private boolean composed; + + public MicroFrontend() { + setSclass("microfrontend"); + } + + /** + * Mounts the microfrontend once the component tree is composed. Property setters invoked + * while the ZUL page is still being parsed (e.g. {@code src}, {@code tag}, dynamic props such + * as {@code userId}) call {@link #mount()} eagerly to support runtime changes, but they are + * no-ops until this flag is set, so the initial mount happens exactly once, with every + * attribute already applied. + */ + @Override + public void afterCompose() { + composed = true; + mount(); + } + + public String getSrc() { + return src; + } + + /** + * Sets the URL of the JavaScript bundle to load, e.g. {@code /bundles/my-app.js}, and + * (re)mounts the microfrontend. + * + * @param src bundle URL + */ + public void setSrc(String src) { + this.src = src; + mount(); + } + + public String getType() { + return type; + } + + /** + * Sets the {@code + + + +
+ + diff --git a/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/MicroFrontend.java b/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/MicroFrontend.java index 145c47a5..1569db7d 100644 --- a/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/MicroFrontend.java +++ b/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/MicroFrontend.java @@ -18,8 +18,10 @@ import org.zkoss.zk.ui.Page; import org.zkoss.zk.ui.WrongValueException; +import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.ext.AfterCompose; import org.zkoss.zk.ui.ext.DynamicPropertied; +import org.zkoss.zk.ui.sys.ComponentCtrl; import org.zkoss.zk.ui.util.Clients; import org.zkoss.zul.Div; import tools.dynamia.commons.StringPojoParser; @@ -44,13 +46,68 @@ *
  • {@link #MODE_MOUNT_FN}: the bundle exposes global mount/unmount functions (a convention * similar to single-spa), invoked explicitly as {@code window[mountFn](container, props)} and * {@code window[unmountFn](container)} when this component is rendered and detached.
  • + *
  • {@link #MODE_AUTO}: for a bundle that self-mounts into a hardcoded selector at import + * time, as a default {@code vite build} of an app (not a custom-element library) does, e.g. + * {@code createApp(App).mount('#app')}. Selected automatically when {@link #setApp(String)} is + * used and neither {@link #setTag} nor {@link #setMountFn} is set: the {@code } markup + * of the discovered {@code index.html} (its mount target element(s), stripped of any + * {@code