基于竞速的 CDN 资源加载方案–以 Vite 插件为例

参考:

vite:https://cn.vitejs.dev/guide/

vite-plugin-fast-cdn-import:https://github.com/CaoMeiYouRen/vite-plugin-fast-cdn-import

前言

关于写插件这个想法,最早可以追溯到 jsdelivr 在中国大陆暂时停止服务的时候(2022 年 5 月,现在貌似恢复了),当时因为笔者大部分网页都或多或少有使用过 jsdelivr 作为 CDN 来加载 js、css、img 资源,因此 jsdelivr 暂时无法访问对笔者的网页基本上是毁灭性的打击。

经过此事后,我就在想,如果能够自动选择最快(且有效)的 CDN,那么就可以规避这个问题,也能解决不同 CDN 加载速度差异的问题,故写了这个插件。

开始

本插件名为 vite-plugin-fast-cdn-import,意为“最快的 CDN”,表达了本插件对于速度的追求。

安装方式同一般的 npm 插件。

npm install vite-plugin-fast-cdn-import
# pnpm install vite-plugin-fast-cdn-import

浏览器要求情况(同 vite3)

  • Chrome >=87
  • Firefox >=78
  • Safari >=13
  • Edge >=88

使用

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tsconfigPaths from 'vite-tsconfig-paths'
import { vitePluginFastCdnImport } from 'vite-plugin-fast-cdn-import'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        tsconfigPaths(),
        vitePluginFastCdnImport({ // 添加依赖即可
            cdnUrls: [ // 指定 CDN 源
                'https://npm.elemecdn.com/:name@:version/:path',
                'https://cdn.jsdelivr.net/npm/:name@:version/:path',
                'https://unpkg.com/:name@:version/:path',
            ],
            modules: [ // 目前仅支持 css 的加载
                {
                    name: 'normalize.css',
                    path: 'normalize.css',
                },
                {
                    name: 'element-plus',
                    path: 'dist/index.css',
                },
            ],
        }),
    ],
    server: {
        port: 4000,
        open: true,
        proxy: {},
    },
})

基本原理

在指定的多个 CDN 源中,会使用 fetch对第一个包的地址进行一次 HEAD 请求,得出最快的源(同时未返回的请求会直接 abort,中断请求),剩余的包不再竞速,直接采用该结果。

第一次的竞速结果会缓存在 localStorage 中,直到包的数量或版本号发生了改变,此时缓存失效,重复上述流程。

存在的问题

凡事都有代价,本项目虽然解决了一个问题,但仍有其他问题。

  1. 由本项目的竞速原理可知,只会对第一个包进行竞速,因此可能会出现第一个包在某个 CDN 源是存在的,后续的包不存在,导致加载失败,故需要开发者手动对所有 CDN 源进行校验,确保所有的包都能在所有 CDN 源加载。 可在配置中设置 allRacetrue启用全量竞速,会对每一个包都单独竞速。虽然可以解决包不存在的问题,但也多了竞速的耗时,请开发者自行权衡。
  2. 由于用到了 fetch,所以在不支持 fetch 的浏览器下无法竞速,也就无法加载包。
    1. fetch支持情况请参考:https://caniuse.com/fetch
  3. 由于用到了 Promise.racePromise.any,所以在不支持 Promise.racePromise.any 的浏览器中需要 polyfill 才能使用
    1. Promise.race支持情况请参考:https://caniuse.com/mdn-javascript_builtins_promise_race
    2. Promise.any支持情况请参考:https://caniuse.com/mdn-javascript_builtins_promise_any
  4. 当缓存里的 CDN 源失效时,无法自动检测出失效的 CDN 源,此时会出现加载资源失败的情况。—— 该问题可通过手动修改 cacheKey 来解决。

评论

发表回复