Developing Cross‑Framework UI Components with Native JavaScript and Iframe Containers
This article explains how to design and implement reusable UI components that work across jQuery, React, and Vue by using a native‑JS container, iframe isolation, and communication techniques such as domain sharing, postMessage alternatives, and Nginx proxying, while providing code examples and practical tips.
Background
The author’s backend team needs to supply UI components to multiple business lines that use different frameworks (jQuery, React, Vue). Maintaining separate implementations for each framework leads to duplicated effort and costly upgrades.
Each framework requires a separate development cycle, consuming time and resources.
Component upgrades force all downstream projects to release new versions, increasing communication overhead and slowing iteration.
Ideal Component
Cross‑framework: Write once, run everywhere
Minimal upgrades: the component should require little or no changes on the consumer side after an update.
Implementation Plan
The simplest idea is to write the component in pure JavaScript, avoiding any framework dependencies.
Native Implementation
Advantages:
Cross‑framework: no reliance on a specific library.
Lightweight: small bundle size.
Disadvantages:
Low ROI: building a full set of UI utilities from scratch is time‑consuming.
Compatibility pitfalls: extensive browser‑compatibility testing is required.
Limited for complex interactions.
Suitable scenarios include simple UI such as static headers or side menus.
Native Container Component + Iframe Loading Business Logic
The solution splits a component into two parts:
Container component – a native‑JS wrapper that collects parameters, registers global callbacks, mounts the component, and loads an iframe.
Business‑logic component – loaded inside the iframe, can be built with any framework because the iframe provides sandboxing.
Benefits: the container handles stable logic while the iframe isolates ever‑changing business code, reducing the need for consumer upgrades.
How to Implement
The overall flow is illustrated in a diagram (omitted). The container component performs the following steps:
Initialization
function getTopLevelDomain(host) {
let data = host || window.location.host;
return data.split('.').slice(-2).join('.');
}
function setDomainToTopLevelDomain() {
try {
window.document.domain = getTopLevelDomain();
} catch (error) {
console.error("设置domain失败");
}
}Render
class Vanilla {
// 获取配置信息
constructor(config) {
const options = { ...defaultConfig, ...config };
this.options = options;
this.elCls = options.elCls;
}
// 生成容器 div
render() {
const div = document.createElement('div');
this.el = div;
const { width, height } = this.options;
div.className = `${prefixCls}-wrap ${prefixCls}-wrap-loading ${this.elCls || ''}`;
const maskNode = getMaskNode(prefixCls);
const iframeNode = getIframeNode(prefixCls, width, height);
div.innerHTML = maskNode + iframeNode;
document.body.appendChild(div);
this.fn();
}
init() {
setDomainToTopLevelDomain();
this.render();
this.initCallbacks();
}
...
}Register Callbacks
class Vanilla {
...
initCallbacks() {
const self = this;
const options = this.options;
window[paramsName] = options;
window.onSuccess = function onSuccess(data, res) {
options.onSuccess && options.onSuccess(data, res);
setTimeout(() => { self.removeNode(); }, 1500);
self.resetCallbacks && self.resetCallbacks();
};
window.onCancel = function onCancel() {
options.onCancel && options.onCancel();
self.removeNode();
self.resetCallbacks && self.resetCallbacks();
};
window.onError = function onError(data) {
options.onError && options.onError(data);
};
}
}Load Iframe
let timer = function timer() {};
class Vanilla {
...
fn() {
const { width, height, isAutoSize } = this.options;
const el = this.el;
const url = getContentUrl('你的iframe地址');
const iframeEle = el.querySelector('.J_CreditIframe');
const modalNode = el.querySelector(`.${prefixCls}`);
if (!isAutoSize && (iframeWidth !== width || iframeHeight !== height)) {
this.setNodeSizeAndPostion(modalNode, iframeEle, iframeWidth, iframeHeight);
}
iframeEle.setAttribute('src', url);
addEvent(iframeEle, 'load', () => {
el.className = `${prefixCls}-wrap ${this.elCls || ''}`;
// auto‑size logic omitted for brevity
});
}
setNodeSizeAndPostion(container, iframe, width, height) {
container.style.cssText = `width:${width}px;height:${height}px;margin-left:-${width/2}px;margin-top:-${height/2}px;`;
iframe.style.cssText = `width:${width}px;height:${height}px;`;
}
removeNode() {
timer && clearInterval(timer);
if (this.el) {
document.body.removeChild(this.el);
}
}
...
}Communication between the container and the business logic component is achieved via global callbacks. When the domains differ, three strategies are discussed:
postMessage – not used because IE9 support is required.
Document.domain + iframe – works when both sides share the same top‑level domain.
Nginx proxy – reverse‑proxy the iframe URL to a unified domain, enabling cross‑origin access.
// Nginx static page location
location ~ ^/your-project/ {
root /opt/front/your-project/;
try_files $uri $uri/ /index.html =404;
access_log off;
}
// Reverse proxy for API
location ~ ^/api/service/(.*)$ {
proxy_pass http://your-ip;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header requestId $request_id;
proxy_http_version 1.1;
proxy_set_header Connection "";
expires 30d;
access_log off;
}Key Points to Note
Handle non‑white rounded corners by letting the iframe set its own background.
Version control: small patches stay backward compatible; major releases can be switched via dynamic iframe URLs.
Conclusion
Repeated component development across multiple frameworks stems from historical technical debt. The most effective long‑term solution is to unify the tech stack or adopt micro‑frontend architecture. The presented approach offers a practical way to serve heterogeneous front‑ends today while keeping upgrade friction low.
政采云技术
ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.