大型企业应用在前端微应用视角下的思考

@GuoYongfeng 2018-12-29 06:46:21发表于 iuap-design/blog

大型企业应用在前端微应用视角下的思考

经过 2018 年的前端开发实践,无论是 Web 端的门户型应用还是 Mobile 端的门户型应用
都有一个相同的特点:需要满足大型企业统一工作入口这样的核心诉求,这背后自然少不了快速的规模化的应用开发工作
并且都有一个典型的应用开发模式:门户应用 + 微应用开发模式 + 应用管理

  • 门户底座:企业门户是个大基座,运行时,能够提供注册机制实现领域应用的集成,并且预至各种自定义的管理能力;
  • 微应用开发模式:细分领域、细分业务模块之后,有太多的功能需要快速开发,一个统一的基础框架下的微应用开发模式能够助力领域应用的快速开发,并且需要遵守一定的规范,保证能够顺利集成到门户基座;
  • 应用管理:也可称之为应用商店,微应用开发模式解决了如何快速开发的问题,那么应用管理则需要解决应用开发完之后如何快速发布、如何在应用商店中搜索发现、如何下载和集成使用、如何授权管理、如何计量计费等。

那么,在这种背景下,前端工程师们该何去何从?如何在置身其中的茫茫大海里从容向前。

Contents


1、应用级前端工程及其规范

一个应用跟前端相关的工程非常多:

  • 门户应用的前端工程
  • 公共组件和工具的前端工程
  • 领域微应用的前端工程(N个)

如果没有合理组织,野蛮生长,有可能出现不少问题:

  • 前端技术选型自由,选 jqeury,react,vue,knoutout 的啥都有
  • 前端目录规范不统一,每个小团队自己做一套,自己管自己的
  • 前端组件不统一,有能力自己造,没能力赶时间各种开源的轮子拿进来用
  • 仓库随便开,功能节点级、业务模块级、领域应用级,每个层级都有很多不同仓库分散管理
  • 每个工程的构建机制自定义,自己基于 webpack 写一套,能力强的做一些优化,能力弱一点的找一套开源脚手架,build出来的dist目录下静态资源肯定是五花八门,什么结构都有。

我想,以上的部分情形,在你开发应用的过程中,总会遇上那么一两种。回过头来看,如果重新做一个这样的庞大应用,你会怎么做。

接下来,先说一下ucf-web 的实现思路。

1、假如新建一个项目,将其命名为 ucf-webapp,项目初步结构为:

ucf-webapp
├── .gitignore
├── README.md
├── lerna.json

同时,我们看到,这里面还出现了一个小工具,叫做 lerna,这是个产品矩阵级的多模块管理工具,可以在一个仓库下同时维护多个模块的发布和更新。

2、关于在ucf-webapp 里面如何区分目录结构,希望可以打破常规前端工程的组织方式(一进入即是项目根目录,直接看到webpack配置文件等),我们可以将整体目录层级往上提一层,再做思考:

  • 如果有应用级全局公共的资源,则放到 ucf-common 中去;
  • 门户应用,则命名为 ucf-workbench
  • 属于门户中本身包含的核心应用,则命名为 ucf-workbench-xx,比如:ucf-workbench-org、ucf-workbench-process
  • 如果是业务模块级,或者领域应用级,则统一命名为 ucf-app-xx,比如:ucf-app-order、ucf-app-sales、ucf-staff等等。
  • 但是,还有一个问题,门户应用、门户核心模块、领域业务模块,都是一个个独立工程,那么如何保证他们都能在这个工程一起启动,并且如何都能一起打包成一个完整应用,解决方案咱们这里暂且按下不表,但是肯定得有这么个东西:启动器,我们命名为:ucf-boot-stater。(使用过 spring boot的同学应该会熟悉)
  • 基于模型驱动的部分UI工程,其本质上也依然是一个微应用,或是微应用中的部分功能页面是基于模型驱动的
    • 如果是整个微应用都是基于模型驱动,则命名规范为:ucf-mdf-xx。
    • 如果是微应用中的部分功能基于模型驱动,则无需在命名中体现,开发时引入相应SDK即可。
ucf-webapp
├── .gitignore
├── README.md
├── lerna.json
├── ucf-app-order
├── ucf-app-sales
├── ucf-app-staff
├── ucf-boot-starter
├── ucf-common
├── ucf-coreapp-org
├── ucf-coreapp-process
└── ucf-workbench

2、标准微服务前端工程

在上一节内容中,我们看到一系列以 ucf-xx 开头的工程,那么,这些工程里面,也得满足一定的章法才行。

2.1、标准工程目录

我们以 ucf-app-order 为例进行讨论。我们约定,遵循以下统一规范:

  • 一致的项目目录结构
  • 默认采用 NPM 进行包管理(内部模块默认采用 YNPM 进行包管理)
  • 默认采用 React 技术栈
  • 统一的构建机制(基于 uba
  • 使用统一的基础组件能力体系(使用 tinper-bee
  • 采用一致的应用状态管理方案(使用 mirrorx
  • 默认采用多页应用架构方式(是否待讨论?)
ucf-app-order
├── README.md
├── dist				# 构建后的静态资源
├── package.json
├── src				# 开发态源码
├── test				# 测试文件
└── uba.config.js		# uba 配置文件,构建入口

2.2、src 下源码目录规范

src
├── components			
│   ├── Alert
│   ├── ButtonRoleGroup
│   ├── RefViews
│   ├── RowField
│   ├── SearchPanel
│   └── SelectMonth
├── pages
│   ├── masterdetail-one
│   ├── singletable-popup-edit
│   ├── singletable-query
├── static
│   ├── font
│   ├── images
│   └── trd
├── styles
│   └── app.less
└── utils
    ├── commonref.js
    ├── docInfo.js
    ├── index.js
    ├── request.js

2.3、pages 下功能节点级的文件规范

singletable-query
├── app.js                # 逻辑入口文件
├── components     # 业务组件层
├── container.js       # 容器层,将UI组件与Model数据进行直接关联绑定,后续无需再绑定
├── index.html         # 模板文件
├── model.js             # 模型层,基于mirrorx定义组件的vm逻辑
├── routes                # 路由层,如果该模块为单页应用,可以在此配置
└── service.js          # 服务请求层,基于axios,只完成服务请求的API编写

2.4、dist 下构建后的静态资源目录规范

ucf-app-duban                                         # 工程目录名称(以督办功能为例)
├── fonts                                               # 字体文件目录
│   ├── iconfont.2b12aa52.eot
│   ├── iconfont.454e95d8.svg
│   ├── iconfont.bed8b35e.ttf
│   ├── iconfont.ee989690.woff
├── duban                                             # 节点目录
│   ├── app.js
│   ├── app.js.map
│   ├── index.html
├── vendors                                          # 打包出的公共资源目录(common chunk、第三方库等)
│   ├── vendors.css
│   ├── vendors.css.map
│   ├── vendors.js
│   ├── vendors.js.map

3、标准业务组件开发

目前基于实战已经沉淀并封装出一个标准业务组件的开发工具 ac-tools,可以直接通过 npm i ac-tools -g进行下载使用。

3.1、ac-tools 能力特性

  • 生成满足统一规范的业务组件脚手架
  • 统一的组件文档规范
  • 统一的组件编译开发机制
  • 统一的示例编写和预览机制
  • 支持组件示例直接发布到 github.io

3.2、基本使用命令

Usage :

1. ac-tools init           Generate best application component project
2. ac-tools h              Help
3. ac-tools v              Version
4. ac-tools sample         Producing example Engineering
5. ac-tools md             README.md documents are translated into HTML to be published on git IO

3.3、使用 ac-tools 快速生成的标准业务组件

使用 ac-tools 快速生成的标准业务组件工程的目录规范为:

ucf-components-auth
├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── config
├── demo
├── docs
├── index.html
├── mock
├── oss.config.json
├── package.json
├── postcss.config.js
├── src
└── static

3.4、业务组件的统一命名规范

在建立 tinper 应用组件中心的时候,便已经考虑了这个问题,创建了统一的开源业务组件 ORG:tinper-acs业务组件中心:

组件命名统一以 ac-xx 开头,比如:ac-refer ac-button等等

4、统一的前端项目工程化能力

现代化前端开发的前置工作非常多,早已没有早期直接创建 HTML 文件就能开发的爽快。除了基于 Webpack 的构建文件编写外,我们还需要默认集成 Babel、PostCSS等工具,这个入口门槛还是存在的,即使大家都有能力去写这个配置,但我们也不需要每个人都花时间在写配置文件上面。

而关于是否要做这一层的封装,一直来讲都没有定论:新轮子的抽象能力肯定没有自己写配置来的爽快,但即使是统一的脚手架也没办法完全避免大家的来回修改和各插件的版本管理。所以我们的结论是:统一的工程化工具还是需要(但对于有能力的微应用开发团队,可以选择重新写一套配置,但必须满足统一的目录规范和资源输出规范)

关于工程化工具的能力,我们有这样的设想和要求:

  • 工具自己的依赖,需在自己工程中维护,不可暴露到项目中;
  • 工具的配置文件需要提供,但可以默认缺省(即使不写配置文件也可按默认配置进行开发);
  • 配置文件需要支持配置代理、配置全局环境变量、配置目录别名、配置入口
  • 其他自定义配置需要支持修改webpackConfig进行最后的灵活定义

4.1、uba 现有实现整理

  • 1、配置文件不易梳理
  • 2、依赖管理不易梳理
  • 3、没有解决打包慢的历史问题
  • 4、缺少默认的减少打包体积的最佳实践集成

3、4两条需求得不到满足很容易造成工具最终被微应用开发者推翻重写。

配置文件 uba.config.js

const path = require('path');
const hotMiddlewareScript = 'webpack-hot-middleware/client?reload=true';
const webpack = require('webpack');
const glob = require("glob");

......

const pathUrl = ''; //http://127.0.0.1:8080 设置host,可选
const context = '/iuap_walsin_fe';//工程节点名称
const contentBase = './build' + context;//打包目录
const staticConfig = {
  folder: "dll"
};

......

const svrConfig = {
  historyApiFallback: false
};

// 远程代理访问,可以配置多个代理服务:https://github.com/chimurai/http-proxy-middleware
const proxyConfig = [
  {
    enable: true,
    headers: {
      // 与下方url一致
      "Referer": "http://172.20.52.215:8888"
    },
    //要代理访问的对方路由
    router: [
      '/iuap_walsin_demo', '/wbalone', '/iuap-saas-message-center/', '/iuap-saas-filesystem-service/', '/eiap-plus/', '/newref/', '/print_service/', '/iuap-print/'
    ],
    url: 'http://172.20.52.215:8888'
  }
];

const globalEnvConfig = new webpack.DefinePlugin({
  __MODE__: JSON.stringify(process.env.NODE_ENV),
  GROBAL_HTTP_CTX: JSON.stringify("/iuap_walsin_demo"),
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})

const MINIMIZE_FLAG = (process.env.NODE_ENV == "production") ? true : false;

//提取package里的包
function getVendors() {
  let pkg = require("./package.json");
  let _vendors = [];
  for (const key in pkg.dependencies) {
    _vendors.push(key);
  }
  return _vendors;
}
//优化配置,对于使用CDN作为包资源的引用从外到内的配置
const externals = {
  // 'axios': 'axios',
  // 'react': 'React',
  // 'react-dom': 'ReactDOM',
  //'tinper-bee': 'TinperBee'
}
//默认加载扩展名、相对JS路径模块的配置
const resolve = {
  extensions: [
    '.jsx', '.js', '.less', '.css', '.json'
  ],
  alias: {
    components: path.resolve(__dirname, 'src/components/'),
    modules: path.resolve(__dirname, 'src/pages/'),
    routes: path.resolve(__dirname, 'src/routes/'),
    layout: path.resolve(__dirname, 'src/layout/'),
    utils: path.resolve(__dirname, 'src/utils/'),
    static: path.resolve(__dirname, 'src/static/'),
    src: path.resolve(__dirname, 'src/')
  }
}


entries.vendors = prodEntries.vendors = ['babel-polyfill'].concat(getVendors());

......

//最终向uba导出配置文件
module.exports = {
  devConfig,
  prodConfig,
  svrConfig,
  proxyConfig,
  staticConfig
};

4.2、期望配置

module.exports = function( webpackConfig ){

    // 配置代理
    // 配置全局环境变量
    // 配置目录别名
    // 配置应用类型:SPA Or Multi-Page-Applicatiion

    // 其他自定义配置,直接获取并修改webpackConfig即可
}

5、关于 ucf-boot-starter 启动器的思考

首先,类似 ucf-app-xx 这样的微应用,是可以单独开发、调试、发布的;
其次,类似 ucf-app-xx 这样的微应用,其代码仓库可以放在ucf-webapp下,也可以另起仓库,不做严格限制。
最后,在满足以上两条之后,才是我们需要考虑的:《如何保证多个微应用在进行自由组合之后,放在在同一仓库下,最终也能打包成一个完整应用》,除了前面提的统一技术栈和相关机制之后,这个启动器尤为重要。

5.1、基于 Node.js 的脚本工具实现方式

1、基于 Node.js + NPM 开发一个名为ucf-boot-starterCLI 命令行工具
2、执行 ucf-boot start 启动整体应用
3、执行 ucf-boot build 构建完整应用的静态资源
4、支持自定义选装业务模块:

var path = require('path');

var modules =  [
  { name: "xx", entry: "" },
  { name: "yy", entry: ""}
]

module.exports = { modules }

6、深化统一的设计语言及其React实现

iuap design 设计规范的整理在 2016 年得到全面落地,其React 实现的组件库 tinper-bee 也在至今的两三年中得到大范围的推广使用。『在整个门户型应用中,UI一致性问题尤为重要,统一的设计语言及其落地实现的组件库是解决这一问题的基础』。

纵观这一年,我们团队取得了非常大的突破:

  • 经过 30+ 个项目的实践,每个组件都得到生产环境级别的检验,这背后无数次的细碎修改和升级工作着实不易;
  • 尤其是在NCC这种量级的混合云应用中使用,稳定性表现突出;
  • 经过华新等项目锤炼出优秀的Grid能力,在业界也表现出较强的竞争力;
  • 组件库级别全系组件的全键盘能力支持、多语言能力支持在企业级应用中得到检验;
  • 性能优化,树组件、表格组件等组件在大数据量下的渲染性能问题突出,通过无限滚动方案及延迟加载方案的实现成功的解决了组件的性能瓶颈。

但回过来看,来自一线开发者的使用反馈仍旧不少,这也是鞭策及激励的下一方向:

  • 组件库的UI规范在近一年多时间来未进行系统性的升级优化,整体对外呈现上存在不少一致性UI的细节工作需要改进;
  • 官网文档+示例的机制,方便了开发者的直观使用,但仍有一些示例需要优化升级;
  • 缺少一批基于基础组件的通用业务组件沉淀。

7、模型驱动的前端开发模式 ucf-mdf

模型驱动背后是一套能够描述UI、数据、交互和通讯的元数据,以及基于之上的统一协议规范。

  • 这种方式适用于通过模型的方式快速描述出一套通用的业务模板,减少频繁的重复工作。
  • 同时,对于业务功能要求快速可配置可定制的功能模块而言,这种方式也是极为方便的。

但并不是适用于所有的业务模块及其开发方式。

7.1、协议规范


7.2、解析协议规范的 SDK (runtime-lib)


7.3、基于 SDK 的模板开发

  • 通用业务UI模板封装
  • 自定义UI模板开发

7.4、UI模板的可视化设计器 IDE