跳到主要内容

vite-react-JSEntry-System-Parcel

一、认识


1.1 SystemJs

SystemJs是一个通用的模块加载器,有属于自己的模块化规范。他能在浏览器和node环境上动态加载模块,微前端的核心就是加载子应用,因此将子应用打包成模块,在浏览器中通过SystemJs来加载模块。SystemJS可兼容到IE11,但是它对于插件版本要求非常严格,而且变化非常大,兼容性也不是特别好,使用体验也不是很好,所以目前实践中用的非常少。它同样支持import映射,但是它的语法稍有不同。

二、实现


2.1 index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Main-App-React</title>
<script type="systemjs-importmap">
{
"imports": {
"react": "https://cdn.jsdelivr.net/npm/react@18.2.0/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@18.2.0/umd/react-dom.production.min.js",
"react-router-dom":"https://cdn.jsdelivr.net/npm/react-router-dom@5.3.3/umd/react-router-dom.min.js",
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.8.2/lib/system/single-spa.min.js",
"single-spa-react": "https://cdn.jsdelivr.net/npm/single-spa-react@4.6.1/lib/system/single-spa-react.js",
"single-spa-vue": "https://cdn.jsdelivr.net/npm/single-spa-vue@2.5.1/dist/system/single-spa-vue.js",
"micro-app-vue3": "https://bolawen.github.io/MicroFrontEnd/Single-Spa-BySystem/micro-app-vue3/output/micro-app-vue3.system.4646c519.js",
"micro-app-react":"https://bolawen.github.io/MicroFrontEnd/Single-Spa-BySystem/micro-app-react/output/micro-app-react.092b73cb.js"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/import-map-overrides@1.16.0/dist/import-map-overrides.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.4.0/dist/system.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.4.0/dist/extras/amd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@6.4.0/dist/extras/named-exports.js"></script>
</head>
<body>
<div id="main-app-react"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

2.2 src/main.tsx

import packageInfo from "../package.json";
import * as app from "./single-spa-config";
import { registerApplication, start } from "single-spa";

registerApplication({
name: packageInfo.name,
app: app,
activeWhen: () => true,
customProps: {
base: "/",
mode: "history",
},
});
start({
urlRerouteOnly: true,
});

2.3 src/single-spa-config.tsx

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import singleSpaReact from "single-spa-react";

const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary(err, info, props) {
return <div>This renders when a catastrophic error occurs</div>;
},
});

export const { bootstrap, mount, unmount, update } = reactLifecycles;

2.4 src/App.tsx

import React from "react";
import {
BrowserRouter,
Routes,
Route,
NavLink,
HashRouter,
} from "react-router-dom";
import Home from "./Home";
import Content from "./Content";

function App(props: any) {
console.log("Micro-App-React props", props);
const { base, mode } = props;
const Router = mode === "history" ? BrowserRouter : HashRouter;
return (
<Router basename={base}>
<h3>Main-App-React 基座Demo</h3>
<NavLink to="/">首页</NavLink>
<NavLink to="/content/">内容</NavLink>
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/content/*" element={<Content />}></Route>
</Routes>
</Router>
);
}

export default App;

2.5 src/Home.tsx

import React from 'react'

function Home(){
return <div>首页</div>
}

export default Home;

2.6 src/Context.tsx

import React, { useState } from "react";
import useLoadMicro from "./loadMicro";

function Content() {
const [microApp, setMicroApp] = useState();
const loadMicro = useLoadMicro();

const handleClick = async (path: string) => {
const result = await loadMicro(path);
setMicroApp(null);
setMicroApp(result);
};

return (
<div>
<div>
<button onClick={() => handleClick("micro-app-vue3")}>
micro-app-vue3 微应用
</button>
<button onClick={() => handleClick("micro-app-react")}>
micro-app-react 微应用
</button>
</div>
{microApp}
</div>
);
}

export default Content;

2.7 src/loadMicro.tsx

import React from "react";
import Parcel from "./parcel";

function useLoadMicro() {
return async function (path:string) {
const parcelConfig = await window.System.import(path);
const parcelProps = {
base: "/content",
mode: "history",
};
return <Parcel path={path} config={parcelConfig} parcelProps={parcelProps} />;
};
}

export default useLoadMicro;

2.8 src/parcel.tsx

import { mountRootParcel } from "single-spa";
import React, { useRef, useEffect } from "react";

interface PropsType {
config: any;
path: string;
wrapWith?: string;
wrapClass?: string;
wrapStyle?: object;
parcelProps: object;
}

function Parcel(props: PropsType) {
const divRef = useRef();
const parcel = useRef();
const { wrapClass, wrapStyle, parcelProps, config, path } = props;

useEffect(() => {
if (divRef.current) {
parcel.current = mountRootParcel(config, {
...parcelProps,
domElement: divRef.current,
});
parcel.current.mountPromise.then(() => {
console.log(`${path}-挂载完成`);
});
return () => {
parcel.current.unmount();
parcel.current.unmountPromise.then(() => {
console.log(`${path}-卸载完成`);
});
};
}
}, [divRef]);

return <div ref={divRef} className={wrapClass} style={wrapStyle}></div>;
}

export default Parcel;

2.9 package.json

{
"name": "main-app-react",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0",
"single-spa": "^5.9.4",
"single-spa-react": "^4.6.1"
},
"devDependencies": {
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.0.0",
"@vitejs/plugin-react-refresh": "^1.3.6",
"terser": "^5.14.2",
"typescript": "^4.6.4",
"vite": "^3.0.0"
}
}

2.10 vite.config.ts

import { defineConfig } from "vite";
import packageInfo from "./package.json";
import reactRefresh from "@vitejs/plugin-react-refresh";

export default ({ mode }) => {
return defineConfig({
plugins: [reactRefresh()],
base:
mode === "production"
? `https://bolawen.github.io/MicroFrontEnd/Single-Spa-BySystem/${packageInfo.name}/`
: "/",
server: {
port: 3001,
},
build: {
assetsDir: "",
outDir: "output",
cssCodeSplit: false,
rollupOptions: {
input: "src/single-spa-config.tsx",
preserveEntrySignatures: "exports-only",
external: [
"axios",
"lodash",
"react",
"react-dom",
"single-spa",
"single-spa-react",
],
output: {
format: "system",
entryFileNames: `${packageInfo.name}.[hash].js`,
},
},
},
});
};