添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

一、前言

大型中后台项目一般包括10个以上的子项目,如果维护在一个单页面应用中,项目就会越来越大,而且不利于版本的迭代,微前端就很好的解决了这些问题。

这篇文章主要来体验下蚂蚁的微前端:qiankun,虽然比较成熟了,但在体验过程中还是有一些问题,记录总结下, 项目代码

实践项目以react单页面应用为主应用,然后构建了三个微应用:react、vue3、node静态页面

二、前期准备

微前端要求多个前端服务,所以我们先准备几个应用,使用不同的技术栈,体验微前端的强大

1、项目架构

mirco-front-demo 作为整个服务的根目录,为了便于实践,主应用和微应用将放在一起。

主应用:

  • my-app

    • port: 10000

    • create-react-app

微应用:

  • micro-reat-1

    • port: 10100

    • create-react-app

  • micro-vue-2

    • port: 10200

    • vue3

  • micro-static-3

    • port: 10300

    • node + html

2、主应用my-app

需要提前安装create-react-app: sudo npx install create-react-app

通过 create-react-app 创建主应用 my-app ,其他的微应用都会挂载到主应用。

# 在根目录 mkdir mirco-front-demo cd mirco-front-demo # 新建主应用my-app npx create-react-app my-app cd my-app # 通过.env文件修改启动端口 echo "PORT=10000" > .env yarn start 复制代码

3、微应用micro-react-1

cd micro-front-demo npx create-react-app micro-react-1 复制代码

同主应用一样创建一个React应用,命名 micro-react-1 ,并修改启动端口号(也可以使用.env文件修改)

修改启动端口号:

{     "scripts": {         "start": "PORT=10100 react-app-rewired start",         "build": "react-app-rewired build",         "test": "react-app-rewired test",         "eject": "react-app-rewired eject"     } } 复制代码

4、微应用micro-vue-2

使用vue3.0,提前安装vue-cli: yarn global add @vue/cli 官方文档

vue create micro-vue-2 cd micro-vue-2 touch vue.config.js 复制代码

除了微应用1和微应用2修改启动端口号的方法,这里也可以对webpack进行覆盖

vue-config.js

const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({   transpileDependencies: true,   devServer: {     // 监听端口     port: 10200,     // 配置跨域请求头,解决开发环境的跨域问题     headers: {       "Access-Control-Allow-Origin": "*",     }   } } 复制代码

启动微应用 yarn serve

5、微应用micro-static-3

# 新建微应用项目3 mkdir micro-static-3 cd micro-static-3 npm init yarn add express cors # 新建项目文件 mkdir static touch index.js cd static touch index.html 复制代码

index.js

const express = require('express') const cors = require('cors') const app = express() const port = 10300 app.use(cors()) app.use(express.static('static')); app.listen(port, () => {   console.log(`Example app listening on port ${port}`) }) 复制代码

启动微应用 node index.js

6、同时启动微应用

cd mirco-front-demo npm init yarn add npm-run-all -D 复制代码

修改当前目录下 package.json

{     "scripts": {         "test": "echo \"Error: no test specified\" && exit 1",         "start": "npm-run-all --parallel start:*",         "start:main": "cd my-app && yarn start",         "start:micro-react": "cd micro-react-1 && yarn start",         "start:micro-vue": "cd micro-vue-2 && yarn serve",         "start:micro-static": "cd micro-static-3 && node index.js"     } } 复制代码

执行命令

cd micro-front-demo yarn start 复制代码

这样就同时启动了四个前端服务

三、配置qiankun

1、主应用路由和样式

改造下主应用样式,整个顶部导航栏和侧边栏属于主应用,而中间空白的部分可以展示主应用或子应用的页面。

主应用路由安装 react-router-dom ,通过 history 模式渲染。

image.png

在侧边栏点击不同的链接会加载不同的子应用,样式和路由具体可以看 示例代码

2、注册微应用

修改单页面应用渲染根节点 root main-root ,防止和微应用中渲染节点冲突。

image.png

并且增加一个通过id标记的DIV,用来嵌入微应用,接着引入 qiankun ,这里 id=subApp 用来挂载微应用

yarn add qiankun  复制代码

image.png

src/index.js 中配置

import React from 'react'; import ReactDOM from 'react-dom'; import { registerMicroApps, start } from 'qiankun'; import './index.css'; import App from './App'; function render(){   ReactDOM.render(<App />, document.querySelector('#main-root')); } render({}); registerMicroApps([   {     name: 'react', // app name registered     entry: '//localhost:10100',     container: "#subApp",     activeRule: '/react'   },   {     name: 'vue', // app name registered     entry: '//localhost:10200',     container: "#subApp",     activeRule: '/vue'   },   {     name: 'static', // app name registered     entry: '//localhost:10300',     container: "#subApp",     activeRule: '/static'   } ], {   beforeLoad: app => {     console.log('before load app.name=====>>>>>', app.name)   },   beforeMount: [     app => {       console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)     }   ],   afterMount: [     app => {       console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)     }   ],   afterUnmount: [     app => {       console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)     }   ] }) start() 复制代码

3、微应用配置

1) micro-react-1

引入 react-router-dom 给微应用配置路由,展示不同的页面,如下:

image.png

修改 src/index.js 下启动文件

function render(props) {   const { container } = props;   ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root')); } if (!window.__POWERED_BY_QIANKUN__) {   render({}); } export async function bootstrap() {   console.log('[react16] react app bootstraped'); } export async function mount(props) {   console.log('[react16] props from main framework', props);   render(props); } export async function unmount(props) {   const { container } = props;   ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); } 复制代码

给微应用添加导出声明周期函数,微应用挂载成功时,渲染到当前应用的root节点。

当前cra并没有释放webpack配置,所以要通过插件覆盖配置:

yarn add react-app-rewired -D 复制代码

"scripts": {     "start": "PORT=10100 react-app-rewired start",     "build": "react-app-rewired build",     "test": "react-app-rewired test",     "eject": "react-app-rewired eject" } 复制代码

在当前微应用根目录下 touch config-overrides.js

const { name } = require('./package'); module.exports = {     webpack: (config, env) => {         config.output.library = `${name}-[name]`;         config.output.libraryTarget = 'umd';         config.output.globalObject = 'window';         config.output.chunkLoadingGlobal = `webpackJsonp_${name}`;         return config;     },     devServer: (_) => {         const config = _;         config.headers = {           'Access-Control-Allow-Origin': '*',         };         config.historyApiFallback = true;         config.hot = false;         config.watchContentBase = false;         config.liveReload = false;         config.injectClient = false         return config;     } } 复制代码

启动微应用 yarn start ,正常运行

2) micro-vue-2

新增 public-path.js 文件,用于修改运行时的 publicPath

src/public-path.js

if (window.__POWERED_BY_QIANKUN__) {     __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } 复制代码

引入vue路由,设置成history模式,baseRouter设置成vue,导出声明周期函数

import { createApp } from 'vue' import App from './App.vue' import router from './router' import './public-path' const app = createApp(App); function render(props) {   const { container } = props;   app.use(router)   .mount(container ? container.querySelector('#app') : '#app') } export async function bootstrap() {     console.log('bootstrap'); } export async function mount(props) {     console.log('mount', props);     render(props); } export async function unmount() {     console.log('unmount');     app.unmount(); } 复制代码

修改webpack配置 vue.config.js

module.exports = defineConfig({     configureWebpack: {         output: {           // 微应用的包名,这里与主应用中注册的微应用名称一致           library: name,           // 将你的 library 暴露为所有的模块定义下都可运行的方式           libraryTarget: "umd",           // 按需加载相关,设置为 webpackJsonp_微应用名称 即可           chunkLoadingGlobal: `webpackJsonp_${name}`,         }     } }) 复制代码

启动应用 yarn serve

3) micro-static-3

这是一个express服务启动的静态服务

文件入口导出声明周期 entry.js

const render = ($) => {     $('#app').html('Hello, render html, 一个通过http服务部署的静态网站');     return Promise.resolve(); }; ((global) => {     global['static'] = {       bootstrap: () => {         console.log('purehtml bootstrap');         return Promise.resolve();       },       mount: () => {         console.log('purehtml mount');         return render($);       },       unmount: () => {         console.log('purehtml unmount');         return Promise.resolve();       },     }; })(window); 复制代码

然后在模版文件导入

image.png

其实也是挂载在了app节点。

启动当前微服务 node index.js

4、启动所有应用

cd micro-front-demo yarn start 复制代码

四、报错处理

1、报错信息

'__webpack_public_path__' is not defined

Uncaught Error: single-spa minified message #20

覆盖CRA的Webpack配置

const { name } = require('./package'); module.exports = {   webpack: (config) => {     config.output.library = `${name}-[name]`;     config.output.libraryTarget = 'umd';     config.output.jsonpFunction = `webpackJsonp_${name}`;     config.output.globalObject = 'window';     return config;   },   devServer: (_) => {     const config = _;     config.headers = {       'Access-Control-Allow-Origin': '*',     };     config.historyApiFallback = true;     config.hot = false;     config.watchContentBase = false;     config.liveReload = false;     return config;   }, }; 复制代码

这里报错,是因为是webpack5.x不兼容 config.output.jsonpFunction 的写法,需要替换成 config.output.chunkLoadingGlobal

2、报错信息

construct.js:17 Uncaught Error: application 'react' died in status LOADING_SOURCE_CODE: [qiankun]: You need to export lifecycle functions in react entry

没有将生命周期暴露出来,需要挨个检查下面的配置

Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.  - configuration.output has an unknown property 'jsonpFunction'. These properties are valid:    object { assetModuleFilename?, asyncChunks?, auxiliaryComment?, charset?, chunkFilename?, chunkFormat?, chunkLoadTimeout?, chunkLoading?, chunkLoadingGlobal?, clean?, compareBeforeEmit?, crossOriginLoading?, cssChunkFilename?, cssFilename?, devtoolFallbackModuleFilenameTemplate?, devtoolModuleFilenameTemplate?, devtoolNamespace?, enabledChunkLoadingTypes?, enabledLibraryTypes?, enabledWasmLoadingTypes?, environment?, filename?, globalObject?, hashDigest?, hashDigestLength?, hashFunction?, hashSalt?, hotUpdateChunkFilename?, hotUpdateGlobal?, hotUpdateMainFilename?, iife?, importFunctionName?, importMetaName?, library?, libraryExport?, libraryTarget?, module?, path?, pathinfo?, publicPath?, scriptType?, sourceMapFilename?, sourcePrefix?, strictModuleErrorHandling?, strictModuleExceptionHandling?, trustedTypes?, umdNamedDefine?, uniqueName?, wasmLoading?, webassemblyModuleFilename?, workerChunkLoading?, workerWasmLoading? }    -> Options affecting the output of the compilation. `output` options tell webpack how to write the compiled files to disk.    Did you mean output.chunkLoadingGlobal (BREAKING CHANGE since webpack 5)?


作者:前端中后台
链接:https://juejin.cn/post/7075607657205710855
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。