本文由体验技术团队 TinyVue 组件库成员陈家梅同学分享,带你手把手实现 TinyVue 组件库适配微前端~
一、前言
以下是我对微前端的一些粗浅理解,对微前端有一定了解的话可以略过,直接进入第二部分。
1、微前端是什么?
我们首先来点熟悉的东西,以我们最常见的页面为例,看下图:
左侧为子应用路由切换,通过点击左侧完成右侧页面子应用的切换,这就是一个最简单的微前端应用架构了。
作为一名资深打工人,为了便于理解,我把微前端类比成一家企业,当它发展到了一定规模时,效率会变得低下,运行迟缓。此时为了便于管理,提高能效,加强部门之间的协作;一般会分化出几家子公司 + 一个总公司(或总部)。
其特点是:每家子公司都可以独立运作,也可以互相协作,但都听从总部的统一管理,至此,我们就有了微前端的基本概念。
微前端借鉴了后端比较成熟的微服务概念:
- 形式上:通过将各个子应用和一主应用统一组织起来,构成一个集成应用;
- 独立性:这些子应用可以单独部署,单独运行,单独扩展,相互之间状态隔离;技术栈独立,相对不受限;
- 通信上:可以相互通信。子应用可以共享主应用数据。也可共享兄弟应用数据。
2、为什么需要微前端,它有什么优点?
一个优秀的微前端框架应当有如下优点:
①:应用之间互相独立:包括了 js 沙箱、css 隔离等;
②:支持父与子,子与子应用之间的通讯;
③:性能方面支持预加载和按需加载机制;
④:多个公共依赖的共享处理。
一个优秀的前端工程像一家优秀的企业一样,当项目的业务达到了一定的规模,高度集成为一个巨无霸,为了降低运行消耗、维护的时间和人力成本,提升用户的终端体验;拆分+整合就势在必行了。
想象一个场景:(作为一名刚入职公司的前端工程师,不管你是初级还是资深,面对使用不同技术栈、相互之间业务耦合性大的多个项目;或者是一个高度集成、体量庞大,运行缓慢的巨石项目,你要怎么处理才能降低项目维护成本,提升性能,从而提升效率,提升用户体验?
第一种情况:项目多且杂我就遇到过,工程师们加班加点的修 bug;但愣是换了一批又一批人,还是没有改善现状,进入了一个恶性循环)。
所以我们急需一种能整合所有项目,并且单个项目又能独立运作的技术方案。
此时有条件的研发团队,一般都会选择微前端,接入同一套主系统,既能独立运行又可相互通信,兼顾了流畅体验和信息共享。
在选择了一款优秀的微前端架构的前提下,需要注意:此时组件库的选择就能展现出工程师眼光的差距了;是有 n 种前端框架就使用 n 个组件库好,还是 n 种前端框架就使用一个组件库好呢?
我比较懒、想节省时间、想早点下班…… 而且一套东西越用越熟练,效率越高,不拘泥于技术栈,可以一当十(想当年六大门派围攻光明顶,我张无忌以一当……额,回归正题),至此 @opentiny/vue 就成了我不二之选。
那么如何做?
3、需要怎么做?
下面我们用 @opentiny/vue 组件库,在各个子应用中引入并使用,共用一套 @opentiny/vue-renderless 方法,实现多个技术栈跨端引用。
经过多方对比,我们选择了目前比较合适的(无界)微前端框架,接入相对简单,并兼具一款优秀微前端框架的优点。
跟着我们一起来动手吧!
二、项目结构
1、动手之前我们先了解一下要实现的项目结构,如图:
根目录主要分为:pnpm单包管理.yaml 文件 + packages 目录;
packages 目录分为:一个主工程+四个子工程。
主工程目录如下:我们需要编写的文件分别是路由文件 router/index.js + views 目录下的五个展示的页面。
四个子工程目录结构分别如下:
前三个我们只简单的在其首页编写 @opentiny/vue 中两个组件的使用,展示的内容都是计时器和按钮;
最后一个 Vue3 项目我们在一个页面展示了 pc、mobile 和 watch 三端的内容,所以需要编写的内容会多一点;
这样就完成了我们此次的目的 => 跨框架使用我们的 @opentiny/vue 组件库。
2、了解了我们要实现的项目结构,接下来就是实现的思路:
我们分为三个阶段:
- 初始化 pnpm 和微前端工程;
- 创建一个主工程
- 创建四个子工程
最后启动查看效果。
三、初始化
使用 pnpm 管理组件库工程和微前端工程
1、创建 monorepo 工程根目录,使用 gitbash 输入以下命令(以下所有命令均在 gitbase 环境下运行):
#创建cross-framework-component工程根目录(名字可以自定义)
mkdir cross-framework-component
#进入cross-framework-component根目录
cd cross-framework-component
#创建多个子包的根目录
mkdir packages
2、创建组件源代码目录:
#进入多包目录
cd packages
#创建components目录
mkdir components
这里就不再介绍跨端组件库的实现了(关于如何实现跨框架组件库可以参考:原来 TinyVue 组件库跨框架(Vue2、Vue3、React、Solid)是这样实现的?),
我们直接将线上文件夹写好的 components 直接复制到我们 packages 目录下的 components ,完成适配 => 线上文件夹地址:https://github.com/opentiny/cross-framework-component/tree/master/packages/components
3、在根目录下创建 package.json,并修改其内容:
npm init-y
package.json 内容主要分为两块:
(1)定义包管理工具和一些启动工程的脚本:
“preinstall”: “npx only-allow pnpm” — 本项目只允许使用pnpm管理依赖
“dev”: “node setup.js” — 启动无界微前端的主工程和所有子工程
“dev:home”: “pnpm -C packages/home dev” — 启动无界微前端的主工程(vue3框架)
“dev:react”: “pnpm -C packages/react dev” — 启动无界微前端的react子工程
“dev:solid”: “pnpm -C packages/solid dev” — 启动无界微前端的solid子工程
“dev:vue2”: “pnpm -C packages/vue2 dev” — 启动无界微前端的vue2子工程
“dev:vue3”: “pnpm -C packages/vue3 dev” — 启动无界微前端的vue3子工程
(2)解决一些 pnpm 针对 Vue 不同版本(Vue2、Vue3)的依赖冲突,packageExtensions 项可以让 Vue2 相关依赖可以找到正确的 Vue 版本,从而可以正常加载 Vue2 和 Vue3 的组件。
package.json 文件具体内容如下所示:
{
"name":"@opentiny/cross-framework",
"version":"1.0.0",
"description":"",
"main":"index.js",
"scripts":{
"preinstall":"npx only-allow pnpm",
"dev":"node setup.js",
"dev:home":"pnpm-C packages/home dev",
"dev:react":"pnpm-C packages/react dev",
"dev:solid":"pnpm-C packages/solid dev",
"dev:vue2":"pnpm-C packages/vue2 dev",
"dev:vue3":"pnpm-C packages/vue3 dev",
"dev:el":"pnpm-C packages/element-to-opentiny dev"
},
"repository":{
"type":"git",
"url":"https://github.com/opentiny/cross-framework-component.git"
},
"keywords":[],
"author":"",
"license":"ISC",
"pnpm":{
"overrides":{
"vue2":"npm:vue@2.6.14"
},
"packageExtensions":{
"vue-template-compiler@2.6.14":{
"peerDependencies":{
"vue":"2.6.14"
}
},
"@opentiny/vue-locale@2.9.0":{
"peerDependencies":{
"vue":"2.6.14"
}
},
"@opentiny/vue-common@2.9.0":{
"peerDependencies":{
"vue":"2.6.14"
}
}
}
}
}
4、在根目录创建配置文件 pnpm-workspace.yaml,文件内容如下:
packages:
-packages/**#packages文件夹下递归所有的子孙npm包,只要包含package.json都是独立的子工程
至此初始化已经完成,我们总共完成了四件事:
- 创建根目录+packages 子包目录;
- 创建 packages 的 components 子目录,完成不同框架的适配;
- 在根目录下创建包管理文件 package.json,用于定义子应用的启动命令和解决依赖冲突的问题;
- 在根目录下创建单包配置文件 pnpm-workspace.yaml;
这样项目配置基本完成,我们就可以开始将注意力集中于编写我们的页面了。
四、创建一个主工程
主工程最重要的是编写页面,用来串联起四个子应用,因此创建页面后,路由便是我们主工程的重中之重。
主工程路由如下,默认路径或 /home 是集成展示所有子应用的首页,剩下的四个路径分别对应子应用的首页展示。
1、使用 vite 脚手架创建一个 Vue3 的工程,运行命令如下:
npm create vite@latest home--templatevue
2、下载安装无界微前端的 Vue3 依赖包和 vue-router 路由:
npm i wujie-vue3 vue-router
这里先简单了解一下无界页面的编写 元素的用法,以如下代码为例:
3、进入主工程,在 packages/home/src 下新建 views 文件夹,并在文件夹中创建一个主页面和四个子页面,分别为:Home.vue(主页–集成页面)、React.vue、Solid.vue、Vue2.vue、Vue3.vue 为四个子页面
Home.vue
exportdefault{
data(){
return{
reactUrl:'http://localhost:2001/',//切换到react子工程的url
solidUrl:'http://localhost:2002/',//切换到solid子工程的url
vue2Url:'http://localhost:2003/',//切换到vue2子工程的url
vue3Url:'http://localhost:200服务器托管网4/'//切换到vue3子工程的url
}
}
}
.multiple{
width:98%;
height:100%;
padding:5px10px010px;
}
.home-box{
display:flex;
justify-content:space-between;
}
.item{
display:block;
border:1pxdashed#ccc;
border-radius:8px;
width:32%;
height:30vh;
overflow-y:auto;
}
.item-vue3{
width:100%;
height:65vh;
margin-top:20px;
}
React.vue:展示 react 子应用的窗口页面
exportdefault{
data(){
return{
reactUrl:'http://localhost:2001/'
}
}
}
Solid.vue、Vue2.vue、Vue3.vue 这三个文件和 React.vue 代码几乎是相同的,不一样的只有 name 属性和端口号;都是用于展示相应子工程的窗口页面;如下图所示:
5、跟 Vue 创建路由一样,在 packages/home/src/router 下创建路由文件 index.js,并引入上文创建的几个页面,内容如下:
import{createRouter,createWebHashHistory}from'vue-router'
importHomefrom'../views/Home.vue'
importReactfrom'../views/服务器托管网React.vue'
importSolidfrom'../views/Solid.vue'
importVue2from'../views/Vue2.vue'
importVue3from'../views/Vue3.vue'
constbasename=process.env.NODE_ENV==='production'?'/demo-main-vue/':''
constroutes=[
{
path:'/home',
name:'home',
component:Home
},
{
path:'/',
redirect:'/home'
},
{
path:'/react',
name:'react',
component:React
},
{
path:'/solid',
name:'solid',
component:Solid
},
{
path:'/vue2',
name:'vue2',
component:Vue2
},
{
path:'/vue3',
name:'vue3',
component:Vue3
}
]
constrouter=createRouter({
history:createWebHashHistory(),
base:basename,
routes
})
exportdefaultrouter
6、然后在 Home 主工程的主入口注册 wujie-vue3 和路由。
import{createApp}from'vue'
importWujieVuefrom'wujie-vue3'
importAppfrom'./App.vue'
importrouterfrom'./router'
import'./index.css'
constapp=createApp(App)
app.use(WujieVue).use(router).mount('#app')
至此,我们主工程的页面和路由已创建完成。每个子工程在主工程都有自己的“展台”,那么接下来我们就着手编写我们的子工程。
五、创建四个子工程
在开始创建之前,前面我们已经了解了四个子工程的项目结构,那就有思路了:
- 通过 vite 可以很方便快捷完成创建;
- 通过 vite 配置文件配置端口号和相应的插件,需要注意:Vue2 和 Vue3 的配置需额外做别名适配,对应上 @opentiny/vue 的 common 层;
- 每个子工程的 package.json 依赖中,添加我们当时复制的 components 文件夹下的 npm 包,对应实时加载上我们要使用的组件;
- 最后就是使用我们的组件编写页面了;
话不多说,跟着我们的步骤走起来~
1、在主工程 home 的同级目录,分别使用 React、Solid、Vue 的 vite 套件创建四个子工程,依次为:react/solid/vue2/vue3:
npm create vite@latest react--templatereact
npm create vite@latest solid--templatesolid
npm create vite@latest vue2--templatevue
npm create vite@latest vue3--templatevue
2、然后分别配置四个子工程vite.config.js,设置不同的端口号(React:2001、Solid:2002、Vue2:2003、Vue3:2004)
React/vite.config.js:
import{defineConfig}from'vite'
importreactfrom'@vitejs/plugin-react'
importsvgrfrom'vite-plugin-svgr'
//https://vitejs.dev/config/
exportdefaultdefineConfig({
plugins:[react(),svgr()],
server:{
port:2001,
host:'localhost'
}
})
Solid/vite.config.js:
import{defineConfig}from'vite'
importsolidfrom'vite-plugin-solid'
exportdefaultdefineConfig({
plugins:[solid()],
server:{
port:2002,
host:'localhost'
}
})
Vue2、Vue3 的 vite.config.js 需要一些别名适配,对接 vue-common 适配层。
Vue2/vite.config.js:
import{defineConfig}from'vite'
import{createVuePlugin}from'vite-plugin-vue2'
importpathfrom'node:path'
//https://vitejs.dev/config/
exportdefaultdefineConfig({
plugins:[createVuePlugin()],
server:{
port:2003,
host:'localhost'
},
resolve:{
alias:{//别名配置
'virtual:common/adapter/vue':path.resolve(
__dirname,
`../components/vue/common/src/adapter/vue2/index`
)
}
},
define:{
'process.env':{...process.env}
}
})
Vue3/vite.config.js:
import{defineConfig}from'vite'
importvuefrom'@vitejs/plugin-vue'
importpathfrom'node:path'
//https://vitejs.dev/config/
exportdefaultdefineConfig({
plugins:[vue()],
server:{
port:2004,
host:'localhost',
https:false,
proxy:{
'/api':{
target:'*',
changeOrigin:true
}
}
},
resolve:{
alias:{//别名配置
'virtual:common/adapter/vue':path.resolve(
__dirname,
`../components/vue/common/src/adapter/vue3/index`
)
}
},
define:{
'process.env':{...process.env}
}
})
3、分别在四个子工程根目录的 package.json 的 dependencies 键中添加如下依赖包,用来加载本地跨框架的 button 组件和倒计时 countdown 组件:
#vue2、vue3
"@opentiny/vue-button":"workspace:~",
"@opentiny/vue-countdown":"workspace:~"
#react
"@opentiny/react-button":"workspace:~",
"@opentiny/react-countdown":"workspace:~"
#solid
"@opentiny/solid-button":"workspace:~",
"@opentiny/solid-countdown":"workspace:~"
4、在四个子工程里使用 button 组件和倒计时 countdown 组件,自定义一些交互逻辑如下:
React/src/app.jsx:
import{Button,Countdown}from'@opentiny/react'
import'./style.css'
constoperation={
start:()=>{},
reset:()=>{}
}
constoperate=({start,reset})=>{
operation.start=start
operation.reset=reset
}
functionApp(){
return(
React
>
)
}
exportdefaultApp
Solid/src/app.jsx:
import{createSignal}from'solid-js'
import{Button,Countdown}from'@opentiny/solid'
constoperation={
start:()=>{},
reset:()=>{},
stop:()=>{}
}
constoperate=({start,reset})=>{
operation.start=start
operation.reset=reset
}
functionApp(){
return(
Solid
>
)
}
exportdefaultApp
Vue2/src/app.vue:
Vue2
Reset
Start
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
前言 最近在学习一个开源社区项目,第一次听说了DDD项目架构,于是通过搜索之后来分享给大家 正文 当涉及到软件架构时,MVC(Model-View-Controller)和DDD(Domain-Driven Design)是两种常见的设计模式和架构思想。 MV…