美文网首页
前端性能优化 之 数据预下载

前端性能优化 之 数据预下载

作者: 刷题刷到手抽筋 | 来源:发表于2022-05-31 04:55 被阅读0次

一、前端预渲染的局限

我们知道前端预渲染的原理是:构建打包之后,插件会在本地启动express静态服务,serve打包好的静态资源。然后再启动一个无头浏览器(例如Puppeteer),浏览器从服务器请求网页,网页运行时候会请求首屏接口,用拿到的数据渲染出包含内容的首屏后,无头浏览器截屏并替换掉原来的html。

但是在单页应用中存在一个问题,前端路由切换时候,还是会请求接口,而不会加载预渲染好的html,例如预渲染index.html和about.html,但是从index.html跳转about.html时候,如果是前端路由,不会从服务器请求about.html,而是加载about组件,那还是会请求接口,从而可能带来界面渲染慢的情况。

二、解决方案:数据预下载

怎么解决这个问题呢?

在优化灵题库(https://www.lingtiku.com)页面加载时候,我想到一个简单的办法,就是仿照前端预渲染的思路,在构建阶段就把一些数据预下载,下载之后保存在一个json文件里,json文件放到前端静态资源目录中。然后前端发起请求时候,用axios拦截预下载的请求,改成请求静态的json数据。

image.png

这样会带来两个好处

  • 减小服务器压力,不需要读数据库和缓存
  • 对于json数据,初始读取一次,后续都可以读浏览器的缓存,速度非常快。而且初始请求因为是静态资源,速度也会比接口更快一些。

写一个rollup插件,来实现数据预下载的功能,这个插件需要做以下事情

  1. 根据插件配置,获取所有的需要预下载的数据接口和接口下载完成保存成的文件名字。还有打包构建输出的静态资源目录。
  2. 在打包输出文件阶段,请求接口并将数据保存到指定文件名的json文件,并将文件放到静态资源目录。
  3. 将请求接口和json文件名的映射关系注入到代码中,以便axios可以拦截处理。

在rollup插件中,需要实现transform钩子,来将配置的json文件名和接口的映射注入到代码中;还要实现写bundle构造,在这个阶段请求数据并把json文件输出到构建目录中。

插件使用request库来请求数据。

另外需要注意,为了保证数据改变时候可以及时更新,需要给json加一个版本号,这里的版本号简单地设置成当前时间,其实使用内容的md5更合适一些。

插件代码如下:

// preload-data-plugin.js
import fs from 'fs';
import path from 'path';
import request from 'request';
import {promisify} from 'util';
import crypto from 'crypto';

const writeFileAsync = promisify(fs.writeFile);

const version = String(Date.now());

const preloadData = options => {
  const {map, staticDir} = options;
  Object.entries(map).forEach(([name, urlOrParams]) => {
    request(urlOrParams, (err, res, body) => {
      writeFileAsync(path.join(staticDir, `${name}_${version}.json`), body);
    });
  });
};

const preloadDataPlugin = (options = {}) => ({
  name: 'preloadDataPlugin',
  transform: (code, id) => {
    const map = {};
    Object.entries(options.map).forEach(([key, value]) => {
      map[`${key}_${version}`] = value;
    });
    return {
      code: code.replace(/__PRELOAD_DATA_MAP__/g, JSON.stringify(map))
    }
  },
  writeBundle: () =>  {
    try {
      preloadData(options)
    }
    catch (e) {
      console.error(e)
    }
  }
});

export default preloadDataPlugin;

rollup的插件配置如下

// rollup.config.js
import preloadDataPlugin from './rollup-plugin/preload-data-plugin';

export default {
    // ...other config
    plugins: [
        preloadDataPlugin({
            staticDir: path.resolve(__dirname, 'dist'),
            map: {
                'main': 'https://api.example.com/all',
                'quiz_1': 'https://api.example.com/quiz/find?id=1',
                'quiz_2': 'https://api.example.com/quiz/find?id=2',
                'quiz_3': 'https://api.example.com/quiz/find?id=3',
            }
        }),
    ]
};

最终会在dist目录生成几个文件

├── main_1651497197552.json
├── quiz_1_1651497197552.json
├── quiz_2_1651497197552.json
└── quiz_3_1651497197552.json

axios拦截代请求,替换成请求的json码如下

// http.js
import axios from 'axios';

const parseURL = url =>
    url.split('?')[1].split('&').filter(Boolean)
        .reduce((result, paramPair) => {
            const [key, value] = paramPair.split('=');
            result[key] = value;
            return result;
        }, {});


axios.interceptors.request.use(
    function (config) {
        Object.entries(__PRELOAD_DATA_MAP__).some(([name, url]) => {
            const query = parseURL(url);
            const isParamsEqual = !config.params || Object.entries(query).every(([key, value]) => {
                return config.params[key] == value;
            });
            // 接口和参数都匹配,则替换
            if (url.includes(`${config.baseURL || ''}${config.url}`) && isParamsEqual) {
                config.baseURL = '';
                config.url = `/${name}.json`;
                config.params = {};
            }
        });
        return config;
    },
    function (error) {
        return Promise.reject(error);
    }
);

再说另一个问题:如果数据很大,及时很快请求到,但是长列表渲染也会很慢。

目前灵题库的解决方法是通过懒加载方式解决,先只渲染前20条。后面的数据渲染过程中加loading,以提供好的交互体验。当然这种问题通过分页来解决是最优雅的方案。

三、后续

  1. 目前只是简单地支持get请求,可以考虑扩展一下,支持post请求。
  2. 安全方面,为了避免数据被轻易爬取,可以考虑让插件支持将请求数据base64加密,然后前端解密,这样增加了爬数据的成本
  3. 容错方面,当请求json失败或者json数据解析失败时候,要fallback到原接口。

相关文章

  • 前端性能优化(中)

    性能优化调研系列文章 《前端性能优化(上)》 《前端性能优化(中)》 《前端性能优化(下)》 《前端性能优化(上)...

  • 前端性能优化(下)

    性能优化调研系列文章 《前端性能优化(上)》 《前端性能优化(中)》 《前端性能优化(下)》 《前端性能优化(中)...

  • 常用的后端性能优化六种方式:缓存化+服务化+异步化等

    性能优化专题 前端性能优化 数据库性能优化 jvm和多线程优化 架构层面优化 缓存性能优化 常用的后端性能优化六大...

  • 前端性能优化(上)

    性能优化调研系列文章 《前端性能优化(上)》 《前端性能优化(中)》 《前端性能优化(下)》 为什么要进行前端性能...

  • 不能错过的web前端性能优化总结

    web前端性能优化主要分为以下几个板块: 加载优化DNS预解析合并img、css、javascript文件,减少h...

  • 前端页面性能

    前端性能优化因为涉及到计算机网络、数据算法、图形图像处理、浏览器渲染等多方面计算机知识。 前端性能优化之雅虎35条...

  • Chrome 性能监控 性能分析

    Chrome开发者工具之JavaScript内存分析 前端性能优化 —— 前端性能分析 Chrome DevToo...

  • 前端性能优化

    js性能小贴士——优化循环 前端网页与js性能优化 我总结的js性能优化的小知识 提高 web 应用性能之 Jav...

  • 前端性能 优化 大全

    js性能小贴士——优化循环 前端网页与js性能优化 我总结的js性能优化的小知识 提高 web 应用性能之 Jav...

  • 前端性能优化

    前端项目性能优化: DNS 预解析 DNS Prefetch 是一种 DNS 预解析技术, 浏览器会在加载网页时对...

网友评论

      本文标题:前端性能优化 之 数据预下载

      本文链接:https://www.haomeiwen.com/subject/umwpurtx.html