@resource-fallback/vite-plugin
v0.3.1
Published
Vite 4+ plugin that wires @resource-fallback/core into your build (HTML injection + dynamic import rewriting)
Maintainers
Readme
@resource-fallback/vite-plugin
Vite 4+ 插件,为 Vite 构建产物(同步 JS/CSS、异步 chunk)提供运行时重试与多 CDN 回退;对 modulepreload 失败则提供受控的错误阻止与规则闸门,不直接替换 preload URL。
安装
pnpm add @resource-fallback/vite-plugin -D基本用法
// vite.config.ts
import { defineConfig } from 'vite';
import resourceFallback from '@resource-fallback/vite-plugin';
export default defineConfig({
base: 'https://cdn.example.com/',
plugins: [
resourceFallback({
rules: [
{
base: 'https://cdn.example.com/',
urls: [
'https://cdn-backup.example.com/',
'/', // 回源
],
},
],
}),
],
});重要:Vite
base应当与rules[].base(rulebase)保持一致,确保构建产物的 URL 能被规则匹配。仅当两边在规范化尾斜杠后相等(ensureTrailingSlash(viteBase) === ensureTrailingSlash(r.base))时才会改写 URL。
工作原理
插件在构建时按下面的顺序工作:
1. configResolved:比较规范化后的 Vite base 与 rule base
插件先在 configResolved 阶段读取 Vite 最终解析后的 base,再和每条 rules[].base 做同样的尾斜杠规范化比较。
- 如果规范化后的 Vite
base至少命中一条 rulebase,后续才继续启用 URL 改写 - 如果没有命中,
writeBundle会跳过动态 import 改写,避免把非 CDN 构建误写成外域地址
2. generateBundle:按需发出 Service Worker 资源
如果开启了 serviceWorker,插件会在 generateBundle 阶段按需发出:
rf-sw.jsmanifest.json
3. writeBundle:解析字面量动态 import,并替换为 window.__RF__.load(filename)
writeBundle 会先用 es-module-lexer 解析 chunk 里的动态 import,再只改写满足以下条件的字面量导入:
- 该 import 是字面量字符串
- 解析出的文件名对应
chunk.dynamicImports里的条目 - 当前构建已经通过规范化 base 门禁
匹配到的 import() 会被替换成 window.__RF__.load(filename),例如:
// 原始代码
const mod = await import('./Lazy.vue');
// 构建后
const mod = await window.__RF__.load('assets/Lazy-abc.js');4. transformIndexHtml:注入运行时与 preconnect 标签
transformIndexHtml 会在 <head> 注入:
<link rel="preconnect">标签- 内联运行时 IIFE 和
install(config)调用的<script>
RF.url 行为
__RF__.url(filename) 只会把文件名和第一条编译后的 rule base 拼接起来,它不会查看熔断状态,也不会在运行时跳过 host。
RF.load 回退循环
__RF__.load(filename) 会先解析初始 URL,然后把 retry / fallback / deadline / cancellation 都交给共享的 Coordinator 处理。它还会使用规范化后的 URL key,这样同一 owner 的并发加载可以共享同一个 recovery Promise。
vite:preloadError 处理
运行时还监听 Vite 的 vite:preloadError 事件。当 modulepreload 失败时:
- 读取
event.payload - 提取出的 URL 若匹配已配置的 rule,就调用
event.preventDefault() - 不会直接更新熔断器状态、发出恢复事件,也不会自己选择 fallback URL
- CSS
<link>失败仍然由 Observer 负责
配置
ViteResourceFallbackOptions 等同于 @resource-fallback/core 的 PluginOptions,完整字段参见根目录 README。
常用配置示例
resourceFallback({
rules: [
{
base: 'https://cdn.example.com/',
urls: ['https://cdn-backup.example.com/', 'https://static.mysite.com/', '/'],
retry: { max: 2, baseDelay: 300, maxDelay: 3000, jitter: true },
circuit: { threshold: 3, cooldown: 30000 },
},
],
debug: 'auto', // localStorage.__RF_DEBUG__ 控制日志
sri: 'strip', // fallback 时移除 integrity
nonce: 'my-csp-nonce', // CSP nonce
injectPreconnect: true, // 注入 <link rel="preconnect">
htmlInject: 'head-prepend', // 注入到 <head> 最前面
});与 @vitejs/plugin-legacy 配合
若项目使用 @vitejs/plugin-legacy 生成 SystemJS 格式的 legacy bundle,运行时会自动安装 SystemJS adapter,通过 hook System.constructor.prototype.instantiate 为 legacy 入口和异步 chunk 提供回退能力。
// vite.config.ts
import legacy from '@vitejs/plugin-legacy';
import resourceFallback from '@resource-fallback/vite-plugin';
export default defineConfig({
base: 'https://cdn.example.com/',
plugins: [
legacy({ targets: ['defaults', 'not IE 11'] }),
resourceFallback({
rules: [{ base: 'https://cdn.example.com/', urls: ['/'] }],
}),
],
});Vite Dev 模式
默认情况下插件在 dev 模式不激活(enableDev: false)。Vite dev server 使用原生 ESM,动态 import 失败无法拦截。
vite build && vite preview设置 enableDev: true 会在 dev 模式下也注入运行时,但仅同步 <script> / <link> 的 error 事件有效。
同步/异步覆盖
| 场景 | Vite (build/preview) | Vite (dev) |
| -------------------------- | -------------------------------------------- | ---------- |
| 同步 <script> / <link> | ✓ Observer | ✓ Observer |
| 异步 chunk(import()) | ✓ __RF__.load + writeBundle 改写 | ✗ |
| CSS 动态注入 | ✓ Observer | ✓ Observer |
| SystemJS(legacy bundle) | ✓ instantiate hook | — |
| 图片 / 字体 / 媒体资源 | ✓ Hybrid SW(opt-in) | ✗ |
| CSS url() / @font-face | ✓ Hybrid SW(opt-in) | ✗ |
| CSS @import | ✓ Hybrid SW(需 CSS referrer 命中 manifest) | ✗ |
许可证
MIT
