跳到主要内容前端微前端架构实践:避免巨石应用 | 极客日志JavaScriptNode.js大前端
前端微前端架构实践:避免巨石应用
探讨前端微前端架构如何解决巨石应用问题。针对代码量大、构建慢、协作难的痛点,介绍了三种主流方案:基于 Webpack 的 Module Federation、路由分发的 Single-SPA 以及主从架构的 Qiankun。通过拆分独立微应用,提升团队协作效率与构建速度。
技术博主1 浏览 前端微前端:别让你的应用变成巨石应用
背景与挑战
这应用做得跟巨石似的,想改个功能都得动全身。
各位前端同行,咱们今天聊聊前端微前端。别告诉我你还在维护一个巨大的单体应用,那感觉就像在没有分区的大房子里生活——能住,但乱得要命。
为什么你需要微前端
最近看到一个项目,代码量超过 100 万行,构建时间超过 10 分钟,团队协作困难。我就想问:你是在做应用还是在做代码仓库?
反面教材
import React from 'react';
import Header from './components/Header';
import Sidebar from './components/Sidebar';
import Dashboard from './components/Dashboard';
import Users from './components/Users';
import Products from './components/Products';
import Orders from './components/Orders';
import Settings from './components/Settings';
function App() {
return (
<div>
<Header />
<div>
< />
);
}
;
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Sidebar
<main>
<Dashboard />
<Users />
<Products />
<Orders />
<Settings />
</main>
</div>
</div>
export
default
App
点评:这代码,就像把所有东西都塞进一个大袋子里,管它有用没用。
正确姿势
1. Module Federation
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
dashboard: 'dashboard@http://localhost:3001/remoteEntry.js',
users: 'users@http://localhost:3002/remoteEntry.js',
products: 'products@http://localhost:3003/remoteEntry.js',
orders: 'orders@http://localhost:3004/remoteEntry.js',
settings: 'settings@http://localhost:3005/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
},
}),
],
};
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'dashboard',
filename: 'remoteEntry.js',
exposes: {
'./Dashboard': './src/Dashboard',
},
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
},
}),
],
};
import React, { lazy, Suspense } from 'react';
import Header from './components/Header';
import Sidebar from './components/Sidebar';
const Dashboard = lazy(() => import('dashboard/Dashboard'));
const Users = lazy(() => import('users/Users'));
const Products = lazy(() => import('products/Products'));
const Orders = lazy(() => import('orders/Orders'));
const Settings = lazy(() => import('settings/Settings'));
function App() {
return (
<div>
<Header />
<div>
<Sidebar />
<main>
<Suspense fallback={<div>加载中...</div>}>
<Dashboard />
<Users />
<Products />
<Orders />
<Settings />
</Suspense>
</main>
</div>
</div>
);
}
export default App;
2. Single-SPA
import { registerApplication, start } from 'single-spa';
registerApplication({
name: '@my-org/dashboard',
app: () => import('@my-org/dashboard'),
activeWhen: (location) => location.pathname.startsWith('/dashboard'),
});
registerApplication({
name: '@my-org/users',
app: () => import('@my-org/users'),
activeWhen: (location) => location.pathname.startsWith('/users'),
});
registerApplication({
name: '@my-org/products',
app: () => import('@my-org/products'),
activeWhen: (location) => location.pathname.startsWith('/products'),
});
registerApplication({
name: '@my-org/orders',
app: () => import('@my-org/orders'),
activeWhen: (location) => location.pathname.startsWith('/orders'),
});
registerApplication({
name: '@my-org/settings',
app: () => import('@my-org/settings'),
activeWhen: (location) => location.pathname.startsWith('/settings'),
});
start();
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import Dashboard from './Dashboard';
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: Dashboard,
errorBoundary(err, info, props) {
return <div>应用出错了</div>;
},
});
export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;
3. Qiankun
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'dashboard',
entry: '//localhost:3001',
container: '#micro-app-container',
activeRule: '/dashboard',
},
{
name: 'users',
entry: '//localhost:3002',
container: '#micro-app-container',
activeRule: '/users',
},
{
name: 'products',
entry: '//localhost:3003',
container: '#micro-app-container',
activeRule: '/products',
},
{
name: 'orders',
entry: '//localhost:3004',
container: '#micro-app-container',
activeRule: '/orders',
},
{
name: 'settings',
entry: '//localhost:3005',
container: '#micro-app-container',
activeRule: '/settings',
},
]);
start();
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<Router>
<div>
<nav>
<Link to="/dashboard">仪表盘</Link>
<Link to="/users">用户</Link>
<Link to="/products">产品</Link>
<Link to="/orders">订单</Link>
<Link to="/settings">设置</Link>
</nav>
<div></div>
</div>
</Router>
);
}
export default App;
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
let instance = null;
function render(props) {
const { container } = props;
instance = createApp(App);
instance.use(router);
instance.mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render({});
}
export async function bootstrap() {
console.log('dashboard bootstraped');
}
export async function mount(props) {
console.log('dashboard mounted', props);
render(props);
}
export async function unmount() {
console.log('dashboard unmounted');
instance.unmount();
instance = null;
}
总结:这才叫前端微前端,将应用拆分成多个独立的微应用,团队协作更高效,构建时间更短,再也不用担心巨石应用的问题了。