子应用在接入微前端项目时需要进行必要的微前端改造,那问题来了,为什么要做微前端改造?这里面其实是有一个前提条件,即在做微前端项目时如果要将一个项目当成子应用接入微前端中,首先需要做的是主应用应能获取所有子应用的生命周期(包括它的一些方法),这样在主应用里就可以控制子应用的加载和卸载。同时,也需要我们在微前端框架里获取子应用的所有结构(包括依赖文件等等这些内容),所以说子应用需要做一些改造才能完成微前端的接入。
一、Vue2 项目改造
1.1 创建 vue.config.js 文件
在 Vue2 项目中创建 vue.config.js 文件,完成以下配置项设置。
const path = require('path');const { name } = require('./package'); // 获取当前项目的名称function resolve(dir) {return path.join(__dirname, dir);}const port = 9004;module.exports = {outputDir: 'dist', // 打包目录assetsDir: 'static', // 打包的静态资源目录filenameHashing: true, // 打包出来的文件带有hash信息publicPath: 'http://localhost:9004', // 公共路径// 本地服务devServer: {contentBase: path.join(__dirname, 'dist'), // 告诉服务器从哪里提供内容hot: true, // 是否热更新disableHostCheck: true,port,headers: {/*** 配置本地服务的跨域,允许所有的资源可以被访问** 注:为什么要配置跨域呢?** 在写微前端框架的时候,其实我们需要在主应用里或者框架里去获取当前本地服务里的内容。* 如果在获取的时候不去给它设置跨域,会出现资源的拦截,导致无法获取子应用中的内容,因* 此需设置成允许跨域*/'Access-Control-Allow-Origin': '*',},},// 自定义webpack配置,替换 vue-cli-service 里面的 webpack 的一些配置configureWebpack: {resolve: {alias: {'@': resolve('src'),},},/*** 在进行微前端项目改造时,必须配置output下方选项*/output: {/*** 把子应用打包成 umd 库格式(支持commonJS,即支持浏览器环境、node环境)*/libraryTarget: 'umd',filename: 'vue2.js', // 文件打包出来的名字/*** 可以使我们在当前全局环境下获取我们当前打包的内容,即通过 window.vue2 可* 以获取子应用打包的内容,在微前端框架里会用到这一信息(value值对应子应用名称)*/library: 'vue2',jsonpFunction: `webpackJsonp_${name}`,},},};
注:vue.config.js 该文件能生效是有前提条件的,即该项目是通过 vue-cli 创建的,并基于vue-cli-service 进行的项目启动。
1.2 修改 main.js
为了实现 vue2 项目能接入微前端,需对文件 main.js 进行如下改造。
原文件
import Vue from 'vue'import App from './App.vue'import router from './router'Vue.config.productionTip = falsenew Vue({router,render: h => h(App),}).$mount('#app')
修改后
import Vue from 'vue';import App from './App.vue';import router from './router';Vue.config.productionTip = false;/*** 创建 render 函数。** 注:可以在微前端框架里进行引用,也可以通过window.vue2来获取到对应内容*/let instance = null;const render = () => {instance = new Vue({router,render: (h) => h(App),}).$mount('#app');};render();/*** 在微前端环境下,通常不会让子应用自行触发render函数,而是通过微前端的生命周期* 来触发对应的render函数。因此需要加一个限制,即判断当前是否处于微前端环境下*/// 如果不在微前端环境下,则直接执行 render 函数if (!window.__MICRO_WEB__) {render();}/*** 如果在微前端环境下,则暴露一组生命周期,生命周期何时执行可以在微前端框架里进行控制。* + 生命周期: 开始* + 生命周期:渲染成功* + 生命周期:卸载*/export const boostrap = () => {console.log('开始加载');};export const mount = () => {render();};export const unmount = () => {console.log('卸载', instance);};
改造后的效果如下,在调试窗口可以看到全局变量 window.vue2 里面暴露了一组生命周期函数。
二、Vue3 项目改造
2.1 创建 vue.config.js 文件
在 Vue3 项目中创建 vue.config.js 文件,完成以下配置项设置(注:基本与Vue2项目配置一样,详见Vue2项目改造)。
const path = require('path');const { name } = require('./package');function resolve(dir) {return path.join(__dirname, dir);}const port = 9005;module.exports = {outputDir: 'dist',assetsDir: 'static',filenameHashing: true,publicPath: 'http://localhost:9005',devServer: {contentBase: path.join(__dirname, 'dist'),hot: true,disableHostCheck: true,port,headers: {'Access-Control-Allow-Origin': '*',},},// 自定义webpack配置configureWebpack: {resolve: {alias: {'@': resolve('src'),},},output: {// 把子应用打包成 umd 库格式libraryTarget: 'umd',filename: 'vue3.js',library: 'vue3',jsonpFunction: `webpackJsonp_${name}`,},},};
注:vue.config.js 该文件能生效是有前提条件的,即该项目是通过 vue-cli 创建的,并基于vue-cli-service 进行的项目启动。
2.2 修改 main.js
为了实现 vue3 项目能接入微前端,需对文件 main.js 进行如下改造。
原文件
import { createApp } from 'vue';import App from './App.vue';import router from './router';createApp(App).use(router).mount('#app')
修改后
import { createApp } from 'vue';import App from './App.vue';import router from './router';let instance = null;function render() {instance = createApp(App); // 在 vue3 中,createApp 返回 vue 实例instance.use(router).mount('#app');}if (!window.__MICRO_WEB__) {render();}export async function bootstrap() {console.log('vue3.0 app bootstrap');}export async function mount(app) {render();}export async function unmount() {console.log('unmount', instance);}
注:针对 Vue2、Vue3 等Vue项目,接入微前端方式基本一样。
三、React15 项目改造
在 React15 项目中,此次项目的启动是通过 webpack-dev-server 进行启动的。
"scripts": {"start": "cross-env NODE_ENV=development webpack-dev-server --mode production"},
因此要完成 React15 项目的微前端接入,需修改 webpack 相关配置。
3.1 修改 webpack.config.js 文件
const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');const MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = {entry: {path: ['./index.js'],},/*** webpack 整体打包的时候会打包成 (function(){ ....代码内容....})() 这样一个立即执行的方法,* 在这个方法里有我们所有的代码内容。当配置了library之后,webpack 在打包的时候就会配置成如下格式:* var react15 = (function(){})()* 变量 react15 是存放在全局的,通过 react15 这个全局变量就可以拿到各个生命周期方法*/output: {path: path.resolve(__dirname, 'dist'),/*** 把子应用打包成 umd 库格式(支持commonJS,即浏览器环境、node环境都可以进行引用)*/libraryTarget: 'umd',filename: 'react15.js', // 文件打包出来的名字/*** 可以使我们在当前全局环境下获取我们当前打包的内容,即通过 window.react15 可* 以获取子应用打包的内容,在微前端框架里会用到这一信息(value值对应子应用名称)*/library: 'react15',// 会把 AMD 模块命名为 UMD 构建umdNamedDefine: true,publicPath: 'http://localhost:9002/',},module: {rules: [{test: /\.js(|x)$/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env', '@babel/preset-react'],},},},{test: /\.(c|sc)ss$/,use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],},{test: /\.(png|svg|jpg|gif)$/,use: {loader: 'url-loader',},},],},optimization: {splitChunks: false,minimize: false,},plugins: [new HtmlWebpackPlugin({template: './public/index.html',}),new MiniCssExtractPlugin({filename: '[name].css',}),],devServer: {contentBase: path.join(__dirname, 'dist'), // 告诉服务器从哪里提供内容hot: true, // 是否热更新compress: true,port: 9002,headers: {/*** 配置本地服务的跨域,允许所有的资源可以被访问** 注:为什么要配置跨域呢?** 在写微前端框架的时候,其实我们需要在主应用里或者框架里去获取当前本地服务里的内容。* 如果在获取的时候不去给它设置跨域,会出现资源的拦截,导致无法获取子应用中的内容,因* 此需设置成允许跨域*/'Access-Control-Allow-Origin': '*',},historyApiFallback: true,},};
主体配置与vue.config.js文件基本差不多。
3.2 修改入口文件index.js
为了实现 react15 项目能接入微前端,需对文件 index.js 进行如下改造。
原文件
import React from 'react'import ReactDOM from 'react-dom'import BasicMap from './src/router/index.jsx';import "./index.scss"ReactDOM.render(<BasicMap />, document.getElementById('app-react'));
修改后
import React from 'react';import ReactDOM from 'react-dom';import BasicMap from './src/router/index.jsx';import './index.scss';/*** 创建render函数** 注:由于ReactDOM.render返回的并不是一个对象内容,故在unmount生命周期* 中无法像Vue那样清除对象内容。后续可以将根元素节点上的内容置为空。*/const render = () => {ReactDOM.render(<BasicMap />, document.getElementById('app-react'));};// 判断当前环境是否在微前端环境,不在微前端环境则直接执行if (!window.__MICRO_WEB__) {render();}/*** 在微前端环境,则暴露一组生命周期*/export function bootstrap() {console.log('react bootstrap');}export function mount(app) {console.log('react mount');render();}export function unmount(ctx) {console.log('react unmout');}
四、React16 项目改造
在 React16 项目中,此次项目的启动是通过 webpack-dev-server 进行启动的。
"scripts": {"start": "cross-env NODE_ENV=development webpack-dev-server --mode production"},
因此要完成 React16 项目的微前端接入,需修改 webpack 相关配置。
在 React16 项目中,webpack的配置以及入口文件 index.js 的修改基本与 React15 的保持一致(除打包应用名称不同),故此处不在复述。
