从零打造微前端框架:实战“汽车资讯平台”项目

Posted Hello 程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零打造微前端框架:实战“汽车资讯平台”项目相关的知识,希望对你有一定的参考价值。

V  fcsboy  学习交流

一、什么是微前端

“微前端”一词最早在2016年提出,它将后端微服务的概念扩展到前端世界。简单来说,就是将大型的wen渐进式项目拆分成一个个小型工程,即微应用。每个微应用都能独立开发、部署、运行,然后由主应用将所有微应用整合在一起,实现所有页面的展示和交互。

二、为什么用微前端

在开发toB项目尤其是中后台项目时,以往的趋势都是搭建一个功能强大且完善的单页应用程序。但是中后台项目生命周期较长(一般3年+),这期间由于参与的人员、团队的增多、变迁,单页应用就会从一个普通应用演变成一个巨石应用。随之而来就导致项目无法维护尾大不掉。这类问题在企业级 Web 应用中尤其常见。

巨石应用往往有以下几个弊端:

  • 项目引用包太多,打包上线巨慢;
  • 首屏请求的接口越来越多,页面资源繁杂,导致首屏加载缓慢;
  • 项目初期野蛮生长过度,代码难以维护,产品迭代举步维艰;
  • toB应用对于客户定制化需求很难满足;

巨石应用发展后期都逃不过重构这一条路,一旦重构导致整个项目功能停止更新迭代,对公司发展来说成本巨大。


 微前端架构旨在解决单体应用长时间迭代后,导致不可维护的问题。其具备以下几个核心价值:

  • 技术栈无关
    • 主框架不限制接入应用的技术栈,微应用具备完全自主权
  • 独立开发、独立部署
    • 微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新
  • 增量升级
    • 在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略
  • 独立运行时
    • 每个微应用之间状态隔离,运行时状态不共享

 这其中的技术难点就是如何将不同技术栈的微应用整合在一起,保持微应用独立运行的同时又能实现跨应用通信。qiankun微前端框架提供了一套简单、无痛的微前端架构解决方案。下面我们一步一步讲解使用qiankun如何从零开始搭建微前端项目。

三、主应用

主应用与技术栈无关,我们可以使用Vue、React、Angular、JQuery甚至ES5语法进行搭建。主应用的目的如下:

  1. 注册微应用;
  2. 为每个微应用提供dom容器;
  3. 启动主应用;

下面我们以vue为主应用一步一步进行讲解,先使用vue-cli3构建项目。

3.1 安装qiankun

$ yarn add qiankun

3.2 注册微应用

在入口文件main.js中添加如下代码:

复制代码

import { registerMicroApps, start, setDefaultMountApp } from 'qiankun'

/**
 * step1 初始化应用
 */
new Vue({
    render: h => h(App),
}).$mount('#mainapp-container')

/**
 * step2 注册微应用
 */
registerMicroApps(
    [
        {
            name: 'vue-app',
            entry: '//localhost:8101',
            container: '#subapp-container',
            activeRule: '/vue',
        },
        {
            name: 'react-app',
            entry: '//localhost:8102',
            container: '#subapp-container',
            activeRule: '/react17',
        },
    ],
    {
        beforeMount: [
            app => {
            console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
            },
        ],
        afterUnmount: [
            app => {
            console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
            },
        ]
    }
)

/**
 * step3 设置默认进入微应用
 */
setDefaultMountApp('/vue')

/**
 * step4 启动
 */
start()

复制代码

注意step1中我们将主应用挂载到自己的dom容器#mainapp-container中,step2中微应用运行的容器#subapp-container我们定义在主应用的根组件App.vue中。

3.3 启动配置

所有的微应用都保存在根目录projects中,之后会讲解如何配置不同技术栈的微应用。等所有微应用都配置好后,就需要一键启动。

$ yarn add npm-run-all

安装npm-run-all支持同时运行多个npm项目,然后修改package.json配置:

复制代码

  "scripts": {
    "mainapp:install": "npm-run-all --parallel install:*",
    "mainapp:start": "npm-run-all --parallel start:*",
    "install:main": "yarn",
    "start:main": "yarn start",
    "install:vue": "cd projects/vue && yarn",
    "start:vue": "cd projects/vue && yarn start",
    "install:react17": "cd projects/react17 && yarn",
    "start:react17": "cd projects/react17 && yarn start",
    "start": "vue-cli-service serve",
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },

复制代码

按照下面命令就能启动微前端项目:

$ yarn install
$ yarn mainapp:install
$ yarn mainapp:start

四、微应用

微应用不用额外安装qiankun即可接入主应用,大致分为下面几个步骤:

  1. 入口js文件平级目录下增加public-path.js文件;
  2. 入口js文件中引入public-path.js,修改并导出qiankun定义的三个钩子函数:bootstrap、mount、unmount;
  3. 使用history路由,并且路由base值要和activeRule匹配规则保持一致;
  4. 修改webpack配置,允许开发环境跨域及umd打包;

下面我们以react17+typescript作为技术栈构建微应用,一步一步进行讲解,首先使用create-react-app构建项目。

$ yarn create react-app react17 --template typescript

4.1 在src下增加public-path.js

if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

注意注释掉的那一行代码必须加上,否则项目有eslint报错无法启动。

4.2 修改入口文件

修改src下index.tsx文件

复制代码

import './public-path';

function render(props: any) {
    const { container } = props;
    ReactDOM.render(<App />, container ? container.querySelector('#root') : document.querySelector('#root'));
}

// 单独启动
if (!(window as any).__POWERED_BY_QIANKUN__) {
    render({});
}

// 修改、导出微应用钩子
export async function bootstrap() {
    console.log('[react16] react app bootstraped');
}

export async function mount(props: any) {
    console.log('[react16] props from main framework', props);
    render(props);
}

export async function unmount(props: any) {
    const { container } = props;
    ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root'));
}

复制代码

4.3 路由配置

在组件内部配置路由时,使用history路由并且设置base值。

复制代码

import React, { lazy, Suspense } from 'react'
import { BrowserRouter as Router, Link, Route, Switch } from 'react-router-dom'

const Home = lazy(() => import('../components/Home'))
const About = lazy(() => import('../components/About'))

const Layout: React.FC = () => {
    return (
        <Router basename={(window as any).__POWERED_BY_QIANKUN__ ? '/react17' : '/'}>
            <nav>
                <Link to="/">Home</Link>
                <Link to="/about">About</Link>
            </nav>
            <Suspense fallback={null}>
                <Switch>
                    <Route path="/" exact component={Home} />
                    <Route path="/about" component={About} />
                </Switch>
            </Suspense>
        </Router>
    )
  }

  export default Layout

复制代码

4.4 配置webpack

安装插件@rescripts/cli

yarn add @rescripts/cli

在根目录下新增.rescriptsrc.js文件:

复制代码

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;
    },
};

复制代码

修改package.json启动文件:

复制代码

-   "start": "react-scripts start",
+   "start": "rescripts start",
-   "build": "react-scripts build",
+   "build": "rescripts build",
-   "test": "react-scripts test",
+   "test": "rescripts test",
-   "eject": "react-scripts eject"

复制代码

4.5 启动端口配置

根目录下增加.env配置端口信息,保证微应用启动端口和主应用中注册的该微应用的入口entry保持一致。

SKIP_PREFLIGHT_CHECK=true
BROWSER=none
PORT=8102
WDS_SOCKET_PORT=8102

到这里以React作为技术栈的微应用就配置成功了。Vue、Angular所需要的配置都一样,实现起来大同小异。

完整微前端项目代码在github上。

五、后记

当然一个优秀的项目最终是要服务于产品,为客户提供便利为公司带来价值,不能为了炫技而本末倒置。微前端项目启动初期,需要和后端协商好如何依据业务模块进行拆分确定接口url,后期上线部署也需要运维支持。这样才能保证项目能健壮发展下去。

以上是关于从零打造微前端框架:实战“汽车资讯平台”项目的主要内容,如果未能解决你的问题,请参考以下文章

Node.js-Koa2框架生态实战-从零模拟新浪微博 完整教程

从零打造在线网盘系统之Hibernate框架起步

Java从零打造企业级电商项目实战-服务端

从零打造在线网盘系统之Struts2框架起步

从零打造在线网盘系统之Struts2框架配置全解析

[Go] 从零开始项目-基于gin框架打造restfull风格API