述职报告

iCafe前端优化

时扬扬

icafe前端优化

  • 核心问题
    • 项目资源请求庞大
      整站的前端响应较慢, 关键环节用户体验较差
    • 交互开发方式随意
      既有DOM操作也有重度模板使用
    • 持续构建前后混合
      前后端构建互相依赖, 导致前端需求响应迟钝, 且优化困难
  • 方案
    • 资源瘦身
    • 模板引入
    • 前端分离

资源瘦身

  • 问题 整站的前端响应较慢, 关键环节用户体验较差
  • 原因
    1. 资源合并不合理
    2. ajax页面资源依赖混乱
    3. 服务端页面渲染造成大量冗余数据
  • 方案
    1. 合并重组
    2. 异步页面资源升级
    3. 核心功能模块优化

资源瘦身 - 合并重组

  • 原因
    1. 第三方库和业务代码合并
    2. 部分合并资源过大
    3. 请求数过多 (10K内小文件较多)
      many-js.png
  • 方案
    1. 分离合并第三方库代码
    2. 按照页面功能分别合并
    3. 引入CDN
      • 梳理资源依赖
        • jquery/bootstrap 版本问题 (评估风险 & 嵌入代码)
      • 了解相关插件功能和版本差异
        • handlebars低版本引入和模板改写
  • 效果
    请求数减少 30%~50%, 首屏响应时间 6s~2.5s
    backgroundColor: '#fff', title: { text: '资源合并请求对比', subtext: '对比主要页面请求数变化' }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, }, legend: { data: ['合并前', '合并后', '前.js', '前.其他', '后.js', '后.其他'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'value', boundaryGap: [0, 0.01] }, yAxis: { type: 'category', data: ['Todo Desk', 'PlanTrack', 'issues'] }, series: [ { name: '前.js', type: 'bar', stack: '合并前', data: [48, 66, 52] }, { name: '后.js', type: 'bar', stack: '合并后', data: [20, 32, 25] }, { name: '前.其他', stack: '合并前', type: 'bar', data: [42, 73, 57] }, { name: '后.其他', stack: '合并后', type: 'bar', data: [27, 58, 52] } ]

资源瘦身 - 异步页面资源升级

  • 原因:ajax页面资源依赖打包不合理
    ajax页面中掺杂了前端资源,导致每次请求重复加载资源

    graph TB subgraph 线上环境 依赖资源被打包合并到大文件 D1[a.js] -- packTo --> E[pkg.js] D2[b.js] -- packTo --> E[pkg.js] D3[c.js] -- packTo --> E[pkg.js] E[pkg.js] -- requireTo --> C1[a.jsp] E[pkg.js] -- requireTo --> C2[b.jsp] end subgraph 开发环境 分别依赖资源 A1[a.jsp] -- require --> B1[a.js] A2[b.jsp] -- require --> B2[b.js] end
  • 方案
    1. 分离部分已经合并的资源 (×)
    2. 相关场景的前后分离改造 (×)
    3. 模块资源提升到父级页面载体 (√)
      • 后续合并优化需要
      • 相对规范的模块化代码书写
graph LR A[a.js] -- packTo --> C[pkg.js] B[b.js] -- packTo --> C C -- requireTo --> D[parent.jsp] D -- contains --> E[ajax-a] D -- contains --> F[ajax-b]
  • 效果 (数据为估值)
场景 新建编辑卡片 plantrack列表 我的任务切换
改造前 2s+ 5~6s 1.5s+
改造后 0.5s- 4~5s 0.3-
  • plantrack列表仍有进一步优化的空间

资源瘦身 - 核心功能模块优化

  • plantrack列表前端模板改写
    • 原因
      • 服务端渲染,数据量极大
    • 方案
      • 完全前后分离异步方式,需要改写大量功能模块 (×)
      • 在异步页面中拼装前端模板,迅速改写 (√)
        • 接口改写成本
        • 逻辑梳理风险
    • 效果
      • 100条数据效果: 2.6M ==> 400~500K
      • 同步的页面使用了异步的方案, 最小化的操作满足需求。

模板引入

  • 问题
    • 修改功能和BUG易引起其他问题
  • 原因
    • 项目中前端交互存在纯DOM操作等, 开发效率不高且维护困难
  • 方案
对比 上手难度 侵入性 交互开发 数据渲染
dom操作 容易 混乱 麻烦
handlebars 较容易 方便
react (√) 复杂 轻微 清晰 方便
angular/vue 复杂 严重 清晰 方便

前端分离

  • 原因:
    1. 前后端构建互相依赖, 导致前端需求响应迟钝, 且优化困难
    2. 同步模块化资源导致全量加载, 页面首屏响应慢
    3. react前端架构引入
      • 基本概念简单
      • ES6 书写减少代码量、提高可读性等
    4. 同步ET各个系统技术栈,公共化的组件应用
  • 方案:
    1. 模块化方案
    2. 包管理工具
    3. fis构建工具
    4. 打包优化

前端分离 - 模块化方案

  • 原因
    • 体验 更少的首屏加载时间
    • 技术 方便的跨域资源引入
  • 方案
    • requirejs(AMD)
  • 难题
    多模块化方式共存
    • FIS/mod.js
    • require.js
    • 如何寻找差异实现共存?
// mod.js
define('moduleId', function () {});
module = require('moduleId');
// require.js
define('moduleId', [], function () {});
require(['moduleId'], function (module) {});
graph LR A((id)) --> B{window.define} B --> C[mod.define] B --> D[requirejs.define] A1((id)) --> B1{window.require} B1 -- id is Array --> C1[requirejs.require] B1 -- id not Array --> D1[mod.require] C1 -- callback --> E1[module] D1 -- return --> E2[module]

前端分离 - 包管理工具

  • 原因 混乱的第三方插件引入
  • 方案
    • fis3-components (×)
    • bower-components (×)
    • npm-packages (√)
包管理工具 类库选择 垃圾资源 标准化
fis3-components 很少 特有commonjs标准
bower-components 多而不完善 较多 比较混乱
npm-packages 完善 很多 标准

前端分离 - 打包优化

  • FIS3构建流程
graph LR B{所有文件
编译完成} B -- NO --> D[fis.compile] D --> E{缓存
过期} E -- YES --> H[单文件编译] E -- NO --> B subgraph 打包过程 C1[prepackager] --> C2[package] C2 --> C3[postpackager] end B -- YES --> C1 subgraph 单文件编译过程 G1[init&parser] --> G2[postprogressor] G2 --> G3[optimizer] G3 --> G4[cache] end
  • 原因
    • 垃圾资源构建输出
  • 方案
    • 设定准入规则
      var files = [
        'node_modules/requirejs/require.js',
        'node_modules/react/dist/react.js',
        'node_modules/react-dom/dist/react-dom.js'];
      fis.set('project.files', [
        'pages/**'
      ].concat(files));
    • 只允许指定路径文件编译
  • ES6、JSX编译 和 AMD模块封装
  • 原因
    • 更好的代码可读性
    • 更方便的组件化开发
  • 方案 引入babel6
      fis.match('pages/(**).jsx', {
          parser: fis.plugin('plugin-babel6', {
              plugins: ['babel-plugin-transform-es2015-modules-amd'],
              presets: ['react', 'es2015']
          }),
          rExt: '.js',
          id: '$1'
      });
  • min文件开发部署处理
  • 原因
    1. 资源开发部署内容差异化 (react 等)
    2. 大文件编译失败(如:antd)
  • 方案:
    • postprogressor事件中替换读取min文件 (×)
    • 单文件编译开始之前修改相关参数使之跳过编译 (√)
      graph LR subgraph 编译过程改写 A[compile:start] --> B{有min文件} B -- YES --> C1[realpath指向min文件] B -- NO --> C2[realpath指向源文件] C1 --> D[compile] C2 --> D[compile] end

Q&A