创见博客
PWA 入门:让网页拥有接近原生的体验
七崽爱吃小饼干2026/09/16阅读 0专栏 webpack构建高级

Web 应用一直有个尴尬的定位:不用安装、跨平台、随时更新,但和原生 App 相比,它断网就白屏、没有桌面图标、打开依赖网络。PWA 正是为了抹平这道差距——让网页拥有接近原生 App 的可靠性、速度和安装体验。

一、为什么需要 PWA

传统 Web 应用的几个短板:

  • 离线不可用:网络一断,页面直接打不开;弱网下白屏等待。
  • 没有安装入口:用户每次都要打开浏览器、输网址,无法像 App 一样「放在桌面」。
  • 启动依赖网络:每次都重新请求 HTML、JS、CSS,首屏慢。
  • 缺少沉浸感:带着浏览器地址栏,不像原生应用。
  • 无法主动触达:不能像 App 一样推送通知(虽然现在有 Web Push)。

而原生 App 虽然体验好,却有开发成本高、要上架审核、跨平台重复开发、更新需用户下载等问题。

PWA 的思路是:用 Web 技术,拿到接近原生的体验——不装 App,也能离线可用、一键添加到桌面、秒开。

二、PWA 是什么

PWA 全称 Progressive Web App(渐进式 Web 应用)。它不是一个具体框架或库,而是一组 Web 能力与最佳实践的组合,核心由三块构成:

  1. Service Worker:运行在后台的独立脚本,能拦截网络请求、管理缓存,是「离线可用」的基础。
  2. Web App Manifest:一份描述应用元信息的 JSON,决定它能否被「安装」到桌面、图标长什么样。
  3. HTTPS:上述能力的前提,必须在安全上下文(HTTPS 或 localhost)下才能工作。

围绕这三块,PWA 通常具备以下特性:

特性依赖效果
离线可用Service Worker + Cache断网也能打开
秒开、弱网可用缓存策略静态资源本地读取
可安装Web App Manifest添加到主屏、独立窗口
沉浸式体验display: standalone隐藏浏览器地址栏
消息推送Push API + Notification主动触达用户
后台同步Background Sync网络恢复后补发请求

「渐进式(Progressive)」的含义是:能力可以逐步增强。老浏览器不支持的,退化成普通网页;支持的,则获得离线、安装等体验,而不是全有或全无。

三、核心之一:Service Worker

1. 它是什么

Service Worker 是浏览器在页面之外单独开的一个后台线程,生命周期不依附于某个页面。它能:

  • 拦截页面发出的所有网络请求(fetch 事件)。
  • 读写 Cache Storage,自己决定「用缓存还是走网络」。
  • 在页面关闭后依然存在,从而实现离线、后台同步等能力。

它的生命周期分三步:

install  → 预缓存资源,安装完成后进入 waiting
activate → 清理旧缓存,正式接管
fetch    → 拦截请求,按策略返回响应

默认情况下,新的 Service Worker 会先进入 waiting,等所有页面关闭后才 activate,以避免版本错乱。用 self.skipWaiting() 可以让它立即接管。

2. 一个最小的 Service Worker

js
// sw.js
const CACHE = 'app-v1';
const ASSETS = ['/', '/index.html', '/js/main.js', '/css/main.css'];

// 预缓存
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
  );
  self.skipWaiting();
});

// 清理旧缓存
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
    )
  );
  self.clients.claim();
});

// 拦截请求:缓存优先
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => cached || fetch(event.request))
  );
});

页面中注册:

js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js');
  });
}

3. 常用缓存策略

不同资源适合不同策略:

策略行为适用资源
Cache First优先读缓存,没有才请求网络带 hash 的静态资源、图片
Network First优先请求网络,失败回退缓存接口数据、频繁更新的内容
Stale-While-Revalidate先返回缓存,同时后台更新缓存更新不频繁但要求快的资源
Cache Only只读缓存预缓存的固定资源
Network Only只走网络实时性要求极高的接口

四、核心之二:Web App Manifest

Manifest 是一个 JSON 文件,在 HTML 里通过 <link> 引入:

html
<link rel="manifest" href="/manifest.webmanifest" />
json
{
  "name": "我的应用",
  "short_name": "应用",
  "description": "一个 PWA 示例",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#1f6feb",
  "orientation": "portrait",
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icons/icon-512-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}

几个关键字段:

  • display: standalone:让应用以独立窗口打开,隐藏地址栏,接近原生体验。
  • icons:至少提供 192×192 和 512×512;maskable 图标适配不同系统的裁切形状。
  • start_url / scope:定义从哪启动、管理哪些路径范围。
  • theme_color / background_color:影响状态栏和启动画面的颜色。

满足「有 manifest + 有 Service Worker + HTTPS」后,浏览器才会显示「安装到桌面」的入口(各浏览器判断标准略有差异)。

五、在 webpack 中落地

手写 Service Worker 要自己处理缓存清单、版本更新,容易出错。工程上更推荐用 Workbox,它是 Google 出品的 PWA 工具库,并提供了 webpack 插件。

1. 安装

bash
npm install workbox-webpack-plugin --save-dev

2. 两种使用方式

GenerateSW:不写 SW,插件根据配置自动生成(适合大多数场景)。

js
const { GenerateSW } = require('workbox-webpack-plugin');

module.exports = {
  plugins: [
    new GenerateSW({
      clientsClaim: true,   // 新 SW 立即接管
      skipWaiting: true,    // 跳过 waiting
      cleanupOutdatedCaches: true,
      runtimeCaching: [
        {
          // 接口:网络优先
          urlPattern: /\/api\//,
          handler: 'NetworkFirst',
          options: {
            cacheName: 'api-cache',
            networkTimeoutSeconds: 5,
            expiration: { maxEntries: 50, maxAgeSeconds: 60 * 60 * 24 },
          },
        },
        {
          // 图片:缓存优先
          urlPattern: /\.(?:png|jpg|jpeg|svg|webp)$/,
          handler: 'CacheFirst',
          options: {
            cacheName: 'image-cache',
            expiration: { maxEntries: 100, maxAgeSeconds: 60 * 60 * 24 * 30 },
          },
        },
      ],
    }),
  ],
};

它会自动把 webpack 产出的资源加入预缓存清单,并生成带 runtime 缓存策略的 service-worker.js。

InjectManifest:自己写 SW(需要更复杂的逻辑时),插件只负责把预缓存清单注入进去。

js
const { InjectManifest } = require('workbox-webpack-plugin');

module.exports = {
  plugins: [
    new InjectManifest({
      swSrc: './src/sw.js',       // 自己写的 SW
      swDest: 'service-worker.js',
    }),
  ],
};

src/sw.js 里用 precacheAndRoute(self.__WB_MANIFEST) 接入注入的清单。

3. 注册 Service Worker

js
// 开发时判断,避免在本地调试缓存干扰
if ('serviceWorker' in navigator && process.env.NODE_ENV === 'production') {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js');
  });
}

4. 生成 manifest

可以用 webpack-pwa-manifest 自动生成图标和 manifest:

bash
npm install webpack-pwa-manifest --save-dev
js
const WebpackPwaManifest = require('webpack-pwa-manifest');

module.exports = {
  plugins: [
    new WebpackPwaManifest({
      name: '我的应用',
      short_name: '应用',
      start_url: '/',
      display: 'standalone',
      theme_color: '#1f6feb',
      background_color: '#ffffff',
      icons: [
        { src: 'src/assets/logo.png', sizes: [192, 512], destination: 'icons' },
      ],
    }),
  ],
};

也可以把写好的 manifest.webmanifest 和图标放进 public,用 copy-webpack-plugin 原样拷贝,再用 HtmlWebpackPlugin 注入 <link rel="manifest">。

六、注意事项

  1. 必须 HTTPS(localhost 例外)。Service Worker 在 http 下不会注册,这是硬性安全要求。
  2. 调试要在 Application 面板。Chrome DevTools 的 Application → Service Workers 可以更新、注销、模拟离线;Cache Storage 可以查看缓存内容。
  3. 缓存要能更新。用了 [contenthash] 的资源更新后文件名会变,预缓存清单随之更新;但要处理好旧缓存清理(cleanupOutdatedCaches)和 SW 更新时机,否则用户可能一直看到旧版本。
  4. 别缓存不该缓存的东西。把登录接口、实时数据缓存住会导致用户看到过期内容;接口通常用 Network First。
  5. 开发环境容易踩缓存坑。本地调试时缓存会造成「改了没生效」的错觉,建议仅在生产环境注册 SW,或使用 DevTools 的 Bypass for network。
  6. 兼容性:现代浏览器普遍支持 Service Worker(Safari 自 11.1 起支持),但仍需做好「不支持时退化为普通网页」的处理。

七、完整 webpack 配置示例

js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { GenerateSW } = require('workbox-webpack-plugin');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash:8].js',
    publicPath: '/',
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({ template: './public/index.html' }),
    new CopyWebpackPlugin({
      patterns: [
        { from: 'public/manifest.webmanifest', to: 'manifest.webmanifest' },
        { from: 'public/icons', to: 'icons' },
      ],
    }),
    new GenerateSW({
      clientsClaim: true,
      skipWaiting: true,
      cleanupOutdatedCaches: true,
      runtimeCaching: [
        {
          urlPattern: /\/api\//,
          handler: 'NetworkFirst',
          options: { cacheName: 'api-cache', networkTimeoutSeconds: 5 },
        },
        {
          urlPattern: /\.(?:png|jpg|jpeg|svg|webp)$/,
          handler: 'CacheFirst',
          options: { cacheName: 'image-cache' },
        },
      ],
    }),
  ],
};

小结

问题答案
PWA 是什么一组让 Web 拥有接近原生体验的技术组合
核心组成Service Worker + Web App Manifest + HTTPS
解决什么离线可用、秒开、可安装、沉浸式、推送
关键概念Service Worker 生命周期、缓存策略、manifest 字段
工程化方案Workbox(workbox-webpack-plugin)
首要前提HTTPS

一句话:Service Worker 让它「可靠、快」,Manifest 让它「能被安装」,HTTPS 让这一切成立。 三者齐备,网页才真正进阶成一个可离线、可安装、可推送的 PWA。

评论
0/100