网站秒变 App!手把手教你搞定 PWA

你有没有想过,为什么有些网站用起来就像 App 一样?点开很快,还能离线访问,甚至还能发通知?没错,这就是传说中的 PWA------渐进式 Web 应用。它是一种让网站"进化"的技术,不需要你下载 App 却能享受到原生应用的体验。很多大厂,比如 Twitter、Instagram、甚至阿里巴巴,都用 PWA 提升了移动端用户体验和留存率。其实实现 PWA 并不复杂,只需要搞懂几个核心概念:manifest.jsonService Worker 和缓存策略。只要你熟悉基本的前端技术,基本几步就能给自己的网站"变身"。

PWA 简介

渐进式 Web 应用(Progressive Web App,PWA)是一种结合 Web 和原生应用优势的技术,旨在提供快速、可靠、可交互的用户体验。PWA 的核心特性包括:

  • 渐进增强:在支持 PWA 的浏览器中提供增强功能,老旧浏览器仍可正常访问。
  • 离线支持:通过 Service Worker 缓存资源,实现离线或弱网环境下的可用性。
  • 可安装:通过 Web App Manifest 提供原生应用的安装体验,支持桌面和移动设备。
  • 推送通知:使用 Web Push API 实现消息推送,提升用户 engagement。
  • 响应式设计:适配多种设备和屏幕尺寸。
  • 安全性:强制使用 HTTPS 确保数据安全。

PWA 的技术栈

  • Service Worker:浏览器后台线程,管理缓存和网络请求。
  • Web App Manifest:JSON 文件,定义应用元数据(如图标、名称)。
  • Web Push API:与推送服务交互,发送通知。
  • HTTPS:确保安全通信。

前端价值

  • 提升用户体验(快速加载、离线访问)。
  • 降低开发成本(跨平台,无需原生开发)。
  • 提高留存率(推送通知、可安装性)。

本教程基于 2025 年 5 月 26 日的浏览器标准(Chrome 125、Firefox 126、Safari 18 等),涵盖 PWA 的完整实现流程。

PWA 基础

环境准备

技术要求

  • 浏览器:支持 Service Worker 和 Web App Manifest(主流浏览器均支持)。
  • Node.js:用于开发和构建(推荐 18.x 或 20.x)。
  • HTTPS :本地开发可使用 localhost 或自签名证书,生产环境需有效证书。
  • 工具
    • Workbox:Google 提供的 PWA 工具库,简化 Service Worker 配置。
    • Lighthouse:Chrome DevTools 的 PWA 审计工具。

安装工具

  1. Node.js

    bash 复制代码
    nvm install 18
    nvm use 18
  2. Workbox CLI

    bash 复制代码
    npm install -g workbox-cli
  3. HTTPS 开发环境 (使用 mkcert 生成本地证书):

    bash 复制代码
    brew install mkcert
    mkcert localhost

项目初始化

创建一个简单的 Web 项目:

bash 复制代码
mkdir pwa-demo
cd pwa-demo
npm init -y
npm install workbox-cli express

项目结构

java 复制代码
pwa-demo/
├── public/
│   ├── index.html
│   ├── styles.css
│   ├── app.js
│   ├── manifest.json
│   └── sw.js
├── server.js
└── package.json

第一个 PWA

1. 创建 HTML 文件

public/index.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My PWA</title>
    <link rel="stylesheet" href="/styles.css">
    <link rel="manifest" href="/manifest.json">
    <meta name="theme-color" content="#007bff">
</head>
<body>
    <h1>Welcome to My PWA</h1>
    <p>This is a Progressive Web App!</p>
    <script src="/app.js"></script>
</body>
</html>

分析

  • viewport 确保响应式。
  • manifest.json 链接 Web App Manifest。
  • theme-color 设置浏览器 UI 颜色。

2. 创建 Web App Manifest

public/manifest.json

json 复制代码
{
    "name": "My PWA",
    "short_name": "PWA",
    "start_url": "/",
    "display": "standalone",
    "background_color": "#ffffff",
    "theme_color": "#007bff",
    "icons": [
        {
            "src": "/icon-192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "/icon-512.png",
            "sizes": "512x512",
            "type": "image/png"
        }
    ]
}

分析

  • nameshort_name 定义应用名称。
  • start_url 指定启动页面。
  • display: standalone 提供原生应用体验。
  • icons 提供不同尺寸的图标。

生成图标

使用工具(如 favicon.io)生成 192x192 和 512x512 的 PNG 图标,放置在 public/

3. 注册 Service Worker

public/sw.js

javascript 复制代码
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open('v1').then(cache => {
            return cache.addAll([
                '/',
                '/index.html',
                '/styles.css',
                '/app.js',
                '/manifest.json',
                '/icon-192.png',
                '/icon-512.png'
            ]);
        })
    );
});

self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request).then(response => {
            return response || fetch(event.request).then(fetchResponse => {
                caches.open('v1').then(cache => {
                    cache.put(event.request, fetchResponse.clone());
                });
                return fetchResponse;
            });
        })
    );
});

public/app.js

javascript 复制代码
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(reg => console.log('Service Worker registered', reg))
        .catch(err => console.error('Service Worker registration failed', err));
}

分析

  • install 事件缓存核心资源。
  • fetch 事件实现"缓存优先,网络更新"策略。
  • app.js 注册 Service Worker。

4. 配置服务器

server.js

javascript 复制代码
const express = require('express');
const path = require('path');
const app = express();

app.use(express.static(path.join(__dirname, 'public')));

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

启动

bash 复制代码
node server.js

5. 测试 PWA

  • 访问 http://localhost:3000
  • 使用 Chrome DevTools 的 Lighthouse 审计:
    • 打开 DevTools(F12)。
    • 切换到 "Lighthouse" 面板。
    • 勾选 "Progressive Web App",生成报告。
  • 结果
    • 确认离线支持:断开网络,刷新页面。
    • 确认可安装性:浏览器显示"安装"提示。

逐步分析

  1. HTML 提供基本结构,Manifest 定义应用元数据。
  2. Service Worker 缓存资源,支持离线访问。
  3. Express 服务器托管静态文件。
  4. Lighthouse 验证 PWA 合规性。

面试题 1:PWA 核心特性

问题:PWA 的核心特性是什么?如何实现离线支持?

答案

  • 特性:离线支持、可安装、推送通知、响应式、安全。
  • 离线支持
    • 使用 Service Worker 缓存资源:

      javascript 复制代码
      self.addEventListener('install', event => {
          event.waitUntil(caches.open('v1').then(cache => cache.addAll(['/', '/index.html'])));
      });
    • 拦截 fetch 请求,返回缓存或网络响应。

PWA 核心功能实现

1. 高级 Service Worker 配置(使用 Workbox)

Workbox 简化 Service Worker 的开发,提供预缓存、运行时缓存等功能。

安装 Workbox

bash 复制代码
npm install workbox-build

生成 Service Worker

workbox-config.js

javascript 复制代码
module.exports = {
    globDirectory: 'public/',
    globPatterns: ['**/*.{html,css,js,png,json}'],
    swDest: 'public/sw.js',
    clientsClaim: true,
    skipWaiting: true
};

生成命令

bash 复制代码
npx workbox generateSW workbox-config.js

生成的 sw.js(简化版)

javascript 复制代码
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');

workbox.precaching.precacheAndRoute([
    { url: '/', revision: '1' },
    { url: '/index.html', revision: '1' },
    // 其他资源
]);

workbox.routing.registerRoute(
    ({ request }) => request.destination === 'image',
    new workbox.strategies.CacheFirst({
        cacheName: 'images',
        plugins: [
            new workbox.expiration.ExpirationPlugin({
                maxEntries: 50,
                maxAgeSeconds: 30 * 24 * 60 * 60 // 30 天
            })
        ]
    })
);

分析

  • precacheAndRoute 预缓存指定资源。
  • CacheFirst 策略优先使用缓存,适合静态资源。
  • ExpirationPlugin 限制缓存大小和有效期。

动态缓存

修改 sw.js

javascript 复制代码
workbox.routing.registerRoute(
    ({ url }) => url.pathname.startsWith('/api/'),
    new workbox.strategies.NetworkFirst({
        cacheName: 'api',
        plugins: [
            new workbox.cacheableResponse.CacheableResponsePlugin({
                statuses: [200]
            })
        ]
    })
);

分析

  • NetworkFirst 优先尝试网络,失败则用缓存。
  • 适合动态 API 数据。

面试题 2:Workbox 缓存策略

问题:Workbox 的常见缓存策略有哪些?适合的场景?

答案

  • CacheFirst:缓存优先,适合静态资源(如图片、CSS)。
  • NetworkFirst:网络优先,适合动态数据(如 API)。
  • StaleWhileRevalidate:返回缓存,同时更新,适合频繁更新的资源。
  • NetworkOnly/CacheOnly:仅网络或仅缓存,适合特殊场景。

2. 推送通知

推送通知通过 Web Push API 实现,需要后端支持。

配置 VAPID 密钥

安装 web-push

bash 复制代码
npm install web-push

生成密钥

javascript 复制代码
const

高级 Service Worker 功能

后台同步(Background Sync)

后台同步允许在网络恢复时执行任务,如发送离线保存的数据。

实现后台同步

public/app.js(更新)

javascript 复制代码
async function saveDataOffline(data) {
    if (!navigator.onLine) {
        // 保存到 IndexedDB
        const db = await openDB('pwa-store', 1, {
            upgrade(db) {
                db.createObjectStore('sync-data', { autoIncrement: true });
            }
        });
        await db.put('sync-data', data);
        
        // 注册同步任务
        const reg = await navigator.serviceWorker.ready;
        await reg.sync.register('sync-data');
    } else {
        await fetch('/api/save', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
    }
}

// IndexedDB 封装
function openDB(name, version, upgrade) {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(name, version);
        request.onupgradeneeded = event => upgrade(event.target.result);
        request.onsuccess = event => resolve(event.target.result);
        request.onerror = () => reject(request.error);
    });
}

public/sw.js(更新)

javascript 复制代码
self.addEventListener('sync', event => {
    if (event.tag === 'sync-data') {
        event.waitUntil(syncData());
    }
});

async function syncData() {
    const db = await openDB('pwa-store', 1);
    const tx = db.transaction('sync-data', 'readonly');
    const store = tx.objectStore('sync-data');
    const data = await store.getAll();
    
    for (const item of data) {
        await fetch('/api/save', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(item)
        });
        await store.delete(item.id);
    }
}

后端server.js 更新):

javascript 复制代码
app.post('/api/save', (req, res) => {
    // 保存数据到数据库
    console.log('Saved:', req.body);
    res.status(200).send();
});

逐步分析

  1. 离线时,数据存储到 IndexedDB,注册 sync 任务。
  2. Service Worker 监听 sync 事件,网络恢复时发送数据。
  3. 后端接收数据,客户端删除已同步的记录。

前端应用

  • 离线表单提交(如评论、订单)。
  • 消息队列同步(如聊天应用)。

面试题 4:后台同步的优势

问题:后台同步与定时轮询相比有何优势?

答案

  • 优势
    • 仅在网络恢复时触发,节省资源。
    • 由浏览器管理,自动优化时机。
    • 支持离线场景,无需用户在线。
  • 定时轮询:持续请求,消耗流量和电池,离线无效。

周期性同步(Periodic Sync)

周期性同步适合定期更新内容,如新闻 feed。

实现周期性同步

public/app.js(更新)

javascript 复制代码
async function registerPeriodicSync() {
    const reg = await navigator.serviceWorker.ready;
    if ('periodicSync' in reg) {
        const status = await navigator.permissions.query({ name: 'periodic-background-sync' });
        if (status.state === 'granted') {
            await reg.periodicSync.register('update-content', {
                minInterval: 24 * 60 * 60 * 1000 // 每天
            });
        }
    }
}

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js').then(reg => {
        registerPeriodicSync();
    });
}

public/sw.js(更新)

javascript 复制代码
self.addEventListener('periodicsync', event => {
    if (event.tag === 'update-content') {
        event.waitUntil(updateContent());
    }
});

async function updateContent() {
    const response = await fetch('/api/content');
    const data = await response.json();
    await caches.open('content').then(cache => cache.put('/content', new Response(JSON.stringify(data))));
}

后端

javascript 复制代码
app.get('/api/content', (req, res) => {
    res.json({ articles: ['News 1', 'News 2'] });
});

逐步分析

  1. 检查 periodicSync 支持和权限。
  2. 注册每天同步任务。
  3. Service Worker 定期获取内容,缓存到 content 存储。

注意

  • 周期性同步需用户授予权限,浏览器限制执行频率。
  • 目前仅 Chrome 支持,需降级方案(如定时 fetch)。

面试题 5:周期性同步限制

问题:周期性同步的限制是什么?如何降级?

答案

  • 限制

    • 仅部分浏览器支持(Chrome)。
    • 需用户权限。
    • 最小间隔由浏览器决定(通常 24 小时)。
  • 降级

    javascript 复制代码
    if (!('periodicSync' in reg)) {
        setInterval(async () => {
            const response = await fetch('/api/content');
            const data = await response.json();
            // 更新 UI
        }, 24 * 60 * 60 * 1000);
    }

推送通知高级场景

富文本通知与动作按钮

增强通知的用户交互性。

实现富文本通知

public/sw.js(更新)

javascript 复制代码
self.addEventListener('push', event => {
    const data = event.data.json();
    event.waitUntil(
        self.registration.showNotification(data.title, {
            body: data.body,
            icon: '/icon-192.png',
            badge: '/badge.png',
            image: '/preview.jpg',
            actions: [
                { action: 'view', title: 'View Details' },
                { action: 'dismiss', title: 'Dismiss' }
            ],
            data: { url: data.url }
        })
    );
});

self.addEventListener('notificationclick', event => {
    event.notification.close();
    if (event.action === 'view') {
        clients.openWindow(event.notification.data.url);
    }
});

后端推送server.js 更新):

javascript 复制代码
app.post('/api/sendNotification', (req, res) => {
    const payload = JSON.stringify({
        title: 'New Article',
        body: 'Check out our latest post!',
        url: 'https://example.com/article',
    });
    Promise.all(subscriptions.map(sub => webPush.sendNotification(sub, payload)))
        .then(() => res.status(200).send())
        .catch(err => res.status(500).send(err));
});

逐步分析

  1. 通知包含图片(image)、徽章(badge)和动作按钮。
  2. notificationclick 事件处理用户交互。
  3. 动作 view 打开指定 URL。

前端应用

  • 电商促销通知。
  • 新闻推送。

数据驱动通知

根据用户数据动态生成通知。

示例

前端订阅app.js 更新):

javascript 复制代码
async function subscribeUser(reg) {
    const publicKey = await fetch('/api/vapidPublicKey').then(res => res.text());
    const subscription = await reg.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(publicKey)
    });
    
    await fetch('/api/subscribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            subscription,
            userId: 'user123' // 假设用户 ID
        })
    });
}

后端server.js 更新):

javascript 复制代码
const subscriptions = new Map();

app.post('/api/subscribe', (req, res) => {
    subscriptions.set(req.body.userId, req.body.subscription);
    res.status(201).send();
});

app.post('/api/sendUserNotification', (req, res) => {
    const { userId, message } = req.body;
    const subscription = subscriptions.get(userId);
    if (subscription) {
        const payload = JSON.stringify({
            title: 'Personalized Message',
            body: message,
            url: '/dashboard'
        });
        webPush.sendNotification(subscription, payload)
            .then(() => res.status(200).send())
            .catch(err => res.status(500).send(err));
    } else {
        res.status(404).send('User not found');
    }
});

测试

bash 复制代码
curl -X POST http://localhost:3000/api/sendUserNotification \
-H "Content-Type: application/json" \
-d '{"userId": "user123", "message": "Your order has shipped!"}'

分析

  • 按用户 ID 存储订阅信息。
  • 后端发送个性化通知。

PWA 安全性

HTTPS 配置

PWA 强制使用 HTTPS,生产环境需有效证书。

使用 Let's Encrypt

安装 Certbot

bash 复制代码
sudo apt install certbot python3-certbot-nginx

获取证书

bash 复制代码
sudo certbot --nginx -d example.com

Nginx 配置

nginx 复制代码
server {
    listen 443 ssl;
    server_name example.com;
    
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    root /var/www/pwa/public;
    
    location / {
        try_files $uri $uri/ /index.html;
    }
}

分析

  • Let's Encrypt 提供免费证书,自动续期。
  • Nginx 重定向 HTTP 到 HTTPS。

权限管理

限制 Service Worker 和通知权限。

检查权限

app.js

javascript 复制代码
async function checkPermissions() {
    const swPermission = await navigator.permissions.query({ name: 'service-worker' });
    const pushPermission = await navigator.permissions.query({ name: 'push', userVisibleOnly: true });
    console.log('Service Worker:', swPermission.state);
    console.log('Push:', pushPermission.state);
}

分析

  • 动态检查权限状态,提示用户授权。

数据加密

加密本地存储的数据。

使用 Web Crypto API

app.js

javascript 复制代码
async function encryptData(data) {
    const key = await crypto.subtle.generateKey(
        { name: 'AES-GCM', length: 256 },
        true,
        ['encrypt', 'decrypt']
    );
    
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encoded = new TextEncoder().encode(JSON.stringify(data));
    
    const encrypted = await crypto.subtle.encrypt(
        { name: 'AES-GCM', iv },
        key,
        encoded
    );
    
    return { encrypted: new Uint8Array(encrypted), iv, key };
}

分析

  • AES-GCM 加密确保数据安全。
  • 适合离线存储敏感信息。

复杂案例:离线文档编辑器

实现

index.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Offline Editor</title>
    <link rel="manifest" href="/manifest.json">
    <meta name="theme-color" content="#007bff">
</head>
<body>
    <textarea id="editor"></textarea>
    <button id="save">Save</button>
    <script src="/app.js"></script>
</body>
</html>

app.js

javascript 复制代码
import { openDB } from 'idb';

const dbPromise = openDB('editor-store', 1, {
    upgrade(db) {
        db.createObjectStore('documents', { keyPath: 'id', autoIncrement: true });
    }
});

document.getElementById('save').addEventListener('click', async () => {
    const content = document.getElementById('editor').value;
    const db = await dbPromise;
    await db.put('documents', { content, timestamp: Date.now() });
    
    if (!navigator.onLine) {
        const reg = await navigator.serviceWorker.ready;
        await reg.sync.register('sync-docs');
    } else {
        await syncDocs();
    }
});

async function syncDocs() {
    const db = await dbPromise;
    const docs = await db.getAll('documents');
    for (const doc of docs) {
        await fetch('/api/saveDoc', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(doc)
        });
        await db.delete('documents', doc.id);
    }
}

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js').then(reg => {
        reg.sync.register('sync-docs').catch(() => {});
    });
}

sw.js

javascript 复制代码
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open('v1').then(cache => cache.addAll([
            '/',
            '/index.html',
            '/manifest.json'
        ]))
    );
});

self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request).then(response => response || fetch(event.request))
    );
});

self.addEventListener('sync', event => {
    if (event.tag === 'sync-docs') {
        event.waitUntil(syncDocs());
    }
});

async function syncDocs() {
    // 与 app.js 的 syncDocs 逻辑相同
}

后端server.js 更新):

javascript 复制代码
app.post('/api/saveDoc', (req, res) => {
    // 保存到数据库
    console.log('Document saved:', req.body);
    res.status(200).send();
});

分析

  • IndexedDB 存储离线文档。
  • 后台同步上传文档。
  • Service Worker 确保离线访问。

与 WebAssembly 整合

结合 WebAssembly 提升 PWA 性能。

示例:Markdown 渲染

Rust 代码src/lib.rs):

rust 复制代码
use wasm_bindgen::prelude::*;
use pulldown_cmark::{html, Parser};

#[wasm_bindgen]
pub fn render_markdown(md: &str) -> String {
    let parser = Parser::new(md);
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);
    html_output
}

编译

bash 复制代码
wasm-pack build --target web

前端app.js 更新):

javascript 复制代码
import init, { render_markdown } from './pkg/markdown.js';

async function initEditor() {
    await init();
    const editor = document.getElementById('editor');
    const preview = document.createElement('div');
    document.body.appendChild(preview);
    
    editor.addEventListener('input', () => {
        preview.innerHTML = render_markdown(editor.value);
    });
}

initEditor();

分析

  • WASM 加速 Markdown 解析。
  • PWA 提供离线支持。

企业级实践(进阶)

微前端与 PWA

Qiankun 配置

javascript 复制代码
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
    {
        name: 'pwaEditor',
        entry: '//localhost:3001',
        container: '#editorContainer',
        activeRule: '/editor'
    }
]);

start();

Service Worker(子应用):

javascript 复制代码
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open('editor-v1').then(cache => cache.addAll([
            '/editor',
            '/editor/index.html'
        ]))
    );
});

分析

  • 微前端分离 PWA 模块。
  • 各模块独立缓存。

Serverless 部署

使用 AWS Amplify

bash 复制代码
amplify init
amplify add hosting
amplify publish

分析

  • Serverless 简化 PWA 部署。
  • 自动配置 HTTPS 和 CDN。

Kubernetes 高级部署

自动扩缩容

bash 复制代码
kubectl create -f - <<EOF
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: pwa-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: pwa-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 80
EOF

分析

  • 根据 CPU 使用率动态调整副本。
  • 确保高流量下的稳定性。
相关推荐
EnCi Zheng12 分钟前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen16 分钟前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技16 分钟前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人27 分钟前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实28 分钟前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha39 分钟前
三目运算符
linux·服务器·前端
晓晨的博客1 小时前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
不可能的是1 小时前
从 /simplify 指令深挖 Claude Code 多 Agent 协同机制
javascript