基于 Vue 微前端 生产项目实践

723 阅读1分钟

微前端背景

对于公司内部管理系统、ToB的SaaS系统等一系列的项目中,由于项目本身的生命周期较长,又长期不断的迭代,加入新的功能模块,最终会导致项目本身的体积越来越大,结构越来越复杂。不但影响了日后的维护成本、性能等方面,而且对于开发来讲,也是越写越写不下去,最后的建议可能就是 Emm... 你懂的。

微前端的概念很早就有了,比如说早期的iframe。但对于现在前端的技术的不断发展出现很多优秀的微前端解决方案,比如single-spa、蚂蚁的qiankun。有兴趣可以了解下。

实现方案

本文所介绍的微前端是基于Vue的路由预加载的方式实现的,具体架构及流程参考下图:

实现流程

具体实现

子项目

通过Vue全家桶来构建子项目

  • 规范子项目命名、端口号,在后续的代理、路由命名都以子模块的名字为准
// package.json
{
    "name": "mobile-wechat",
    "devPort": 10011,
}
  • 调整目录结构,由于子项目依赖于主项目,所以可以将pubic文件夹删掉,最终大致目录结构如下

├── src              
│   ├── views
│   ├── router
│   ├── main.js
├── babel.config.js          
├── vue.config.js      
├── .env.dev       
├── .env.production 
├── .env.test   

  • 调整之后配置config文件
// vue.config.js

const APP_NAME = require('./package.json').name; // 定义项目名
const PORT = require('./package.json').devPort;

module.exports = {
  publicPath: `/${APP_NAME}/`,
  chainWebpack: (config) => {
    config.externals({
      'vue': 'Vue'
    })

    config.output
      .filename('main.js')
      .chunkFilename('[name].[chunkhash:8].js')
      .jsonpFunction(`webpackJsonp-${APP_NAME}`)
      .library(`app-${APP_NAME}`)
      .libraryExport('default')
      .libraryTarget('umd')

    config.plugin('define').use(webpack.DefinePlugin, [{
      'process.env.VUE_APP_NAME': JSON.stringify(APP_NAME)
    }])

    config.plugins
      .delete('html')
      .delete('preload')
      .delete('prefetch')
  },

  devServer: {
    port: PORT,
  },
};

  • 配置好config后,在入口文件中,将定义的路由表挂载到Vue上,并将路由整理成以下格式,方便管理。

子项目路由
代码如下:

import Vue from 'vue'
import router from './router'

// 定义__shareRouter__属性用来存放路由信息
const shareRouter = (Vue.__shareRouter__ = Vue.__shareRouter__ || {})

// 将router定义到属性上
shareRouter[process.env.VUE_APP_NAME] = router

最后启动项目

主项目

  • 首先通过webpack将子项目的入口文件打包到主项目中并且在主项目入口文件之前,可以通过 insert-script-webpack-plugin插件来实现
// webpack.config.base.js

const InsertScriptWebpackPlugin  = require('insert-script-webpack-plugin');

module.exports = {
    // ...
    plugins: [
        // ...
        new InsertScriptWebpackPlugin({
            paths: ['/mobile-crm/main.js', '/mobile-wechat/main.js']
        }),
    ]
}
  • 开发环境配置代理,生产环境可以通过Nginx实现代理,加载子项目的入口文件
// webpack.config.dev.js

const PROXY = {
    '/mobile-crm/': {
      target: 'http://127.0.0.1:10011/'
    },
    '/mobile-wechat/': {
        target: 'http://127.0.0.1:10012/'
    }
}
  • 在主项目的路由文件中拦截子项目路由,并添加到主项目路由表中
import Vue from 'vue';
import Router from 'vue-router';
import routes from './routes/index';

Vue.use(Router);

const routes_crm = Vue.__shareRouter__

routes = Object.values(routes_crm).reduce((acc, prev) =>{
    // 将子项目路由注册到 主项目router-view中
    const b = {
        path: '/',
        component: () => import('@/views/index.vue'),
        children: [...prev]
    }
    return  acc.concat(b)
}, routes)

const router = new Router({
    routes,
});

启动项目可以看到,主项目已经可以加载子项目的main.js文件了。

注意的问题

  • 要确保资源加载顺序,需要优先加载共同依赖的资源->子模块->父模块。
  • 需要保证同一前端框架。
  • 如果router的模式为 history,需要服务端配置,将代理资源文件类型限制在js/css/image等类型,而非html。并设置不能缓存入口文件,具体配置如下:
location ~ \/mobile-crm\/(.+\.js)$ {
    if ( $request_uri = /mobile-crm/main.js ){
        add_header Cache-Control no-cache;
    }
}