javascript - 如何将 Service Worker 预取与跨导航取结合起来?

标签 javascript google-chrome caching asynchronous service-worker

我正在尝试使用“预取”和获取“收集”技术来缓存 SPA 应用程序上的 js、CSS 和内容。

为了预取脚本,我尝试了非常类似于此代码片段的代码:

self.addEventListener('install', function(event) {
      var now = Date.now();
    
      var urlsToPrefetch = [
        'static/pre_fetched.txt',
        'static/pre_fetched.html'
      ];
    
         
      event.waitUntil(
        caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
          var cachePromises = urlsToPrefetch.map(function(urlToPrefetch) {
            var url = new URL(urlToPrefetch, location.href);
            url.search += (url.search ? '&' : '?') + 'cache-bust=' + now;
           var request = new Request(url, {mode: 'no-cors'});
            return fetch(request).then(function(response) {
              if (response.status >= 400) {
                throw new Error('request for ' + urlToPrefetch +
                  ' failed with status ' + response.statusText);
              }
    
              return cache.put(urlToPrefetch, response);
            }).catch(function(error) {
              console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
            });
          });
    
          return Promise.all(cachePromises).then(function() {
            console.log('Pre-fetching complete.');
          });
        }).catch(function(error) {
          console.error('Pre-fetching failed:', error);
        })
      );
    });

完整代码可查看here

预取后,我在缓存上几乎拥有所有关键脚本(例如 angular.js、模块和 Controller ,可能还有一些 jqueries),因此,我执行一个 fetch 事件来收集由 require.js 加载的所有其他脚本异步。

self.addEventListener('fetch', function (event) {
    if (event.request.method === "GET" && testes_to_know_if_it_area_a_js_or_css) {
        event.respondWith(
                caches.match(event.request)
                .then(function (response) {
                    if (response) {
                        loggger && console.log('From Cache', event.request.url);
                        return response;
                    }

                    // IMPORTANT: Clone the request. A request is a stream and
                    // can only be consumed once. Since we are consuming this
                    // once by cache and once by the browser for fetch, we need
                    // to clone the response
                    var fetchRequest = event.request.clone();

                    return fetch(fetchRequest).then(
                            function (response) {
                                // Check if we received a valid response
                                if (!response || response.status !== 200 || response.type !== 'basic') {
                                    return response;
                                }

                                // IMPORTANT: Clone the response. A response is a stream
                                // and because we want the browser to consume the response
                                // as well as the cache consuming the response, we need
                                // to clone it so we have 2 stream.
                                var responseToCache = response.clone();

                                caches.open(CURRENT_CACHES['general-cache'])
                                        .then(function (cache) {
                                            try {
                                                loggger && console.log('Add to Cache', event.request.url);
                                                cache.put(event.request, responseToCache);
                                            } catch (e) {
                                                console.error(e);
                                            }
                                        });

                                return response;
                            }
                    );
                })
                );
    }
});
抱歉,我没有找到用于构建此脚本的原始脚本。

两者都工作得很好,但没有达到预期。第二次获取再次将其添加到缓存中,我认为这是因为 caches.match(event.request) 并不真正匹配。因此,我放置了一个控制台来查看两个请求对象、预取创建的合成对象和从获取克隆的请求对象。

  • 合成: enter image description here
  • 克隆: enter image description here

所以,我不确定是否可以将这些属性覆盖为与克隆相同的合成属性,我可以安全地做到这一点吗?我该如何解决这个问题?

PS:此代码不作为常见脚本运行。该片段只是为了组织。

最佳答案

我没有找到任何引用来确认我的解决方案,但是,它有效。

解决方案是创建 2 个不同的缓存,并将请求“标准化”,将其克隆到合成请求中,删除所有引用,仅保留基本的:

var CURRENT_CACHES = {
    'prefetch-cache': 'prefetch-cache-v' + CACHE_VERSION, //Prefetch cach
    'general-cache': 'general-cache-v' + CACHE_VERSION,
};

prefetch-cache 负责存储我想要在服务工作人员上预取的所有文件,而通用缓存则用于存储所有其他文件(当您有 SPA 并想要时,这是有意义的)积累一些请求,如翻译文件、js 组件、css 和其他东西)。

您可以使用要预取的所有文件的 URI 创建一个数组:

var urlsToPrefetch = [
    //JS
    "plugin/angular/angular.min.js", "plugin/requirejs/require.min.js","app/main.js","app/app.js","app/includes.js"
    //CSS
    ,"styles/css/print.css","styles/css/bootstrap.css","styles/css/fixes.css",
    //Html
    ,"app/layout/partials/menu.tpl.html",  "app/layout/public.tpl.html",
    //JSON
    ,"app/i18n/languages.json","app/i18n/pt-br.json", "app/i18n/en.json"
];

在安装事件中,您应该创建此数组中所有文件的新请求并将其存储到预取缓存中:

self.addEventListener('install', function (event) {
    logger && console.log('Handling install event:', event);

    //var now = Date.now();

    // All of these logging statements should be visible via the "Inspect" interface
    // for the relevant SW accessed via chrome://serviceworker-internals   
    if (urlsToPrefetch.length > 0) { 
        logger && console.log('Handling install event. Resources to prefetch:', urlsToPrefetch.length , "resources");
        event.waitUntil(
                caches.open(CURRENT_CACHES['prefetch-cache']).then(function (cache) {
            var cachePromises = urlsToPrefetch.map(function (urlToPrefetch) {
                urlToPrefetch +=  '?v=' + CACHE_VERSION;
                // This constructs a new URL object using the service worker's script location as the base
                // for relative URLs.
                //var url = new URL(urlToPrefetch + '?v=' + CACHE_VERSION, location.href);
                var url = new URL(urlToPrefetch, location.href);
                // Append a cache-bust=TIMESTAMP URL parameter to each URL's query string.
                // This is particularly important when precaching resources that are later used in the
                // fetch handler as responses directly, without consulting the network (i.e. cache-first).
                // If we were to get back a response from the HTTP browser cache for this precaching request
                // then that stale response would be used indefinitely, or at least until the next time
                // the service worker script changes triggering the install flow.
                //url.search += (url.search ? '&' : '?') + 'v=' + CACHE_VERSION;

                // It's very important to use {mode: 'no-cors'} if there is any chance that
                // the resources being fetched are served off of a server that doesn't support
                // CORS (http://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
                // In this example, www.chromium.org doesn't support CORS, and the fetch()
                // would fail if the default mode of 'cors' was used for the fetch() request.
                // The drawback of hardcoding {mode: 'no-cors'} is that the response from all
                // cross-origin hosts will always be opaque
                // (https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#cross-origin-resources)
                // and it is not possible to determine whether an opaque response represents a success or failure
                // (https://github.com/whatwg/fetch/issues/14).
                var request = new Request(url, {mode: 'no-cors'});
                return fetch(request).then(function (response) {
                    logger && console.log('Add to Cache (Prefetch)', url.href);


                    if (!response || response.status !== 200 || response.type !== 'basic') {
                        throw new Error('request for ' + urlToPrefetch +
                                ' failed with status ' + response.statusText);
                    }
                    //var responseToCache = response.clone();

                    // Use the original URL without the cache-busting parameter as the key for cache.put().
//                    return cache.put(urlToPrefetch, responseToCache);
                    return cache.put(urlToPrefetch, response);
                }).catch(function (error) {
                    logger && console.error('Not caching ' + urlToPrefetch + ' due to ' + error);
                });
            });

            return Promise.all(cachePromises).then(function () {
                logger && console.log('Pre-fetching complete.');
            });
        }).catch(function (error) {
           logger && console.error('Pre-fetching failed:', error);
        }));
    }

    // Perform install steps
//    if (urlsToPrefetch.length > 0) {
//        event.waitUntil(
//                caches.open(CURRENT_CACHES['perma-cache'])
//                .then(function (cache) {
//                    return cache.addAll(urlsToPrefetch);
//                })
//                );
//    }
    // `skipWaiting()` forces the waiting ServiceWorker to become the
    // active ServiceWorker, triggering the `onactivate` event.
    // Together with `Clients.claim()` this allows a worker to take effect
    // immediately in the client(s).
    return self.skipWaiting();
});

对于将来将存储在缓存中的所有其他文件,您必须将其声明到 fetch 事件监听器中并将此请求存储到通用缓存中:

self.addEventListener('fetch', function (event) {
    //console.log(event);
    if (event.request.method === "GET") {
        var qSFilter = "" + ((event.request.url).split('?'))[0];//Filtrar Quetry Stirng
        //console.log(event.request.url, qSFilter, qSFilter.split(CACHE_SCOPE), CACHE_SCOPE);
        var leUrl = (qSFilter.split(CACHE_SCOPE))[1];
        //Is possible to implement some logic to skip backend calls and other uncachable calls
        if (/^(app|style|plugin).*(js|css|html|jpe?g|png|gif|json|woff2?)$/.test(leUrl)
                || /^backend\/server\/file\/i18n\/((?!client).+)\//.test(leUrl)
                || /^backend\/server\/static\/images\/.*$/.test(leUrl)
                || /^backend\/server\/static\/style.*$/.test(leUrl)
                ) {
            var url = new URL(leUrl + '?v=' + CACHE_VERSION, location.href);
            var synthetic = new Request(url, {mode: 'no-cors'});
            //console.log(event.request,response.clone(),synthetic);
            event.respondWith(
//                    caches.match(event.request)
                    caches.match(synthetic)
                    .then(function (response) {
                        // Cache hit - return response
                        if (response) {
                            logger && console.log('From Cache', event.request.url);
                            return response;
                        }

                        // IMPORTANT: Clone the request. A request is a stream and
                        // can only be consumed once. Since we are consuming this
                        // once by cache and once by the browser for fetch, we need
                        // to clone the response
                        var fetchRequest = event.request.clone();

                        return fetch(fetchRequest).then(
                                function (response) {
                                    // Check if we received a valid response
                                    if (!response || response.status !== 200 || response.type !== 'basic') {
                                        return response;
                                    }

                                    // IMPORTANT: Clone the response. A response is a stream
                                    // and because we want the browser to consume the response
                                    // as well as the cache consuming the response, we need
                                    // to clone it so we have 2 stream.
                                    var responseToCache = response.clone();

                                    caches.open(CURRENT_CACHES['general-cache'])
                                            .then(function (cache) {
                                                try {
                                                    logger && console.log('Add to Cache', event.request.url, qSFilter,leUrl);
                                                    cache.put(event.request, responseToCache);
                                                } catch (e) {
                                                    console.error(e);
                                                }
                                            });

                                    return response;
                                }
                        );
                    })
                    );
        }
    }
});

可以在此处访问完整的工作脚本: https://gist.github.com/LeonanCarvalho/0527526a6b784b23facf56fa3cc12d22

关于javascript - 如何将 Service Worker 预取与跨导航取结合起来?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42284176/

相关文章:

javascript - Angular 4 - 使组件更具可重用性

javascript - 如何在 Javascript 中使用旁边元素的值更改单元格的元素?

javascript - ServiceWorker 执行文件中未定义 Notification.requestPermission。 Chrome

caching - 在 Apache Spark 中缓存 RDD 的目的是什么?

c# - 虚拟路径提供程序禁用缓存?

javascript - 根据在 JavaScript HTML 和 CSS 中选中的单选按钮显示特定的 div

javascript - 如何删除 Bootstrap 上显示上次使用日期的工具提示?

google-chrome - 角 6 : DevTools failed to parse SourceMap: https://example. com/ngsw_worker.es6.js.map

linux - 如何实用地通过代理路由 watir?

php - Symfony 的缓存和权限错误