Предотвратить RequireJS от кэширования необходимых сценариев

302

RequireJS, кажется, делает что-то внутреннее, что кэширует требуемые файлы JavaScript. Если я внесу изменения в один из обязательных файлов, мне придется переименовать файл, чтобы изменения вступили в силу.

Обычный прием добавления номера версии в качестве параметра строки запроса к концу имени файла не работает с requirejs <script src="jsfile.js?v2"></script>

То, что я ищу, - это способ предотвратить внутреннее кэширование необходимых скриптов RequireJS без необходимости переименовывать мои файлы скриптов каждый раз, когда они обновляются.

Кроссплатформенное решение:

Я сейчас использую urlArgs: "bust=" + (new Date()).getTime()для автоматического кеширования во время разработки и urlArgs: "bust=v2"для производства, где я увеличиваю жестко закодированную версию num после развертывания обновленного необходимого скрипта.

Примечание:

@Dustin Getz упоминал в недавнем ответе, что Chrome Developer Tools будет сбрасывать точки останова во время отладки, когда файлы Javascript постоянно обновляются таким образом. Одним из обходных путей является написание debugger;кода для запуска точки останова в большинстве отладчиков Javascript.

Специфичные для сервера решения:

Для конкретных решений, которые могут работать лучше для вашей серверной среды, таких как Node или Apache, см. Некоторые ответы ниже.

BumbleB2na
источник
Вы уверены, что это не сервер или клиент, выполняющий кеширование? (уже несколько месяцев использовал требуемый js и ничего подобного не заметил) IE обнаружил, что кэширует результаты действий MVC, chrome кэширует наши html-шаблоны, но все файлы js, похоже, обновляются после сброса кэша браузера. Я полагаю, если вы пытались использовать кэширование, но вы не можете сделать это как обычно, потому что запросы от требуемого js удаляли строку запроса, которая могла вызвать проблему?
PJUK
Я не уверен, что RequireJS удаляет добавленные номера версий, как это. Возможно, это был мой сервер. Интересно, что RequireJS имеет настройку кеш-буфера, так что вы можете быть правы, удалив добавленные в мою версию номера необходимых файлов.
BumbleB2na
я обновил свой ответ потенциальным решением для кэширования
Дастин Гетц
Теперь я могу добавить следующее к списку, который я изложил сегодня утром в своем блоге: codrspace.com/dexygen/… Иными словами , мне нужно не только добавить очистку кэша, но и require.js игнорирует жесткое обновление.
Кислород
Я запутался по поводу варианта использования для этого .... Это для горячей перезагрузки модулей AMD в интерфейс или что?
Александр Миллс

Ответы:

457

RequireJS может быть настроен на добавление значения к каждому из URL-адресов сценариев для очистки кэша.

Из документации RequireJS ( http://requirejs.org/docs/api.html#config ):

urlArgs : дополнительные аргументы строки запроса, добавленные к URL-адресам, которые RequireJS использует для извлечения ресурсов. Наиболее полезно для кэширования бюста, когда браузер или сервер настроены неправильно.

Пример добавления 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
9
... также в качестве оптимизатора производительности вы можете использовать Math.random () вместо (new Date ()). getTime (). Это больше красоты, а не создания объекта и немного быстрее jsperf.com/speedcomparison .
Влад Цепелев
2
Вы можете получить его чуть меньше:urlArgs: "bust=" + (+new Date)
mrzmyr
11
Я думаю, что уничтожение кэша каждый раз - ужасная идея. К сожалению, RequireJS не предлагает другой альтернативы. Мы используем urlArgs, но не используем для этого случайные или временные метки. Вместо этого мы используем наш текущий Git SHA, этот путь меняется только при развертывании нового кода.
Иван Торрес
5
Как этот вариант "v2" может работать в рабочей среде, если файл, содержащий строку "v2", кэшируется? Если я выпустлю новое приложение в производство, добавление «v3» ничего не даст, так как приложение продолжает работать с кэшированными файлами v2, включая старую конфигурацию с v2 urlArgs.
Бенни Боттема
54

Не используйте urlArgs для этого!

Требуется загрузка скрипта с учетом http-кэширования заголовков. (Скрипты загружаются с динамической вставкой <script>, что означает, что запрос выглядит так же, как любой старый загружаемый актив.)

Служите вашим ресурсам JavaScript с надлежащими заголовками HTTP, чтобы отключить кэширование во время разработки.

Использование require urlArgs означает, что любые установленные вами точки останова не будут сохраняться при обновлении; в конечном итоге вам нужно помещать debuggerоператоры повсюду в вашем коде. Плохой. Я использую urlArgsресурсы для кеширования во время производственного обновления с помощью git sha; тогда я могу установить, что мои активы будут кешироваться навсегда, и у меня никогда не будет устаревших активов.

В процессе разработки я выполняю макетирование всех запросов ajax со сложной конфигурацией mockjax , а затем могу обслуживать свое приложение в режиме только javascript с 10-строчным сервером Python http с отключенным кэшированием . Это увеличило для меня довольно большое «корпоративное» приложение с сотнями спокойных конечных точек веб-сервиса. У нас даже есть контрактный дизайнер, который может работать с нашей реальной рабочей базой кода, не предоставляя ему доступ к нашему внутреннему коду.

Дастин Гетц
источник
8
@ JamesP.Wright, потому что (по крайней мере, в Chrome), когда вы устанавливаете точку останова для чего-то, что происходит при загрузке страницы, затем нажимаете кнопку «Обновить», точка останова не срабатывает, поскольку URL-адрес изменился, и Chrome отбросил точку останова. Я хотел бы знать обходной путь только для клиента к этому.
Дрю Ноакс
1
Спасибо, Дастин. Если вы найдете способ обойти это, пожалуйста, напишите. В то же время вы можете использовать debugger;в своем коде, где вы хотите, чтобы точка останова сохранялась.
BumbleB2na
2
Для тех, кто использует http-сервер на узле (npm устанавливает http-сервер). Вы также можете отключить кэширование с помощью -c-1 (т. Е. Http-server -c-1).
Yourpalal
5
Вы можете отключить кэширование в Chrome, чтобы обойти проблему отладки во время разработки: stackoverflow.com/questions/5690269/…
Deepak Joy
5
+1 к !!! НЕ ИСПОЛЬЗУЙТЕ urlArgs В ПРОИЗВОДСТВЕ !!! , Представьте себе, что на вашем веб-сайте есть 1000 файлов JS (да, возможно!), И их загрузка контролируется требуемым JS. Теперь вы выпускаете v2 или ваш сайт, на котором изменены только несколько файлов JS! но добавив urlArgs = v2, вы заставите перезагрузить все 1000 файлов JS! Вы заплатите много трафика! только измененные файлы должны быть загружены повторно, все остальные должны иметь статус 304 (не изменен).
Walv
24

У решения urlArgs есть проблемы. К сожалению, вы не можете контролировать все прокси-серверы, которые могут находиться между вами и веб-браузером вашего пользователя. К сожалению, некоторые из этих прокси-серверов могут быть настроены на игнорирование параметров URL при кэшировании файлов. Если это произойдет, неправильная версия вашего JS-файла будет доставлена ​​вашему пользователю.

Я наконец сдался и внедрил свое собственное исправление непосредственно в require.js. Если вы хотите изменить свою версию библиотеки requirejs, это решение может подойти вам.

Вы можете увидеть патч здесь:

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

После добавления вы можете сделать что-то вроде этого в вашей конфигурации config:

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

Используйте вашу систему сборки или серверную среду, чтобы заменить buildNumberее идентификатором ревизии / версией программного обеспечения / любимым цветом.

Использование требуют вот так:

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

Будет вызывать запросить этот файл:

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

В нашей серверной среде мы используем правила перезаписи url для удаления buildNumber и предоставления правильного файла JS. Таким образом, нам не нужно беспокоиться о переименовании всех наших файлов JS.

Патч будет игнорировать любой сценарий, который определяет протокол, и он не повлияет на файлы не-JS.

Это хорошо работает для моей среды, но я понимаю, что некоторые пользователи предпочли бы префикс, а не суффикс, должно быть легко изменить мой коммит в соответствии с вашими потребностями.

Обновить:

В обсуждении pull-запроса автор requirejs предполагает, что это может работать как решение для префикса номера ревизии:

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

Я не пробовал это, но подразумевается, что это будет запрашивать следующий URL:

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

Что может очень хорошо работать для многих людей, которые могут использовать префикс.

Вот несколько возможных дублирующих вопросов:

RequireJS и кеширование прокси

require.js - Как я могу установить версию на необходимые модули как часть URL?

JBCP
источник
1
Мне бы очень хотелось, чтобы ваше обновление попало в официальную сборку requirejs. Основной автор requirejs тоже может быть заинтересован (см. Ответ @ Louis выше).
BumbleB2na
@ BumbleB2na - не стесняйтесь комментировать PullRequest ( github.com/jrburke/requirejs/pull/1017 ), jrburke не заинтересовался. Он предлагает решение, используя префикс имени файла, я обновлю свой ответ, чтобы включить это.
JBCP
1
Хорошее обновление. Я думаю , что мне действительно нравится это предложение автор, но это только потому , что я использую это соглашение об именах в последнее время: /scripts/myLib/v1.1/. Я попытался добавить постфикс (или префикс) к моим именам файлов, возможно, потому, что это делает jquery, но через некоторое время я [стал ленивым и] начал увеличивать номер версии в родительской папке. Я думаю, что это облегчило мне обслуживание на большом веб-сайте, но теперь вы заставили меня беспокоиться о кошмарах переписывания URL.
BumbleB2na
1
Современные интерфейсные системы просто переписывают файлы JS с суммой MD5 в имени файла, а затем переписывают файлы HTML, чтобы использовать новые имена файлов при сборке, но это становится сложным с устаревшими системами, где код переднего плана обслуживается серверной стороной.
JBCP
это работает, когда мне нужно несколько js внутри файла jspx?, например,<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>
masT
19

Вдохновленный кешем Expire на data-main require.js, мы обновили наш скрипт deploy следующей задачей 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@',
    ...
});
dvtoever
источник
11

В производстве

urlArgs может вызвать проблемы!

Основной автор requirejs предпочитает не использоватьurlArgs :

Для развернутых ресурсов я предпочитаю поместить версию или хеш для всей сборки в качестве каталога сборки, а затем просто изменить baseUrlконфигурацию, используемую для проекта, для использования этого каталога с версиями в качестве baseUrl. Тогда никакие другие файлы не изменяются, и это помогает избежать некоторых проблем с прокси, когда они могут не кэшировать URL со строкой запроса.

[Укладка моя.]

Я следую этому совету.

В развитие

Я предпочитаю использовать сервер, который интеллектуально кэширует файлы, которые могут часто меняться: сервер, который генерирует 304 Last-Modifiedи отвечает на него, If-Modified-Sinceкогда это необходимо. Даже сервер, основанный на экспресс- наборе Node для обслуживания статических файлов, делает это прямо из коробки. Он не требует никаких действий с моим браузером и не портит точки останова.

Луис
источник
Хорошие моменты, но ваш ответ зависит от вашей серверной среды. Возможно, хорошей альтернативой для всех, кто наткнулся на это, является недавняя рекомендация добавить номер версии в имя файла вместо параметра querystring. Вот больше информации на эту тему: stevesouders.com/blog/2008/08/23/…
BumbleB2na
Мы сталкиваемся с этой конкретной проблемой в производственной системе. Я рекомендую сохранять ваши имена файлов, а не использовать параметр.
JBCP
7

Я взял этот фрагмент из AskApache и поместил его в отдельный файл .conf моего локального веб-сервера Apache (в моем случае /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.

myrho
источник
6

Быстрое решение для разработки

Для разработки вы можете просто отключить кеш в Chrome Dev Tools ( Отключение кеша Chrome для разработки веб-сайтов ). Отключение кэша происходит только в том случае, если открыто диалоговое окно dev tools, поэтому вам не нужно беспокоиться о переключении этой опции каждый раз, когда вы выполняете обычный просмотр.

Примечание. Использование « urlArgs » - это правильное решение в рабочей среде, чтобы пользователи получали самый последний код. Но это затрудняет отладку, потому что chrome делает недействительными точки останова при каждом обновлении (потому что каждый раз обслуживается «новый» файл).

Дипак Джой
источник
3

Я не рекомендую использовать urlArgs для разрыва кеша с RequireJS. Поскольку это не решает проблему полностью. Обновление версии 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.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-модуль с номерами ревизий для moduleNames, который включен как «oRevision» в main.js, где вы перезаписываете функцию 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 Вы можете сделать это с любым языком на стороне сервера ... не обязательно должен быть c #. Это также автоматизирует процесс, обновляя кэш-память при создании новой версии проекта, гарантируя, что клиент всегда будет кэшировать последнюю версию сценариев. Я не думаю, что это заслуживает негативной уценки. Он просто расширяет ответ, предлагая автоматизированный подход к решению.
Зак Художник
Я в основном создал это, потому что при поддержке приложения, которое использовало require.js, я счел довольно раздражающим необходимость вручную закомментировать "(new Date ()). GetTime ()) и раскомментировать статический кеш-буфер каждый раз, когда я обновлял приложение . Легко забыть , вдруг клиент проверяет изменения и видеть кэшированные сценарий так , они думают , что ничего не изменилось .. Все потому , что вы просто забыли изменить CACHEBUSTER Это немного дополнительного кода стирает вероятность , что это произойдет...
Зак Художник
2
Я не пометил это, я не знаю, что на самом деле произошло. Я просто предлагаю код, который не только не js, но и язык шаблонов C #, не будет таким полезным для разработчиков JS, пытающихся получить ответ на свою проблему.
МАДХАДТ
-2

В моем случае я хотел загружать одну и ту же форму каждый раз, когда я нажимал, я не хотел, чтобы изменения, которые я внес в файл, оставались. Это может не относиться к этому посту, но это может быть потенциальное решение на стороне клиента без настройки config для require. Вместо того, чтобы отправлять содержимое напрямую, вы можете сделать копию необходимого файла и сохранить фактический файл без изменений.

LoadFile(filePath){
    const file = require(filePath);
    const result = angular.copy(file);
    return result;
}
Mahib
источник