umijs / qiankun

📦 🚀 Blazing fast, simple and complete solution for micro frontends.
https://qiankun.umijs.org
MIT License
15.68k stars 2k forks source link

vue3项目,webpackage启用externals,loadMicroApp加载子应用时主应用根节点的内容会被子应用的App.vue替换 #1791

Open zwj9297 opened 2 years ago

zwj9297 commented 2 years ago

现象:

vue3项目,webpackage启用externals,loadMicroApp加载子应用时主应用根节点的内容会被子应用的App.vue替换

https://user-images.githubusercontent.com/26342286/139656491-935ca4af-e902-4300-b2ab-d4f975257f37.mp4

  1. 创建2个空白的vue3应用,分别为主应用和子应用
  2. 主应用项目改造 · 新建vue.config.js
    module.exports = {
    configureWebpack: {
    externals: {
      vue: 'Vue'
    }
    }
    }
    • 修改 public/index.html 2.1 引用https://unpkg.com/vue@next 2.2 修改根节点id为"mainApp"
      <!DOCTYPE html>
      <html lang="">
      <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width,initial-scale=1.0">
      <link rel="icon" href="<%= BASE_URL %>favicon.ico">
      <title><%= htmlWebpackPlugin.options.title %></title>
      <script src="https://unpkg.com/vue@next"></script>
      </head>
      <body>
      <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
      </noscript>
      <div id="mainApp"></div>
      <!-- built files will be auto injected -->
      </body>
      </html>
    • 修改 src/main.ts
      
      import { createApp } from 'vue'
      import App from './App.vue'

createApp(App).mount('#mainApp')

- 修改 src/App.vue
```vue
<template>
  <div>
    <h1>This is Main App!</h1>
    <div id="container">
      <button @click="load">load micro app</button>
    </div>
  </div>
</template>

<script>
import { loadMicroApp } from 'qiankun'

export default {
  methods: {
    load(e) {
      e.target.innerHTML = 'loading...'
      loadMicroApp({
        name: 'microApp',
        entry: 'http://localhost:7777/',
        container: '#container'
      })
    }
  }
}
</script>
  1. 子应用项目改造
    • 新建vue.config.js
      
      const port = 7777
      const name = 'micro'

module.exports = { configureWebpack: { externals: { vue: 'Vue' }, devServer: { port, // 跨域 headers: { 'Access-Control-Allow-Origin': '*', } }, output: { // 把子应用打包成 umd 库格式(必须) library: ${name}-[name], libraryTarget: 'umd', jsonpFunction: webpackJsonp_${name}, } } }

- 修改 public/index.html
 3.1 子应用根节点id改为"microApp"
```html
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="microApp"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

let instance: any = null // 根组件实例

async function render(props: any) { const { container } = props instance = createApp(App) instance.mount(container ? container.querySelector('#microApp') : '#microApp') }

// webpack打包公共文件路径 if (window.POWERED_BY_QIANKUN) { webpack_public_path = window.INJECTED_PUBLIC_PATH_BY_QIANKUN }

// 生命周期 export async function bootstrap() { console.log('[vue] vue app bootstraped') } export async function mount(props: any) { console.log('mounted') console.log('[vue] props from main framework', props) render(props) } export async function unmount() { console.log('unmounted') instance.unmount() }

- 修改 src/App.vue
```vue
<template>
  <div>this is micro app!</div>
</template>
  1. 启动两个项目,现象如上面视频所示

项目依赖

vue: ^3.0.0 qiankun: 2.4.10

zwj9297 commented 2 years ago

备注一点:在禁用了externals之后是正常的

Liaozzzzzz commented 2 years ago

我这边碰到同样的问题。我的场景是因为vue-loader开发环境热更新是通过文件相对路径生成hash值,嵌套子应用的时候hash值相同所以导致主应用被覆盖,设置webpack的mode或者NODE_ENV为production都可以解决

228443632 commented 1 month ago

这个问题在我们 monorepo 项目下,我们调整了vue-loader, 这里版本号 "vue-loader": "17.2.2", 底层代码调整如下:

1、第一步

调整源码:node_modules/vue-loader/dist/hotReload.js

'use strict'
// __VUE_HMR_RUNTIME__ is injected to global scope by @vue/runtime-core
Object.defineProperty(exports, '__esModule', { value: true })
exports.genHotReloadCode = void 0
function genHotReloadCode(id, templateRequest) {
  return `
/* hot reload */
if (module.hot) {
  __exports__.__hmrId = "${id}"
  const api = __VUE_HMR_RUNTIME__
  module.hot.accept()
  if (!api.createRecord('${id}', __exports__)) {
    api.reload('${id}', __exports__)
  }
  ${templateRequest ? genTemplateHotReloadCode(id, templateRequest) : ''}
}
`
}
exports.genHotReloadCode = genHotReloadCode
function genTemplateHotReloadCode(id, request) {
  return `
  module.hot.accept(${request}, () => {
    api.rerender('${id}', render)
  })
`
}

改成:

'use strict'
// __VUE_HMR_RUNTIME__ is injected to global scope by @vue/runtime-core
Object.defineProperty(exports, '__esModule', { value: true })
exports.genHotReloadCode = void 0
function genHotReloadCode(id, templateRequest) {
  return `
/* hot reload */
if (module.hot) {
  function debounce(func, wait) {
    let timeoutId
    return function(...args) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        func.apply(this, args);
      }, wait)
    }
  }
  __exports__.__hmrId = "${id}"
  const api = __VUE_HMR_RUNTIME__

  const debounceReload = debounce(function(...args) {
    api.reload(...args)
    __exports__.__isReloading = false
  }, 100)

  module.hot.accept();
  /* fix: vue-loader hmr时同一个vue在qiankun中热更新失效或者打开空白页面问题 */
  if (!api.createRecord('${id}', __exports__)) {
    if (window.__POWERED_BY_QIANKUN__) {
      if (window.__VUE_HMR_RUNTIME_SIGN__) {
        if (!__exports__.__isReloading) {
           const href = window.location.href
           if (!__exports__.__routePath || __exports__.__routePath == href) {
             api.rerender('${id}', render)
           }
           __exports__.__routePath = href
        }
      }
    } else {
      api.reload('${id}', __exports__)
    }
  }
  ${templateRequest ? genTemplateHotReloadCode(id, templateRequest) : ''}
}
`
}
exports.genHotReloadCode = genHotReloadCode
function genTemplateHotReloadCode(id, request) {
  return `
  module.hot.accept(${request}, () => {
    // console.log('rerender');
    api.rerender('${id}', render)
  })
`
}

第2步

关闭主应用热更新,子应用不用调整。主要是解决子应用可以热更新问题,记住子应用在开发环境的时候,一定css方面一定开启内联方式,不要通过 mini-css-extract-plugin方式提炼出单独的css文件,通过link加载进来 两种方案: 1、调整 node_modules/vue-loader/dist/index.js 如果是主应用启动的时候,要在96行

const needsHotReload = !isServer &&
        !isProduction &&
        !!(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
        options.hotReload !== false;

修改如下:

const needsHotReload = false && !isServer &&
        !isProduction &&
        !!(descriptor.script || descriptor.scriptSetup || descriptor.template) &&
        options.hotReload !== false;

2、在webpack层面dev-server做限制。设置hot为false,禁止热更新(hmr)

image