Angular服务工作者和index.html缓存

14
尽管有类似的帖子,但我找不到清晰的答案来确定是否应该使用Cache-Control头缓存index.html
纠正我如果我错了,但现在我为index.html返回Cache-Control: no-store以避免哈希不匹配错误,这会导致服务工作者进入降级模式。
我认为,如果将具有Cache-Control:max-age = 3600index.html缓存在CDN服务器上,并且在缓存过期之前更新应用程序,则ngsw.json将返回与包含在index.html中的脚本文件的不同文件哈希值,而且会发生糟糕的事情。对吧?
此外,只是为了明确,我注意到一些人在ngsw-config.json中添加index.html,但这也没有意义,因为index.html在服务工作者之前加载。

当你说“服务工作者进入降级模式”时,你是指你的应用程序进入了其中一个降级状态吗?这些状态在此链接中有提到:https://angular.io/guide/service-worker-devops#driver-state。如果是这样,那么是哪一个状态呢? - Krishnan
2
你能否尝试添加Cache-Control: no-storengsw.json中?我想可能是你的ngsw.json文件正在使用较旧的版本进行服务,如果这样做可以解决问题。如果有效,请尝试从index.html中删除Cache-Control: no-store。我认为这也应该有效,因为Angular的Service Worker使用缓存破坏器获取新资源。如果这有效,请告诉我,我会将其发布为答案。 - Krishnan
@Krishnan,你是如何在ngsw.json文件中添加一个标题来表明Cache-Control: no-store的呢? - Willie
1
这取决于您的后端,它提供静态文件。有太多选项可以描述。它可能是您的Web服务器配置,也可能是CDN。 - Zygimantas
我同意 ^ ^。标题必须在您的服务器中进行配置。 - Krishnan
显示剩余5条评论
3个回答

4
默认情况下,index.html会被包含在内。如果您不在清单中包含它,则它将不会成为验证的文件的一部分。如果它不在清单(随后是ngsw.json)中,则对index.html的更改不会触发服务工作者中的事件。当然,当您下次加载/刷新站点时,它将获取新的index.html。
如果您从CDN提供index.html,则可能它是上次部署时构建的分发的一部分。它应该被正确计算。您上面突出显示的区域很重要,如果您有文件与ngsw.json中的哈希值不匹配,则需要理解这一点。如果由于某种原因,您在没有更新整个分销版的情况下修改了index.html,则服务工作者将假定文件已损坏。它将再试一次;由于文件与ngsw.json中的哈希值不匹配,SW将假定第二次尝试也已损坏并关闭。
在我的情况下,是因为应用程序在构建过程中保留了令牌,这些令牌在发布管道中使用Azure资源密钥替换。在构建应用程序时,哈希值是正确的。在发布之后,在运行令牌替换之后,我的main*.js文件与ngsw.json中的哈希值不再一致。我选择的修复方法是添加一个PowerShell步骤并重新计算哈希值。需要注意的是,虽然实际文件名具有唯一的嵌入式哈希代码,但您不必为服务工作者纠正它们。文件名/哈希键/值对必须指向有效文件,并且该文件的SHA1哈希值必须与ngsw.json中的值匹配。我编写的用于后编译验证/校正哈希的脚本如下。如果您有一些独立于整个分销版更新index.html的过程,请使用此脚本更新ngsw.json并将其与您的index.html推送一起包含。
备注:
- 脚本接受3个参数。如果未传递这些参数,则假定:
- 脚本在angular项目的根目录中运行 - 工作目录为"./dist"(要检查的脚本所在的位置) - 输入路径为"/ngsw.json" - 输出路径为"/ngsw_out.json"
- 确保如果您要修改文件,则指定相同的输入路径和输出路径。 - 如果您将此放在AzDO中,则需要选中“使用Powershell Core”复选框。
PowerShell脚本开始:
param([string]$working_path = "./dist"
  , [string]$input_file_path = "$working_path/ngsw.json"
  , [string]$output_file_path = "$working_path/ngsw_out.json")

"Checking for existence of hash script..."

$fileExists = Test-Path -Path $input_file_path

if ($fileExists) {
  "Service Worker present.  Beginning hash reconciliation."
  ""
  $files_to_calc = @()
  $ngsw_json = (Get-Content $input_file_path -Raw) | ConvertFrom-Json

  "-----------------------------------------"
  "Getting list of javascript files to check"
  "-----------------------------------------"
  $found_count = 0
  for ($idx = 0; $idx -lt $ngsw_json.hashtable.psobject.properties.name.count; $idx++) {
    $current_file = $ngsw_json.hashtable.psobject.properties.name[$idx]
    if ($current_file.Contains(".js")) {
      $files_to_calc += $current_file
      "   File {$idx} $($files_to_calc[-1]) found."
      $found_count++
    }
  }

  "---------------------------------------"
  "$($files_to_calc.count) files to check."
  "---------------------------------------"
  $replaced_count = 0
  $files_to_calc | ForEach-Object {
    $new_hash_value = (Get-FileHash -Algorithm SHA1 "$($working_path)$_").Hash.ToLower()
    $current_hash_value = $ngsw_json.hashTable.$_
    $current_index = [array]::IndexOf($ngsw_json.hashTable.psobject.properties.name, $_)
    $replaced = $false

    if ($ngsw_json.hashTable.$_ -ne $new_hash_value) {
      $ngsw_json.hashTable.$_ = "$new_hash_value"
      $replaced = $true
      $replaced_count++
    }

    "$($replaced ? '** ' : '   '){$current_index}:$_ --- Current Value: " +
    "$($current_hash_value.substring(0, 8))... New Value: " +
    "$($new_hash_value.substring(0, 8))..."

  }
  ""
  "--> Replaced $replaced_count hash values"

  $ngsw_json | ConvertTo-Json -depth 32 | set-content "$output_file_path"
}
else {
  "Service Worker missing.  Skipping."
}

感谢分享! - Zygimantas

1

虽然我不是一个专家,但我相当确定以下链接可以帮助你解决疑问。

https://angular.io/guide/service-worker-getting-started#whats-being-cached

什么被缓存了? 请注意,浏览器渲染此应用程序所需的所有文件都已被缓存。ngsw-config.json样板配置已设置为缓存CLI使用的特定资源:
  • index.html。

  • favicon.ico。

  • 构建工件(JS和CSS捆绑包)。

  • 任何位于资产下的内容。

  • 直接位于配置的outputPath(默认为./dist/)或resourcesOutputPath下的图像和字体。有关这些选项的更多信息,请参见ng build。

以下链接提供了关于服务工作者和应用资源缓存的信息。我希望您阅读应用版本, 更新检查资源完整性的相关内容。

https://angular.io/guide/service-worker-devops#service-worker-and-caching-of-app-resources

我也将这三个部分的内容粘贴在这里,以避免这个答案成为“仅链接答案”。

应用程序版本

在 Angular 服务工作者的上下文中,“版本”是表示特定 Angular 应用程序构建的资源集合。每当部署应用程序的新构建时,服务工作者将该构建视为应用程序的新版本。即使只更新了单个文件,这也是正确的。在任何给定时间,服务工作者可能会在其缓存中拥有多个应用程序版本,并且可以同时提供它们。有关更多信息,请参见下面的应用程序选项卡部分。

为了保持应用程序的完整性,Angular服务工作器会将所有文件分组到一个版本中。通常,分组到一个版本的文件包括HTML、JS和CSS文件。将这些文件分组是至关重要的,因为HTML、JS和CSS文件经常相互引用并依赖于特定的内容。例如,一个index.html文件可能有一个标签,引用bundle.js,并尝试从该脚本内部调用startApp()函数。每次提供此版本的index.html时,必须与之一起提供相应的bundle.js。例如,假设startApp()函数在两个文件中都被重命名为runApp()。在这种情况下,不能用调用startApp()的旧index.html和定义了runApp()的新bundle一起提供。
当惰性加载模块时,文件完整性尤其重要。一个JS捆绑包可能引用许多惰性块,而惰性块的文件名对于应用程序的特定构建是唯一的。如果运行版本X的应用程序尝试加载惰性块,但服务器已经更新到版本X + 1,那么惰性加载操作将失败。

应用程序的版本标识符是由所有资源内容确定的,如果它们中的任何一个发生更改,它也会发生变化。在实践中,版本是由ngsw.json文件的内容确定的,它包括所有已知内容的哈希值。如果缓存的文件中有任何更改,则该文件的哈希值将在ngsw.json中更改,导致Angular服务工作器将活动文件集视为新版本。

通过Angular服务工作器的版本控制行为,应用程序服务器可以确保Angular应用始终具有一致的文件集。

更新检查

每次用户打开或刷新应用程序时,Angular服务工作者都会通过查找ngsw.json清单的更新来检查应用程序的更新。如果找到更新,则会自动下载和缓存,并在下次加载应用程序时提供服务。

资源完整性

长时间缓存的潜在副作用之一是无意中缓存无效资源。在普通HTTP缓存中,硬刷新或缓存过期限制了缓存无效文件的负面影响。服务工作者忽略这种约束,并有效地长时间缓存整个应用程序。因此,确保服务工作者获得正确的内容非常重要。

为确保资源完整性,Angular服务工作者验证其具有哈希值的所有资源的哈希值。通常对于使用Angular CLI创建的应用程序,这是用户src/ngsw-config.json配置覆盖的dist目录中的所有内容。
如果特定文件未通过验证,则Angular服务工作者尝试使用“缓存破坏”URL参数重新获取内容,以消除浏览器或中间缓存的影响。如果该内容也未通过验证,则服务工作者认为整个应用程序版本无效,并停止提供应用程序。如有必要,服务工作者进入安全模式,其中请求回退到网络,选择不使用其缓存,如果提供无效、损坏或过时的内容的风险很高。
哈希不匹配可能出现各种原因:
  • 源服务器和最终用户之间的缓存层可能提供过时的内容。
  • 非原子部署可能导致Angular服务工作者看到部分更新的内容。
  • 构建过程中的错误可能导致更新的资源而没有更新ngsw.json。反之亦然,导致更新的ngsw.json而没有更新的资源。

2
谢谢您的回答。最后一部分“资源完整性”是最接近我的问题,但仍未得到解答:“如果特定文件未通过验证,则Angular服务工作者尝试使用“缓存破坏”URL参数重新获取内容,以消除浏览器或中间缓存的影响。如果该内容也未通过验证,则服务工作者认为整个应用程序版本无效,并停止提供应用程序。”因此,如果CDN服务器返回过时的index.html和ngsw.json?cache-busted是最新的,则服务工作者会遇到麻烦。 - Zygimantas

0

我认为你有必要了解Angular应用程序的工作流程和Angular Service Worker运行时缓存机制。因此,我将在这里写下关于它们的内容。这将有助于你的理解。

Angular的工作步骤如下:

  • Angular从main.ts开始。
  • Angular应用程序被引导,并将app.module.ts作为参数传递。
  • Angular分析应用程序组件,读取传递的设置,并有一个选择器app-root
  • 现在,Angular能够在index.html中处理app-root并知道选择器的规则。
  • 选择器应该插入应用程序组件并具有一些HTML代码-附加到它的模板-HTML组件。

Angular ServiceWorker

Angular CLI 还在 Angular 应用程序根模块中包含了Service Worker 模块。CLI 还添加了一个名为 ngsw-config.json 的新配置文件,该文件配置了Angular Service Worker的运行时行为,并且生成的文件带有一些智能默认值。这里有很多内容,让我们逐步分解它。该文件包含Angular Service Worker的默认缓存行为,它针对应用程序静态资产文件: index.htmlCSSJavascript 捆绑包。 Angular Service Worker可以将各种内容缓存在浏览器缓存存储中。这是一种基于 JavaScript 的键/值缓存机制,与标准浏览器Cache-Control 机制无关,两种机制可以分别使用。
app 部分下的文件是应用程序:单个页面由其index.html加上其CSSJs捆绑包组成。每个页面都需要这些文件,而且不能进行延迟加载
对于这些文件,我们希望尽早并永久地将它们缓存起来,这就是应用程序缓存配置所做的事情。应用程序文件将由Service Worker在后台主动下载和安装,这就是安装模式预取的含义。 Service Worker不会等待应用程序请求这些文件,而是会提前下载它们并缓存起来,以便下次请求时可以立即提供服务。对于组成应用程序本身(index.htmlCSSJavascript 捆绑包)的文件,采用这种策略是一个好方法,因为我们已经知道我们一直需要它们。
index.html 依赖于 index.jsindex.js 依赖于 chunk.jschunk.js 依赖于 jquery.js。 chunk 从浏览器缓存中加载。

谢谢,我知道Angular的基本工作原理以及服务工作者的目的。问题是:当ngsw获取到index.html的v2版本引用,但CDN仍然提供v1版本且缓存破坏无效时,它会如何行动?我发现它会进入降级状态,并且在清除浏览器缓存之前永远不会恢复。 - Zygimantas
@zygimantas,ngsw和CDN分别工作。从index.html加载的CDN资源是从浏览器缓存中加载的,但您设置了Cache-Control:no-store,因此它没有更新,我想。 - Amir Christian

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