Zapobiegaj buforowaniu wymaganych skryptów przez RequireJS

302

RequireJS wydaje się robić wewnętrznie coś, co buforuje wymagane pliki javascript. Jeśli dokonam zmiany w jednym z wymaganych plików, muszę zmienić nazwę pliku, aby zastosować zmiany.

Powszechna sztuczka polegająca na dodawaniu numeru wersji jako parametru zapytania na końcu nazwy pliku nie działa z wymaganiem <script src="jsfile.js?v2"></script>

To, czego szukam, to sposób, aby zapobiec wewnętrznemu buforowaniu skryptów wymaganych przez RequireJS bez konieczności zmiany nazwy moich plików skryptów przy każdej aktualizacji.

Rozwiązanie wieloplatformowe:

Teraz używam urlArgs: "bust=" + (new Date()).getTime()do automatycznego pomijania pamięci podręcznej podczas programowania iurlArgs: "bust=v2" do produkcji, w których zwiększam zakodowaną wersję num po uruchomieniu zaktualizowanego wymaganego skryptu.

Uwaga:

@Dustin Getz wspomniał w niedawnej odpowiedzi, że Narzędzia dla programistów Chrome upuszczą punkty przerwania podczas debugowania, gdy pliki JavaScript są ciągle odświeżane w ten sposób. Jednym obejściem jest napisaniedebugger; kodu, aby uruchomić punkt przerwania w większości debuggerów Javascript.

Rozwiązania specyficzne dla serwera:

Aby znaleźć konkretne rozwiązania, które mogą lepiej działać w środowisku serwera, takie jak Węzeł lub Apache, zapoznaj się z niektórymi odpowiedziami poniżej.

BumbleB2na
źródło
Jesteś pewien, że to nie serwer lub klient buforuje? (używam wymaganego js od kilku miesięcy i nie zauważyłem niczego podobnego) IE został przyłapany na buforowaniu wyników akcji MVC, chrome buforował nasze szablony HTML, ale wszystkie pliki js wydają się odświeżać po zresetowaniu pamięci podręcznej przeglądarki. Podejrzewam, że jeśli chcesz skorzystać z buforowania, ale nie możesz zrobić tego zwykle, ponieważ żądania z wymaganych plików J usuwają ciąg zapytania, który może powodować problem?
PJUK,
Nie jestem pewien, czy RequireJS usuwa tak dołączone numery wersji. To mógł być mój serwer. Ciekawe, jak RequireJS ma ustawienie pomijania pamięci podręcznej, więc możesz mieć rację, usuwając moje dołączone numery wersji na wymaganych plikach.
BumbleB2na
zaktualizowałem swoją odpowiedź potencjalnym rozwiązaniem buforowania
Dustin Getz
Teraz mogę dodać następujący tekst do litanii, którą wyłożyłem dziś rano na swoim blogu: codrspace.com/dexygen/… I to znaczy, że nie tylko muszę dodawać pomijanie pamięci podręcznej, ale także wymaga.js, aby zignorować intensywne odświeżanie.
Dexygen
Jestem zdezorientowany co do przypadku użycia tego ... Czy to jest do ponownego ładowania modułów AMD na front-end czy co?
Alexander Mills,

Odpowiedzi:

457

RequireJS można skonfigurować tak, aby dodawał wartość do każdego adresu URL skryptu w celu pomijania pamięci podręcznej.

Z dokumentacji RequireJS ( http://requirejs.org/docs/api.html#config ):

urlArgs : dodatkowe argumenty ciągu zapytania dołączane do adresów URL używanych przez RequireJS do pobierania zasobów. Najbardziej przydatne do buforowania awarii, gdy przeglądarka lub serwer nie jest poprawnie skonfigurowany.

Przykład, dodając „v2” do wszystkich skryptów:

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

Do celów programistycznych można zmusić RequireJS do ominięcia pamięci podręcznej przez dodanie znacznika czasu:

require.config({
    urlArgs: "bust=" + (new Date()).getTime()
});
Phil McCullick
źródło
46
Bardzo pomocny, dziękuję. Używam urlArgs: "bust=" + (new Date()).getTime()do automatycznego pomijania pamięci podręcznej podczas programowania i urlArgs: "bust=v2"do produkcji, gdzie zwiększam zakodowaną wersję num po uruchomieniu wymaganego zaktualizowanego skryptu.
BumbleB2na
9
... także jako optymalizator wydajności możesz użyć Math.random () zamiast (new Date ()). getTime (). To więcej piękna, nie twórz obiektów i trochę szybciej jsperf.com/speedcomparison .
Vlad Tsepelev
2
Możesz go trochę zmniejszyć:urlArgs: "bust=" + (+new Date)
mrzmyr
11
Myślę, że niszczenie pamięci podręcznej za każdym razem to okropny pomysł. Niestety RequireJS nie oferuje innej alternatywy. Używamy urlArgs, ale nie używamy do tego losowego ani znacznika czasu. Zamiast tego używamy naszego obecnego Git SHA, ten sposób zmienia się tylko po wdrożeniu nowego kodu.
Ivan Torres
5
Jak ten wariant „v2” może działać w środowisku produkcyjnym, jeśli plik, który zawiera sam łańcuch „v2”, jest buforowany? Jeśli wypuszczę nową aplikację do produkcji, dodanie „v3” nic nie da, ponieważ aplikacja chętnie pracuje z buforowanymi plikami v2, w tym ze starą konfiguracją z v2 urlArgs.
Benny Bottema
54

Nie używaj do tego urlArgs!

Wymagaj, aby skrypty ładowały się zgodnie z nagłówkami buforowania HTTP. (Skrypty ładowane są dynamicznie wstawiane <script>, co oznacza, że ​​żądanie wygląda jak ładowany stary zasób).

Podaj zasoby javascript z odpowiednimi nagłówkami HTTP, aby wyłączyć buforowanie podczas programowania.

Korzystanie z urlArgs wymaga, że ​​wszelkie ustawione punkty przerwania nie zostaną zachowane podczas odświeżania; w końcu musisz umieścić debuggerinstrukcje w całym kodzie. Zły. Używam urlArgsdo pomijania pamięci podręcznej podczas aktualizacji produkcji za pomocą git sha; wtedy mogę ustawić moje zasoby do buforowania na zawsze i zagwarantować, że nigdy nie będę mieć starych zasobów.

W fazie projektowania wyśmiewam wszystkie żądania ajax ze złożoną konfiguracją mockjax , a następnie mogę obsługiwać moją aplikację w trybie tylko javascript z 10-liniowym serwerem python http z wyłączonym buforowaniem . Skalowałem to dla mnie do dość dużej „korporacyjnej” aplikacji z setkami spokojnych punktów końcowych usługi internetowej. Mamy nawet zakontraktowanego projektanta, który może pracować z naszą prawdziwą bazą kodu produkcyjnego, nie dając mu dostępu do naszego kodu zaplecza.

Dustin Getz
źródło
8
@ JamesP.Wright, ponieważ (przynajmniej w Chrome), gdy ustawisz punkt przerwania dla czegoś, co dzieje się przy ładowaniu strony, a następnie kliknij odśwież, punkt przerwania nie jest trafiany, ponieważ adres URL się zmienił, a Chrome upuścił punkt przerwania. Chciałbym poznać obejście tego problemu tylko dla klientów.
Drew Noakes
1
Dzięki, Dustin. Jeśli znajdziesz sposób na obejście tego, opublikuj. W międzyczasie możesz używać debugger;w kodzie wszędzie tam, gdzie chcesz, aby punkt przerwania trwał.
BumbleB2na
2
Dla każdego, kto korzysta z serwera http w węźle (npm zainstaluj serwer http). Możesz także wyłączyć buforowanie za pomocą opcji -c-1 (tj. Serwer http -c-1).
Yourpalal
5
Możesz wyłączyć buforowanie w Chrome, aby obejść problem z debugowaniem podczas programowania: stackoverflow.com/questions/5690269/…
Deepak Joy,
5
+1 do !!! NIE UŻYWAJ adresów URL W PRODUKCJI !!! . Wyobraź sobie, że Twoja witryna zawiera 1000 plików JS (tak, możliwe!), A ich ładowanie jest kontrolowane przez wymagany JS. Teraz wypuszczasz wersję 2 lub swoją witrynę, na której zmienia się tylko kilka plików JS! ale dodając urlArgs = v2, zmuszasz do przeładowania wszystkich 1000 plików JS! zapłacisz dużo ruchu! tylko zmodyfikowane pliki powinny być ponownie ładowane, wszystkie pozostałe powinny mieć status 304 (niezmodyfikowane).
walv
24

Rozwiązanie urlArgs ma problemy. Niestety nie możesz kontrolować wszystkich serwerów proxy, które mogą znajdować się między tobą a przeglądarką użytkownika. Niektóre z tych serwerów proxy można niestety skonfigurować tak, aby ignorowały parametry adresów URL podczas buforowania plików. Jeśli tak się stanie, niewłaściwa wersja pliku JS zostanie dostarczona użytkownikowi.

W końcu poddałem się i wdrożyłem własną poprawkę bezpośrednio w pliku wymaganej.js. Jeśli chcesz zmodyfikować swoją wersję biblioteki RequJS, to rozwiązanie może Ci pomóc.

Możesz zobaczyć łatkę tutaj:

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

Po dodaniu możesz zrobić coś takiego w wymaganej konfiguracji:

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

Do zamiany użyj systemu kompilacji lub środowiska serwerowego buildNumber identyfikatorem wersji / wersją oprogramowania / ulubionym kolorem.

Korzystanie wymaga takiego:

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

Spowoduje, że zażądam tego pliku:

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

W naszym środowisku serwerowym używamy reguł przepisywania adresów URL w celu usunięcia buildNumber i podania poprawnego pliku JS. W ten sposób nie musimy się martwić o zmianę nazwy wszystkich naszych plików JS.

Łatka zignoruje każdy skrypt, który określa protokół, i nie wpłynie na żadne pliki inne niż JS.

Działa to dobrze w moim środowisku, ale zdaję sobie sprawę, że niektórzy użytkownicy wolą prefiks niż sufiks, więc modyfikowanie mojego zatwierdzenia powinno być łatwe.

Aktualizacja:

W dyskusji na temat żądania ściągnięcia autor Requjs sugeruje, że może to działać jako rozwiązanie do prefiksu numeru wersji:

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

Nie próbowałem tego, ale implikuje to, że wymagałoby to następującego adresu URL:

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

Co może działać bardzo dobrze dla wielu osób, które mogą używać prefiksu.

Oto kilka możliwych duplikatów pytań:

Wymagaj buforowania JS i proxy

wymagania.js - Jak ustawić wersję wymaganych modułów jako część adresu URL?

JBCP
źródło
1
Naprawdę chciałbym zobaczyć, jak twoja aktualizacja wkracza do oficjalnej wersji wymaganej. Zainteresowany może być także główny autor wymagań (patrz odpowiedź @ Louisa powyżej).
BumbleB2na
@ BumbleB2na - zachęcamy do komentowania PullRequest ( github.com/jrburke/requirejs/pull/1017 ), jrburke nie wydawał się zainteresowany. Sugeruje rozwiązanie za pomocą prefiksu nazwy pliku, zaktualizuję swoją odpowiedź, aby to uwzględnić.
JBCP
1
Niezła aktualizacja. Chyba lubię tej sugestii autora, ale to tylko dlatego, że używam tej konwencji nazewnictwa ostatnio: /scripts/myLib/v1.1/. Próbowałem dodać postfiks (lub prefiks) do moich nazw plików, prawdopodobnie dlatego, że właśnie to robi jquery, ale po pewnym czasie [leniwie i] zacząłem zwiększać numer wersji w folderze nadrzędnym. Wydaje mi się, że ułatwiło mi to utrzymanie na dużej stronie, ale teraz martwię się o koszmary przepisywania adresów URL.
BumbleB2na
1
Nowoczesne systemy frontonu przepisują tylko pliki JS i sumę MD5 w nazwie pliku, a następnie przepisują pliki HTML, aby używać nowych nazw plików podczas budowania, ale jest to trudne w przypadku starszych systemów, w których kod frontonu jest obsługiwany po stronie serwera.
JBCP
czy to działa, gdy potrzebuję js wewnątrz pliku jspx ?, jak to<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

Zainspirowani pamięcią podręczną Expire na data.js main.j zaktualizowaliśmy nasz skrypt wdrażania o następujące zadanie 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>

Gdzie wygląda początek main.js:

require.config({
    baseUrl: '/js',
    urlArgs: 'bust=@Revision@',
    ...
});
dvtoever
źródło
11

W produkcji

urlArgs może powodować problemy!

Główny autor requjs woli nie używaćurlArgs :

W przypadku wdrożonych zasobów wolę umieścić wersję lub skrót dla całej kompilacji jako katalog kompilacji, a następnie zmodyfikuj baseUrlkonfigurację używaną w projekcie, aby użyć tego katalogu z wersją jako baseUrl. Wówczas żadne inne pliki nie ulegają zmianie i pomaga uniknąć niektórych problemów z serwerem proxy, w których mogą nie buforować adresu URL zawierającego ciąg zapytania.

[Mój styl.]

Postępuję zgodnie z tą radą.

W rozwoju

Wolę używać serwera, który inteligentnie buforuje pliki, które mogą się często zmieniać: serwer, który emituje Last-Modifiedi odpowiada za If-Modified-Sincepomocą 304, gdy jest to właściwe. Nawet serwer oparty na ekspresowym zestawie Node do obsługi plików statycznych robi to od razu po wyjęciu z pudełka. Nie wymaga robienia czegokolwiek z moją przeglądarką i nie psuje punktów przerwania.

Louis
źródło
Dobre punkty, ale twoja odpowiedź jest specyficzna dla twojego środowiska serwerowego. Być może dobrą alternatywą dla każdego, kto natknie się na to, jest ostatnia rekomendacja dodania numeru wersji do nazwy pliku zamiast parametru querystring. Oto więcej informacji na ten temat: stevesouders.com/blog/2008/08/23/…
BumbleB2na
Napotykamy na ten konkretny problem w systemie produkcyjnym. Zalecam sprawdzenie nazw plików zamiast używania parametru.
JBCP
7

Wziąłem ten fragment z AskApache i umieściłem go w osobnym pliku .conf mojego lokalnego serwera Apache (w moim przypadku /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>

W przypadku programowania działa to dobrze bez konieczności zmiany kodu. Jeśli chodzi o produkcję, mogę użyć podejścia @ dvtoever.

myrho
źródło
6

Szybka poprawka dla programistów

Aby opracować, możesz po prostu wyłączyć pamięć podręczną w Chrome Dev Tools ( Wyłączanie pamięci podręcznej Chrome na potrzeby tworzenia witryn internetowych) ). Wyłączenie pamięci podręcznej ma miejsce tylko wtedy, gdy okno dialogowe narzędzi deweloperskich jest otwarte, więc nie musisz martwić się przełączaniem tej opcji za każdym razem, gdy przeglądasz regularnie.

Uwaga: Używanie „ urlArgs ” jest właściwym rozwiązaniem w produkcji, dzięki czemu użytkownicy otrzymują najnowszy kod. Utrudnia to jednak debugowanie, ponieważ chrome unieważnia punkty przerwania przy każdym odświeżeniu (ponieważ za każdym razem jest obsługiwany „nowy” plik).

Deepak Joy
źródło
3

Nie polecam używania „ urlArgs ” do pękania pamięci podręcznej przy użyciu RequireJS. Ponieważ nie rozwiązuje to w pełni problemu. Aktualizacja wersji no spowoduje pobranie wszystkich zasobów, nawet jeśli zmieniłeś tylko jeden zasób.

Aby poradzić sobie z tym problemem, zalecam używanie modułów Grunt, takich jak „filerev”, do tworzenia wersji nr. Ponadto napisałem niestandardowe zadanie w Gruntfile, aby zaktualizować wersję, gdziekolwiek jest to wymagane.

W razie potrzeby mogę udostępnić fragment kodu dla tego zadania.

Amit Sagar
źródło
Używam kombinacji grunt-filerev i grunt-cache-buster do przepisywania plików JavaScript.
Ian Jamieson,
2

Tak to robię w Django / Flask (można łatwo dostosować do innych języków / systemów VCS):

W twoim config.py(używam tego w python3, więc może być konieczne dostosowanie kodowania w python2)

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

Następnie w swoim szablonie:

{% if config.DEBUG %}
     require.config({urlArgs: "bust=" + (new Date().getTime())});
{% else %}
    require.config({urlArgs: "bust=" + {{ config.GIT_HASH|tojson }}});
{% endif %}
  • Nie wymaga ręcznego procesu budowania
  • Działa tylko git rev-parse HEADraz po uruchomieniu aplikacji i zapisuje ją w configobiekcie
Stephen Fuhry
źródło
0

Dynamiczne rozwiązanie (bez urlArgs)

Istnieje proste rozwiązanie tego problemu, dzięki czemu można załadować unikalny numer wersji dla każdego modułu.

Możesz zapisać oryginalną funkcję requjs.load, nadpisać ją własną funkcją i ponownie przeanalizować zmodyfikowany adres URL do oryginalnego Requjs.load:

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

W naszym procesie budowania użyłem „gulp-rev”, aby zbudować plik manifestu ze wszystkimi wersjami wszystkich używanych modułów. Uproszczona wersja mojego zadania przełknięcia:

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('./'));
});

wygeneruje to moduł AMD z numerami wersji do moduleNames, który jest zawarty jako „oRevision” w main.js, w którym zastąpisz funkcję wymaganą.jload, jak pokazano wcześniej.

mata
źródło
-1

Jest to dodatek do zaakceptowanej odpowiedzi @phil mccull.

Korzystam z jego metody, ale automatyzuję ten proces, tworząc szablon T4 do uruchomienia przed kompilacją.

Polecenia przed kompilacją:

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"

wprowadź opis zdjęcia tutaj

Szablon T4:

wprowadź opis zdjęcia tutaj

Wygenerowany plik: wprowadź opis zdjęcia tutaj

Przechowuj w zmiennej przed załadowaniem pliku request.config.js: wprowadź opis zdjęcia tutaj

Odwołanie w pliku wymaga.config.js:

wprowadź opis zdjęcia tutaj

Zach Painter
źródło
2
Prawdopodobnie dlatego, że rozwiązaniem problemu JavaScript jest garść kodu C #. Jest to również garść dodatkowej pracy i kodu, aby zrobić coś, co ostatecznie zostanie wykonane dokładnie tak samo, jak zaakceptowana odpowiedź.
mAAdhaTTah
@mAAdhaTTah Możesz to zrobić z dowolnym językiem po stronie serwera ... nie musi to być c #. Również to automatyzuje proces, aktualizując biust pamięci podręcznej podczas tworzenia nowej wersji projektu, zapewniając, że klient zawsze buforuje najnowszą wersję skryptów. Nie sądzę, że zasługuje na ujemne przeceny. Po prostu rozszerza odpowiedź, oferując zautomatyzowane podejście do rozwiązania.
Zach Painter
Zasadniczo to utworzyłem, ponieważ utrzymywanie aplikacji, która używała pliku wymaganego.js, było dość denerwujące, aby ręcznie komentować „(new Date ()). GetTime ()) i odkomentować statyczny bufor pamięci podręcznej za każdym razem, gdy aktualizowałem aplikację . łatwo zapomnieć nagle klient sprawdza zmiany i widząc buforowane skryptu tak uważają nic się nie zmieniło .. Wszystko dlatego, że po prostu zapomniał zmienić CACHEBUSTER To trochę dodatkowego kodu kasuje szansę tego dzieje...
Zach Painter
2
Nie zaznaczyłem tego, nie wiem, co się właściwie stało. Po prostu sugeruję kod, który nie jest nie tylko js, ​​ale językiem szablonów C #, nie będzie tak pomocny dla twórców JS, którzy próbują uzyskać odpowiedź na swój problem.
mAAdhaTTah
-2

W moim przypadku chciałem ładować ten sam formularz za każdym razem, gdy klikam, nie chciałem, aby zmiany wprowadzone w pliku pozostały. Może to nie dotyczyć dokładnie tego postu, ale może to być potencjalne rozwiązanie po stronie klienta bez ustawiania konfiguracji dla wymagania. Zamiast wysyłać zawartość bezpośrednio, możesz wykonać kopię wymaganego pliku i zachować nienaruszony rzeczywisty plik.

LoadFile(filePath){
    const file = require(filePath);
    const result = angular.copy(file);
    return result;
}
Mahib
źródło