Laravel Vite中如何打包带import的Service Worker?
Laravel Vite 结合 Workbox 配置 Service Worker 解决方案
核心问题梳理
import语法报错:Service Worker 默认以经典脚本模式运行,直接用import会触发语法错误- 打包后文件名带 Hash:Laravel Vite 默认将资源打包到
assets目录并添加 Hash,不符合 Service Worker 固定文件名的要求 - 开发环境 404 错误:ES Module 模式下注册 Service Worker 时,无法正确找到脚本文件
关键前提确认
你关于「Service Worker 文件名应固定」的想法完全正确:浏览器通过文件名识别已注册的 Service Worker,仅当文件内容变更时才会触发更新流程。使用 Hash 文件名会导致每次打包都生成新文件,打乱浏览器的 Service Worker 更新逻辑。
分步解决方案
1. 调整 Vite 配置,固定 SW 输出文件名
修改 vite.config.js,单独配置 SW 文件的打包规则,输出到 public 根目录且不带 Hash:
import { defineConfig } from 'vite' import laravel from 'laravel-vite-plugin' export default defineConfig({ plugins: [ laravel({ input: [ 'resources/css/app.css', 'resources/js/app.js', ], refresh: true, }), ], build: { rollupOptions: { input: { sw: 'resources/js/sw.js', }, output: { entryFileNames: (chunkInfo) => { // 仅给 SW 文件固定文件名,其他资源保留 Hash return chunkInfo.name === 'sw' ? 'sw.js' : 'assets/[name]-[hash].js'; }, dir: 'public', }, }, }, })
2. 修改 SW 文件,兼容开发/生产环境
将 Workbox 的导入改为动态导入,开发环境用 CDN ES 模块,生产环境依赖 Vite 打包:
// sw.js let warmStrategyCache, offlineFallback; let CacheFirst, StaleWhileRevalidate; let registerRoute; let CacheableResponsePlugin; let ExpirationPlugin; if (import.meta.env.DEV) { // 开发环境加载 Workbox ES 模块 CDN const recipes = await import('https://esm.sh/workbox-recipes'); const strategies = await import('https://esm.sh/workbox-strategies'); const routing = await import('https://esm.sh/workbox-routing'); const cacheableResponse = await import('https://esm.sh/workbox-cacheable-response'); const expiration = await import('https://esm.sh/workbox-expiration'); ({ warmStrategyCache, offlineFallback } = recipes); ({ CacheFirst, StaleWhileRevalidate } = strategies); ({ registerRoute } = routing); ({ CacheableResponsePlugin } = cacheableResponse); ({ ExpirationPlugin } = expiration); } else { // 生产环境使用打包后的 Workbox 依赖 ({ warmStrategyCache, offlineFallback } = await import('workbox-recipes')); ({ CacheFirst, StaleWhileRevalidate } = await import('workbox-strategies')); ({ registerRoute } = await import('workbox-routing')); ({ CacheableResponsePlugin } = await import('workbox-cacheable-response')); ({ ExpirationPlugin } = await import('workbox-expiration')); } // 缓存逻辑保持不变 const pageCache = new CacheFirst({ cacheName: 'page-cache', plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }), new ExpirationPlugin({ maxAgeSeconds: 30 * 24 * 60 * 60, }), ], }) warmStrategyCache({ urls: ['/index.html', '/'], strategy: pageCache, }) registerRoute(({ request }) => request.mode === 'navigate', pageCache) registerRoute( ({ request }) => ['style', 'script', 'worker'].includes(request.destination), new StaleWhileRevalidate({ cacheName: 'asset-cache', plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }), ], }), ) offlineFallback({ pageFallback: '/offline.html', })
3. 修正 Service Worker 注册逻辑
修改 app.js,根据环境切换注册路径和脚本类型:
// app.js if ('serviceWorker' in navigator) { window.addEventListener('load', async () => { try { const swUrl = import.meta.env.DEV ? '/resources/js/sw.js' : '/sw.js'; const reg = await navigator.serviceWorker.register(swUrl, { type: import.meta.env.DEV ? 'module' : 'classic', }); console.log('Service worker registered! 😎', reg); } catch (err) { console.log('😥 Service worker registration failed: ', err); } }); }
工作原理说明
- 生产环境:Vite 会将 SW 文件打包为无 Hash 的
public/sw.js,并将 Workbox 依赖内联,最终以经典脚本模式运行 - 开发环境:直接加载
resources/js/sw.js,以 ES Module 模式运行,通过 CDN 获取 Workbox 模块,避免打包开销
内容的提问来源于stack exchange,提问作者Arjen
相关产品推荐
相关产品推荐

