【问题标题】:Prevent RequireJS from Caching Required Scripts防止 RequireJS 缓存所需的脚本
【发布时间】:2012-01-09 01:09:17
【问题描述】:

RequireJS 似乎在内部做了一些缓存所需的 javascript 文件的事情。如果我对所需文件之一进行更改,我必须重命名该文件才能应用更改。

将版本号作为查询字符串参数附加到文件名末尾的常见技巧不适用于 requirejs <script src="jsfile.js?v2"></script>

我正在寻找一种方法来防止 RequireJS 所需脚本的这种内部缓存,而不必在每次更新我的脚本文件时重命名它们。

跨平台解决方案:

我现在在开发期间使用urlArgs: "bust=" + (new Date()).getTime() 进行自动缓存清除,在生产过程中使用urlArgs: "bust=v2" 在推出更新的所需脚本后增加硬编码版本号。

注意:

@Dustin Getz 在最近的回答中提到,当 Javascript 文件像这样不断刷新时,Chrome 开发者工具会在调试期间删除断点。一种解决方法是在代码中编写 debugger; 以在大多数 Javascript 调试器中触发断点。

特定于服务器的解决方案:

对于可能更适合您的服务器环境(例如 Node 或 Apache)的特定解决方案,请参阅下面的一些答案。

【问题讨论】:

  • 你确定这不是服务器或客户端在做缓存吗? (已经使用所需的 js 几个月了,没有注意到任何类似的东西) IE 被捕获缓存 MVC 操作结果,chrome 正在缓存我们的 html 模板,但是当浏览器缓存被重置时,js 文件似乎都刷新了。我想如果你想使用缓存但你不能像往常一样做,因为来自所需 js 的请求正在删除可能导致问题的查询字符串?
  • 我不确定 RequireJS 是否会删除类似的附加版本号。它可能是我的服务器。有趣的是 RequireJS 如何有一个缓存破坏器设置,所以你可能是对的,它删除了我在所需文件上附加的版本号。
  • 我用潜在的缓存解决方案更新了我的答案
  • 现在我可以将以下内容添加到我今天早上在博客文章中提出的长篇大论中:codrspace.com/dexygen/… 也就是说,我不仅必须添加缓存破坏,而且 require.js 会忽略硬刷新。
  • 我对这个用例感到困惑......这是为了将 AMD 模块热加载到前端还是什么?

标签: javascript jquery requirejs


【解决方案1】:

RequireJS 可以配置为将一个值附加到每个脚本 url 以进行缓存清除。

来自 RequireJS 文档 (http://requirejs.org/docs/api.html#config):

urlArgs:附加到 RequireJS 的 URL 的额外查询字符串参数 用于获取资源。当浏览器或 服务器配置不正确。

示例,将“v2”附加到所有脚本:

require.config({
    urlArgs: "bust=v2"
});

出于开发目的,您可以通过附加时间戳来强制 RequireJS 绕过缓存:

require.config({
    urlArgs: "bust=" + (new Date()).getTime()
});

【讨论】:

  • 非常有帮助,谢谢。我在开发过程中使用urlArgs: "bust=" + (new Date()).getTime() 进行自动缓存清除,在生产过程中使用urlArgs: "bust=v2" 在推出更新的所需脚本后增加硬编码的版本号。
  • ...作为性能优化器,您可以使用 Math.random() 代替 (new Date()).getTime()。它更美,而不是创建对象并且更快jsperf.com/speedcomparison
  • 你可以把它弄小一点:urlArgs: "bust=" + (+new Date)
  • 我认为每次都破坏缓存是一个糟糕的主意。不幸的是,RequireJS 没有提供另一种选择。我们确实使用 urlArgs 但不为此使用随机或时间戳。相反,我们使用当前的 Git SHA,这种方式只有在我们部署新代码时才会改变。
  • 如果提供“v2”字符串本身的文件被缓存,这个“v2”变体如何在生产中工作?如果我将一个新应用程序发布到生产环境中,添加“v3”不会做任何事情,因为该应用程序很乐意继续使用缓存的 v2 文件,包括 v2 urlArgs 的旧配置。
【解决方案2】:

不要为此使用 urlArgs!

要求脚本加载尊重 http 缓存标头。 (脚本使用动态插入的<script> 加载,这意味着请求看起来就像加载任何旧资产一样。)

使用适当的 HTTP 标头为您的 javascript 资产提供服务,以在开发期间禁用缓存。

使用 require 的 urlArgs 意味着您设置的任何断点都不会在刷新时保留;您最终需要在代码中的任何位置放置 debugger 语句。坏的。在使用 git sha 进行生产升级期间,我使用 urlArgs 来清除缓存资产;然后我可以将我的资产设置为永远缓存,并保证永远不会有陈旧的资产。

在开发中,我使用复杂的 mockjax 配置模拟所有 ajax 请求,然后我可以使用 10 line python http server with all caching turned off 在纯 JavaScript 模式下为我的应用程序提供服务。这对我来说已经扩展到一个相当大的“企业”应用程序,它有数百个安静的 web 服务端点。我们甚至有一个签约设计师,他可以使用我们的真实生产代码库,而无需让他访问我们的后端代码。

【讨论】:

  • @JamesP.Wright,因为(至少在 Chrome 中)当您为页面加载时发生的事情设置断点时,然后单击刷新,因为 URL 已更改并且 Chrome已删除断点。我很想知道一个仅限客户端的解决方法。
  • 谢谢,达斯汀。如果您找到解决此问题的方法,请发布。同时,您可以在代码中使用debugger; 来保留断点。
  • 适用于在节点上使用 http-server (npm install http-server) 的任何人。您还可以使用 -c-1 禁用缓存(即 http-server -c-1)。
  • 您可以在 Chrome 中禁用缓存以解决开发过程中的调试问题:stackoverflow.com/questions/5690269/…
  • +1 到 !!!不要在生产中使用 urlArgs !!! .想象一下,您的网站有 1000 个 JS 文件(是的,可能!)并且它们的负载由 requiredJS 控制。现在您发布了 v2 或您的站点,其中仅更改了几个 JS 文件!但是通过添加 urlArgs=v2,你强制重新加载所有 1000 个 JS 文件!你会付出很多流量!只应重新加载修改过的文件,所有其他文件应以状态 304(未修改)响应。
【解决方案3】:

urlArgs 解决方案有问题。不幸的是,您无法控制您和用户的网络浏览器之间可能存在的所有代理服务器。不幸的是,其中一些代理服务器可能被配置为在缓存文件时忽略 URL 参数。如果发生这种情况,您的 JS 文件的错误版本将交付给您的用户。

我终于放弃了,直接implemented my own fix进入require.js。如果您愿意修改您的 requirejs 库版本,此解决方案可能适合您。

你可以在这里看到补丁:

https://github.com/jbcpollak/requirejs/commit/589ee0cdfe6f719cd761eee631ce68eee09a5a67

添加后,您可以在需要配置中执行以下操作:

var require = {
    baseUrl: "/scripts/",
    cacheSuffix: ".buildNumber"
}

使用您的构建系统或服务器环境将 buildNumber 替换为修订 ID/软件版本/喜欢的颜色。

像这样使用要求:

require(["myModule"], function() {
    // no-op;
});

将导致需要请求此文件:

http://yourserver.com/scripts/myModule.buildNumber.js

在我们的服务器环境中,我们使用 url 重写规则来去除 buildNumber,并提供正确的 JS 文件。这样我们实际上不必担心重命名所有 JS 文件。

补丁将忽略任何指定协议的脚本,并且不会影响任何非 JS 文件。

这适用于我的环境,但我意识到有些用户更喜欢前缀而不是后缀,应该很容易修改我的提交以满足您的需求。

更新:

在拉取请求讨论中,requirejs 作者建议这可以作为修订号前缀的解决方案:

var require = {
    baseUrl: "/scripts/buildNumber."
};

我没有尝试过,但暗示这将请求以下 URL:

http://yourserver.com/scripts/buildNumber.myModule.js

这对于许多可以使用前缀的人来说可能非常有效。

这里有一些可能重复的问题:

RequireJS and proxy caching

require.js - How can I set a version on required modules as part of the URL?

【讨论】:

  • 我真的很想看到你的更新进入官方的 requirejs 版本。 requirejs 的主要作者可能也有兴趣(见上面@Louis 的回答)。
  • @BumbleB2na - 随时对 PullRequest (github.com/jrburke/requirejs/pull/1017) 发表评论,jrburke 似乎不感兴趣。他确实建议了一个使用文件名前缀的解决方案,我会更新我的答案以包含它。
  • 不错的更新。我想我确实喜欢作者的这个建议,但这只是因为我最近一直在使用这个命名约定:/scripts/myLib/v1.1/。我尝试在我的文件名中添加后缀(或前缀),可能是因为这就是 jquery 所做的,但过了一会儿我 [变得懒惰并] 开始在父文件夹上增加版本号。我认为它使我在大型网站上的维护变得更容易,但是,现在你让我担心 URL 重写的噩梦。
  • 现代前端系统只是重写 JS 文件并在文件名中添加一个 MD5 和,然后在构建时重写 HTML 文件以使用新文件名,但是对于前端的遗留系统来说,这会变得很棘手代码由服务器端提供。
  • 当我在 jspx 文件中需要一些 js 时,这是否有效?例如 <script data-main="${pageContext.request.contextPath}/resources/scripts/main" src="${pageContext.request.contextPath}/resources/scripts/require.js"> <jsp:text/> </script> <script> require([ 'dev/module' ]); </script>
【解决方案4】:

Expire cache on require.js data-main 的启发,我们使用以下 ant 任务更新了部署脚本:

<target name="deployWebsite">
    <untar src="${temp.dir}/website.tar.gz" dest="${website.dir}" compression="gzip" />       
    <!-- fetch latest buildNumber from build agent -->
    <replace file="${website.dir}/js/main.js" token="@Revision@" value="${buildNumber}" />
</target>

main.js 开头的样子:

require.config({
    baseUrl: '/js',
    urlArgs: 'bust=@Revision@',
    ...
});

【讨论】:

    【解决方案5】:

    生产中

    urlArgs 可能会导致问题!

    requirejs的主要作者prefers not to use urlArgs

    对于已部署的资产,我更喜欢将版本或哈希作为整体 构建为构建目录,然后只需修改 baseUrl 使用的配置 让项目将该版本化目录用作baseUrl。然后 没有其他文件更改,它有助于避免 一些代理问题 可能不会缓存带有查询字符串的 URL。

    [我的造型。]

    我听从了这个建议。

    开发中

    我更喜欢使用智能缓存可能经常更改的文件的服务器:发出 Last-Modified 并在适当时以 304 响应 If-Modified-Since 的服务器。即使是基于 Node 的 express 设置为提供静态文件的服务器也可以立即执行此操作。它不需要对我的浏览器做任何事情,也不会弄乱断点。

    【讨论】:

    • 好点,但是,您的答案特定于您的服务器环境。对于遇到此问题的其他人来说,也许一个不错的选择是最近建议在文件名中添加版本号而不是查询字符串参数。以下是有关该主题的更多信息:stevesouders.com/blog/2008/08/23/…
    • 我们在生产系统中遇到了这个特定问题。我建议修改你的文件名而不是使用参数。
    【解决方案6】:

    我从AskApache 获取了这个 sn-p 并将其放入本地 Apache 网络服务器的单独 .conf 文件中(在我的例子中是 /etc/apache2/others/preventcaching.conf):

    <FilesMatch "\.(html|htm|js|css)$">
    FileETag None
    <ifModule mod_headers.c>
    Header unset ETag
    Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
    Header set Pragma "no-cache"
    Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
    </ifModule>
    </FilesMatch>
    

    对于开发来说,这可以正常工作,无需更改代码。至于制作,我可能会使用@dvtoever的方法。

    【讨论】:

      【解决方案7】:

      快速修复开发

      对于开发,您可以在 Chrome 开发工具中禁用缓存 (Disabling Chrome cache for website development)。仅当开发工具对话框打开时才会禁用缓存,因此您不必担心每次进行常规浏览时都切换此选项。

      注意:使用 'urlArgs' 是生产中的正确解决方案,以便用户获得最新代码。但它使调试变得困难,因为 chrome 每次刷新都会使断点失效(因为每次都提供一个“新”文件)。

      【讨论】:

        【解决方案8】:

        我不建议使用 'urlArgs' 来使用 RequireJS 进行缓存突增。因为这并不能完全解决问题。更新版本号会导致下载所有资源,即使您只更改了单个资源。

        为了处理这个问题,我建议使用像“filerev”这样的 Grunt 模块来创建修订号。最重要的是,我在 Gruntfile 中编写了一个自定义任务来更新修订版,无需任何地方。

        如果需要,我可以共享此任务的代码 sn-p。

        【讨论】:

        • 我结合使用 grunt-filerev 和 grunt-cache-buster 来重写 Javascript 文件。
        【解决方案9】:

        这就是我在 Django / Flask 中的做法(可以轻松适应其他语言 / VCS 系统):

        在你的config.py中(我在python3中使用这个,所以你可能需要在python2中调整编码)

        import subprocess
        GIT_HASH = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip().decode('utf-8')
        

        然后在你的模板中:

        {% if config.DEBUG %}
             require.config({urlArgs: "bust=" + (new Date().getTime())});
        {% else %}
            require.config({urlArgs: "bust=" + {{ config.GIT_HASH|tojson }}});
        {% endif %}
        
        • 不需要手动构建过程
        • 仅在应用启动时运行一次git rev-parse HEAD,并将其存储在config 对象中

        【讨论】:

          【解决方案10】:

          动态解决方案(无 urlArgs)

          这个问题有一个简单的解决方案,这样您就可以为每个模块加载一个唯一的修订号。

          你可以保存原来的 requirejs.load 函数,用你自己的函数覆盖它,然后再次解析你修改后的 url 到原来的 requirejs.load:

          var load = requirejs.load;
          requirejs.load = function (context, moduleId, url) {
              url += "?v=" + oRevision[moduleId];
              load(context, moduleId, url);
          };
          

          在我们的构建过程中,我使用“gulp-rev”构建了一个清单文件,其中包含正在使用的所有模块的所有修订版。我的 gulp 任务的简化版本:

          gulp.task('gulp-revision', function() {
              var sManifestFileName = 'revision.js';
          
              return gulp.src(aGulpPaths)
                  .pipe(rev())
                  .pipe(rev.manifest(sManifestFileName, {
                  transformer: {
                      stringify: function(a) {
                          var oAssetHashes = {};
          
                          for(var k in a) {
                              var key = (k.substr(0, k.length - 3));
          
                              var sHash = a[k].substr(a[k].indexOf(".") - 10, 10);
                              oAssetHashes[key] = sHash;
                          }
          
                          return "define([], function() { return " + JSON.stringify(oAssetHashes) + "; });"
                      }
                  }
              }))
              .pipe(gulp.dest('./'));
          });
          

          这将生成一个带有模块名称修订号的 AMD 模块,它包含在 main.js 中的“oRevision”中,您可以在其中覆盖 requirejs.load 函数,如前所示。

          【讨论】:

            【解决方案11】:

            这是@phil mccull 接受的答案的补充。

            我使用他的方法,但我也通过创建一个 T4 模板来自动执行该过程,以便在构建前运行。

            预构建命令:

            set textTemplatingPath="%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe"
            if %textTemplatingPath%=="\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe" set textTemplatingPath="%CommonProgramFiles%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\texttransform.exe"
            %textTemplatingPath% "$(ProjectDir)CacheBuster.tt"
            

            T4 模板:

            生成的文件:

            在加载 require.config.js 之前存储在变量中:

            require.config.js 中的参考:

            【讨论】:

            • 可能是因为您对 JavaScript 问题的解决方案是一堆 C# 代码。这也是一堆额外的工作和代码来做一些最终以与接受的答案完全相同的方式完成的事情。
            • @mAAdhaTTah 您可以使用任何服务器端语言来执行此操作...不必是 c#。这也使过程自动化,在您构建项目的新版本时更新缓存块,确保客户始终缓存最新版本的脚本。我认为这不值得负面降价。它只是通过提供解决方案的自动化方法来扩展答案。
            • 我基本上创建了这个,因为在维护使用 require.js 的应用程序时,我发现必须手动注释掉 "(new Date()).getTime()) 并取消注释每次我更新应用程序时都使用静态 cachebuster。很容易忘记。突然之间,客户正在验证更改并看到缓存的脚本,所以他们认为没有任何变化。所有这些都是因为您只是忘记更改 cachebuster。这一点额外的代码消除了发生这种情况的机会。
            • 我没有记下来,我不知道实际发生了什么。我只是建议不仅不是 js 的代码,而是一种 C# 模板语言,对于试图解决问题的 JS 开发人员没有太大帮助。
            【解决方案12】:

            在我的情况下,我希望每次单击时都加载相同的表单,我不希望我对文件所做的更改保持不变。它可能与这篇文章不完全相关,但这可能是客户端的潜在解决方案,无需为 require 设置配置。您可以制作所需文件的副本并保持实际文件完整,而不是直接发送内容。

            LoadFile(filePath){
                const file = require(filePath);
                const result = angular.copy(file);
                return result;
            }
            

            【讨论】:

              猜你喜欢
              • 2013-03-25
              • 1970-01-01
              • 2011-07-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-19
              • 1970-01-01
              • 2011-02-20
              相关资源
              最近更新 更多