有什么方法可以在 Service Worker 中缓存定义的 folder/path 的所有文件?

Is there any way to cache all files of defined folder/path in service worker?

在 service worker 中,我可以定义在 service worker 启动期间缓存的资源数组,如下所述:

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

我如何在 service worker 中定义 path/directory,这样 service worker 就不会写入所有文件名,而是从给定的 path/directory 中选择所有文件并将其全部添加到缓存中?

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

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

编辑:

最常用的 SW 工具库之一是 Workbox。他们同时提供 runtime-caching and precaching 资产。他们也有构建工具插件,例如。 Webpack 和 Gulp.

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

编辑2:

是的,您可以在某种程度上使用没有 NPM 的 Workbox。您需要 运行 NPM 脚本等来收集要缓存的文件的文件名但是您仍然可以通过在手写软件中导入 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文件放在自己的服务器上,然后从相对路径导入。

使用 Workbox 软件进行运行时缓存

服务-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>

是的,你可以。我也有这样的问题,我使用 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));
}

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

是的。缓存多个目录中的多个文件...
我在服务工作者中使用了一个辅助函数 我将其命名为“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 );
?>

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