vite性能优化之数据预取

预先说明,本思路与常规方案不同,也不太推荐。有能力的建议直接一步到位用ssr改造项目,后面我也会出一个vite ssr博客出来。

"数据预取"功能类似于SSR中的注水,SSR的注水,是在服务端获取数据并渲染好页面结构,客户端只需要渲染好页面就行了,省去了创建DOM的过程。在服务器端获取的数据需要通过’注水‘ 插入到页面上,到客户端读取服务器请求的数据,这个过程叫’脱水‘,通过‘脱水’,避免了再次向服务器请求的操作;

而我们今天做的事 只是通过打包的时候注水,到客户端读取数据直接去渲染DOM就行了,不需要再请求服务器数据(保险点的方案是延迟请求一遍)。通过vite在打包时提供的hooks钩子函数,我们在打包完成之后向服务器去请求某些需要的接口数据,再把数据插入到html中;在项目运行时我们去html中拿打包好的数据,然后直接去渲染到页面上即可;为避免数据过期,我们应当处理的是:

  1. 及时重新打包部署;
  2. 或者在页面加载完成之后再请求数据,对比是否有更新;

阅读vite文档中的 插件API这一章vite独有的钩子
我们知道vite在开启服务时不同时间段会触发对应的钩子函数,具体可以看文档,今天主要用到的是这个hooks
transformIndexHtml

转换 index.html 的专用钩子。钩子接收当前的 HTML 字符串和转换上下文。上下文在开发期间暴露ViteDevServer实例,在构建期间暴露 Rollup 输出的包。

这个钩子可以是异步的,并且可以返回以下其中之一:

经过转换的 HTML 字符串
注入到现有 HTML 中的标签描述符对象数组({ tag, attrs, children })。每个标签也可以指定它应该被注入到哪里(默认是在 之前)
一个包含 { html, tags } 的对象
默认情况下 order 是 undefined,这个钩子会在 HTML 被转换后应用。为了注入一个应该通过 Vite 插件管道的脚本, order: ‘pre’ 指将在处理 HTML 之前应用。 order: ‘post’ 是在所有未定义的 order 的钩子函数被应用后才应用。

我们可以在这个钩子函数里把请求完的数据插入到html中,这样就能实现上述说的流程方法。

占位符

在html中插入一个占位符,用于等待数据请求回来之后替换,在head里新增一个script标签:

	<title>Vite + Vue + TS</title>
    <!--preload-links-->
    <script>
      window.__INITIAL_STATE__ = <!--pinia-state-->;
    </script>
  </head>

window.__INITIAL_STATE__ 对象就是请求完之后的数据放置的地方,取数据直接在里面找即可。

构建vite插件

完整的钩子签名

type IndexHtmlTransformHook = (
  html: string,
  ctx: {
    path: string
    filename: string
    server?: ViteDevServer
    bundle?: import('rollup').OutputBundle
    chunk?: import('rollup').OutputChunk
  },
) =>
  | IndexHtmlTransformResult
  | void
  | Promise<IndexHtmlTransformResult | void>

type IndexHtmlTransformResult =
  | string
  | HtmlTagDescriptor[]
  | {
      html: string
      tags: HtmlTagDescriptor[]
    }

interface HtmlTagDescriptor {
  tag: string
  attrs?: Record<string, string>
  children?: string | HtmlTagDescriptor[]
  /**
   * 默认: 'head-prepend'
   */
  injectTo?: 'head' | 'body' | 'head-prepend' | 'body-prepend'
}

上面是官方文档里插件的类型,下面开始写插件:

/**
 * 预取数据插件 非ssr
 * 功能依赖Node内置fetch 否则无法请求
 */

interface PrefetchDataOptions {
  url: string;
  data: unknown;
  key: string;
}
interface VitePluginPrefetchOption {
  /**
   * 是否禁用
   */
  disabled?: boolean;
  /**
   * 预取的接口数据
   */
  data: PrefetchDataOptions[];
  /**
   * 请求的base url
   */
  BASE_URL?: string;
}
export const prefetchHttpData = async (params: VitePluginPrefetchOption) => {
  const { data, BASE_URL, disabled } = params;
  let temp = "{}";
  if (!disabled) {
    try {
      temp = await prefetch(
        data.map((item) => {
          return {
            ...item,
            url: `${BASE_URL}${item.url}`,
          };
        })
      ) || '{}';
    } catch {
      console.log(`请求出错了!`)
    } 
  }

  return {
    name: "vite-plugin-prefetch",
    transformIndexHtml(html: string) {
      return html.replace("<!--prefetch-data-->", temp);
    },
  };
};

prefetchHttpData 返回一个vite hooks对象,而transformIndexHtml则返回字符串形式的html,而我们只需要替换html里的占位符即可。
prefetch 则是一个请求后的数据字符串形式。

async function prefetch(data: PrefetchDataOptions[]) {
  try {
    const temp = (await Promise.all(data.map(request))).reduce(
      (pre, next) => Object.assign(pre, next),
      {}
    );
    return JSON.stringify(temp);
  } catch {
    return "";
  }
}

async function request({ url, data: body, key }: PrefetchDataOptions) {
  const { Errcode, data } = (await fetch(url, {
    method: "POST",
    body: JSON.stringify(body),
    headers: new Headers({
      "Content-Type": "application/json",
    }),
  }).then((res) => res.json())) as {
    Errcode: number;
    data: {
      Data: unknown;
    };
  };
  if (Errcode) return Promise.reject({});
  else return Promise.resolve({ [key]: data.Data });
}

使用插件

在vite.config.ts里配置插件plugins,插件需要传入请求地址的参数 格式为定义好的数组形式,以及请求的basrurl (可以自行改造,把baseurl放到请求地址里)

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值