最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • qiankun 微前端实践及常见问题

    正文概述 掘金(赖先生)   2021-03-26   1277

    关注公众号: 微信搜索 前端工具人 ; 收获更多的干货

    一、介绍:

    qiankun 项目实际搭建, 及各种微应用流行框架技术 (vue2 、vue3、react 、 umi2 、umi3)的配置;

    初衷:自己当时摸索qiankun构建项目时,问题百出, 特别是umi2 umi3,百度了几天才把热门框架都集合完毕;

    目的:总结出的模板项目, 便于自己后期重构项目技术选型及项目快速搭建;也为其他有需要的朋友提供示例及参考;

    项目源码:已上传到 github https://github.com/laijinxian/qiankun-template 如有对你有帮助,麻烦 star 下

    末尾的常见问题多数为目前开发中遇到的疑难点, 特地整理出来;有其他问题欢迎留言交流

    实际项目源码就没贴出来了,都是依据这个模板构建的;

    后面看下好不好把实际项目源码抽离出来,上传到github; 目前子项目使用的是 vite2.0 + vue3 + ts 以及 react + Umi3 + dva + ts

    二、什么是微前端 qiankun 篇

    推荐阅读 qiankun文档

    其实我个人更喜欢叫成 前端微服务架构, 感觉逼格更高点...

    2.1 官方介绍:

    • 微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略 (有点高深...)

    2.2 我的观点:

    • 与技术栈无关、独立开发、独立部署、增量升级、独立运行;

    • 拆分、细化、解耦你的巨无霸项目; 提升开发及打包部署效率;

    • 不在局限于一个项目只能使用一种技术,一个项目可以使用 N 种技术,扩展自己技术知识面;

    • 对于比如 大型 erp、OA 之类的系统, 微前端可以让你更加的得心应手的开发;

    • 对于想重构公司辣眼睛的项目尤为合适;这也算我入手微前端的主要原因之一,下面会讲到;

    • 顺应时代潮流, 作为主流技术现在非常多的公司招聘面试基本都会问微前端,细化程度不一样;

    三、 为什么用qiankun, 为什么选择qiankun

    3.1 为什么用qiakun

    自打进入公司,看到了现有的项目,我总结了几点

    • 项目全都使用 Vue, 一直开发下去你会发现 React 忘得快差不多了;技术的局限性;
    • 现有项目代码又臭又长,毫无规范;eslint、css预编译啥都没有;
    • 2-3层 for 循环,var之类的,粘贴复制无用代码不删除到处可见;公共代码提取、接口统一处理、工具类编写不存在的;
    • 一个项目同时出现 vue、jQuery两个大框架;运行项目、热编译、你可以先上趟厕所;
    • 每期功能迭代,先要花大半天时间去熟悉这个代码、还真不敢乱改,有毒、谁改谁后悔的那种
    • 想重构,奈何刚接手的时候项目已经很大了,并且不怎么熟悉业务,且不断的加功能; 一时重构基本不可能;千万级别的用户量出问题了这锅背不动, 时间也不允许

    后面需求排期不是很紧凑,正直qiankun微前端很火,就想着使用qiankun微前端方案重构;

    思路如下:

    • 目标 把一个项目按照菜单划分,一个大菜单分为一个子服务(子项目)
    • 刚开始原有项目全部划分为一个子服务,新加功能菜单划分为另一个子服务;这样既保证原有项目不变,新项目完全使用新的框架及开发风格规范;
    • 时间充裕下情况下,慢慢把其他功能按照菜单划分成子服务,慢慢的最小粒度去重构项目

    3.2 目前微前端方案有:

    • iframe
    • single-spa
    • qiankun 基于 single-spa 方案实现, 更强大更易上手

    推荐阅读 掘金大佬文章, 文章有详细介绍及常见问题

    四、 构建步骤

    项目结构:

    ├── main-service    // 主应用
    └── sub-service     // 微应用
        └── sub-react   // react 子应用
        └── sub-umi2    // umi2 子应用
        └── sub-umi3    // umi3 子应用
        └── sub-vue2    // vue2 子应用
        └── sub-vue3    // vue3 子应用
    

    推荐阅读:

    • 详细结构代码已上传github 请前往 github 查看

    • qiankun官方文档

    • 参考掘金文章

    4.1 项目结构组成

    主应用

    vue2.x + vuec-li3 主要业务功能就是登录注册及菜单;官方推荐主应用尽可能的简单,不要涉及其他的业务功能

    微应用

    • vue2.x + vue-cli3
    • vue3.x + vue-cli4 + typescript
    • react16
    • react16 + umi2 + dva
    • react16 + umi3 + dva

    4.2 主应用配置

    qiankun 只需要在主应用中引入,微应用不需要

    yarn add qiankun # 或者 npm i qiankun -S
    

    4.3 主应用 src 下 注册微应用

    主应用 src 下新建 qiankun/index.js

    import {
      registerMicroApps,
      runAfterFirstMounted,
      setDefaultMountApp,
      start
    } from "qiankun";
    import store from '../store/index'
    import { instance } from "../main";
    import 'nprogress/nprogress.css'
    
    /**
     * Step1 初始化应用(可选)
     */
    
    function loader(loading) {
      if (instance && instance.$children) {
        // instance.$children[0] 是App.vue,此时直接改动App.vue的isLoading
        instance.$children[0].isLoading = loading;
      }
    }
    
    /**
     * Step2 注册子应用
     */
    
    const microApps = [
      {
        name: 'sub-vue2',
        developer: 'vue2.x',
        entry: '//localhost:7788',
        activeRule: '/sub-vue2',
      },
      {
        name: 'sub-vue3',
        developer: 'vue3.x',
        entry: '//localhost:7799',
        activeRule: '/sub-vue3'
      },
      {
        name: 'sub-react',
        developer: 'react16',
        entry: '//localhost:7755',
        activeRule: '/sub-react'
      },
      {
        name: 'sub-umi2',
        developer: 'umi2.x',
        entry: '//localhost:7766',
        activeRule: '/sub-umi2'
      },
      {
        name: 'sub-umi3',
        developer: 'umi3.x',
        entry: '//localhost:7733',
        activeRule: '/sub-umi3'
      }
    ]
    
    const apps = microApps.map(item => {
      return {
        ...item,
        loader, // 给子应用配置加上loader方法
        container: '#subapp-container', // 子应用挂载的div
        props: {
          developer: item.developer, // 下发基础路由
          routerBase: item.activeRule, // 下发基础路由
          getGlobalState: store.getGlobalState // 下发getGlobalState方法
        }
      }
    })
    
    registerMicroApps(apps, {
      beforeLoad: app => {
        console.log('before load app.name====>>>>>', app.name)
      },
      beforeMount: [
        app => {
          console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
        }
      ],
      afterMount: [
        app => {
          console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)
        }
      ],
      afterUnmount: [
        app => {
          console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
        }
      ]
    })
    
    /**
     * Step3 设置默认进入的子应用
     */
    setDefaultMountApp('/sub-vue2')
    
    /**
     * Step4 启动应用
     */
    start();
    
    runAfterFirstMounted(() => {
      console.log("[MainApp] first app mounted");
    });
    
    export default apps
    
    

    4.4 微应用导出生命周期钩子

    各种框架配置推荐阅读 官方文档

    下面以 vue3.xreact umi3 为例; 其他微服务配置请前往 [github](https://github.com/laijinxian/qiankun-template) 源码查看

    子应用的名称最好与父应用在 qiankun/index.js 中配置的名称一致(这样可以直接使用package.json中的name作为output

    vue3.x 微应用

    首先 vue create sub-vue3 创建项目

    修改 main.js 导出生命周期函数
    // @ts-nocheck
    import "./public-path";
    import { createApp } from "vue";
    import { createRouter, createWebHistory } from "vue-router";
    import App from "./App.vue";
    import routes from "./router";
    import store from "./store";
    
    let router = null;
    let instance = null;
    
    function render(props = {}) {
      const { container } = props;
      router = createRouter({
        history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? "/sub-vue3" : "/"),
        routes
      });
    
      instance = createApp(App);
      instance.use(router);
      instance.use(store);
      instance.mount(container ? container.querySelector("#app") : "#app");
    }
    
    if (!window.__POWERED_BY_QIANKUN__) {
      render();
    }
    
    export async function bootstrap() {
      console.log("%c ", "color: green;", "vue3.0 app bootstraped");
    }
    
    function storeTest(props) {
      props.onGlobalStateChange &&
        props.onGlobalStateChange(
          (value, prev) =>
            console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
          true
        );
      props.setGlobalState &&
        props.setGlobalState({
          ignore: props.name,
          user: {
            name: props.name
          }
        });
    }
    
    export async function mount(props) {
      storeTest(props);
      render(props);
      instance.config.globalProperties.$onGlobalStateChange =
        props.onGlobalStateChange;
      instance.config.globalProperties.$setGlobalState = props.setGlobalState;
    }
    
    export async function unmount() {
      instance.unmount();
      instance._container.innerHTML = "";
      instance = null;
      router = null;
    }
    
    
    新建 vue.config.js
    const path = require('path');
    const { name } = require('./package');
    
    function resolve(dir) {
      return path.join(__dirname, dir);
    }
    
    module.exports = {
      outputDir: 'dist',
      assetsDir: 'static',
      filenameHashing: true,
      devServer: {
        hot: true,
        disableHostCheck: true,
        port: '7799',
        overlay: {
          warnings: false,
          errors: true,
        },
        clientLogLevel: "warning",
        disableHostCheck: true,
        compress: true,
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
        historyApiFallback: true,
        overlay: { warnings: false, errors: true }
      },
      // 自定义webpack配置
      configureWebpack: {
        resolve: {
          alias: {
            '@': resolve('src'),
          },
        },
        output: {
          // 把子应用打包成 umd 库格式
          library: `${name}-[name]`,
          libraryTarget: 'umd',
          jsonpFunction: `webpackJsonp_${name}`,
        },
      },
    };
    
    
    src 新建 public-path.js 并引入
    /* eslint-disable @typescript-eslint/camelcase */
    if ((window as any).__POWERED_BY_QIANKUN__) {
      /* eslint-disable @typescript-eslint/camelcase */
      __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    

    react umi3 微应用

    创建项目

    推荐阅读: umi 官方文档

    $ mkdir myapp && cd myapp
    $ yarn create umi
    
    安装
    $ npm install --save-dev @umijs/plugin-qiankun
    $ yarn add @umijs/plugin-qiankun
    
    修改 src/app.js 导出生命周期函数
    import './public-path'
    
    export const dva = {
      config: {
        onError(err) {
          err.preventDefault();
          console.error(err.message);
        },
      },
    };
    
    export const qiankun = {
      // 应用加载之前
      async bootstrap(props) {
        console.log('app1 bootstrap', props);
      },
      // 应用 render 之前触发
      async mount(props) {
        console.log('app1 mount', props);
        storeTest(props);
      },
      // 应用卸载之后触发
      async unmount(props) {
        console.log('app1 unmount', props);
      },
    };
    
    function storeTest(props) {
      props.onGlobalStateChange &&
        props.onGlobalStateChange(
          (value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
          true,
        );
      props.setGlobalState &&
        props.setGlobalState({
          ignore: props.name,
          user: {
            name: props.name,
          },
        });
    }
    
    修改 .umirc.js 文件 引入 @umijs/plugin-qiankun 插件
    // ref: https://umijs.org/config/
    export default {
      mountElementId: 'sub-umi3',
      base: `sub-umi3`, // 子应用的 base,默认为 package.json 中的 name 字段
      treeShaking: true,
      routes: [
        { exact: false, path: '/', component: '../layouts/index',
          routes: [
            { exact: false, path: '/', component: '../pages/index' },
            { component: './404.js' }
          ],
        }
      ],
      plugins: [
        ['@umijs/plugin-qiankun', {
          keepOriginalRoutes: true
        }],
        // ref: https://umijs.org/plugin/umi-plugin-react.html
        ['umi-plugin-react', {
          antd: true,
          dva: true,
          dynamicImport: { webpackChunkName: true },
          title: 'react',
          dll: false,
          
          routes: {
            exclude: [
              /models\//,
              /services\//,
              /model\.(t|j)sx?$/,
              /service\.(t|j)sx?$/,
              /components\//,
            ],
          },
        }],
      ],
    }
    
    
    src 新建 public-path.js 并引入
    /* eslint-disable @typescript-eslint/camelcase */
    if ((window as any).__POWERED_BY_QIANKUN__) {
      /* eslint-disable @typescript-eslint/camelcase */
      __webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    

    以上就是微前端的基本配置 demo, 源码 [github](https://github.com/laijinxian/qiankun-template) 查看

    接下来真正的项目重构实操及进阶

    五、 项目重构实践、进阶中常见问题

    5.1 qiankun 常见报错

    推荐阅读: 官方文档总结 qiankun.umijs.org/zh/faq

    5.2 状态管理, 主应用和微应用之间的通信

    qiankun 通过 initGlobalState: 定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法;

    onGlobalStateChange: 在当前应用监听全局状态,有变更触发 callback;

    setGlobalState: 按一级属性设置全局状态,微应用中只能修改已存在的一级属性; 换句话说只能修改主用于预先定义的属性,后面添加的属性无效

    官方列子 发布-订阅的设计模式主应用

    import { initGlobalState, MicroAppStateActions } from 'qiankun';
    
    // 初始化 state
    const actions: MicroAppStateActions = initGlobalState(state);
    
    actions.onGlobalStateChange((state, prev) => {
      // state: 变更后的状态; prev 变更前的状态
      console.log(state, prev);
    });
    actions.setGlobalState(state);
    actions.offGlobalStateChange();
    

    微应用

    // 从生命周期 mount 中获取通信方法,使用方式和 master 一致
    export function mount(props) {
    
      props.onGlobalStateChange((state, prev) => {
        // state: 变更后的状态; prev 变更前的状态
        console.log(state, prev);
      });
    
      props.setGlobalState(state);
    }
    

    5.3 各应用之间的独立仓库以及聚合管理

    实际开发中项目存储在公司仓库中,以 gitLab 为例, 当子应用一多,全部放在一个仓库下面, 这时候就显得很臃肿了,也很庞大,大大的增加了维护成本,和开发效率;

    我们可以通过 sh 脚本, 初始只需要克隆主仓库代码, 然后通过 sh 脚本去一键拉取所有子应用;

    主仓库新建 script/clone-all.sh 文件 内容如下

    # 子服务 gitLab 地址
    SUB_SERVICE_GIT=('http://gitlab.qinlinkeji.com/xxxxxx/qiankun-sub-service-vue.git' 'http://gitlab.qinlinkeji.com/xxxxxx/qiankun-sub-service-react.git')
    SUB_SERVICE_NAME=('qiankun-sub-service-vue' 'qiankun-sub-service-react')
    
    # 子服务
    if [ ! -d "sub-service" ]; then
      echo '创建sub-service目录...'
      mkdir sub-service
    fi
    echo '进入sub-service目录...'
    cd sub-service
    
    
    # 遍历克隆微服务
    for i in ${!SUB_SERVICE_NAME[@]}
    do
      if [ ! -d ${SUB_SERVICE_NAME[$i]} ]; then
        echo '克隆微服务项目'${SUB_SERVICE_NAME[$i]}
        git clone ${SUB_SERVICE_GIT[$i]}
      fi
    done
    
    echo '脚本结束...'
    # 克隆完成
    

    代码拉取完成后, 紧接着就是下载各个项目的依赖及运行

    应用根目录安装 npm i npm-run-all -D package.json 文件 scripts 命令如下

    "scripts": {
        "clone:all": "bash ./scripts/clone-all.sh",
        "install": "npm-run-all --serial install:*",
        "install:main": "cd main-service && cnpm i",
        "install:sub-vue2": "cd  sub-service/sub-vue2 && yarn install",
        "install:sub-vue3": "cd  sub-service/sub-vue3 && yarn install",
        "install:sub-react": "cd sub-service/sub-react && cnpm i",
        "install:sub-umi2": "cd sub-service/sub-umi2 && yarn install",
        "install:sub-umi3": "cd sub-service/sub-umi3 && yarn install",
        "start": "npm-run-all --parallel start:*",
        "start:sub-react": "cd sub-service/sub-react && npm start",
        "start:sub-vue2": "cd sub-service/sub-vue2 && npm start",
        "start:sub-vue3": "cd sub-service/sub-vue3 && yarn start",
        "start:sub-umi2": "cd sub-service/sub-umi2 && yarn start",
        "start:sub-umi3": "cd sub-service/sub-umi3 && yarn start",
        "start:main": "cd main-service && yarn start",
        "test": "echo \"Error: no test specified\" && exit 1"
      },
    

    步骤: 第一步 clone 主应用, 然后依次执行 yarn clone:all --> yarn install --> yarn start 即可运行整个项目

    5.4 子应用之间的独立开发

    需求: 每次项目的迭代,有可能只涉及其中某个应用功能,

    期望: 只需要单独打开这个子应用修改即可;并不是整个庞大项目一起启用

    目标: 应用解耦的同时也能高效撸代码

    问题: 整个项目中都需要一个登录态(登录凭证 token), 上面说到登录token是在主应用中维护的, 不启动主应用,子应用怎么拿到登录态token呢;

    解析: 其实登录的主要作用都是获取到用户信息及 token 后, 保存在浏览器缓存中,比如 localStorage、sessionStorage、cookie、IndexedDB , 需要的地方获取即可;

    方法: 子应用中通过 qiankun 提供的 window.__POWERED_BY_QIANKUN__ 属性, 很直接的知道目前是否运行在 qiankun 的主应用的上下文中;全局维护一个变量,控制是否展示 iframe的登录页

    if (!window.__POWERED_BY_QIANKUN__) {
      // 不在主应用的上下文中
    }
    

    当不在qiankun主应用上下文环境中时, 通过 iframe 形式, 直接引入登录页面, 完成登录把用户数据及token存入缓存中即可;

    要注意的是:

    • 浏览器默认不支持iframe文件的 script 脚本执行; 需要设置 sandbox="allow-scripts allow-same-origin" 两个属性即可
    • 下面代码是通过本地的 html 文件 (登录页);在vue-cli3中我们需要吧 html 静态、文件放在 public/static下面
    • 当然当你项目发布到服务器之后, 把上面步骤删了,直接在iframe里引用登录页面即可; iframeurl 指向你的线上登录页; 这样下来子应用只需要加个 iframe 一行代码,即可完成子应用的登录态获取

    实例: vue 子应用 某页面

    <template>
      <!-- <iframe src="https://juejin.cn/" width="400" height="300" sandbox="allow-scripts allow-same-origin"></iframe> -->
      <iframe ref="iframe" name="iframe" width="400" height="300" sandbox="allow-scripts allow-same-origin"></iframe>
    </template>
    <script>
    export default {
      data () {
        return {
          html: require("static/login.html")
        }
      },
      mounted() {
        this.$refs.iframe.srcdoc = this.html
        console.log(localStorage.getItem('userInfo'))
      }
    }
    </script>
    
    

    5.5 如何提取出公共的依赖库

    官方说法: 并不推荐这种做法, 因为微服务主要目标是解耦大型应用, 并且当你升级某个项目的公共依赖之后,意味着其他子应用也升级了, 很难保证不出问题;但你确实想那么做,那么也有方法:

    方法1: 官方推荐你可以在微应用中将公共依赖配置成 external,然后在主应用中导入这些公共依赖; 推荐阅读: 掘金文章

    方法2: 我的做法是 通过 webpackDllPlugin 动态链接库, 生成静态 json 在子应用中引 入; DllPluginwebpack内置的插件,不需要额外安装; 这里就不贴代码了, 代码有点多, DllPlugin教程很多, 百度到处是, 跟着配置下webpack.dll.config.js就行;

    DllPlugin 也是项目优化的一个手段, 自己配置一遍印象更深

    5.6 如何提取出公共方法

    在这我个人也不怎么推荐, 因为子应用是不同框架 vue\react\umi-react 并不能保证方法能同时作用于这几个框架项目, 不能的话那何来公共方法一说;

    当然你的子应用全是同一个框架那上面的话当我没说。。。

    有需求就有方法: 推荐参考 掘金文章 更详细:

    • npm指向本地file地址:npm install file:../common。直接在根目录新建一个common目录,然后npm直接依赖文件路径。
    • npm指向私有git仓库: npm install git+ssh://xxx-common.git
    • 发布到npm私服

    demo 中我用的是第一种方法,当然不嫌麻烦可以选用第三种发布到npm私服,嫌私服难搭,可以用后台的manven私服,把你的公共代码给后台让让后台发布; manven 私服按我的理解后台标配;

    第一种方法 指向本地file地址事例; vue 子应用 main.js 引入你的本地公共代码并注册

    import globalRegister from '../../../main-service/src/store/global-register'
    export async function mount(props) {
      console.log('[vue] props from main framework', props);
      storeTest(props);
      render(props);
      globalRegister(store, props)
    }
    

    5.7 微应用之间如何跳转

    • 主应用和微应用都是 hash 模式,主应用根据 hash 来判断微应用,则不用考虑这个问题。
    • 主应用根据 path 来判断微应用

    history 模式的微应用之间的跳转,或者微应用跳主应用页面,直接使用微应用的路由实例是不行的,原因是微应用的路由实例跳转都基于路由的 base。有两种办法可以跳转:

    1. history.pushState():mdn用法介绍
    2. 将主应用的路由实例通过 props 传给微应用,微应用这个路由实例跳转。
    // 用法 第二、第三参数分别是子应用名称及激活路由
    history.pushState(null, 'sub-react', '/sub-react');
    

    5.8 微应用文件更新之后,访问的还是旧版文

    项目上线后由于是独立仓库独立开发独立部署, 微应用文件更新之后,访问的还是旧版文;

    服务器需要给微应用的 index.html 配置一个响应头:Cache-Control no-cache,意思就是每次请求都检查是否更新。

    Nginx 为例:

    location = /index.html {
      add_header Cache-Control no-cache;
    }
    

    5.9 应用加载的资源会 404

    原因是 webpack 加载资源时未使用正确的 publicPath

    可以通过以下两个方式解决这个问题:

    a. 使用 webpack 运行时 publicPath 配置 qiankun 将会在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,你需要做的是在微应用的 entry js 的顶部添加如下代码:

    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    

    关于运行时 publicPath 的技术细节,可以参考 webpack 文档。

    runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、图片 等地址不正确的问题。

    b. 使用 webpack 静态 publicPath 配置 你需要将你的 webpack publicPath 配置设置成一个绝对地址的 url,比如在开发环境可能是:

    {
      output: {
        publicPath: `//localhost:${port}`,
      }
    }
    

    5.10 如何部署

    推荐阅读 官方文档 更详细

    主应用和微应用都是独立开发和部署,即它们都属于不同的仓库和服务

    场景:主应用和微应用部署到同一个服务器(同一个IP和端口) 如果服务器数量有限,或不能跨域等原因需要把主应用和微应用部署到一起。

    通常的做法是主应用部署在一级目录,微应用部署在二/三级目录。

    微应用想部署在非根目录,在微应用打包之前需要做两件事:

    1. 必须配置 webpack 构建时的 publicPath 为目录名称,更多信息请看 webpack 官方说明 和 vue-cli3 的官方说明

    2. history 路由的微应用需要设置 base ,值为目录名称,用于独立访问时使用。

    部署之后注意三点:

    1. activeRule 不能和微应用的真实访问路径一样,否则在主应用页面刷新会直接变成微应用页面。
    2. 微应用的真实访问路径就是微应用的 entryentry 可以为相对路径。
    3. 微应用的 entry 路径最后面的 / 不可省略,否则 publicPath 会设置错误,例如子项的访问路径是 http://localhost:8080/app1,那么 entry 就是 http://localhost:8080/app1/

    以上问题大多数可前端官方文档常见问题查看, 只不过不是很详细, 有的需要自己去百度完善

    六、参考链接

    掘金: juejin.cn/post/687546…


    起源地下载网 » qiankun 微前端实践及常见问题

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元