pwa 相关
pwaservice worker

pwa 相关

To implement a custom service worker in a Next.js 14 application without using the next-pwa package,...

更新于 2024-12-23
8807

To implement a custom service worker in a Next.js 14 application without using the next-pwa package, you can follow these steps. This approach allows you to write your service worker code in a sw.ts file and ensure it is compiled to sw.js in the root directory after the build process.

Step-by-Step Implementation

  1. Create the Service Worker File:

    • In your Next.js project, create a file named sw.ts in the root directory. This file will contain your service worker logic.
    typescript
    // sw.ts
    self.addEventListener('install', (event) => {
    console.log('Service Worker installing...');
    });
    self.addEventListener('activate', (event) => {
    console.log('Service Worker activating...');
    });
    self.addEventListener('fetch', (event) => {
    event.respondWith(
    caches.match(event.request).then((response) => {
    return response || fetch(event.request);
    })
    );
    });
  2. Configure Next.js to Build the Service Worker:

    • You need to modify your next.config.js to ensure that TypeScript files are compiled correctly. You can use a custom Webpack configuration to handle this.
    javascript
    // next.config.js
    const path = require('path');
    module.exports = {
    webpack: (config, { isServer }) => {
    if (isServer) {
    config.module.rules.push({
    test: /\.ts$/,
    include: path.resolve(__dirname, 'sw.ts'),
    use: {
    loader: 'ts-loader',
    options: {
    transpileOnly: true,
    },
    },
    });
    }
    return config;
    },
    };
  3. Register the Service Worker:

    • You need to register the service worker in your application. This is typically done in the _app.js or _app.tsx file.
    javascript
    // pages/_app.js
    import { useEffect } from 'react';
    function MyApp({ Component, pageProps }) {
    useEffect(() => {
    if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
    .then((registration) => {
    console.log('Service Worker registered with scope:', registration.scope);
    })
    .catch((error) => {
    console.error('Service Worker registration failed:', error);
    });
    }
    }, []);
    return <Component {...pageProps} />;
    }
    export default MyApp;
  4. Build Your Application:

    • Run the build command to generate the production files:
    bash
    npm run build

    After building, ensure that sw.js is generated in the public directory of your project. You may need to manually copy or move the compiled sw.js file from the output directory to the public directory if it doesn't automatically go there.

  5. Deploy Your Application:

    • Once you deploy your application, the service worker will be served from the root of your domain, allowing it to manage caching and offline capabilities.

Additional Considerations

  • Testing the Service Worker: After deploying, you can test if the service worker is registered correctly by checking the Application tab in your browser's developer tools.
  • Handling Updates: Implement logic in your service worker to handle updates and caching strategies effectively.

Resources for Further Reading

This approach gives you full control over your service worker implementation without relying on third-party libraries like next-pwa. If you have any further questions or need additional assistance, feel free to ask!

sub app

To implement a Progressive Web App (PWA) that operates as a sub-application under a different URL from the main app, you can follow several strategies. This approach allows you to maintain distinct functionalities while leveraging the benefits of PWAs, such as offline capabilities and app-like experiences.

Key Concepts for Sub-Apps in PWAs

  1. Separate Manifests: Each PWA, including sub-apps, should have its own manifest.json file. This file defines how the app appears on the user's device, including its name, icons, and start URL. For a sub-app, the start_url should point to the subdirectory or subdomain where the app is hosted.

  2. Service Workers: Each PWA must register its own service worker with a scope that matches its URL path. For example, if your main app is at / and your sub-app is at /subdirectory, you would register the service worker for the sub-app with a scope of /subdirectory/. This ensures that the service workers do not interfere with each other.

  3. URL Handling: If your sub-app is hosted on a different origin (e.g., sub.example.com), you need to ensure that the main app can handle URLs correctly. This may involve setting up a web-app-origin-association file to verify ownership of the different origins.

  4. Installation and User Experience: Users can install both the main app and the sub-app independently. When navigating to the sub-app's URL, the user should be prompted to install it as a separate PWA. This can be managed by dynamically changing the manifest endpoint based on the URL being accessed, as discussed in various community forums and documentation.

Implementation Steps

  1. Create Separate Manifests:

    • Main app manifest (/manifest.json):
      json
      {
      "name": "Main App",
      "short_name": "Main",
      "start_url": "/",
      "display": "standalone",
      "icons": [ /* icons here */ ]
      }
    • Sub-app manifest (/subdirectory/manifest.json):
      json
      {
      "name": "Sub App",
      "short_name": "Sub",
      "start_url": "/subdirectory/",
      "display": "standalone",
      "icons": [ /* icons here */ ]
      }
  2. Register Service Workers:

    • Main app service worker:
      javascript
      navigator.serviceWorker.register('/sw.js', { scope: '/' });
    • Sub-app service worker:
      javascript
      navigator.serviceWorker.register('/subdirectory/sw.js', { scope: '/subdirectory/' });
  3. Dynamic Manifest Loading: You can dynamically load the appropriate manifest based on the URL:

    javascript
    if (window.location.pathname.startsWith('/subdirectory')) {
    document.querySelector('link[rel="manifest"]').setAttribute('href', '/subdirectory/manifest.json');
    } else {
    document.querySelector('link[rel="manifest"]').setAttribute('href', '/manifest.json');
    }

Challenges and Considerations

  • Browser Compatibility: Ensure that the browsers you target support the features you are using, especially service workers and manifests.
  • User Experience: Consider how users will navigate between the main app and sub-app. Clear navigation and branding can help maintain a cohesive experience.
  • Testing: Thoroughly test both apps to ensure they function correctly in isolation and when accessed from the main app.

Additional Resources

By following these guidelines, you can effectively create a PWA that functions as a sub-app, providing users with a seamless experience across different parts of your application.

缓存外部资源

使用 Service Worker 和 Caches API 缓存外部请求

在 Progressive Web Apps (PWA) 中,Service Worker 可以使用 Caches API 来缓存本域名以外的请求。以下是一些关键点,帮助你理解如何实现这一点:

1. Service Worker 的跨域请求处理

Service Worker 可以拦截并处理跨域请求。虽然它主要用于控制同一域名下的请求,但它也可以处理来自其他域名的请求。具体来说,当 Service Worker 控制一个页面时,它可以拦截所有网络请求,包括跨域请求。此时,fetch 事件会被触发,event.request.mode 会根据请求的上下文设置为 "cors""no-cors"【6](https://stackoverflow.com/questions/39432717/how-can-i-cache-external-urls-using-service-worker)【5](https://web.dev/articles/service-worker-caching-and-http-caching)。

2. 缓存外部资源

要缓存外部资源,你可以在 Service Worker 的 fetch 事件中使用 Caches API。以下是一个简单的示例代码,展示如何缓存外部请求:

javascript
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('my-cache').then((cache) => {
return cache.match(event.request).then((response) => {
// 如果缓存中有响应,直接返回
if (response) {
return response;
}
// 否则从网络获取并缓存
return fetch(event.request).then((networkResponse) => {
// 只缓存成功的响应
if (networkResponse && networkResponse.status === 200) {
cache.put(event.request, networkResponse.clone());
}
return networkResponse;
});
});
})
);
});

在这个示例中,Service Worker 会检查缓存中是否已有请求的响应。如果没有,它会从网络获取该请求,并将响应缓存起来,以便下次使用。

3. 注意事项

  • CORS 策略:确保外部资源的服务器支持 CORS(跨源资源共享),否则可能会遇到安全限制,导致无法成功获取和缓存这些资源。
  • 缓存策略:根据你的应用需求,选择合适的缓存策略(如 Cache First、Network First 等)来优化性能和用户体验【5](https://developer.chrome.com/docs/workbox/caching-strategies-overview/)。

结论

通过适当的配置,Service Worker 可以有效地缓存外部请求的内容,从而提升 PWA 的性能和用户体验。确保遵循 CORS 策略,并根据需要选择合适的缓存策略,以实现最佳效果。如果你有更多具体的需求或问题,欢迎继续提问!

检测 PWA 打开方式

在区分用户打开PWA(Progressive Web App)时是从应用程序图标打开的还是从URL访问的方面,可以通过以下几种方式进行判断:

  1. 检查display-mode: 可以通过检查display-mode媒体查询来确定PWA是否以独立应用的形式打开。如果display-mode的值为standalonefullscreenminimal-ui,则表明PWA是以独立应用的形式打开的。可以通过以下代码进行检查:

    javascript
    const displayModes = ['fullscreen', 'standalone', 'minimal-ui'];
    const matchesPwa = displayModes.some(displayMode => window.matchMedia('(display-mode: ' + displayMode + ')').matches);

    如果matchesPwatrue,则PWA可能是通过应用程序图标打开的。

  2. 检查navigator.standalone属性: 在iOS设备上,如果PWA已通过Safari添加到主屏幕,navigator.standalone属性将返回true,否则返回false。这可以帮助判断PWA是否通过主屏幕图标打开。

  3. 检查document.referrer: 对于Android设备,可以通过检查document.referrer中是否包含字符串'android-app://'来判断用户是否通过Android应用进入当前网页。如果是,则表明PWA可能是通过应用程序图标打开的。

综合以上方法,可以通过以下代码来判断PWA是否通过应用程序图标打开:

javascript
const isPWA = (): boolean => {
const displayModes = ['fullscreen', 'standalone', 'minimal-ui'];
const matchesPwa = displayModes.some(displayMode => window.matchMedia('(display-mode: ' + displayMode + ')').matches);
return (
matchesPwa ||
window.navigator?.standalone ||
document.referrer.includes('android-app://')
)
}

如果isPWA()函数返回true,则表明用户可能是通过应用程序图标打开PWA的。