9

在 Docker中部署我的应用程序的新版本后,

我看到console以下错误会破坏我的应用程序

Uncaught SyntaxError: Unexpected token '<'

缺少 webpack 块时的错误

在此屏幕截图中,缺少的源称为:10.bbfbcd9d.chunk.js,该文件的内容如下所示:

(this.webpackJsonp=this.webpackJsonp||[]).push([[10],{1062:function(e,t,n){"use strict";var r=n(182);n.d(t,"a",(function(){return r.a}))},1063:function(e,t,n){var ...{source:Z[De],resizeMode:"cover",style:[Y.fixed,{zIndex:-1}]})))}))}}]);
//# sourceMappingURL=10.859374a0.chunk.js.map

发生此错误是因为:

  1. 在每个版本中,我们都会构建一个包含最新版本的块Docker的新图像
  2. 一些客户端正在运行一个过时的版本,并且由于(1) ,服务器无法解决旧块

块是.js由 生成的文件,有关更多信息webpack,请参阅代码拆分

重新加载应用程序会将版本更新到最新版本,但对于所有使用过时版本的用户,它仍然会破坏应用程序。

我尝试过的一个可能的解决方法是刷新应用程序。如果服务器上缺少请求的块,如果对.js文件的请求以通配符路由结束,我将发送重新加载信号。

通配符服务于index.htmlWeb 应用程序,用于在用户刷新其页面的情况下将路由委托给客户端路由

// Handles any requests that don't match the ones above
app.get('*', (req, res) => {
  // prevent old version to download a missing old chunk and force application reload
  if (req.url.slice(-3) === '.js') {
    return res.send(`window.location.reload(true)`);
  }
  return res.sendFile(join(__dirname, '../web-build/index.html'));
});

这似乎是一个糟糕的修复,尤其是在 Android 版 Google Chrome 上,我看到我的应用程序在无限循环中刷新。(是的,这也是一个丑陋的修复!)

由于它对我的最终用户来说不是一个可靠的解决方案,因此我正在寻找另一种方法来在用户客户端过时时重新加载应用程序。

我的 Web 应用程序是使用 构建webpack的,就像它是一个create-react-app应用程序一样,分布式构建目录包含许多.js块文件。

这些是我在 webpack issue tracker 上获得的一些可能的修复,一些是由 webpack 创建者自己提供的:

  • 不要删除旧版本。 <= 我正在构建一个 Docker 映像,所以这有点挑战性
  • 捕获import()错误并重新加载。您也可以通过在__webpack_load_chunk__某处打补丁来全局执行此操作。 <= 我没有得到那个补丁或在哪里使用import(),我不是自己制作这些块,它只是一个生产功能
  • 让服务器发送window.location.reload(true)不存在的 js 文件,但这是一个非常奇怪的 hack。 <= 它使我的应用程序在 chrome android 上循环重新加载
  • 不要为.js请求发送 HTML,即使它们不存在,这只会导致奇怪的错误 <= 这不能解决我的问题

相关问题

如何实施可以防止此错误的解决方案?

4

3 回答 3

4

如果我正确理解了这个问题,那么有几种方法可以解决这个问题,我将从最简单的一种到更复杂的方法列出它们:

使用以前的版本来构建新版本

这是迄今为止最简单的方法,只需要为您的新版本更改基本映像。

考虑以下内容Dockerfile来构建应用程序的版本 2:

FROM version1

RUN ...

然后构建它:

docker build -t version2 .

然而,这种方法有一个问题——所有旧块都将在更新的图像中累积。它可能是可取的,也可能不是可取的,但要考虑到一些事情。

另一个问题是您无法轻松更新基础映像。

使用多阶段构建

多阶段构建允许您运行多个阶段并将每个阶段的结果包含到最终图像中。每个阶段可能会使用不同的 Docker 镜像和不同的工具,例如 GCC 来编译一些本地库,但你的最终镜像中并不需要 GCC。

为了使其与多阶段构建一起使用,您需要能够创建第一个图像。让我们考虑以下Dockerfile正是这样做的:

FROM alpine

RUN mkdir -p /app/latest && touch /app/latest/$(cat /proc/sys/kernel/random/uuid).chunk.js

它使用随机名称的新块创建一个新的新 Docker 映像,并将其放入名为的目录latest中 - 这对于建议的方法很重要!

为了创建后续版本,我们需要一个Dockerfile.next如下所示的:

FROM version2 AS previous
RUN rm -rf /app/previous && mv /app/latest/ /app/previous

FROM alpine

COPY --from=previous /app /app
RUN mkdir -p /app/latest && touch /app/latest/$(cat /proc/sys/kernel/random/uuid).chunk.js

在第一阶段,它通过删除previous版本来轮换版本,然后latest进入previous.

在第二阶段,它会复制第一阶段剩余的所有版本,创建一个新版本并将其放入latest.

以下是如何使用它:

docker build -t image:1 -f Dockerfile .

>> /app/latest/99cfc0e6-3773-40a0-82d4-8c8643cc243b.chunk.js

docker build -t image:2 --build-arg PREVIOUS_VERSION=1 -f Dockerfile.next .

>> /app/previous/99cfc0e6-3773-40a0-82d4-8c8643cc243b.chunk.js
>> /app/latest/2adf34c3-c50c-446b-9e85-29fb32011463.chunk.js

docker build -t image:3 --build-arg PREVIOUS_VERSION=2 -f Dockerfile.next 

>> /app/previous/2adf34c3-c50c-446b-9e85-29fb32011463.chunk.js
>> /app/latest/2e1f8aea-36bb-4b9a-ba48-db88c175cd6b.chunk.js

docker build -t image:4 --build-arg PREVIOUS_VERSION=3 -f Dockerfile.next 

>> /app/previous/2e1f8aea-36bb-4b9a-ba48-db88c175cd6b.chunk.js
>> /app/latest/851dbbf2-1126-4a44-a734-d5e20ce05d86.chunk.js

注意块是如何从 移动latest到 的previous

此解决方案要求您的服务器能够发现不同目录中的静态文件,但这可能会使本地开发复杂化,认为此逻辑可能是基于环境的条件。

或者,您可以在容器启动时将所有文件复制到一个目录中。这可以ENTRYPOINT在 Docker 本身的脚本中或在您的服务器代码中完成 - 这完全取决于您,取决于更方便的方法。

此外,此示例仅查看一个版本,但可以通过更复杂的旋转脚本将其扩展到多个版本。例如,要保留 3 个最新版本,您可以执行以下操作:

RUN rm -rf /app/version-0; \
    [ -d /app/version-1 ] && mv /app/version-1 /app/version-0; \
    [ -d /app/version-2 ] && mv /app/version-2 /app/version-1; \
    mv /app/latest /app/version-2; 

或者,它可以使用 Docker 参数化ARG,并保留要保留的版本数。

您可以在官方文档中阅读有关多阶段构建的更多信息。

于 2020-05-08T12:40:33.990 回答
4

一个简单的解决方案是禁用缓存index.html

Cache-Control: no-store
于 2020-05-13T02:27:05.193 回答
3

我们在生产中使用的一种方法是让两个不同的环境为您的.js资产提供服务。首先我们有一个最前沿的:这个只知道最近构建的版本。所有请求都指向此环境。

例如,当请求命中assets文件夹并且找不到.js文件时,我们会发出重定向到“救援”环境。这是一个简单的 AWS Cloudfront 发行版,由 AWS S3 存储桶支持。在构建最前沿的环境后,我们将所有新资产推送到该 S3 存储桶。

如果用户使用的是最新版本的应用程序,他们只会轻松地使用最新版本。一旦应用程序更新服务器端,或者用户没有使用最新版本,所有资产都通过“备份域”提供。由于前沿发出重定向*而不是提供 404,因此用户在这里不会遇到问题(除了必须将请求重做到不同的位置)。

此设置可确保即使是非常旧的客户端也可以继续运行。我们已经看到 Googlebot 仍然可以请求超过 1000 次部署的资产的情况!

大缺点:修剪 S3 存储桶需要更多的工作。由于存储相对便宜,所以现在我们只是将资产保留在那里。当我们在文件名中添加一个块标识符时,存储使用量不会增加那么多。

需要考虑的是重定向的实现。您会希望您的应用程序对其构造不可知。我们通过以下方式完成了它:

  1. 请求进入https://example.com/assets/asset-that-is-no-longer-available.js
  2. 服务器检测到请求是针对assets目录的,但文件不存在。
  3. 服务器将请求 url 中的主机名替换为assets.example.com并重定向到该位置。
  4. 浏览器资产请求被重定向到https://assets.example.com/assets/asset-that-is-no-longer-available.js可用的位置。
  5. 申请继续正常进行。

这可以使您的主 Docker 映像没有非常不经常访问的文件,并确保您的内部部署可以继续以更高的速度进行。它还消除了您的 CI 应该始终能够访问每个先前完成的部署代码的要求。

我们一直在使用 Docker 进行部署的设置中使用这种方法,并且没有看到任何客户端的问题。

于 2020-05-14T20:19:22.003 回答