1. 程式人生 > 其它 >vue 全域性 prefix_微前端vue+react(qiankun)

vue 全域性 prefix_微前端vue+react(qiankun)

技術標籤:vue 全域性 prefix

新建專案

mkdir qiankun-m
  • 建立.gitignore檔案
node_modules/

建立主專案(vue)

vue create main
  1. 安裝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();

測試

  • 分別啟動三個專案

3958cf3941589c30924be7a74c1fc553.png

訪問主專案main專案 http://localhost:8080/

1335b9c85bd01a3cb57ad0fb382d9c73.png

訪問react-demo專案 http://localhost:8080/react

544e66a58e7fbf3a6ac19fd9bddfc893.png

訪問vue-demo專案 http://localhost:8080/vue

d3028079735ff13b22792d048532f17e.png