如何实现多个应用之间的资源共享?
之前比较多的处理方式是npm包形式抽离和引用,比如多个应用项目之间,可能有某业务逻辑模块或者其他是可复用的,便抽离出来以npm包的形式进行管理和使用。但这样却带来了以下几个问题:
这些问题让我们意识到,扩展前端开发规模以便于多个团队可以同时开发一个大型且复杂的产品是一个重要但又棘手的难题。
因此,早在2016年,微前端概念诞生了。
Micro Frontends: https://micro-frontends.org/ 官网定义了微前端概念:
Techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently.
从 Micro Frontends 官网可以了解到,微前端概念是从微服务概念扩展而来的,摒弃大型单体方式,将前端整体分解为小而简单的块,这些块可以独立开发、测试和部署,同时仍然聚合为一个产品出现在客户面前。可以理解微前端是一种将多个可独立交付的小型前端应用聚合为一个整体的架构风格。
值得留意的几个点:
对比了npm包方式抽离,让我们意识到更新流程和效率的重要性。微前端由于是多个子应用的聚合,如果多个业务应用依赖同一个服务应用的功能模块,只需要更新服务应用,其他业务应用就可以立马更新,从而缩短了更新流程和节约了更新成本。
迁移是一项非常耗时且艰难的任务,比如有一个管理系统使用 AngularJS 开发维护已经有三年时间,但是随时间的推移和团队成员的变更,无论从开发成本还是用人需求上,AngularJS 已经不能满足要求,于是团队想要更新技术栈,想在其他框架中实现新的需求,但是现有项目怎么办?直接迁移是不可能的,在新的框架中完全重写也不太现实。
使用微前端架构就可以解决问题,在保留原有项目的同时,可以完全使用新的框架开发新的需求,然后再使用微前端架构将旧的项目和新的项目进行整合。这样既可以使产品得到更好的用户体验,也可以使团队成员在技术上得到进步,产品开发成本也降到的最低。
在目前的单页应用架构中,使用组件构建用户界面,应用中的每个组件或功能开发完成或者bug修复完成后,每次都需要对整个产品重新进行构建和发布,任务耗时操作上也比较繁琐。
在使用了微前端架构后,可以将不能的功能模块拆分成独立的应用,此时功能模块就可以单独构建单独发布了,构建时间也会变得非常快,应用发布后不需要更改其他内容应用就会自动更新,这意味着你可以进行频繁的构建发布操作了。
因为微前端构架与框架无关,当一个应用由多个团队进行开发时,每个团队都可以使用自己擅长的技术栈进行开发,也就是它允许适当的让团队决策使用哪种技术,从而使团队协作变得不再僵硬。
自组织模式:通过约定进行互调,但会遇到处理第三方依赖等问题。
基座模式:通过搭建基座、配置中心来管理子应用。如基于SIngle Spa的偏通用的乾坤方案,也有基于本身团队业务量身定制的方案。
去中心模式:脱离基座模式,每个应用之间都可以彼此分享资源。如基于Webpack 5 Module Federation实现的EMP微前端方案,可以实现多个应用彼此共享资源分享。
其中,目前值得关注是去中心模式中的EMP微前端方案,既可以实现跨技术栈调用,又可以在相同技术栈的应用间深度定制共享资源,如果刚开始调研微前端的话,可以先尝试了解一下EMP微前端方案,或许会给你带来不错的使用体验
Systemjs:https://github.com/systemjs/systemjs
在微前端架构中,微应用被打包为模块,但浏览器不支持模块化,需要使用 systemjs 实现浏览器中的模块化。
systemjs 是一个用于实现模块化的 JavaScript 库,有属于自己的模块化规范。
在开发阶段我们可以使用 ES 模块规范,然后使用 webpack 将其转换为 systemjs 支持的模块。
案例:通过 webpack 将 react 应用打包为 systemjs 模块,在通过 systemjs 在浏览器中加载模块
npm install webpack@5.17.0 webpack-cli@4.4.0 webpack-dev-server@3.11.2 html-webpack-plugin@4.5.1 @babel/core@7.12.10 @babel/cli@7.12.10 @babel/preset-env@7.12.11 @babel/preset-react@7.12.10 babel-loader@8.2.2
package.json
{
"name": "systemjs-react",
"scripts": {
"start": "webpack serve"
},
"dependencies": {
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"babel-loader": "^8.2.2",
"html-webpack-plugin": "^4.5.1",
"webpack": "^5.17.0",
"webpack-cli": "^4.4.0",
"webpack-dev-server": "^3.11.2"
}
}
webpack.config.js
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
mode: "development",
entry: "./src/index.js", // 入口
output: { // 出口
// 打包目录及文件
path: path.join(__dirname, "build"),
filename: "index.js",
// 指定构建时所需要的库
libraryTarget: "system"
},
devtool: "source-map",
// 服务器运行配置
devServer: {
port: 9000, // 端口
// 静态资源文件夹
contentBase: path.join(__dirname, "build"),
historyApiFallback: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
// 对应语法转换
presets: ["@babel/preset-env", "@babel/react"]
}
}
}
]
},
plugins: [ // 插件
new HtmlWebpackPlugin({
/* 打包时,不需要自动引入JS文件(<script> 标签) */
inject: false,
/* 使用微前端的方式,我们需要自己加载对应的 JS 文件 */
template: "./src/index.html"
})
],
// 添加打包排除选项,微前端中需要使用公共的 React ,打包是不需要的
externals: ["react", "react-dom", "react-router-dom"]
}
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>systemjs-react</title>
<!-- 按照 systemjs 模块化的方式引入React框架应用 -->
<script type="systemjs-importmap">
{
"imports": {
"react": "https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js",
"react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom.min.js"
}
}
</script>
<!-- systemjs 库 -->
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.0/dist/system.min.js"></script>
</head>
<body>
<div id="root"></div>
<script>
// 按照 systemp 的方式,引入具体应用
System.import("./index.js")
</script>
</body>
</html>
src/index.js
import React from "react"
import ReactDom from "react-dom"
import App from './App.js'
ReactDom.render(<App />, document.getElementById("root"))
src/App.js
import React from "react"
export default function App(){
return <div>React micro for systemjs</div>
}
https://zh-hans.single-spa.js.org/
single-spa:https://single-spa.js.org/ 是一个实现微前端架构的框架。
在 single-spa 框架中有三种类型的微前端应用:
安装 single-spa 脚手架工具:npm install create-single-spa@2.0.3 -g
创建微前端容器应用:create-single-spa
@组织名称/应用名称
,比如 @study/todos
4. 启动应用:cd ./singletest && npm start
5. 访问应用:localhost:9000
src/xx-root-config.js
// 从框架中引入 两个 方法,下面调用
import { registerApplication, start } from "single-spa";
/*
注册微前端应用
1. name: 字符串类型, 微前端应用名称 "@组织名称/应用名称"
2. app: 函数类型, 返回 Promise, 通过 systemjs 引用打包好的微前端应用模块代码 (umd)
3. activeWhen: 路由匹配时激活应用
*/
registerApplication({
name: "@single-spa/welcome",
app: () =>
System.import(
"https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
),
activeWhen: ["/"],
});
// 当前组织的微应用引入示例
// registerApplication({
// name: "@xl/navbar",
// app: () => System.import("@xl/navbar"),
// activeWhen: ["/"]
// });
// start 方法必须在 single spa 的配置文件中调用
// 在调用 start 之前, 应用会被加载, 但不会初始化, 挂载或卸载.
start({
// 是否可以通过 history.pushState() 和 history.replaceState() 更改触发 single-spa 路由
// true 不允许 false 允许 (先了解,有印象)
urlRerouteOnly: true,
});
index.ejs
<body>
<main></main>
<!-- 导入微前端容器应用 -->
<script>
System.import('@xl/root-config');
</script>
<!--
import-map-overrides 可以覆盖导入映射
当前项目中用于配合 single-spa Inspector 调试工具使用.
可以手动覆盖项目中的 JavaScript 模块加载地址, 用于调试.
-->
<import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>
<!-- 用于支持 Angular 应用 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/zone.js@0.11.3/dist/zone.min.js"></script> -->
<!-- 用于覆盖通过 import-map 设置的 JavaScript 模块下载地址 -->
<script src="https://cdn.jsdelivr.net/npm/import-map-overrides@2.2.0/dist/import-map-overrides.js"></script>
<!-- 判断是否是本地 -->
<% if (isLocal) { %>
<!-- 模块加载器 -->
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.js"></script>
<!-- systemjs 用来解析 AMD 模块的插件 -->
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/amd.js"></script>
<% } else { %>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/system.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.8.3/dist/extras/amd.min.js"></script>
<% } %>
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js"
}
}
</script>
<!-- single-spa 预加载 -->
<link rel="preload" href="https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js" as="script">
<!-- Add your organization's prod import map URL to this script's src -->
<!-- <script type="systemjs-importmap" src="/importmap.json"></script> -->
<!-- JavaScript 模块下载地址 此处可放置微前端项目中的公共模块 -->
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"@xl/root-config": "//localhost:9000/xl-root-config.js"
}
}
</script>
<% } %>
创建应用:create-single-spa
,注意组织及项目名字,后面注册微应用是会用到
修改应用端口 && 启动应用
{
"scripts": {
"start": "webpack serve --port 9002",
}
}
启动应用: npm start
将 React 项目的入口文件注册到基座应用 (容器应用) 中
\container\src\study-root-config.js :
// React -- todos
registerApplication({
name: "@study/todos",
app: () => System.import("@study/todos"),
activeWhen: ["/todos"]
});
指定微前端应用模块的引用地址:
(可以直接访问对应应用服务器,有提示 URL 加载地址)
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"@study/root-config": "//localhost:9000/study-root-config.js",
"@study/todos": "//localhost:9002/study-todos.js"
}
}
</script>
<% } %>
指定公共库的访问地址,默认情况下,应用中的 react 和 react-dom 没有被 webpack 打包, single-spa 认为它是公共库,不应该单独打包。
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js",
"react": "https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@17.0.1/umd/react-dom.production.min.js"
}
}
</script>
修改默认应用代码,已独立页面展示应用内容
container\src\study-root-config.js
// registerApplication({
// name: "@single-spa/welcome",
// app: () =>
// System.import(
// "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
// ),
// activeWhen: ["/"],
// });
// 修改默认应用注册方式,独立页面展示应用内容
registerApplication(
"@single-spa/welcome",
() => System.import(
"https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
),
location=>location.pathname === '/'
);
micro\container\src\index.ejs
<body>
<main></main>
<h2>
<!-- 指定应用展示位置 -->
<div id="myreact"></div>
</h2>
<script>
System.import('@study/root-config');
</script>
<import-map-overrides-full show-when-local-storage="devtools" dev-libs></import-map-overrides-full>
</body>
micro\todos\src\study-todos.js
const lifecycles = singleSpaReact({
React,
ReactDOM,
// 渲染根组件
rootComponent: Root,
// 错误边界函数
errorBoundary(err, info, props) {
// Customize the root error boundary for your microfrontend here.
return null;
},
// 指定根组件的渲染位置
domElementGetter:()=>document.getElementById('myreact')
});
micro\todos\src\study-todos.js
import React from "react";
import ReactDOM from "react-dom";
// single-spa-react 用于创建使用 React 框架实现的微前端应用
import singleSpaReact from "single-spa-react";
// 用于渲染在页面中的根组件 就相当于传统React应用的App.js文件
import Root from "./root.component";
const lifecycles = singleSpaReact({
React,
ReactDOM,
// 渲染根组件
rootComponent: Root,
// 错误边界函数
errorBoundary(err, info, props) {
// Customize the root error boundary for your microfrontend here.
// return null;
return ()=> <div>发生错误时此处内容将会被渲染</div>
},
// 指定根组件的渲染位置
domElementGetter:()=>document.getElementById('myreact')
});
// 暴露必要的生命周期函数
export const { bootstrap, mount, unmount } = lifecycles;
准备好两个路由组件
micro\todos\src\home.js && micro\todos\src\about.js
import React, { Component } from 'react'
export default class home extends Component {
render() {
return (
<div>
<h2>什么是快乐星球</h2>
</div>
)
}
}
=========我是两个组件之间的秀丽华美分割线=============
import React from 'react'
function about() {
return (
<div>
<h2>快乐星球就是学习微前端</h2>
</div>
)
}
export default about
micro\todos\src\root.component.js
import React from "react";
// 引入路由相关组件
import {BrowserRouter, Switch, Route, Redirect, Link} from "react-router-dom"
// 引入组件
import Home from './home'
import About from './about'
export default function Root(props) {
// return <section>{props.name} is mounted! && 拉勾大前端</section>;
return (
// 使用路由组件,设计基础路由路径
<BrowserRouter basename="/todos">
<div>{props.name}</div>
{/* 设置点击链接,跳转路由 */}
<div>
<Link to="/home">Home</Link> |
<Link to="/about">About</Link>
</div>
{/* 路由展示 */}
<Switch>
<Route path="/home">
<Home />
</Route>
<Route path="/about">
<About></About>
</Route>
<Route path="/">
{/* 路由重定向 */}
<Redirect to="/home"></Redirect>
</Route>
</Switch>
</BrowserRouter>
)
}
路由文件已公共模块引入,\micro\container\src\index.ejs
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js",
"react": "https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@17.0.1/umd/react-dom.production.min.js",
"react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom.min.js"
}
}
</script>
修改 webpack 配置文件,排除路由模块打包,micro\todos\webpack.config.js
return merge(defaultConfig, {
// modify the webpack config however you'd like to by adding to this object
externals: ["react-router-dom"]
});
创建应用:create-single-spa
因为 vue && vue-router 需要通过公共模块打包,所以,在应用内部需要配置不打包
micro\realworld\vue.config.js
module.exports = {
chainWebpack:config=>{
// 配置不打包 Vue 及 vue-router
config.externals(["vue","vue-router"])
}
}
修改项目启动命令:micro\realworld\package.json
"scripts": {
"serve": "vue-cli-service serve",
"start": "vue-cli-service serve --port 9003",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"serve:standalone": "vue-cli-service serve --mode standalone"
},
注册应用:micro\container\src\study-root-config.js
// Vue -- todos
registerApplication({
name: "@study/realworld",
app: () => System.import("@study/realworld"),
activeWhen: ["/realworld"],
});
micro\container\src\index.ejs
加载 vue && vue-router
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.9.0/lib/system/single-spa.min.js",
"react": "https://cdn.jsdelivr.net/npm/react@17.0.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@17.0.1/umd/react-dom.production.min.js",
"react-router-dom": "https://cdn.jsdelivr.net/npm/react-router-dom@5.2.0/umd/react-router-dom.min.js",
"vue": "https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js",
"vue-router": "https://cdn.jsdelivr.net/npm/vue-router@3.0.7/dist/vue-router.min.js"
}
}
</script>
导入应用,应用地址可以直接访问应用后,在浏览器的提示中获取;
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"@study/root-config": "//localhost:9000/study-root-config.js",
"@study/todos": "//localhost:9002/study-todos.js",
"@study/realworld": "//localhost:9003/js/app.js"
}
}
</script>
<% } %>
\micro\realworld\src\main.js
import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
import App from './App.vue';
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 路由组件
const Foo = { template: "<div>Foooooo</div>" }
const Bar = { template: "<div>Barrrrr</div>" }
// 路由规则
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar },
]
// 路由实例
const router = new VueRouter({ routes, mode: "history", base: "/realworld" })
Vue.config.productionTip = false;
const vueLifecycles = singleSpaVue({
Vue,
appOptions: {
// 注册路由
router,
render(h) {
return h(App, {
props: {
// 组件传参
},
});
},
},
});
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;
micro\realworld\src\App.vue
<template>
<div id="app">
<div>
<router-link to="/foo">Goto Foo</router-link> |
<router-link to="/bar">Goto Bar</router-link>
</div>
<div>
<router-view></router-view>
</div>
</div>
</template>
用于放置跨应用共享的 JavaScript 逻辑,它也是独立的应用,需要单独构建单独启动。
create-single-spa
in-browser utility module (styleguide, api cache, etc)
导出公共方法 : micro\tools\src\study-tools.js
export function happyStar(who){
console.log(`${who} hahahhahahhah`)
return 'happy star 之 快乐的源泉'
}
在模板文件中声明应用模块访问地址 : micro\container\src\index.ejs
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"@study/root-config": "//localhost:9000/study-root-config.js",
"@study/todos": "//localhost:9002/study-todos.js",
"@study/realworld": "//localhost:9003/js/app.js",
"@study/tools": "//localhost:9005/study-tools.js"
}
}
</script>
<% } %>
MicroFrontends\micro\todos\src\about.js
import React, { useEffect, useState } from "react";
// 自定义钩子函数
function useToolsModule() {
const [toolsModule, setToolsModule] = useState();
useEffect(() => {
// 导入,异步promise返回
System.import("@study/tools").then(setToolsModule);
}, []);
return toolsModule;
}
function about() {
var back = "";
// 调用钩子函数
const toolsModule = useToolsModule();
if (toolsModule) {
// 调用共享逻辑的方法
back = toolsModule.happyStar("React todo");
}
return (
<div>
<h2>快乐星球就是学习微前端--{back}</h2>
</div>
);
}
export default about;
micro\realworld\src\main.js
// 路由组件
// const Foo = { template: "<div>猜不到吧</div>" };
import Foo from './components/Foo'
const Bar = { template: "<div>还是快乐星球啊哈哈哈哈</div>" };
micro\realworld\src\components\Foo.vue
<template>
<div>
<h2>什么是快乐星球 {{ msg }}</h2>
<button @click="getHappy">点我获取答案</button>
</div>
</template>
<script>
export default {
data() {
return {
msg: "",
};
},
methods: {
async getHappy() {
let toolsModule = await window.System.import("@study/tools");
this.msg = toolsModule.happyStar("Vue");
},
},
};
</script>
<style>
</style>
基础应用代码安装
// webpack5
npm install webpack webpack-cli html-webpack-plugin css-loader style-loader babel-loader @babel/core @babel/preset-env @babel/preset-react webpack-dev-server -D
npm install react react-dom
基础代码:
// ======insex.html========
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RootReact</title>
</head>
<body>
<!-- 加一行注释 -->
<div id="root"></div>
</body>
</html>
// ======insex.js========
import React from "react"
import ReactDom from "react-dom"
import App from "./App"
ReactDom.render(<App/>,document.getElementById('root'))
// ======App.js===========
import React from "react"
import User from "./User"
let App = () => {
return (
<div>
<h3>webpack55</h3>
<User/>
</div>
)
}
export default App;
// ===== User.js==========
import React from "react"
const User = ()=>{
return (
<div>
UserList
</div>
)
}
export default User
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// entry 入口,output出口,module模块,plugins 插件 mode工作模式,devServer开发服务器
// mode 工作模式
mode: 'development', // production 、 development、none
// 入口
entry: './src/index.js',
// 出口
output: {
filename: './bundle.js',
path: path.resolve(__dirname, 'dist')
},
// 模块
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react'
]
}
}
]
},
]
},
// 插件
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
],
// 服务器
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 3000,
open: true
},
}
package.json 启动命令:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build":"webpack",
"start":"webpack serve"
},
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 导入模块联邦插件
const Mfp = require('webpack').container.ModuleFederationPlugin;
^^^^^^^^Codes^^^^^^^^^^^^^
// 插件
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 实例化模块联邦插件
new Mfp({
// 对外提供的打包后的文件名,引入时使用
filename:'myuser.js',
// 微应用(模块) 名称,类似 single-spa的团队名字
name:'study',
exposes:{
// 具体的一个文件可以当作一个模块应用,
// 每一个应用都有一个名字和具体指定的代码文件
// 名字:具体打包的代码文件
'./xx':'./src/User.js',
'./goods':'./src/Goods.js'
}
})
],
const Mfp = require('webpack').container.ModuleFederationPlugin
// 插件
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new Mfp({
// // 微应用(模块) 名称,当前模块自己的名字
name:'roots',
// 导入模块
remotes:{
// 导入后给模块起个别名:"微应用名称@地址/导出的文件名"
one:"study@http://localhost:3001/myuser.js"
}
})
],
在组件中使用
// const xx = React.lazy(()=>import("导入时模块别名/导出时具体文件对应的名字"))
const Us = React.lazy(()=>import("one/xx"))
const Gos = React.lazy(()=>import("one/goods"))
let App = () => {
return (
<div>
<h3>webpack5 root</h3>
<User/>
<React.Suspense fallback="Loading app">
<Us />
<Gos />
</React.Suspense>
</div>
)
}
export default App;
完整代码示例:modulefederationvue3: 基于模块联邦实现的 Vue3.0 微前端架构示例 (gitee.com)
package.json
{
"name": "@vue3-demo/layout",
"private": true,
"version": "1.0.0",
"scripts": {
"start": "webpack serve",
"serve": "serve dist -p 3001",
"build": "webpack --mode production",
"clean": "rm -rf dist"
},
"dependencies": {
"@babel/core": "^7.13.16",
"babel-loader": "^8.2.2",
"serve": "^11.3.2",
"vue": "^3.0.0-rc.5",
"vue-router": "^4.0.0",
"vuex": "^4.0.0"
},
"devDependencies": {
"@vue/compiler-sfc": "3.0.0",
"css-loader": "5.2.4",
"file-loader": "6.2.0",
"html-webpack-plugin": "5.3.1",
"mini-css-extract-plugin": "0.9.0",
"url-loader": "4.1.1",
"vue-loader": "16.0.0-beta.8",
"webpack": "5.35.0",
"webpack-dev-server": "3.11.2",
"webpack-cli": "4.6.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2"
}
}
home\webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = (env = {}) => ({
^^^^^^^………………^^^^^^^^^^^
plugins: [
new VueLoaderPlugin(),
// 模块联邦
new ModuleFederationPlugin({
name: "home",
filename: "remoteEntry.js",
// 导出
exposes: {
"./User": "./src/components/User",
// "./Button": "./src/components/Button",
},
}),
]
});
layout\webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
…………
module.exports = (env = {}) => ({
plugins: [
…………
new ModuleFederationPlugin({
name: "layout",
filename: "remoteEntry.js",
// 导入
remotes: {
importUser: "home@http://localhost:3002/remoteEntry.js",
},
exposes: {},
}),
]
});
layout\src\views\About.vue
<template>
<div class="about">
永远记得 西岭老湿 爱你
<hr>
<p>开启home应用的3002端口应用可见,模块联邦内容</p>
<User />
</div>
</template>
<script>
import { defineAsyncComponent } from "vue";
// 导入模块联邦组件
const User = defineAsyncComponent(() => import("importUser/User"));
export default {
components: {
User
}
};
</script>
写好基础代码及对应配置后,分别启动 home 及 layout 两个应用项目就可以在 layout 应用的 about 中看到 home 应用中的 User 组件的内容了;
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。