@resource-fallback/core
v0.3.1
Published
Browser runtime + Node helpers for resource-fallback (multi-CDN retry & fallback)
Maintainers
Readme
@resource-fallback/core
浏览器运行时 + Node 端工具函数,是 resource-fallback 方案的核心包。
终端用户通常不需要直接依赖本包——请安装 @resource-fallback/vite-plugin 或 @resource-fallback/webpack-plugin。仅当需要自定义集成或手动注入运行时时才需要直接使用。
安装
pnpm add @resource-fallback/core常用 Node 端 API
import {
defineConfig,
buildInjectedTags,
getRuntimeCode,
getRuntimePath,
getServiceWorkerCode,
getServiceWorkerPath,
buildResourceFallbackManifest,
buildServiceWorkerAssets,
serialiseConfig,
} from '@resource-fallback/core';| 函数 | 说明 |
| ------------------------------------------ | ------------------------------------------------------------------------------------------- |
| defineConfig(opts) | 恒等辅助函数,提供类型安全的配置编写体验 |
| getRuntimePath() | 返回 IIFE 运行时文件的绝对路径 |
| getRuntimeCode() | 返回 IIFE 运行时文件的字符串内容(首次调用后缓存) |
| getServiceWorkerPath() | 返回 Service Worker 运行时文件的绝对路径 |
| getServiceWorkerCode() | 返回 Service Worker 运行时文件的字符串内容(首次调用后缓存) |
| buildResourceFallbackManifest(input) | 根据版本种子、规则和资源列表构建稳定版本的资源 manifest |
| buildServiceWorkerAssets(options, input) | 按配置生成预置 manifest/config 的 Service Worker 产物;未启用 SW 时返回 null |
| buildInjectedTags(opts) | 根据配置构建需要注入 HTML 的 <script> / <link> 标签描述数组;会先序列化配置并丢弃函数值 |
| serialiseConfig(cfg) | 将运行时配置序列化为可嵌入页面的 JSON 字符串;函数字段不会保留 |
defineConfig
import { defineConfig } from '@resource-fallback/core';
export default defineConfig({
rules: [
{
base: 'https://cdn.example.com/',
urls: ['https://backup.example.com/', '/'],
retry: { max: 2, baseDelay: 300 },
circuit: { threshold: 3 },
},
],
debug: 'auto',
});buildInjectedTags
为自定义插件/构建流程手动注入运行时:
import { buildInjectedTags } from '@resource-fallback/core';
const tags = buildInjectedTags({
rules: [{ base: 'https://cdn.example.com/', urls: ['/'] }],
nonce: 'abc123',
injectPreconnect: true,
});
// tags 结构示例:
// [
// { tagName: 'link', attributes: { rel: 'preconnect', href: 'https://cdn.example.com', crossorigin: 'anonymous' } },
// { tagName: 'script', attributes: { nonce: 'abc123' }, innerHTML: '<IIFE code>;window.__RF__.install({...})' },
// ]注意:buildInjectedTags() 和插件自动生成的 window.__RF__.install(...) 调用都会先序列化配置对象。hooks 里的函数、以及其他函数类型字段,都会在这一阶段被丢弃。externalRuntime 只把 runtime IIFE 改为外链,自动 install(...) 仍是一段内联脚本;严格 CSP 下应为它配置 nonce 或等效授权。它同样不会保留这些函数;自动注入场景推荐监听 DOM rf:* 事件。如需 JS hooks,请在页面里手动调用 window.__RF__.install()。
浏览器运行时
运行时以 IIFE 格式注入页面(当前构建约 10KB gzip,具体以构建产物为准),通过 window.__RF__ 暴露接口:
interface RfGlobal {
install(config: RuntimeConfig): void;
url(filename: string): string;
load(filename: string): Promise<unknown>; // Vite 专用
dispose(): void;
installed: boolean;
version: string;
}运行时模块
| 模块 | 职责 |
| -------------------- | ---------------------------------------------------------------------------------------------------------------- |
| entry | 初始化 window.__RF__ 全局对象,创建共享 RecoveryCoordinator / ownership / lifecycle,并调度各适配器安装 |
| observer | 监听 window 上的 error 事件(捕获阶段),把 <script> 和 <link rel="stylesheet"> 失败交给共享 coordinator |
| coordinator | 页面侧恢复决策引擎;统一负责规则匹配、重试、回退、熔断、超时、取消和恢复事件;适配器只提供原生 transport |
| circuit | per-host 熔断状态;页面侧当前只创建一个 registry,并通过 localStorage 实现跨标签页状态共享 |
| retry | 指数退避延迟计算(baseDelay × 2^(attempt-1)),可选 ±25% 抖动 |
| hooks | 事件总线,同时分发 DOM CustomEvent 和 JS 函数钩子 |
| kill-switch | 三重紧急开关检测(全局变量 / 查询参数 / Cookie) |
| logger | 可选的日志输出,支持 debug: 'auto'(通过 localStorage.__RF_DEBUG__ 控制) |
| adapter-vite | 通过共享 coordinator 执行 Vite 动态 import(__RF__.load)+ vite:preloadError 处理 |
| adapter-webpack | 拦截 chunkLoadingGlobal 的 push 方法 + 包装 __webpack_require__.l |
| adapter-systemjs | hook System.constructor.prototype.instantiate,为 legacy bundle 提供回退 |
Observer 行为细节
- 仅处理顶层
<script>和<link rel="stylesheet">的error事件 - 自动跳过
<link rel="preload|prefetch|modulepreload">等预加载提示 - 自动跳过带
data-webpack属性的<script>(由 webpack adapter 处理) - 通过共享 ownership registry 避免与 Webpack/SystemJS 等运行时适配器重复接管同一资源
- ES Module 脚本重试时自动添加
__rf=查询参数,绕过浏览器模块缓存 - 经典脚本和 CSS 不添加 cache-bust 参数,避免降低 CDN 缓存命中率
- 替换标签使用
createElement而非cloneNode,避免浏览器的 "already started" 标记
共享恢复与规则限制
- 页面侧 RecoveryCoordinator 会按
owner + logical resource key共享一次进行中的恢复 Promise - 同一个 owner 命中同一个逻辑资源时会加入同一条 retry / fallback 链;不同 owner 或不同逻辑 key 不共享
- ownership registry 会阻止 Observer 与 Vite / Webpack / SystemJS 适配器分别接管同一个逻辑资源
- 规则编译时按
base长度降序排序,匹配时优先更长前缀 window.__RF__.url(filename)只使用第一条已编译规则的base构造首轮 URL,不感知熔断状态- 页面 runtime 当前只有一个 circuit registry,使用第一条已编译规则的 circuit 选项初始化;Service Worker 仍使用 resolver,但其熔断状态始终是独立内存
事件
运行时在每个决策点分发 DOM CustomEvent:
| 事件 | 触发时机 | event.detail |
| ------------- | ---------------------------------------- | ------------------------------------------------ |
| rf:retry | 同一 URL 重试 | { url: string, attempt: number } |
| rf:fallback | 切换到下一个候选 URL | { from: string, to: string, reason?: unknown } |
| rf:success | 页面或 SW 经 retry / fallback 后恢复成功 | { url: string, attempts: number } |
| rf:error | 页面侧 session 失败,或 SW 透传错误 | { url: string, reason?: unknown } |
页面侧和 SW 首轮直接成功都不会触发 rf:success。
导出
// package.json exports
{
".": "Node 端 API(defineConfig / buildInjectedTags / 类型等)",
"./runtime": "浏览器 IIFE 运行时文件(runtime.iife.js)",
}类型导出
export type {
CircuitOptions,
BuildInjectedTagsOptions,
ErrorEvent,
FallbackEvent,
FallbackRule,
HtmlTag,
HtmlTagAttributes,
NormalizedServiceWorkerOptions,
PluginOptions,
ResolveResult,
ResourceFallbackAssetOwner,
ResourceFallbackAssetType,
ResourceFallbackManifest,
ResourceFallbackManifestAsset,
RetryEvent,
RetryOptions,
RuntimeConfig,
RuntimeHooks,
ServiceWorkerOptions,
SriPolicy,
SuccessEvent,
} from '@resource-fallback/core';许可证
MIT
