A Brief Discussion on Remote Component Loading Solutions for Low‑Code Platforms
This article examines low‑code development platforms and presents three remote component loading strategies—global‑object, AMD (require.js), and ESModule—detailing their packaging, CDN deployment, loading logic, advantages, drawbacks, and a final recommendation for ESModule‑based loading in modern front‑end development.
Preface
Low‑code development platforms (LCDP) enable rapid application creation with little or no code by using visual drag‑and‑drop components and model‑driven logic. Many companies now build their own low‑code platforms to reduce cost and increase efficiency. This article shares a problem encountered during platform development and proposes solutions.
Problem
Low‑code platforms rely on a rich component library that must be loaded on demand. While tools like Webpack support code splitting, the platform’s core application is separate from its components, requiring remote loading of component code when a user selects a component.
Loading Solutions
Component Code Example (Vue)
Below is a simple Vue component (Component A) that will be used in the remote‑loading examples.
<template>
<div class="wp">{{text}}</div>
</template>
<script>
import { defineComponent, ref } from 'vue';
import _ from 'lodash';
export default defineComponent({
setup(props) {
console.log(_.get(props, 'a'));
return {
onAdd,
option,
size,
text: 'hello world',
};
},
});
</script>
<style>
.wp {
color: pink;
}
</style>Solution 1: Put on Global Object
Steps
Package the component as a UMD bundle and configure Webpack externals so that common dependencies are excluded.
Upload the generated JS file to a CDN.
When the component is needed, insert a <script> tag that assigns the component to a global object.
After the script loads, retrieve the component from the global object and register it with the Vue app.
Component Packaging
Add an entry file that attaches the component to window.share :
import Component from './index.vue';
if (!window.share) {
window.share = {};
}
window.share[Component.name] = Component;Then use Webpack to output a UMD bundle:
// Component packaging Webpack config
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
mode: 'production',
entry: path.resolve(__dirname, './comps/index.js'),
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
library: { type: 'umd' }
},
module: {
rules: [
{ test: /\.vue$/, use: 'vue-loader', exclude: /node_modules/ },
{ test: /\.js$/, loader: 'babel-loader' },
{ test: /\.css$/, use: ['vue-style-loader', 'css-loader'] }
]
},
plugins: [new VueLoaderPlugin()],
externals: { vue: 'vue', lodash: 'lodash' }
};HTML Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn/vue.global.js"></script>
<script src="https://cdn/[email protected]"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>Component Loading Logic
const loadComponent = (name) => new Promise((resolve) => {
const script = document.createElement('script');
script.src = `http://xxx/${name}.js`;
script.onload = script.onreadystatechange = function() { resolve(); };
document.querySelector('head').appendChild(script);
});
const addComp = async (name) => {
await loadComponent(name);
// register component, where app is the Vue instance
app.component(name, window.share[name]);
};
addComp('A');Drawbacks
All component dependencies must be loaded globally, requiring frequent changes to the HTML template.
The global object grows with each component, hurting load performance and not achieving true on‑demand loading.
Dependency version conflicts arise because only one version can exist on the global object.
Solution 2: AMD (Require.js)
Use Require.js as the AMD loader.
Steps
Package the component as UMD or AMD, keeping common dependencies external.
Upload the bundle to a CDN.
Load and register the component via Require.js when needed.
Component Packaging
No extra entry file is needed; the .vue file can be the entry point. Webpack configuration is similar to Solution 1, but the output library type is set to umd (which also supports AMD).
// Component packaging Webpack config (AMD)
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
mode: 'production',
entry: path.resolve(__dirname, './comps/index.vue'),
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'dist'),
library: { type: 'umd' } // supports AMD
},
module: { /* same rules as before */ },
plugins: [new VueLoaderPlugin()],
externals: { vue: 'vue', lodash: 'lodash' }
};HTML Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./require.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>Component Loading Logic
// main.js
requirejs.config({
baseUrl: 'https://cdn.xxx.com',
map: { '*': { css: 'require-css' } },
paths: {
echarts: '[email protected]',
vueDemo: 'vue-demo',
vue: '[email protected]',
moment: 'https://cdn/[email protected]'
},
shim: { 'ant-design-vue': ['css!https://cdn/[email protected]'] }
});
requirejs(['vue', 'vue-demo', 'vue-app'], function (vue, vueDemoModule, VueAppModule) {
const app = Vue.createApp(VueAppModule.default);
app.component('vue-demo', vueDemoModule.default);
app.mount('#app');
});Drawbacks
The platform code itself must also be compiled to AMD and uploaded, changing the release workflow.
Some third‑party libraries do not provide AMD/UMD builds, requiring custom conversion tools.
Advantages
Component dependencies can have different versions without conflict.
Both components and their dependencies can be truly loaded on demand.
Require.js offers built‑in CSS loading support.
Solution 3: ESModule
Steps
Package the component as an ES module (esm) while keeping common dependencies external.
Upload the generated JS file to a CDN.
Load and register the component with dynamic import() at runtime.
Component Packaging
Configure Webpack to output an ES module and map externals to CDN URLs.
import path from 'path';
import VueLoader from 'vue-loader';
const VueLoaderPlugin = VueLoader.VueLoaderPlugin;
const __dirname = path.resolve();
export default {
mode: 'development',
entry: path.resolve(__dirname, './src/vue-demo.vue'),
output: {
filename: 'vue-demo.esm.js',
path: path.resolve(__dirname, 'components'),
library: { type: 'module' }
},
experiments: { outputModule: true },
module: { /* same loaders as before */ },
plugins: [new VueLoaderPlugin()],
externals: {
vue: 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm-browser.js',
lodash: 'https://cdn.jsdelivr.net/npm/[email protected]/lodash.js'
}
};After bundling, imports of 'vue' are replaced with the CDN URL.
// input
import Vue from 'vue';
// output
import Vue from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm-browser.js';Component Loading Logic
const list = ref([]);
const addComp = async () => {
const VueDemo = await import(/* @vite-ignore */ `http://cdn/components/vue-demo.esm.js`);
window.app.component('vue-demo', VueDemo.default);
list.value.push({ key: new Date().valueOf(), name: 'vue-demo' });
};Vite Configuration
Alias the Vue import to the CDN version so that local development also uses the remote module.
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'vue': 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm-browser.js'
}
}
});Drawbacks
Browser compatibility: many features used by Webpack are not yet supported in all browsers.
Converting existing CommonJS packages to ES modules can be cumbersome, and many npm packages still ship only CommonJS code, requiring extra tooling.
Advantages
True on‑demand loading with a clean, modern syntax.
More elegant code structure compared with script‑injection approaches.
About Webpack Module Federation
Based on the author’s experience, Webpack Module Federation is better suited for micro‑frontend scenarios and is not ideal for low‑code platforms, though opinions may vary.
Conclusion
Solution 1 is the simplest to implement but does not achieve real on‑demand loading. Solutions 2 (Require.js) and 3 (ESModule) both provide true on‑demand loading; Require.js has fewer compatibility issues despite its age, while ESModule is the future direction thanks to tools like Vite and Snowpack. The author is currently migrating their internal low‑code platform to ESModule loading.
References
Require.js documentation – https://www.requirejs-cn.cn/
ESModule series – https://mp.weixin.qq.com/s/0AHmP70HnLUZeJWQlRtUKw
Require.js loading CSS – https://blog.csdn.net/lihefei_coder/article/details/81333036
政采云技术
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.