【问题标题】:Is there any way to cache all files of defined folder/path in service worker?有没有办法缓存服务工作者中定义的文件夹/路径的所有文件?
【发布时间】:2018-03-31 12:38:42
【问题描述】:

在服务工作者中,我可以定义在服务工作者启动期间缓存的资源数组,如下所述:

self.addEventListener('install', event => {
    event.waitUntil(caches.open('static-${version}')
        .then(cache => cache.addAll([
            '/styles.css',
            '/script.js'
        ]))
    );
});

如何在 Service Worker 中定义路径/目录,以便 Service Worker 从给定路径/目录中选择所有文件并将所有文件添加到缓存中,而不是写入所有文件名?

【问题讨论】:

    标签: caching browser-cache service-worker


    【解决方案1】:

    这是不可能的。 SW(或浏览器,就此而言)对 Web 服务器上特定路径中的文件没有任何线索。您必须提供要缓存的文件的名称。更多关于同一问题here.

    您是否正在使用一些构建工具来自动生成文件列表?如果没有,你很可能应该:)

    编辑:

    软件工具最常用的库之一是 Workbox。他们提供runtime-cachingprecaching 的资产。他们也有构建工具插件,例如。 Webpack 和 Gulp。

    运行时缓存的工作原理是从缓存中提供资产(如果它存在的话)并从服务器更新它。基本上,每个新资产最初都会从网络请求,然后在后续请求中从缓存中返回。

    EDIT2:

    是的,你可以在某种程度上使用没有 NPM 的 Workbox。您需要运行 NPM 脚本等来收集要缓存的文件的文件名,但您仍然可以通过在手写 SW 文件中导入 Workbox.js 脚本来实现运行时缓存。

    随便说说

    importScript("https://unpkg.com/workbox-sw@2.1.0/build/importScripts/workbox-sw.prod.v2.1.0.js") 
    

    在您的 SW 顶部导入最新(截至目前)版本的 Workbox。你可以看到runtime-caching example here too中发生的事情。

    您也可以下载上面的 .js 文件并将其放在您自己的服务器上,然后从相对路径导入。

    【讨论】:

    • 谢谢@pate。现在正在考虑自动生成文件列表。你能建议我最好的方法吗?
    • 我可以在运行时缓存吗?
    • 我读过工作箱理论。它还需要 Node 和 npm。不用Node和npm也能用吗?任何示例都会很有帮助。
    • @MhodYasin 是的,可以使用。查看我的更新答案。
    • 嗨@pate,下面我发布了示例代码来实现Workbox sw运行时缓存。这是运行没有任何错误。它写得很完美还是需要改进?我需要您的反馈或帮助来改进这一点。
    【解决方案2】:

    使用 Workbox sw 进行运行时缓存。

    service-worker.js:

    importScripts('https://unpkg.com/workbox-sw@0.0.2/build/importScripts/workbox-sw.dev.v0.0.2.js');
    importScripts('https://unpkg.com/workbox-runtime-caching@1.3.0/build/importScripts/workbox-runtime-caching.prod.v1.3.0.js');
    importScripts('https://unpkg.com/workbox-routing@1.3.0/build/importScripts/workbox-routing.prod.v1.3.0.js');
    
    const assetRoute = new workbox.routing.RegExpRoute({
        regExp: new RegExp('^http://localhost:8081/jobs/static/*'),
        handler: new workbox.runtimeCaching.CacheFirst()
    });
    
    const router = new workbox.routing.Router();
    //router.addFetchListener();
    router.registerRoutes({routes: [assetRoute]});
    router.setDefaultHandler({
        handler: new workbox.runtimeCaching.CacheFirst()
    });
    

    在我的 html 文件中编写脚本以加载 Servcie worker。

    <script>
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', function() {
                navigator.serviceWorker.register('http://localhost:8081/jobs/static/service-worker.js?v=4').then(function(registration) {
                // Registration was successful
                console.log('ServiceWorker registration successful with scope: ', registration.scope);
            }, function(err) {
                // registration failed :(
                console.log('ServiceWorker registration failed: ', err);
                });
            });
        }
    </script>
    

    【讨论】:

    • 缓存所有以localhost:8081/jobs/static/*开头的请求
    • 如何获得这些库的更新版本(由上面的 importScript 调用),或者有没有办法通过 Yarn/NPM 获得它们?
    【解决方案3】:

    是的,你可以。我也有这样的问题,我使用 performance 找到了很酷的解决方案。这是我的sw.js

    const KEY = 'key';
    
    self.addEventListener('install', (event) => {
        event.waitUntil(self.skipWaiting());
    });
    
    self.addEventListener('message', (event) => {
        if (event.data.type === 'CACHE_URLS') {
            event.waitUntil(
                caches.open(KEY)
                    .then( (cache) => {
                        return cache.addAll(event.data.payload);
                    })
            );
        }
    });
    

    这是我的main.js

    if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('/sw.js', { scope: '/' })
            .then((registration) => {
                const data = {
                    type: 'CACHE_URLS',
                    payload: [
                        location.href,
                        ...performance.getEntriesByType('resource').map((r) => r.name)
                    ]
                };
                registration.installing.postMessage(data);
            })
            .catch((err) => console.log('SW registration FAIL:', err));
    }
    

    通过这个你还可以添加一些过滤器来缓存特定的路径。

    【讨论】:

      【解决方案4】:

      是的。缓存多个目录中的许多文件 ...
      我在服务人员中使用了一个辅助函数 我将其命名为“getFileArray(...)”。 它采用目录名称的一个字符串参数。 对于多个目录,我在 Promise.all:

      let cache_name = "cache-A";
      let filesToCache = [
          "https://myApp.com/",
          "index.php?launcher=true;homescreen=1",
          "manifest.json",
          "favicon.ico",
      ];
      
      self.addEventListener( "install", eo => {
          self.skipWaiting();
          eo.waitUntil( filesAreCached() );
      } );
      
      ///////| helper functions |/////////
      function filesAreCached(){
          
          Promise.all([
              /* name the directories whose files you wish to cache */
              getFileArray( "js" ),
              getFileArray( "css" ),
              getFileArray( "images" ),
              getFileArray( "screens" ),
              getFileArray( "modals" )        
          ])
          .then( promiseArray => {
              let promisedFiles = [];
              promiseArray.forEach( array => {
                  promisedFiles = promisedFiles.concat( array ) ;
              } );
              return promisedFiles;       
          }) 
          .then( promisedFiles => {
              filesToCache = filesToCache.concat( promisedFiles );
              console.log( "Cached files:", filesToCache  );
              return self.caches.open( cache_name );
          })
          .then( cache => cache.addAll( filesToCache ) );
      }
      
      /* 
       the following function calls a server script that returns an array of filenames,
       each one prepended with the directory name:  
      */
      async  function getFileArray( directory ){
          let form = new FormData();
          form.append( `directory`, directory );
          let fileArray = await fetch( `php/getFileArray.php`, { method: `POST`, body: form })
          .then( response => response.json() ) ;
          
          return fileArray;
      }
      
      

      PHP 代码 (getFileArray.php) 如下所示:

      <?php
      /*  
      Motivation: To help provide an accurate list of files
      for JavScript service workers to cache. Saves time,
      tedium, and possible typos in doing it manually.
              
      Use the POSTed directory path to return an array
      that lists all the files in that directory,
      less the "." and ".." entries.
      Prepend the directory name to the filenames so that
      we have the "full path" to each file.
      Return this array as a json string.
      */   
           $directory = $_POST["directory"] ;
            /* 
             You should probably sanitize $directory of all "../" prefixes
             in order to prevent a Directory Traversal Attack. 
             Using str_replace("/", "", $directory) has been suggested.
             It throws an error but prevents the attack.
           */
           $filelistArray = scandir( "../" . $directory );
           $cleanFileArray =  array();
           foreach( $filelistArray  as $file ){
               if ( $file !== "." and $file !== ".." ){
                      array_push( $cleanFileArray, $directory . "/" . $file );
               }       
           }
           $fileArrayJson = json_encode( $cleanFileArray );
           exit( $fileArrayJson );
      ?>
      

      当然,可以使用任何后端语言。
      这种技术可能有点简单, 但它对我有用:)

      【讨论】:

      • 非常感谢您提供这个“不雅”的解决方案。我已经将它改编为 Flask 项目,效果非常好!
      猜你喜欢
      • 2018-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多