微前端利用分为主利用与子利用,部署形式是别离编译好主利用与子利用,将主利用与子利用部署到 nginx 配置好的目录即可。

代码仓库 https://github.com/jwchan1996/qiankun-micro-app

别离进入 portalapp1app2 根目录,执行:

开发模式
# portalyarnyarn start
# app1、app2npm installnpm run dev
生产模式
# portalyarn build
# app1、app2npm run build

主利用

主利用 js 文件引入 qiankun 注册子利用,并编写导航页显示跳转逻辑。

<!DOCTYPE html><html lang="zh"><head>  <meta charset="UTF-8">  <title>QianKun Example</title></head><body>  <div class="mainapp">    <!-- 标题栏 -->    <header class="mainapp-header">      <h1>导航</h1>    </header>    <div class="mainapp-main">      <!-- 侧边栏 -->      <ul class="mainapp-sidemenu">        <li class="app1">利用一</li>        <li class="app2">利用二</li>      </ul>      <!-- 子利用  -->      <main id="subapp-container"></main>    </div>  </div>  <script src="./index.js"></script></body></html>

主利用 js 入口文件:

import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from 'qiankun';import './index.less';/** * 主利用 **能够应用任意技术栈** * 以下别离是 React 和 Vue 的示例,可切换尝试 */import render from './render/ReactRender';// import render from './render/VueRender';/** * Step1 初始化利用(可选) */render({ loading: true });const loader = loading => render({ loading });/** * Step2 注册子利用 */registerMicroApps(  [    {      name: 'app1',      entry: process.env.NODE_ENV === 'production' ? '//192.168.2.192:7100' : '//localhost:7100',      container: '#subapp-viewport',      loader,      activeRule: '/app1',    },    {      name: 'app2',      entry: process.env.NODE_ENV === 'production' ? '//192.168.2.192:7101' : '//localhost:7101',      container: '#subapp-viewport',      loader,      activeRule: '/app2',    }  ],  {    beforeLoad: [      app => {        console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);      },    ],    beforeMount: [      app => {        console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);      },    ],    afterUnmount: [      app => {        console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);      },    ],  },);const { onGlobalStateChange, setGlobalState } = initGlobalState({  user: 'qiankun',});onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev));setGlobalState({  ignore: 'master',  user: {    name: 'master',  },});/** * Step3 设置默认进入的子利用 */// setDefaultMountApp('/app1');/** * Step4 启动利用 */start();runAfterFirstMounted(() => {  console.log('----------------------------------')  console.log(process.env.NODE_ENV)  console.log('----------------------------------')  console.log('[MainApp] first app mounted');});//浏览器地址入栈function push(subapp) { history.pushState(null, subapp, subapp) }//配合导航页显示逻辑function initPortal(){  //主利用跳转  document.querySelector('.app1').onclick = () => {    document.querySelector('.mainapp-sidemenu').style.visibility = 'hidden'    push('/app1')  }  document.querySelector('.app2').onclick = () => {    document.querySelector('.mainapp-sidemenu').style.visibility = 'hidden'    push('/app2')  }  //回到导航页  document.querySelector('.mainapp-header h1').onclick = () => {    push('/')  }  if(location.pathname !== '/'){    document.querySelector('.mainapp-sidemenu').style.visibility = 'hidden'  }else{    document.querySelector('.mainapp-sidemenu').style.visibility = 'visible'  }  if(location.pathname.indexOf('login') > -1){    document.querySelector('.mainapp-header').style.display = 'block'  }else{    document.querySelector('.mainapp-header').style.display = 'none'  }  //监听浏览器后退回退  window.addEventListener('popstate', () => {     if(location.pathname === '/'){      document.querySelector('.mainapp-sidemenu').style.visibility = 'visible'    }    if(location.pathname.indexOf('login') > -1){      document.querySelector('.mainapp-header').style.display = 'block'    }else{      document.querySelector('.mainapp-header').style.display = 'none'    }  }, false)}initPortal()

docker nginx 配置

此处 nginx 次要作用是用于端口目录转发,并配置主利用拜访子利用的跨域问题。

应用 docker 配置部署 nginx

# docker-compose.ymlversion: '3.1'services:  nginx:    restart: always    image: nginx    container_name: nginx    ports:       - 8888:80      - 8889:8889      - 7100:7100      - 7101:7101    volumes:       - /app/volumes/nginx/nginx.conf:/etc/nginx/nginx.conf      - /app/volumes/nginx/html:/usr/share/nginx/html      - /app/micro/portal:/app/micro/portal      - /app/micro/app1:/app/micro/app1      - /app/micro/app2:/app/micro/app2

将编译后的主利用以及子利用放到对应的数据卷挂载目录即可,如主利用 /app/micro/portal
同理,也须要将配置好的 nginx.conf 文件放到指定的数据卷挂载目录,应用 docker-compose up -d 启动即可。

nginx 端口目录转发配置:

# nginx.confuser  nginx;worker_processes  1;error_log  /var/log/nginx/error.log warn;pid        /var/run/nginx.pid;events {    worker_connections  1024;}http {    include       /etc/nginx/mime.types;    default_type  application/octet-stream;    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '                      '$status $body_bytes_sent "$http_referer" '                      '"$http_user_agent" "$http_x_forwarded_for"';    access_log  /var/log/nginx/access.log  main;    sendfile        on;    #tcp_nopush     on;    keepalive_timeout  65;    #gzip  on;    include /etc/nginx/conf.d/*.conf;    server {      listen    8889;      server_name 192.168.2.192;            location / {        root /app/micro/portal;        index index.html;                try_files $uri $uri/ /index.html;      }    }    server {      listen    7100;      server_name 192.168.2.192;            # 配置跨域拜访,此处是通配符,严格生产环境的话能够指定为主利用 192.168.2.192:8889      add_header Access-Control-Allow-Origin *;      add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';      add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';            location / {        root /app/micro/app1;        index index.html;                try_files $uri $uri/ /index.html;      }    }    server {      listen    7101;      server_name 192.168.2.192;            # 配置跨域拜访,此处是通配符,严格生产环境的话能够指定为主利用 192.168.2.192:8889      add_header Access-Control-Allow-Origin *;      add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';      add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';            location / {        root /app/micro/app2;        index index.html;                try_files $uri $uri/ /index.html;      }    }}

子利用适配框架

下体面利用以惯例 vue 我的项目为例。

入口文件 main.js

在入口文件减少 qiankun 环境判断,判断以后是 qiankuan 环境的则将子利用引入到主利用框架内,而后在主框架内执行失常的 vue 元素挂载。

// 在所有代码的文件之前引入判断if (window.__POWERED_BY_QIANKUN__) {  // eslint-disable-next-line no-undef  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;}import Vue from "vue";import App from "./App";import router from "./router";let instance = null;function render(props = {}) {  // 此处 container 是主利用生成的用于装载子利用的 div 元素  // 如 <div id="__qiankun_microapp_wrapper_for_app_1_1596504716562__" />  const { container } = props;    instance = new Vue({    router,    render: h => h(App),  }).$mount(container ? container.querySelector('#app') : '#app');}if (!window.__POWERED_BY_QIANKUN__) {  render();}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 bootstrap() {  console.log('[vue] vue app bootstraped');}export async function mount(props) {  console.log('[vue] props from main framework', props);  storeTest(props);  render(props);}export async function unmount() {  instance.$destroy();  instance.$el.innerHTML = '';  instance = null;}

router 配置

路由须要依据 qiankun 环境配置 base 门路,以及设置路由的 history 模式。

// router/index.jsconst router = new Router({  // 此处 /app1 是子利用在主利用注册的 activeRule  base: window.__POWERED_BY_QIANKUN__ ? '/app1' : '/',  mode: 'history',  routes: [    {        ……        ……    }  ]})// portal/index.jsregisterMicroApps(  [    {      name: 'app1',      entry: process.env.NODE_ENV === 'production' ? '//192.168.2.192:7100' : '//localhost:7100',      container: '#subapp-viewport',      loader,      activeRule: '/app1',    }  ])

子利用打包

打包 umd 格局

output: {    library: 'portal',    libraryTarget: 'umd'}

字体图标与 css 背景图片门路问题

默认状况下,在 css 援用的资源应用 url-loader 加载打包进去是相对路径的,所以会呈现子利用的资源拼接到主利用的 domain 的状况,造成加载资源失败。

因为 element-ui 的字体图标是在 css 外面引入的,还有相干背景图片的引入也是在 css 里,所以须要配置 webpackurl-loader,生产模式状况下间接指定资源前缀。

module: {  rules: [    {      test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,      loader: "url-loader",      options: {        limit: 10000,        name: utils.assetsPath("img/[name].[hash:7].[ext]"),        //这里 192.168.2.192:7100 是子利用部署地址        publicPath: process.env.NODE_ENV === 'production' ? '//192.168.2.192:7100' : ''      }    },    {      test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,      loader: "url-loader",      options: {        limit: 10000,        name: utils.assetsPath("fonts/[name].[hash:7].[ext]"),        publicPath: process.env.NODE_ENV === 'production' ? '//192.168.2.192:7100' : ''      }    }  ]}