防止RequireJS缓存所需的脚本


302

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

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

我正在寻找的是一种防止对RequireJS必需脚本进行内部缓存的方法,而无需在每次更新脚本文件时都对其重命名。

跨平台解决方案:

现在,我用于urlArgs: "bust=" + (new Date()).getTime()开发和urlArgs: "bust=v2"生产期间的自动缓存清除,在部署更新的必需脚本后,我增加了硬编码版本num。

注意:

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

服务器特定的解决方案:

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


您确定这不是服务器或客户端进行缓存吗?(几个月来一直在使用必需的js,并且没有发现任何类似的东西)IE被捕获到缓存MVC操作结果,chrome缓存了我们的html模板,但是当浏览器缓存重置后,js文件似乎都刷新了。我想如果您想利用缓存,但是您不能执行常规操作,因为来自必需js的请求正在删除可能导致问题的查询字符串?
PJUK

我不确定RequireJS是否会删除这样的附加版本号。可能是我的服务器。不过,有趣的是RequireJS如何设置缓存无效化设置,因此删除它在所需文件上附加的版本号可能是对的。
BumbleB2na 2011年

我用潜在的缓存解决方案更新了答案
Dustin Getz 2013年

现在,我可以将以下内容添加到今天早上在我的博客文章中提出的连载内容中codrspace.com/dexygen / ... 也就是说,不仅我必须添加缓存清除,而且require.js忽略了硬刷新。
Dexygen

我对此用例感到困惑。...这是用于将AMD模块热重装到前端还是什么?
亚历山大·米尔斯

Answers:


457

可以将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()
});

46
非常有帮助,谢谢。我urlArgs: "bust=" + (new Date()).getTime()在开发和urlArgs: "bust=v2"生产过程中使用了自动缓存清除功能,在这些产品中,我推出了更新的必需脚本后增加了硬编码版本num。
BumbleB2na 2011年

9
...此外,作为性能优化器,您可以使用Math.random()代替(new Date())。getTime()。它更加美观,而不是创建对象,并且速度更快jsperf.com/speedcomparison
弗拉德·谢佩列夫

2
您可以将它缩小一些:urlArgs: "bust=" + (+new Date)
mrzmyr 2014年

11
我认为每一次清除缓存都是一个糟糕的主意。不幸的是,RequireJS没有提供其他选择。我们确实使用urlArgs,但不为此使用随机或时间戳。取而代之的是,我们使用当前的Git SHA,那样只有在部署新代码时才会更改。
伊万·托雷斯

5
如果提供“ v2”字符串本身的文件已被缓存,该“ v2”变体如何在生产环境中工作?如果我将一个新的应用程序发布到生产环境中,则添加“ v3”将无济于事,因为该应用程序很乐意继续使用缓存的v2文件,包括使用v2的旧配置urlArgs
Benny Bottema

54

不要为此使用urlArgs!

要求脚本加载遵守http缓存标头。(脚本加载时带有动态插入的<script>,这意味着请求看起来就像正在加载的任何旧资产一样。)

使用适当的HTTP标头为您的JavaScript资产提供服务,以在开发过程中禁用缓存。

使用require的urlArgs意味着您设置的任何断点将不会在刷新之间保留;您最终需要debugger在所有代码中放置语句。坏。我在使用urlArgsgit sha进行生产升级时用于破坏缓存的资产;然后我可以将资产设置为永久缓存,并保证永远不会过时的资产。

在开发中,我使用复杂的mockjax配置模拟了所有ajax请求,然后可以使用 JavaScript模式为我的应用程序提供服务,其中包含10行python http服务器,并且所有缓存均已关闭。对于我来说,这已经扩展到具有数百个宁静的Web服务终结点的大型“企业”应用程序。我们甚至有一个签约设计师,他可以使用我们的实际生产代码库,而无需授予他访问我们的后端代码的权限。


8
@ JamesP.Wright,因为(至少在Chrome中)当您为页面加载时发生的事情设置断点,然后单击刷新时,由于URL更改且Chrome已删除断点,因此未命中该断点。我很想知道一个仅针对客户的解决方法。
Drew Noakes

1
谢谢,达斯汀。如果您找到解决方法,请发布。同时,您可以debugger;在要断点持续存在的任何地方使用代码。
BumbleB2na

2
对于在节点(npm上安装http-server)上使用http-server的任何人。您也可以使用-c-1(即http-server -c-1)禁用缓存。
Yourpalal

5
:您可以禁用在Chrome缓存来解决发展中的问题,调试stackoverflow.com/questions/5690269/...
迪帕克·乔伊

5
+1到!!!请勿在生产中使用urlArgs !!!。想象一下,您的网站有1000个JS文件(是的,可能!),并且其负载是由requiredJS控制的。现在,您发布v2或您的站点,仅更改了少量JS文件!但是通过添加urlArgs = v2,您将强制重新加载所有1000个JS文件!您将支付大量流量!仅应重新加载已修改的文件,所有其他文件都应返回状态304(未修改)。
2014年

24

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

我最终放弃了,并将自己的修复程序直接实现到require.js中。如果您愿意修改requirejs库的版本,则此解决方案可能适用。

您可以在此处查看补丁:

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

添加后,您可以在require config中执行以下操作:

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

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

这样使用require:

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和代理缓存

require.js-如何在所需模块上设置版本作为URL的一部分?


1
我真的很想看到您的更新进入正式的requirejs构建。requirejs的主要作者可能也有兴趣(请参见上方的@Louis答案)。
BumbleB2na 2014年

@ BumbleB2na-随意评论PullRequest(github.com/jrburke/requirejs/pull/1017),jrburke似乎没有兴趣。他确实建议使用文件名前缀的解决方案,我将更新答案以包括该文件名。
JBCP

1
不错的更新。我认为我确实喜欢作者的建议,但这只是因为我最近一直在使用该命名约定:/scripts/myLib/v1.1/。我尝试在文件名中添加后缀(或前缀),可能是因为这就是jquery所做的事情,但是过了一会儿我[懒惰了]开始增加父文件夹的版本号。我认为这使我在大型网站上的维护更加容易,但是,现在您让我担心URL重写的噩梦。
BumbleB2na 2014年

1
现代的前端系统只需重写JS文件并在文件名中添加MD5的总和,然后在构建时重写HTML文件以使用新的文件名,但这在服务器端提供前端代码的旧式系统中会遇到麻烦。
JBCP

当我在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>
行得通

19

require.js数据主体上Expire缓存的启发,我们使用以下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@',
    ...
});

11

在生产中

urlArgs 会引起问题!

requirejs的主要作者不喜欢使用urlArgs

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

[造型我的]

我遵循这个建议。

开发中

我更喜欢使用智能缓存可能经常更改的文件的服务器:在适当的时候发出Last-Modified并响应If-Modified-Since304 的服务器。即使是基于Node的快速服务器来提供静态文件的服务器也可以做到这一点。它不需要对我的浏览器做任何事情,也不会弄乱断点。


不错,但是,您的答案仅针对您的服务器环境。对于其他绊脚石的人来说,一个不错的选择是最近建议在文件名中添加版本号而不是querystring参数。下面是关于这个问题的更多信息: stevesouders.com/blog/2008/08/23/...
BumbleB2na

我们正在生产系统中处理此特定问题。我建议您修改文件名而不是使用参数。
JBCP

7

我从AskApache中提取了此代码段,并将其放入本地Apache Web服务器的单独.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的方法。


6

快速修复开发

为了发展,你可以只禁用Chrome开发工具中的缓存禁用浏览器缓存网站开发)。仅当打开开发工具对话框时,才会禁用缓存,因此您不必担心每次进行常规浏览时都会切换此选项。

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


3

我不建议对RequireJS 使用“ urlArgs ”进行缓存爆发。因为这不能完全解决问题。更新版本no将导致下载所有资源,即使您仅更改了一个资源也是如此。

为了解决这个问题,我建议使用Grunt模块(例如“ filerev”)来创建版本号。在此之上,我在Gruntfile中编写了一个自定义任务,以在不需要任何地方的情况下更新修订。

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


我结合使用grunt-filerev和grunt-cache-buster重写Javascript文件。
伊恩·贾米森

2

这就是我在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对象中

0

动态解决方案(无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('./'));
});

这将生成一个带有moduleNames修订版号的AMD模块,该模块作为main.js中的'oRevision'包含在内,您可以在其中覆盖如前所示的requirejs.load函数。


-1

这是@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中的参考:

在此处输入图片说明


2
可能是因为您对JavaScript问题的解决方案是一堆C#代码。这也是一堆额外的工作和代码,可以完成某些与最终答案完全相同的操作。
mAadhaTTah '02

@mAAdhaTTah您可以使用任何服务器端语言进行此操作...不必是c#。同样,这将自动执行该过程,并在构建项目的新版本时更新缓存中断,以确保客户始终缓存最新版本的脚本。我认为这不应该带来负面的降价。它只是通过提供解决方案的自动化方法来扩展答案。
Zach Painter

我基本上是创建此文件的,因为在维护使用require.js的应用程序时,我发现每次手动更新应用程序时都必须手动注释掉“(new Date())。getTime())并取消注释静态缓存无效对象,这很烦人。 。容易忘记突然所有客户验证的变化,看到缓存脚本,以便他们认为一切都没有改变。所有因为你根本忘了更改缓存无效这一额外的代码点点清除这种事情发生的几率。
Zach Painter

2
我没有记下来,我不知道实际发生了什么。我只是在建议,不仅是js的代码,而且不是C#模板语言的代码,对于试图获取其问题答案的JS开发人员来说不会有太大帮助。
mAadhaTTah

-2

就我而言,我希望每次单击时都加载相同的表单,但我不希望保留对文件所做的更改。它可能与本帖子不完全相关,但是如果不为require设置config,这可能是客户端上的潜在解决方案。您可以直接复制所需文件,而无需保留直接发送内容的方式,而是保持实际文件的完整性。

LoadFile(filePath){
    const file = require(filePath);
    const result = angular.copy(file);
    return result;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.