我该如何使用service worker缓存外部URL?

28

我一直在使用Google Web Starter Kit (https://github.com/google/web-starter-kit),并成功地制作了一个小型渐进式Web应用程序。但是,我在一个问题上卡住了:缓存来自外部CDN的静态文件。

例如,我正在使用来自https://fonts.googleapis.com/icon?family=Material+Icons的MDL图标,但我无法找到缓存该请求的方法,因为服务工作者仅响应我的应用程序域内的URL。

我看到的选项有:

  1. 下载文件并将其放入供应商文件夹中。优点:易于设置SW缓存。缺点:随着添加新图标,文件将无法保持最新状态(但这并不重要,因为我的代码只会使用可用的图标)。

  2. 使用NPM存储库:https://www.npmjs.com/package/material-design-icons,并使用构建步骤从node_modules复制CSS文件。优点:将允许从NPM进行自动更新。缺点:设置略微复杂。

  3. 一些花哨的代理方法,可以让我使用SW缓存外部URL。例如:myapp.com/loadExternal?url=https://fonts.googleapis.com/icon?family=Material+Icons

目前,我倾向于选项#2,但如果选项#3可行,则会很酷。


你好, self.addEventListener('install', e => { e.waitUntil( caches.open('cache').then(cache => { return cache.addAll([ '/', '/index.html', '/styles/main.css', '/scripts/main.min.js', 'https://fonts.googleapis.com/css?family=Quicksand:300' ]) .then(() => self.skipWaiting()); }) ) }); 这段代码能正常工作吗? - Asim K T
5个回答

13

简而言之:尝试选项3。你以后会感谢我的。

来自Google文档

默认情况下,如果第三方网址不支持CORS,则从其处获取资源将失败。您可以向请求添加no-CORS选项来克服此问题,尽管这将导致“opaque”响应,这意味着您将无法确定响应是否成功。

所以

选项1

添加no-cors头。

var CACHE_NAME = 'my-site-cache-v1';
var urlsToPrefetch = [
  '/',
  '/styles/main.css',
  '/script/main.js',
  'https://fonts.googleapis.com/icon?family=Material+Icons'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        // Magic is here. Look the  mode: 'no-cors' part.
        cache.addAll(urlsToPrefetch.map(function(urlToPrefetch) {
           return new Request(urlToPrefetch, { mode: 'no-cors' });
        })).then(function() {
          console.log('All resources have been fetched and cached.');
        });
      })
  );
});

正如OP所说,当资源更新时,在这种情况下很难获得最新的副本。另一个问题是,就像我所说的,你不会知道响应是否成功。

选项2

或者像OP所说,我们可以创建一个代理服务器:像这样简单的东西(伪代码,未经测试的Node Express代码)

var request = require('request');
app.get('/library', function(req,res) {
  // read the param 
  var thirdPartyUrl = req.query.thirdPartyUrl;
  request(thirdPartyUrl).pipe(res);
});

当您访问/library?thirdPartyUrl=https://fonts.googleapis.com/icon?family=Material+Icons时,应该像通常缓存响应一样缓存响应。例如:删除no-cors并将urlsToPrefetch替换为以下值:

var urlsToPrefetch = [
      '/',
      '/library?thirdPartyUrl=https://fonts.googleapis.com/icon?family=Material+Icons',
      '/library?thirdPartyUrl=https://fonts.googleapis.com/icon?family=Roboto'
    ];

选项3

我认为这是最好且更容易的方法。使用workbox。我们试过使用和不使用workbox来创建PWA,而使用workbox很简单。

了解workbox:https://developers.google.com/web/tools/workbox/

在初始设置完成后实现以下路由:

workbox.routing.registerRoute(
  new RegExp('^https://third-party.example.com/images/'),
  new workbox.strategies.CacheFirst({
    cacheName: 'image-cache',
    plugins: [
      new workbox.cacheableResponse.Plugin({
        statuses: [0, 200],
      })
    ]
  })
);

3
请添加一个外部/第三方资源获取的示例到您的示例中。 - karns
对于选项3,我使用了以下代码: import { CacheableResponsePlugin } from 'workbox-cacheable-response'因此,我更新了插件,如下所示: plugins: [ new CacheableResponsePlugin({ statuses: [0, 200], }), ], - dustydojo

4
我看不到缓存请求的方法,因为服务工作线程只响应我的应用程序域内的URL。

这是不正确的。正在主动控制页面的服务工作线程将有机会拦截并响应跨源资源的网络请求;标准的fetch事件将被触发,并且event.request.mode将是"cors""no-cors",取决于页面所做请求的上下文。

简而言之,只要有一个服务工作线程在控制页面,当该页面进行任何网络请求时,无论是同源还是跨源资源,服务工作线程都能够响应fetch事件。


谢谢你的澄清,很酷。我已经弄明白了,会发布答案。 - jeznag
Jeff,请发布一些代码来说明如何做OP所问的! - karns

4

我阅读了sw-toolbox文档,并找到了如何操作的方法。只需要将以下内容添加到我的运行时缓存中:

// cache fonts hosted on google CDN
global.toolbox.router.get(/googleapis/, global.toolbox.fastest);

为了使其正常工作,我们需要将“Service Worker Toolbox”安装到我们的应用程序中。对吗? - Asim K T
1
没错。值得一提的是,它很轻便,同时提供了许多有用的辅助工具。 - jeznag
4
您可能想使用 Workbox,它是 sw-toolbox 和 sw-precache 的后继者:https://developers.google.com/web/ilt/pwa/lab-migrating-to-workbox-from-sw-precache-and-sw-toolbox - Sam Dutton

2
在实现Service Worker缓存时遇到了同样的问题。问题在于如何从服务器获取图标。
1.通过应用程序的主URL发出请求(黄色部分)。我想这是您正在尝试缓存的静态请求。
2.实际动态网络请求以获取图标(红色部分)。

enter image description here

要解决这个问题,您需要动态填充缓存(类似于这样的方式)。
self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
    .then((response)=>{
      if(response){
        return response;
      }
      else{
        return fetch(event.request) // response of requests
        .then((res)=>{
          return caches.open('dynamic') //create dynamic cache
          .then((cache)=>{
            cache.put(event.request.url,res.clone());
            return res;
          })
        })
      }
    })
    .catch(()=>{})
  )
});

0

我可能有点偏离主题,但是这是否就像下面这样简单呢?

  caches.open(version)
  .then(function(cache) {
    return cache.addAll([
      '/',
      'index.html',
      '/css/app.css',
      '/js/app.min.js',
      '/assets/images/logo_target_white.png',
      '/scripts/angular-jwt.min.js',
      '/scripts/ng-file-upload.min.js',

       // this guy here
      'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'
    ]);
  })

使用这种方法并在Chrome工具中检查我的应用程序缓存似乎显示它正确地进行了缓存。

3
我尝试将此网址添加到服务工作者的文件缓存数组中:https://fonts.googleapis.com/css?family=Open+Sans。当我在 DevTools 中检查我的应用程序缓存时,返回了一个大小为 0KB 的空文件 'css/' - David Gaskin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接