5. 感受Single-Spa

我们先通过小demo,来感受下Single-Spa如何实现一个项目两个框架同时存在的

开始

  1. 创建根目录

mkdir single-spa-app
cd single-spa-app

  1. 初始化项目

npm init -y

  1. 安装依赖,直接展示package.json上的依赖吧
  • dependencies 用 “npm i 依赖名” 来安装
  • devDependencies 用 “npm i 依赖名 -D” 来安装
"dependencies": {
"react": "^16.13.1",
"react-dom": "^16.13.1",
"single-spa": "^5.4.0",
"single-spa-react": "^2.14.0",
"single-spa-vue": "^1.8.2",
"vue": "^2.6.11"
},
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/plugin-proposal-object-rest-spread": "^7.9.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.9.4",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.5.3",
"html-loader": "^1.1.0",
"style-loader": "^1.2.1",
"vue-loader": "15.9.1",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}

  1. 创建html入口文件
  • 在根目录创建

touch index.html

<html>
<body>
<div id="react"></div>
<div id="vue"></div>
<script src="/dist/single-spa.config.js"></script>
</body>
</html>

  1. 创建业务代码文件夹及内容
  • 由于我们需要同时存在vue和react两个框架,所以我们可以创建两个框架的业务入口

mkdir src src/vue src/react

  • 在src/vue下创建vue.app.js和main.vue
// vue.app.js

import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
import Hello from './main.vue'

const vueLifecycles = singleSpaVue({
Vue,
appOptions: {
el: '#vue',
render: r => r(Hello)
}
});

export const bootstrap = [
vueLifecycles.bootstrap,
];

export const mount = [
vueLifecycles.mount,
];

export const unmount = [
vueLifecycles.unmount,
];

<!-- main.vue -->
<template>
<div>
<h1>Hello from Vue</h1>
</div>
</template>

  • 在src/react下创建main.app.js和root.component.js
// main.app.js
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import Home from './root.component.js';

// if (process.env.NODE_ENV === 'development') {
// // 开发环境直接渲染
// ReactDOM.render(<Home />, document.getElementById('react'))
// }

function domElementGetter() {
return document.getElementById("react")
}

// 创建生命周期实例
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: Home,
domElementGetter,
})

// 项目启动钩子
export const bootstrap = [
reactLifecycles.bootstrap,
];

// 项目挂载钩子
export const mount = [
reactLifecycles.mount,
];

// 项目卸载钩子
export const unmount = [
reactLifecycles.unmount,
];

// root.component.js 

import React from "react"

const App = () => <h1>Hello from React</h1>

export default App

  1. webpack.config.js
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
mode: 'development',
entry: {
'single-spa.config': './single-spa.config.js',
},
output: {
publicPath: '/dist/',
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
exclude: /node_modules/,
}, {
test: /\.js$/,
exclude: [path.resolve(__dirname, 'node_modules')],
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.vue$/,
loader: 'vue-loader',
exclude: /node_modules/,
}
],
},
node: {
fs: 'empty'
},
resolve: {
alias: {
vue: 'vue/dist/vue.js'
},
modules: [path.resolve(__dirname, 'node_modules')],
},
plugins: [
new CleanWebpackPlugin(),
new VueLoaderPlugin()
],
devtool: 'source-map',
externals: [],
devServer: {
historyApiFallback: true
}
};

7.配置babel

  • 配置babel有好几种方式,我们采用.babelrc的方式

touch .babelrc

{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["last 2 versions"]
}
}],
["@babel/preset-react"]
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-object-rest-spread"
]
}

  1. 重点: 创建single-spa的配置
  • 首先创建single-spa.config.js

touch single-spa.config.js

  • 这个文件是你注册所有应用程序的地方,这些应用程序将是主单页应用程序的一部分,每次调用registerApplication都会注册一个新应用程序,并接受三个参数:
  • App name 应用名称
  • Loading function 加载应用的入口文件
  • Activity function 判断是否加载应用程序的逻辑,函数返回true则加载,返回false则不加载
import { registerApplication, start } from 'single-spa'

registerApplication(
'vue',
() => import('./src/vue/vue.app.js'), // 加载vue项目的入口文件
(location) => location.pathname === "/react" ? false : true // 当url为/vue的时候,返回true, 返回true代表渲染
);



registerApplication(
'react',
() => import('./src/react/main.app.js'),
(location) => location.pathname === "/vue" ? false : true
);

// 所以,如果url为/的时候,那第一个和第二个registerApplication函数的第三个参数,都是返回true,这样就说明当部位/react或/vue时,就都渲染

start();

  1. 创建启动脚本
  • 在package.json中修改scripts如下:
"scripts": {
"start": "webpack-dev-server --open",
"build": "webpack --config webpack.config.js -p"
}

  1. 这时候你可以运行程序了

npm start

访问

# 地址为非/react或非/vue时,vue和react都加载
http://localhost:8080/

# 地址为/react时,只加载react应用
http://localhost:8080/react

# 地址为/vue时,只加载vue应用
http://localhost:8080/vue

重点

  • 在本demo中,最关键的其实就以下几点:
  1. webpack.config.js的入口改为single-spa.configs.js
  2. single-spa.config.js文件主要用于注册应用程序
  3. src/vue/vue.app.js和src/react/main.app.js业务入口文件需要创建微服务生命周期实例并导出,以便外部跟踪该应用的声明活动。