前端微前端:别让你的应用变成巨石应用

前端微前端:别让你的应用变成巨石应用

毒舌时刻

这应用做得跟巨石似的,想改个功能都得动全身。

各位前端同行,咱们今天聊聊前端微前端。别告诉我你还在维护一个巨大的单体应用,那感觉就像在没有分区的大房子里生活——能住,但乱得要命。

为什么你需要微前端

最近看到一个项目,代码量超过 100 万行,构建时间超过 10 分钟,团队协作困难。我就想问:你是在做应用还是在做代码仓库?

反面教材

// 反面教材:单体应用 // App.jsx 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> <Sidebar /> <main> <Dashboard /> <Users /> <Products /> <Orders /> <Settings /> </main> </div> </div> ); } export default App; 

毒舌点评:这代码,就像把所有东西都塞进一个大袋子里,管它有用没用。

正确姿势

1. Module Federation

// 正确姿势:Module Federation // 1. 安装依赖 // npm install webpack@5 // 2. 配置 webpack // webpack.config.js 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', }, }, }), ], }; // 3. 远程应用配置 // dashboard/webpack.config.js 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', }, }, }), ], }; // 4. 主机应用 // App.jsx 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

// 正确姿势:Single-SPA // 1. 安装依赖 // npm install single-spa single-spa-react // 2. 注册应用 // src/registerApplications.js 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(); // 3. 微应用配置 // packages/dashboard/src/index.js 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

// 正确姿势:Qiankun // 1. 安装依赖 // npm install qiankun // 2. 注册应用 // src/main.js 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(); // 3. 主应用 // src/App.jsx 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; // 4. 微应用配置 // packages/dashboard/src/main.js 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; } 

毒舌点评:这才叫前端微前端,将应用拆分成多个独立的微应用,团队协作更高效,构建时间更短,再也不用担心巨石应用的问题了。

Read more

致创作的第365天:从Flowable起步,在软考与AIGC中寻找技术人的星辰大海

致创作的第365天:从Flowable起步,在软考与AIGC中寻找技术人的星辰大海

大家好,我是 BOB-wangbaohai。 今天清晨登录后台,收到了一张特别的官方通知卡片。它静静地提醒我:今天,是我成为创作者的第 365 天。 看着屏幕上的“1周年”字样,思绪瞬间被拉回到了去年的今天——2025年4月8日。在那看似平凡的一天里,我敲下了自己博客生涯的第一行文字:《Flowable7.x学习笔记(一)基础环境准备》。 当时的我,只是单纯想把项目中用到工作流引擎的踩坑经验沉淀下来。未曾想,那篇为了“备忘”而写的文章,最终生长成了包含 23篇文章 的专栏,也彻底推开了我技术写作的大门。 回望这 365 天:6个专栏,75个脚印 今天点开后台的“专栏管理”,看着这一行行数据,这 365 天的技术轨迹变得无比清晰。作为一名在日常工作中需要统筹全局的系统架构师,这75篇文章,不仅是技术分享,更是我对自己职业生涯的一次次复盘与死磕。 1. 夯实底座:死磕理论与架构的日与夜 大家如果关注我的专栏,

VSCode 中精准禁用 Copilot 代码补全:按语言与场景灵活配置

1. 为什么需要精准控制 Copilot 代码补全 作为一个用了 VSCode 和 Copilot 好几年的开发者,我深刻体会到 AI 代码补全的双刃剑效应。刚开始用 Copilot 的时候,那种"它怎么知道我要写什么"的惊喜感真的很棒,但后来我发现,在某些场景下,这种自动补全反而会成为负担。 比如我在刷算法题的时候,刚写了个函数名,Copilot 就直接把整个实现都给我补全了。这还训练什么?完全达不到练习的目的。还有时候在写一些特定语言的代码,Copilot 的补全风格和团队规范不一致,每次都要手动调整,反而增加了工作量。 更让我头疼的是在不同项目间切换的时候。有些项目我希望充分利用 Copilot 提高效率,有些项目则需要完全自己动手写代码。如果每次都去全局开关 Copilot,那也太麻烦了。 其实 Copilot 的设计团队早就想到了这些场景,他们在 VSCode 中提供了非常精细的控制方式。不只是简单的开和关,你可以按编程语言禁用,

Copilot 的agent、ask、edit、plan模式有什么区别

Copilot 的 ask、edit、agent、plan 四种模式,核心区别在于权限范围、操作主动性、代码修改权限、适用场景,以下从定义、工作机制、核心特点、典型场景与操作流程展开,帮你快速区分并选对模式。 一、核心区别速览(表格版) 二、分模式详细解析 1. Ask 模式:纯问答与代码理解 * 工作机制:基于当前文件 / 选中代码的上下文,回答自然语言问题,不修改任何代码,仅输出文字解释、建议或思路。 * 典型用法: * 解释某段代码逻辑(如 “这段 Python 函数做了什么”); * 咨询技术方案(如 “如何在 Go 中实现重试机制”); * 调试思路(如 “这个死循环可能的原因”)。 * 关键特点:安全无风险,适合学习、快速澄清和非修改类咨询。

ChatGLM-6B智能写作助手开发指南

ChatGLM-6B智能写作助手开发指南 1. 引言 你有没有过这样的经历?面对空白的文档,脑子里有无数想法,但就是不知道从何下笔。写工作报告时,总觉得语言干巴巴的,缺乏感染力;写营销文案时,绞尽脑汁也想不出吸引人的标题;写技术文档时,又担心表达不够专业准确。 如果你也有这些困扰,那么今天要聊的这个话题可能会让你眼前一亮。基于ChatGLM-6B开发一个智能写作助手,听起来可能有点技术含量,但实际上并没有想象中那么复杂。这个助手不仅能帮你生成各种文体的内容,还能检查语法错误、优化表达风格,甚至根据你的需求调整语气和长度。 我最近就在自己的项目中尝试了这套方案,用下来感觉确实能节省不少时间。特别是那些重复性的写作任务,比如写产品介绍、整理会议纪要、生成邮件模板等等,现在基本上交给助手就能搞定,我只需要做最后的润色和调整。 接下来,我就详细分享一下如何从零开始搭建这样一个智能写作助手,包括环境部署、功能开发、实际应用等各个环节。无论你是开发者想要集成写作功能,还是内容创作者想要提升效率,相信都能从中找到有用的信息。 2. ChatGLM-6B模型简介 在开始动手之前,我们