React Vite Service Worker 无法缓存 .tsx/.jsx 文件?原因及解决方案
2024-12-25 13:41:52
Service Worker 在 React Vite 中无法缓存 .tsx/.jsx 文件的问题探究
使用 Service Worker 可以增强 Web 应用的离线能力和加载速度。但有时候,开发者可能会遇到 Service Worker 无法正确缓存 .tsx 或 .jsx 文件的情况,导致离线时页面无法正常显示。 这篇文章将深入分析问题根源,并提供切实可行的解决方案。
问题分析
通常,出现此类问题的主要原因是 Vite 的构建流程和 Service Worker 的工作方式存在不匹配。Vite 作为一款高速前端构建工具,默认对项目中的资源进行处理和优化,比如文件名添加 hash 值等操作。这与 Service Worker 预期缓存静态资源的方式不同。 当 Service Worker 尝试访问未经过特殊处理的原始文件路径时,将无法命中缓存。
一个常见的错误是将未构建的文件路径添加到 Service Worker 的 OFFLINE_ASSETS
列表中,如下面的代码示例,其中尝试缓存 /src/main.tsx
,这是一个源代码路径,在部署中会被 Vite 处理,生成实际用于部署的js bundle。
const OFFLINE_ASSETS = [
"/",
"/src/main.tsx", // 此路径有问题,应该改为构建后的输出路径
"/vite.svg",
`https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRmCy16nhIbV3pI1qLYHMJKwbH2458oiC9EmA&s`,
];
因此,Service Worker 会因为在预期的位置找不到这些静态资源而失败。这与像 "/vite.svg"
或 远程图片可以被缓存形成鲜明对比,因为这些资源没有通过构建流程转化, 而是直接可以被 Service Worker 获取和缓存。
解决方案
下面介绍一些有效的方法来解决这个问题。
方案一:缓存构建后的文件
最直接的解决方案是确保 Service Worker 缓存的是 Vite 构建后的实际资源,而不是源代码路径。Vite 构建会将你的 main.tsx
以及其他的依赖打包成 .js
文件,以及其他静态资源文件。这些文件名可能会带有 hash 值。这些构建后的资源需要通过配置正确的路径加载和缓存,并且通过环境变量获取最终生成的输出文件名。
-
生成 manifest 文件:
可以通过 Vite 插件,生成一个 manifest 文件 (例如 manifest.json), 此文件包含所有构建后的静态资源的对应关系(如输入文件与输出文件名),之后,service worker 读取此文件缓存对应路径。以下示例使用 vite-plugin-pwa,可配置输出 manifest.json:npm install vite-plugin-pwa -D
vite.config.ts
添加以下内容:import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { VitePWA } from 'vite-plugin-pwa' export default defineConfig({ plugins: [ react(), VitePWA({ manifest:{ //配置manifest文件相关信息 }, // PWA 相关的其他设置 registerType: 'autoUpdate', workbox: { globPatterns: ['**/*.{js,css,html,ico,png,svg}'], } }) ], })
配置workbox
,保证vite-plugin-pwa
会缓存相关的文件,当然这里是根据文件名类型做了glob
,也可以根据路径进行匹配。
2. 修改 Service Worker 代码: 读取 manifest.json 文件,使用文件中的构建后路径。需要获取构建输出的目录和资源路径映射。
// 假定构建后的静态资源位于 dist/assets 目录
async function loadManifest() {
const response = await fetch('./manifest.json');
const manifest = await response.json();
const assets = []
for(let k in manifest) {
assets.push(manifest[k].file)
assets.push(manifest[k].css)
}
console.log("manifest_assets", assets);
return assets
}
const addResourcesToCache = async (resources) => {
const cache = await caches.open("v1");
const results = resources.filter(item => item) // 处理资源加载时候,有可能undefined情况出现
await cache.addAll(results);
};
self.addEventListener("install", async (e) => {
console.log("installing");
const manifest_assets = await loadManifest()
e.waitUntil(addResourcesToCache(manifest_assets));
});
这样Service Worker就会缓存Vite 构建后的静态资源,从而使你的 React 应用离线可用。
方案二:使用预缓存列表
一个可选的方法是在构建时生成预缓存列表。
- 编写构建脚本: 在 package.json 中,可以通过编写脚本在每次构建之前从
vite.manifest
生成可预缓存的文件列表:
{
"scripts": {
"build:manifest":"node ./scripts/build-manifest.js && vite build"
}
}
在项目根目录下创建一个名为scripts/build-manifest.js
的文件, 代码如下:
import {readFile,writeFile} from 'fs/promises'
async function build_manifest() {
const buildManifest = await readFile('./dist/manifest.json','utf8')
const assets = JSON.parse(buildManifest)
const targetAssetList= []
for(let k in assets){
targetAssetList.push(`/` + assets[k].file);
if(assets[k].css){
targetAssetList.push(`/` + assets[k].css);
}
}
let template =` const PRECACHE_ASSETS = ${JSON.stringify(targetAssetList)} ; export { PRECACHE_ASSETS }`
await writeFile('./src/precache_asset_list.js', template, "utf-8")
}
build_manifest();
通过 node 脚本解析dist/manifest.json
中的内容,将其路径提取出来,并生成 precache_asset_list.js
文件
- Service Worker 代码中导入
precache_asset_list.js
, 进行使用 :
import { PRECACHE_ASSETS} from './precache_asset_list.js';
const addResourcesToCache = async (resources) => {
const cache = await caches.open("v1");
const results = resources.filter(item => item)
await cache.addAll(results);
};
self.addEventListener("install", async (e) => {
console.log("installing");
e.waitUntil(addResourcesToCache(PRECACHE_ASSETS));
});
此方案核心也是需要获取构建后的资源路径,手动实现了一遍读取 manifest 并传递给 Service Worker 的过程,相较于第一种方案而言稍微繁琐些,但是它减少了对 vite-plugin-pwa
等工具的依赖。
其他安全建议
在处理缓存时需要注意缓存策略,确保在代码变更时 Service Worker 会更新,避免出现用户端始终是旧版本的问题。在注册和更新 Service Worker 时,可以适当增加调试信息。
通过合理的配置和策略,可以让 Service Worker 更好地为 React 应用服务。