vue 全局 prefix_微前端vue+react(qiankun)
程序员文章站
2023-12-22 22:09:58
...
新建项目
mkdir qiankun-m
- 创建.gitignore文件
node_modules/
创建主项目(vue)
vue create main
- 安装QianKun
yarn add qiankun
- 创建子项目配置文件(micro-app.ts)
/**
* 路由变化, 获取子项目前缀
* @param {string} prefix
* @return {*}
*/
const getActiveRule = (prefix: string) => {
return (location: any) => location.pathname.startsWith(prefix)
}
/**
* 自项目配置列表
* @param {any} props
* @return {*}
*/
const microApps = (props: any) => {
const el = document.createElement('div')
document.body.append(el)
return [
{
name: 'vue',
entry: '//localhost:8081/', // 子项目入口
activeRule: getActiveRule('/vue'),
container: el, // 子项目渲染到的节点
props: props // 传参
},
{
name: 'react',
entry: '//localhost:3000/',
activeRule: getActiveRule('/react'),
container: el,
props: props
}
]
}
export default microApps
- 创建全局store,用于主项目, 子项目的信息交互(store/global.ts)
import { initGlobalState, MicroAppStateActions } from 'qiankun'
...
export const state: RecodeType = {
user: {
userName: '',
avatar: '',
mobile: ''
}
}
const localCacheUser = getLocalstore('user')
if (localCacheUser) {
Object.assign(state.user, localCacheUser)
}
export const actions: MicroAppStateActions = initGlobalState(state)
/**
* 监听全局状态改变
* state: 变更后的状态
* prev 变更前的状态
*/
actions.onGlobalStateChange((state: RecodeType, prev: RecodeType) => {
console.log('主项目监听', state, prev)
})
/**
* 设置用户状态
* @param {UserType} _user
* @return {*}
*/
export function setGlobalUser (_user: UserType) {
const { user } = state
state.user = Object.assign(user, _user)
actions.setGlobalState(state)
}
/**
* 获取用户状态信息
* @param {*}
* @return {*}
*/
export function getGlobalUser (): UserType {
return state.user
}
- 更改入口文件(main.ts)
...
import microApps from './micro-app'
import { registerMicroApps, start } from 'qiankun'
...
// 注册子应用
registerMicroApps(microApps(state),
{
beforeLoad: app => {
console.log('before load app.name====>>>>>', app.name)
return Promise.resolve()
},
beforeMount: [
app => {
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
return Promise.resolve()
}
],
afterMount: [
app => {
console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)
return Promise.resolve()
}
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
return Promise.resolve()
}
]
}
)
// 开始
start()
new Vue({
router,
store,
render: h => h(App)
}).$mount('#base')
- 更新、监听全局状态(router.ts)
...
import { setGlobalUser, getGlobalUser } from '../store/global'
...
const routes: Array<RouteConfig> = [
...
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
/**
* 登陆请求处理
* @param {*} async
* @return {Promise<string>}
*/
const loginRequest = async (to: Route): Promise<string> => {
const user: UserType = await getUser()
if (!user || !user.mobile) {
return Promise.resolve('/login')
}
const res: any = await Instance.post('/user/mobileDingLogin', {
phoneNumber: user ? user.mobile : ''
})
const { detail } = res
detail.mobile = detail.phoneNumber
setGlobalUser(detail)
setLocalStore('user', detail)
return Promise.resolve((to.path as string))
}
/**
* 处理登录逻辑
* @param {to} Route 前往的路由
* @return {Promise<string>}
*/
const handlerLogin = async (to: Route): Promise<string> => {
const user = getGlobalUser()
if (!user.phoneNumber) {
return await loginRequest(to)
}
return Promise.resolve(to.path as string)
}
router.beforeEach(async (to, from, next) => {
const path = await handlerLogin(to)
if (path === '/login' && to.name !== 'Login') {
next({
path
})
return
}
next()
})
export default router
创建子项目(VUE)vue-demo
vue create vue-demo
- 创建public-path.ts, 用户处理路径问题(图片等静态资源)
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
- 修改入口文件main.ts
...
import './public-path'
...
const render = () => {
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/',
mode: 'history',
routes
})
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
}
export async function bootstrap () {
console.log('vue app bootstraped')
}
/**
* @param props
* 从生命周期 mount 中获取通信方法,使用方式和 master 一致
*/
export async function mount (props: any) {
// console.log('props from main app', props)
Object.assign(user, props.user)
props.onGlobalStateChange((state: RecodeType, prev: RecodeType) => {
// state: 变更后的状态; prev 变更前的状态
console.log('子组件监听挂载全局变量改变', state, prev)
})
render()
}
export async function unmount () {
if (instance) {
instance.$destroy()
}
instance = null
router = null
}
// 本地调试
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
- 新建vue.config.js
const { name } = require('./package');
...
const writeFile = generateWriteFile()
module.exports = {
configureWebpack: config => {
return {
output: {
// 把子应用打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
module: {
rules: [
{
enforce: 'pre',
test: /.(js|ts|vue)$/,
loader: 'eslint-loader',
options: {
fix: true
}
}
]
}
}
},
devServer: {
hot: true,
disableHostCheck: true,
port: 8081,
overlay: {
warnings: false,
errors: true,
},
headers: {
'Access-Control-Allow-Origin': '*', // 跨域
}
}
}
创建子项目(react) react-demo
npx react-create-app react-demo
yarn 调出配置文件
yarn eject
- 修改config/webpackDevServer.config.js
...
module.exports = function (proxy, allowedHost) {
return {
headers: {
'Access-Control-Allow-Origin': '*', // 允许跨域
},
...
};
修改config/webpack.config.config.js
...
return {
...
output: {
// The build folder.
...
// 微应用配置
library: `${appPackageJson.name}-[name]`,
libraryTarget: 'umd'
},
...
};
- 新建public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
- 更新main.js
...
import './public-path'
...
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap() {
console.log('react app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
console.log('update props', props);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
测试
- 分别启动三个项目
访问主项目main项目 http://localhost:8080/
访问react-demo项目 http://localhost:8080/react
访问vue-demo项目 http://localhost:8080/vue