Vue CLI PWA 插件实战指南:@vue/cli-plugin-pwa 的 Service Worker 与 Web App Manifest 配置详解
【免费下载链接】vue-cli🛠️ webpack-based tooling for Vue.js Development项目地址: https://gitcode.com/gh_mirrors/vu/vue-cli
导读
本文以 Vue CLI 官方仓库中的 PWA 核心插件@vue/cli-plugin-pwa为对象,系统讲解如何在 Vue 项目中一键接入渐进式 Web 应用(PWA)能力。你将掌握该插件在生产构建中自动生成 Service Worker(基于 Workbox)、在开发模式下用 no-op 脚本清理旧缓存的工作机制,以及vue.config.js中pwa字段全部配置项的取值、默认值与底层实现,最终能独立配置出可安装、可离线运行、带完整图标体系的 PWA 应用。
插件概览:一个命令,让 Vue 应用具备 PWA 能力
@vue/cli-plugin-pwa是 Vue CLI 官方插件体系中负责 PWA 能力的核心模块(源码位于 packages/@vue/cli-plugin-pwa,版本 5.0.9)。它基于workbox-webpack-plugin(依赖版本^6.1.0,见 package.json)实现 Service Worker 的生成,同时借助html-webpack-plugin的钩子在构建产物 HTML 的<head>中注入 Web App Manifest、各类图标与移动端 meta 标签,让应用可以被添加到主屏、支持离线访问。
插件的工作可划分为三条主线,分别对应仓库中的三份关键源码:
- 生产环境的 Service Worker 生成——由 index.js 在
NODE_ENV === 'production'时向 webpack 配置注入workbox插件; - HTML 头部标签与 manifest.json 注入——由 lib/HtmlPwaPlugin.js 实现,它挂在 html-webpack-plugin 的
alterAssetTagGroups钩子上; - 开发模式的缓存清理——由 lib/noopServiceWorkerMiddleware.js 提供 dev server 中间件。
Service Worker 只在生产环境启用:为什么?
插件注册的 Service Worker仅在NODE_ENV === 'production'时生效,即只有执行npm run build或yarn build之后才真正写入service-worker.js。官方文档明确给出了理由:如果在开发模式启用 Service Worker,浏览器可能直接使用先前缓存过的旧资源,导致本地最新改动无法生效,造成"改了代码看不到效果"的假象。
开发模式下,插件改为向 dev server 注入一个名为noopServiceWorkerMiddleware的中间件(见 index.js 与 lib/noopServiceWorkerMiddleware.js)。当请求路径为/service-worker.js时,它直接返回 lib/noopServiceWorker.js 的内容。这份 "no-op" 脚本的作用是重置同一 host:port 下先前注册的任何 Service Worker:
// lib/noopServiceWorker.js(节选) self.addEventListener('install', () => self.skipWaiting()) self.addEventListener('fetch', () => {}) self.addEventListener('activate', () => { self.clients.matchAll({ type: 'window' }).then(windowClients => { for (const windowClient of windowClients) { // 强制已打开的页面刷新,使其从本地 dev server 拿到最新响应 windowClient.navigate(windowClient.url) } }) })它通过skipWaiting()跳过等待阶段、通过空fetch监听放弃对请求的拦截,并在activate阶段强制刷新所有已打开的窗口页面。这正是为了应对"开发版与生产版在同一个 host:port 上交替访问,生产 Service Worker 劫持开发请求"的场景。
注意:如果承载生产应用的服务对
/service-worker.js设置了很长的缓存头,这个 no-op 脚本可能无法立即生效,此时需要在浏览器 DevTools → Application → Service Workers 中手动注销 Service Worker。
本地测试 Service Worker 的正确姿势:先执行npm run build完成构建,再从构建输出目录(dist)起一个简单的 HTTP 服务器(如npx serve dist),并推荐使用浏览器无痕窗口访问,以避免浏览器缓存带来的干扰。
配置文件与优先级:vue.config.js 中的 pwa 字段
所有 PWA 配置都通过vue.config.js中的pwa属性(或package.json的"vue"字段)传入。插件入口首先读取该配置并处理public/manifest.json的优先级问题(见 index.js):
- 若项目
public/目录下存在manifest.json且用户未在vue.config.js中显式配置pwa.manifestOptions,则自动加载该文件内容作为 manifest 数据; - 若两者同时存在,插件会打印警告:
public/manifest.json将被忽略,以pwa.manifestOptions为准。
也就是说,manifest 数据有三层来源,按优先级从低到高为:插件内置默认值 →public/manifest.json→vue.config.js中的pwa.manifestOptions。
pwa 配置项全解:字段、默认值与底层行为
以下配置项均在vue.config.js的pwa对象下生效。它们的默认值定义在 lib/HtmlPwaPlugin.js 的defaults常量中,与官方文档完全一致。
pwa.workboxPluginMode:选择 Workbox 的两种工作模式
- 默认值:
'GenerateSW' - 可选值:
'GenerateSW'或'InjectManifest',对应底层workbox-webpack-plugin的两个插件类。
两种模式的行为差异:
| 模式 | 行为 | 适用场景 |
|---|---|---|
'GenerateSW'(默认) | 每次重新构建时由插件自动生成一份全新的 Service Worker 文件 | 绝大多数标准项目:自动收集构建产物的文件清单,生成预缓存清单并内置运行时缓存策略 |
'InjectManifest' | 以你提供的一个既有 Service Worker 文件(swSrc)为起点,将"预缓存清单"注入该文件的副本 | 需要完全掌控 Service Worker 逻辑(如自定义推送、自定义缓存策略)的高级场景 |
从源码 index.js 可以看到,插件从workbox-webpack-plugin模块中按模式名取用对应插件类,如果传入的模式不在支持列表内会直接抛出错误:
if (!(workboxPluginMode in workboxWebpackModule)) { throw new Error( `${workboxPluginMode} is not a supported Workbox webpack plugin mode. ` + `Valid modes are: ${Object.keys(workboxWebpackModule).join(', ')}` ) }pwa.workboxOptions:透传给 Workbox 的原始配置
该对象会被直接透传给workbox-webpack-plugin。在GenerateSW模式下可配置缓存策略、globPatterns、maximumFileSizeToCacheInBytes等;在InjectManifest模式下则至少需要提供swSrc(你的自定义 Service Worker 入口文件路径)。
值得关注的是,插件在合并配置时(index.js)会先注入一组默认排除规则,这些文件不会被写入预缓存清单:
const defaultOptions = { exclude: [ /\.map$/, /img\/icons\//, /favicon\.ico$/, /^manifest.*\.js?$/ ] }即:source map、img/icons/目录下的图标、favicon.ico以及 manifest 相关文件都不参与预缓存。另外,GenerateSW模式还会额外注入cacheId: name(取自package.json的name字段),用于在 Cache Storage 中区分不同应用的缓存;这些默认值均可被你传入的workboxOptions覆盖。
pwa.name:应用名称与 iOS 标题
- 默认值:
package.json中的name字段 - 作用:作为生成 HTML 中
apple-mobile-web-app-titlemeta 标签的值。注意文档提醒:若要修改它,需要同步编辑public/manifest.json保持两者一致。
pwa.themeColor:主题色
- 默认值:
'#4DBA87'(Vue 品牌绿) - 作用:生成
<meta name="theme-color">标签,同时用于mask-icon图标的color属性,并作为生成 manifest 时theme_color字段的来源。
pwa.msTileColor:Windows 磁贴颜色
- 默认值:
'#000000' - 作用:生成
<meta name="msapplication-TileColor">,配合msapplication-TileImage让应用在 Windows 磁贴上拥有正确外观。
pwa.appleMobileWebAppCapable:iOS 全屏能力开关
- 默认值:
'no' - 作用:生成
<meta name="apple-mobile-web-app-capable">。默认'no'是因为 iOS 11.3 之前对 PWA 支持不完善,盲目开启该 meta 标签可能导致 iOS Safari 行为异常。
pwa.appleMobileWebAppStatusBarStyle:iOS 状态栏样式
- 默认值:
'default' - 作用:生成
<meta name="apple-mobile-web-app-status-bar-style">,可选值通常为default、black、black-translucent。
pwa.assetsVersion:静态资源缓存破版本号
- 默认值:
'' - 作用:当需要对抗浏览器对图标和 manifest 的缓存时,给图标与 manifest 的 URL 追加
?v=<pwa.assetsVersion>查询参数。实现见 HtmlPwaPlugin.js 的assetsVersionStr = assetsVersion ??v=${assetsVersion}: ''。
pwa.manifestPath:Web App Manifest 路径
- 默认值:
'manifest.json' - 作用:应用 manifest 的路径。若该值是一个绝对 URL(如 CDN 地址,源码通过
/(http(s?)):\/\//正则判断),插件在构建时不会在 dist 目录生成manifest.json,而只输出指向该 URL 的<link rel="manifest">标签;否则会在构建产物中生成该文件(见 HtmlPwaPlugin.js 的isHrefAbsoluteUrl判断分支)。
pwa.manifestOptions:覆盖 manifest.json 内容
- 默认值:
{} - 作用:该对象用于生成
manifest.json。若以下属性未在对象中定义,则回退到pwa配置项或插件默认值:
| manifest 字段 | 取值来源 |
|---|---|
name | pwa.name |
short_name | pwa.name |
start_url | '.' |
display | 'standalone' |
theme_color | pwa.themeColor |
从源码 HtmlPwaPlugin.js 可以看到,插件内置了一份defaultManifest,除上述字段外还包含默认图标组(android-chrome-192x192.png、android-chrome-512x512.png及对应的 maskable 版本)与background_color: '#000000'。最终的 manifest 合并顺序为:
Object.assign(publicOptions, defaultManifest, manifestOptions) // publicOptions = { name, short_name, theme_color }(来自 pwa 配置)即你的manifestOptions拥有最高优先级,会覆盖内置默认值。
pwa.manifestCrossorigin:manifest 链接的跨域属性
- 默认值:
undefined(即不输出crossorigin属性) - 作用:为生成的 manifest
<link>标签设置crossorigin属性。当你的 PWA 部署在需要认证的反向代理之后时可能需要设置。从 ui.js 的配置界面可以看到,可取值为none(null)、anonymous、use-credentials。
pwa.iconPaths:自定义图标路径
- 默认值:
{ faviconSVG: 'img/icons/favicon.svg', favicon32: 'img/icons/favicon-32x32.png', favicon16: 'img/icons/favicon-16x16.png', appleTouchIcon: 'img/icons/apple-touch-icon-152x152.png', maskIcon: 'img/icons/safari-pinned-tab.svg', msTileImage: 'img/icons/msapplication-icon-144x144.png' }- 作用:替换各平台使用的图标路径。自 v4.3.0 起,将任一值设为
null即可跳过对应图标的注入。这些路径相对于public目录(生成器默认提供完整的图标组,见 generator/template/public/img/icons)。在 HtmlPwaPlugin.js 中,每个非null的图标都会生成对应的<link>或<meta>标签:
| 配置项 | 生成的标签 |
|---|---|
faviconSVG | <link rel="icon" type="image/svg+xml"> |
favicon32/favicon16 | <link rel="icon" type="image/png" sizes="32x32/16x16"> |
appleTouchIcon | <link rel="apple-touch-icon">(iOS 添加到主屏) |
maskIcon | <link rel="mask-icon" color=themeColor>(Safari 固定标签页) |
msTileImage | <meta name="msapplication-TileImage">(Windows 磁贴) |
另外注意 HtmlPwaPlugin.js 中一个细节:插件会用 IE 条件注释包裹原有 favicon,即<!--[if IE]>...<![endif]-->,避免旧版 IE 对 SVG favicon 的兼容问题。
完整示例配置
官方文档给出的可运行示例(放入vue.config.js):
module.exports = { // ...other vue-cli plugin options... pwa: { name: 'My App', themeColor: '#4DBA87', msTileColor: '#000000', appleMobileWebAppCapable: 'yes', appleMobileWebAppStatusBarStyle: 'black', // configure the workbox plugin workboxPluginMode: 'InjectManifest', workboxOptions: { // swSrc is required in InjectManifest mode. swSrc: 'dev/sw.js', // ...other Workbox options... } } }一个典型的GenerateSW模式配置则更为简洁,通常只需设置品牌相关字段:
module.exports = { pwa: { name: 'My App', themeColor: '#4DBA87', msTileColor: '#000000', appleMobileWebAppCapable: 'yes', appleMobileWebAppStatusBarStyle: 'black', manifestOptions: { background_color: '#ffffff', icons: [ { src: './img/icons/android-chrome-192x192.png', sizes: '192x192', type: 'image/png' } // ...更多图标 ] } } }在已有项目中安装插件
对一个已经创建好的 Vue 项目,执行:
vue add pwa该命令会运行 generator/index.js,完成三件事:
- 为
package.json添加依赖register-service-worker(^1.7.2); - 在入口文件(如
src/main.js)注入import './registerServiceWorker'; - 渲染模板文件——包括
public/下的全套图标、robots.txt以及src/registerServiceWorker.js;若项目启用了 TypeScript 插件,还会自动把模板文件转换为.ts。
其中 generator/template/src/registerServiceWorker.js 是应用侧注册 Service Worker 的入口,它仅在NODE_ENV === 'production'时执行:
import { register } from 'register-service-worker' if (process.env.NODE_ENV === 'production') { register(`${process.env.BASE_URL}service-worker.js`, { ready () { /* 应用正在由 Service Worker 从缓存提供 */ }, registered () { /* Service Worker 已注册 */ }, cached () { /* 内容已缓存,可离线使用 */ }, updatefound () { /* 正在下载新内容 */ }, updated () { /* 新内容可用,请刷新页面 */ }, offline () { /* 无网络连接,应用处于离线模式 */ }, error (error) { /* 注册失败 */ } }) }它使用register-service-worker库监听 Service Worker 生命周期的各个阶段,你可以在这些回调中接入自己的业务逻辑,例如在updated时弹出"发现新版本"提示。
注入的 webpack-chain 规则
插件通过api.chainWebpack注入以下 webpack 配置(见 index.js):
config.plugin('pwa'):注册HtmlPwaPlugin,置于 html-webpack-plugin(html)之后,负责 HTML 标签与 manifest 注入;config.plugin('workbox'):仅在NODE_ENV === 'production'时注册,使用GenerateSW或InjectManifest插件类生成/service-worker.js。
注意:若设置了环境变量
VUE_CLI_BUILD_TARGET且其值不是'app'(例如构建库或 Web Components 组件时),插件会直接跳过注入,避免为非应用产物生成 Service Worker(见 index.js)。
Vue CLI UI 中的可视化配置
插件还提供 Vue CLI UI 的可视化配置支持(ui.js),在 UI 的"项目配置"面板中可以看到名为 "PWA" 的配置卡片,可图形化编辑workboxPluginMode、name、themeColor、msTileColor、appleMobileWebAppStatusBarStyle、manifestCrossorigin等字段,并可直接打开vue.config.js与public/manifest.json文件。配置写入时,若项目使用public/manifest.json,UI 会同步把name、short_name、theme_color、background_color写回该文件。
测试与验证
仓库在 packages/@vue/cli-plugin-pwa/tests下提供了pwaPlugin.spec.js与pwaGenerator.spec.js两组测试,分别覆盖插件的 webpack 配置注入行为与生成器模板渲染行为,可作为理解插件集成方式的参考样例。若你在自己的项目中使用本插件,构建后可打开 Chrome DevTools → Application → Service Workers 面板,检查/service-worker.js是否已注册、预缓存清单是否包含期望的资源。
小结
@vue/cli-plugin-pwa把 PWA 集成的复杂度封装在vue add pwa一条命令与pwa一个配置对象之内:生产环境由 Workbox 自动生成带预缓存能力的 Service Worker,开发环境由 no-op 中间件保证本地调试不被旧缓存干扰,HtmlPwaPlugin则统一完成图标、manifest 与移动端 meta 标签的注入。理解其默认值体系(GenerateSW模式、#4DBA87主题色、manifest.json默认路径、图标默认路径等)和配置合并优先级(默认值 <public/manifest.json<pwa.manifestOptions),即可在实际项目中快速产出符合各平台规范、可安装可离线的 PWA 应用。
【免费下载链接】vue-cli🛠️ webpack-based tooling for Vue.js Development项目地址: https://gitcode.com/gh_mirrors/vu/vue-cli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考