子应用在接入微前端项目时需要进行必要的微前端改造,那问题来了,为什么要做微前端改造?这里面其实是有一个前提条件,即在做微前端项目时如果要将一个项目当成子应用接入微前端中,首先需要做的是主应用应能获取所有子应用的生命周期(包括它的一些方法),这样在主应用里就可以控制子应用的加载和卸载。同时,也需要我们在微前端框架里获取子应用的所有结构(包括依赖文件等等这些内容),所以说子应用需要做一些改造才能完成微前端的接入。
一、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 = false
new 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 的保持一致(除打包应用名称不同),故此处不在复述。