Jak zmusić przeglądarkę do ponownego załadowania buforowanych plików CSS / JS?

993

Zauważyłem, że niektóre przeglądarki (w szczególności Firefox i Opera) bardzo gorliwie używają buforowanych kopii plików .css i .js , nawet między sesjami przeglądarki. Prowadzi to do problemu podczas aktualizacji jednego z tych plików, ale przeglądarka użytkownika nadal korzysta z kopii w pamięci podręcznej.

Pytanie brzmi: jaki jest najbardziej elegancki sposób zmuszania przeglądarki użytkownika do ponownego załadowania pliku po jego zmianie?

Idealnie, rozwiązanie nie zmusiłoby przeglądarki do ponownego ładowania pliku przy każdej wizycie na stronie. W odpowiedzi opublikuję własne rozwiązanie, ale jestem ciekawy, czy ktoś ma lepsze rozwiązanie, i pozwolę, aby głosowanie zdecydowało.

Aktualizacja :

Po dłuższej dyskusji tutaj znalazłem przydatną sugestię Johna Millikina i sugestii da5id . Okazuje się, że istnieje na to termin: automatyczne wersjonowanie .

Poniżej zamieściłem nową odpowiedź, będącą połączeniem mojego oryginalnego rozwiązania i sugestii Johna.

Innym pomysłem zasugerowanym przez SCdF byłoby dodanie do pliku fałszywego ciągu zapytania. (Niektóre kody Pythona do automatycznego używania znacznika czasu jako fałszywego ciągu zapytania zostały przesłane przez pi .). Trwa jednak dyskusja, czy przeglądarka buforuje plik z ciągiem zapytania. (Pamiętaj, że chcemy, aby przeglądarka buforowała plik i używała go podczas przyszłych wizyt. Chcemy, aby pobierał plik ponownie tylko po jego zmianie).

Ponieważ nie jest jasne, co dzieje się z fałszywym ciągiem zapytania, nie akceptuję tej odpowiedzi.

Pmpr
źródło
Mam to w moim .htaccess i nigdy żadnych problemów z pamięci podręcznej plików: ExpiresActive On ExpiresDefault "modification".
Frank Conijn
2
Zdecydowanie zgodzę się, że dodanie informacji o wersji do adresu URL pliku jest zdecydowanie najlepszym sposobem. Działa przez cały czas dla wszystkich. Ale jeśli go nie używasz i wystarczy od czasu do czasu ponownie załadować ten plik CSS lub JS we własnej przeglądarce ... po prostu otwórz go na własnej karcie i naciśnij SHIFT-reload (lub CTRL-F5)! Możesz zrobić to samo za pomocą JS, ładując plik do (ukrytej) ramki iframe, czekając, aż się załaduje, a następnie wywołując iframe.contentWindow.location.reload(true). Zobacz metodę (4) stackoverflow.com/a/22429796/999120 - dotyczy to obrazów, ale to samo dotyczy.
Doin
2
Naprawdę doceniam sposób, w jaki pytanie zostało zadane i zostało zaktualizowane od tego czasu. Całkowicie opisał to, czego powinienem oczekiwać w odpowiedziach. Odtąd będę stosować to podejście w moich pytaniach. Twoje zdrowie!
rd22

Odpowiedzi:

455

Aktualizacja: Przepisano, aby uwzględnić sugestie Johna Millikina i da5id . To rozwiązanie zostało napisane w języku PHP, ale powinno być łatwe do dostosowania do innych języków.

Aktualizacja 2: Włączanie komentarzy od Nicka Johnsona, że oryginalne .htaccesswyrażenie regularne może powodować problemy z takimi plikami json-1.3.js. Rozwiązaniem jest przepisanie tylko, jeśli na końcu jest dokładnie 10 cyfr. (Ponieważ 10 cyfr obejmuje wszystkie znaczniki czasu od 9/9/2001 do 11/20/2286.)

Najpierw używamy następującej reguły przepisywania w .htaccess:

RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

Teraz piszemy następującą funkcję PHP:

/**
 *  Given a file, i.e. /css/base.css, replaces it with a string containing the
 *  file's mtime, i.e. /css/base.1221534296.css.
 *  
 *  @param $file  The file to be loaded.  Must be an absolute path (i.e.
 *                starting with slash).
 */
function auto_version($file)
{
  if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file))
    return $file;

  $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
  return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
}

Teraz, gdziekolwiek podasz swój CSS, zmień go z tego:

<link rel="stylesheet" href="/css/base.css" type="text/css" />

Do tego:

<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />

W ten sposób nigdy nie będziesz musiał ponownie modyfikować tagu łącza, a użytkownik zawsze zobaczy najnowszy CSS. Przeglądarka będzie mogła buforować plik CSS, ale po wprowadzeniu jakichkolwiek zmian w CSS przeglądarka zobaczy to jako nowy adres URL, więc nie będzie używać kopii w pamięci podręcznej.

Może to również działać z obrazami, ulubionymi i JavaScript. Zasadniczo wszystko, co nie jest generowane dynamicznie.

Wyrko
źródło
16
Mój własny statyczny serwer treści robi dokładnie to samo, z wyjątkiem tego, że używam parametru do wersjonowania (base.css? V = 1221534296) zamiast zmiany nazwy pliku (base.1221534296.css). Podejrzewam, że Twoja droga może być nieco bardziej wydajna. Bardzo fajny.
Jens Roland
4
@Kip: Bardzo sprytne rozwiązanie. Przepisywanie adresów URL ma oczywiście znacznie więcej do zaoferowania niż urocze adresy URL.
James P.,
37
Widzę problem z tym, że wielokrotnie uzyskuje on dostęp do systemu plików - dokładnie - liczba linków * liczba żądań / s ... co może, ale nie musi stanowić dla ciebie problemu.
Tomáš Fejfar
3
@AlixAxel: Nie, przeglądarki pobiorą go ponownie, gdy zmieni się parametr, ale niektóre publiczne serwery proxy nie będą buforować plików z parametrami adresu URL, dlatego najlepszą praktyką jest umieszczenie wersji na ścieżce. A koszt mod_rewrite jest niewielki w porównaniu do każdego innego wąskiego gardła w wydajności WPO
Jens Roland,
8
Czy pierwsza file_existskontrola jest naprawdę konieczna? filemtimezwróci false w przypadku niepowodzenia, więc dlaczego po prostu nie przypisać wartości zmiennej filemtime do zmiennej i sprawdzić, czy jest fałszywa przed zmianą nazwy pliku? To ograniczyłoby jedną niepotrzebną operację na pliku, która naprawdę by się sumowała.
Gavin
184

Prosta technika po stronie klienta

Zasadniczo buforowanie jest dobre. Istnieje kilka technik, w zależności od tego, czy rozwiązujesz problem samodzielnie podczas tworzenia witryny, czy też próbujesz kontrolować pamięć podręczną w środowisku produkcyjnym.

Ogólni odwiedzający twoją stronę nie będą mieli takiego samego doświadczenia, jak podczas jej tworzenia. Ponieważ przeciętny użytkownik odwiedza witrynę rzadziej (może tylko kilka razy w miesiącu, chyba że jesteś siecią Google lub hi5), prawdopodobieństwo, że Twoje pliki będą przechowywane w pamięci podręcznej, jest mniejsze. Jeśli chcesz wymusić nową wersję w przeglądarce, zawsze możesz dodać ciąg zapytania do żądania i podnieść numer wersji po dokonaniu poważnych zmian:

<script src="/myJavascript.js?version=4"></script>

Zapewni to, że wszyscy otrzymają nowy plik. Działa, ponieważ przeglądarka sprawdza adres URL pliku, aby ustalić, czy ma on kopię w pamięci podręcznej. Jeśli twój serwer nie jest skonfigurowany do robienia czegokolwiek z ciągiem zapytania, zostanie zignorowany, ale nazwa będzie wyglądać jak nowy plik w przeglądarce.

Z drugiej strony, jeśli tworzysz witrynę internetową, nie chcesz zmieniać numeru wersji za każdym razem, gdy zapisujesz zmianę w wersji programistycznej. To byłoby nudne.

Dlatego podczas tworzenia witryny dobrym pomysłem byłoby automatyczne wygenerowanie parametru ciągu zapytania:

<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

Dodanie ciągu zapytania do żądania jest dobrym sposobem na zaktualizowanie zasobu, ale w przypadku prostej witryny może to być niepotrzebne. I pamiętaj, że buforowanie jest dobrą rzeczą.

Warto również zauważyć, że przeglądarka niekoniecznie skąpa jest w przechowywaniu plików w pamięci podręcznej. Przeglądarki mają zasady dotyczące tego rodzaju rzeczy i zwykle postępują zgodnie z zasadami określonymi w specyfikacji HTTP. Gdy przeglądarka wysyła żądanie do serwera, częścią odpowiedzi jest nagłówek EXPIRES. Data, która informuje przeglądarkę, jak długo powinien być przechowywany w pamięci podręcznej. Następnym razem, gdy przeglądarka napotka żądanie dotyczące tego samego pliku, zobaczy, że ma kopię w pamięci podręcznej, i szuka daty WYGASANIA, aby zdecydować, czy należy go użyć.

Wierzcie lub nie, tak naprawdę to twój serwer sprawia, że ​​pamięć podręczna przeglądarki jest tak trwała. Możesz dostosować ustawienia serwera i zmienić nagłówki EXPIRES, ale ta mała technika, którą napisałem powyżej, jest prawdopodobnie znacznie prostszym sposobem na obejście tego. Ponieważ buforowanie jest dobre, zwykle chcesz ustawić tę datę daleko w przyszłość („Nagłówek dalekiej przyszłości wygasa”) i użyć techniki opisanej powyżej, aby wymusić zmianę.

Jeśli interesują Cię dodatkowe informacje na temat HTTP lub sposób, w jaki te żądania są wysyłane, dobra książka to „Witryny o wysokiej wydajności” Steve'a Soudersa. To bardzo dobre wprowadzenie do tematu.

keparo
źródło
3
Szybka sztuczka polegająca na generowaniu ciągu zapytania za pomocą Javascript działa świetnie podczas aktywnego programowania. Zrobiłem to samo z PHP.
Alan Turing
2
Jest to najprostszy sposób na osiągnięcie pożądanego rezultatu oryginalnego plakatu. Metoda mod_rewrite działa dobrze, jeśli chcesz wymusić przeładowanie pliku .css lub .js KAŻDY raz, gdy ładujesz stronę. Ta metoda nadal pozwala na buforowanie, dopóki nie zmienisz pliku i nie chcesz, aby wymusił przeładowanie.
scott80109
@ Keparo, mam wystarczającą liczbę jquery na wszystkich stronach, jeśli zamierzam to zmienić ręcznie, zajmie to miesiąc. Jeśli możesz mi pomóc rozwiązać w całości bez kodowania na każdej stronie.
cracker
1
To nie wydaje się działać w moim CSS, gdy używam:<link href='myCss.css?dev=14141'...>
Noumenon,
3
To nie jest realne rozwiązanie. Spora liczba przeglądarek po prostu odmówi buforowania czegokolwiek zawierającego ciąg zapytania. To jest powód, dla którego Google, GTMetrix i podobne narzędzia podniosą flagę, jeśli masz ciągi zapytania dotyczące odwołań do treści statycznej. Chociaż z pewnością jest to przyzwoite rozwiązanie dla rozwoju, absolutnie nie jest rozwiązaniem dla produkcji. Przeglądarka kontroluje również buforowanie, a nie serwer. Serwer po prostu SUGERUJE, kiedy należy go odświeżyć; przeglądarka nie musi nasłuchiwać serwera (i często tego nie robi). Urządzenia mobilne są tego najlepszym przykładem.
Nate I
113

Google mod_pagespeed plugin do apache zrobi automatycznego wersjonowania dla Ciebie. To jest naprawdę śliskie.

Analizuje HTML wychodząc z serwera (działa z PHP, szynami, pythonem, statycznym HTML - czymkolwiek) i przepisuje linki do CSS, JS, plików graficznych, aby zawierały kod identyfikacyjny. Obsługuje pliki pod zmodyfikowanymi adresami URL z bardzo długą kontrolą pamięci podręcznej. Gdy pliki się zmieniają, automatycznie zmienia adresy URL, więc przeglądarka musi je ponownie pobrać. Zasadniczo po prostu działa, bez żadnych zmian w kodzie. To nawet zminimalizuje twój kod przy wyjściu.

Leopd
źródło
1
To świetnie, ale wciąż w fazie beta. Czy można go wykorzystać do obsługi przedsiębiorstwa?
Sanghyun Lee,
26
Jest to NIEWŁAŚCIWE (automatyczne fałszowanie źródła), gdy jest to wyraźnie problem z przeglądarką. Daj nam (programistom) prawdziwe odświeżenie
pamięci
25
mod_pagespeed jest funkcjonalnie równoważny całkowicie automatycznemu krokowi kompilacji / kompilacji dla twojego html / css / js. Myślę, że trudno byłoby znaleźć poważnych programistów, którzy uważają, że systemy kompilacji są z natury złe lub że jest coś nie tak z tym, że jest całkowicie automatyczny. Analogią czystej wersji jest wyczyszczenie pamięci podręcznej mod_pagespeed: code.google.com/p/modpagespeed/wiki/… ?
Leopd
3
@ T4NK3R mod_pagespeed nie musi nic robić ze swoim źródłem, aby zarządzać pamięcią podręczną, po prostu wspomniano, że może pomóc w takich sprawach jak minimalizacja. Co do tego, czy jest to „NIEPRAWIDŁOWE”, to całkowicie subiektywne. Może to być dla ciebie złe, ale to nie znaczy, że jest instynktownie złe .
Madbreaks,
2
Działa również z nginx, choć musisz go zbudować ze źródła: developers.google.com/speed/pagespeed/module/…
Rohit
93

Zamiast ręcznie zmieniać wersję, zalecam użycie skrótu MD5 rzeczywistego pliku CSS.

Twój adres URL byłby więc podobny

http://mysite.com/css/[md5_hash_here]/style.css

Nadal możesz użyć reguły przepisywania w celu usunięcia skrótu, ale zaletą jest to, że teraz możesz ustawić swoją zasadę buforowania na „buforuj na zawsze”, ponieważ jeśli adres URL jest taki sam, oznacza to, że plik pozostaje niezmieniony.

Następnie możesz napisać prosty skrypt powłoki, który obliczy skrót pliku i zaktualizuje tag (prawdopodobnie będziesz chciał przenieść go do osobnego pliku w celu włączenia).

Po prostu uruchom ten skrypt za każdym razem, gdy zmienia się CSS i jesteś dobry. Przeglądarka przeładuje pliki TYLKO po ich zmianie. Jeśli dokonasz edycji, a następnie ją cofniesz, nie będzie problemu z ustaleniem, do której wersji musisz wrócić, aby użytkownicy nie mogli ponownie pobrać.

levik
źródło
1
niestety nie wiem jak to zaimplementować. Porada proszę ... więcej szczegółów ...
Michael Phelps
Implementacja w powłoce, rubinie itp. Byłaby świetna
Peter
3
Bardzo fajne rozwiązanie .. ale myślę, że zasoby pochłaniają obliczanie wartości skrótu pliku przy każdym żądaniu pliku (css, js, images, html..etc) dla każdej wizyty na stronie.
DeepBlue
Jest to standardowe rozwiązanie dla osób korzystających z pakietu js lub css z pakietem gulp, grunt lub webpack, implementacja jest różna dla każdego rozwiązania, ale mieszanie plików jest krokiem kompilacji i jest zalecane dla nowoczesnych aplikacji w pakiecie
Brandon Søren Culley
@DeepBlue - odpowiedź mówi „uruchamiaj ten skrypt za każdym razem, gdy zmienia się CSS” . To NIE jest przy każdej wizycie na stronie. OTOH Odpowiedź pomija główne szczegóły - w jaki sposób zmieniony skrót staje się częścią adresu URL? Nie wiem ...
ToolmakerSteve
70

Nie jestem pewien, dlaczego wdrażacie to rozwiązanie tak bardzo.

Wystarczy pobrać zmodyfikowany znacznik czasu pliku i dołączyć go do pliku jako kwerendę

W PHP zrobiłbym to jako:

<link href="mycss.css?v=<?= filemtime('mycss.css') ?>" rel="stylesheet">

filemtime to funkcja PHP zwracająca zmodyfikowany znacznik czasu pliku.

Phantom007
źródło
Możesz po prostu użyć mycss.css?1234567890.
Gavin
3
bardzo elegancki, choć nieco go zmodyfikowałem <link rel="stylesheet" href="mycss.css?<?php echo filemtime('mycss.css') ?>"/>, na wypadek, gdyby niektóre argumenty w tym wątku dotyczące buforowania adresów URL za pomocą zmiennych GET (w sugerowanym formacie) były prawidłowe
luke_mclachlan
dalej do mojego ostatniego komentarza, widziałem, że wordpress używa, ?ver=więc kto wie!
luke_mclachlan
Świetne rozwiązanie. Ponadto dla mnie stwierdziłem, że czas pliku nie działa dla w pełni kwalifikowanej nazwy domeny (FQDN), więc użyłem nazwy FQDN dla części href i $ _SERVER [„DOCUMENT_ROOT”] dla części pliku. EX: <link rel = "stylesheet" href = "http: //theurl/mycss.css? V = <? Php echo filemtime ($ _ SERVER [" DOCUMENT_ROOT "]. '/Mycss.css')?>" />
rrtx2000
Wielkie dzięki. Prosty i dobry. Tutaj jest w Pythonie: progpath = nazwa.path.dirname (sys.argv [0]) def wersjonowanie (plik): timestamp = os.path.getmtime ('% s /../ web /% s'% (progpath , plik)) return '% s? v =% s'% (plik, znacznik czasu) print <link href = "% s" rel = "arkusz stylów" '' type = "text / css" /> '\% versionize ( „css / main.css”)
dlink
52

Możesz po prostu umieścić ?foo=1234na końcu importu css / js, zmieniając 1234 na dowolne. Spójrz na przykład na źródło html SO.

Chodzi o to, że? parametry są w każdym razie odrzucane / ignorowane i można zmienić ten numer po uruchomieniu nowej wersji.


Uwaga: Istnieje pewien argument dotyczący tego, jak dokładnie wpływa to na buforowanie. Uważam, że ogólną zasadą jest to, że żądania GET, z parametrami lub bez, powinny być buforowalne, więc powyższe rozwiązanie powinno działać.

Jednak to serwer WWW decyduje, czy chce zastosować się do tej części specyfikacji, i przeglądarki, z której korzysta użytkownik, ponieważ może po prostu iść naprzód i poprosić o nową wersję.

SCdF
źródło
Nonsens. Ciąg zapytania (znany również jako parametry GET) jest częścią adresu URL. Mogą i będą buforowane. To dobre rozwiązanie.
troelskn
9
@troelskn: Specyfikacja HTTP 1.1 mówi inaczej (w odniesieniu do żądań GET i HEAD z parametrami zapytania): pamięci podręczne NIE MOGĄ traktować odpowiedzi na takie identyfikatory URI jako nowe, chyba że serwer podaje wyraźny czas wygaśnięcia. Zobacz w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9
Michael Johnson,
4
Próbowałem typu ciągu zapytania wersji wersjonowania we wszystkich głównych przeglądarkach i one buforują plik, specyfikacje lub nie. Myślę jednak, że lepiej jest użyć formatu style.TIMESTAMP.css bez nadużywania ciągów zapytań, ponieważ nadal istnieje możliwość, że buforujące oprogramowanie proxy NIE BĘDZIE buforować pliku.
Tomas Andrle
34
Warto zauważyć, z jakiegokolwiek powodu, że Stackoverflow sam używa metody ciągu zapytania.
jason
2
Czy sprawdzono, że użycie parametru? = Nie spowoduje, że przeglądarki ponownie pobiorą buforowany plik po zmianie parametru. Jedynym sposobem jest programowa zmiana samej nazwy pliku na końcu serwera, na co odpowiedział Kip
arunskrish
41

Słyszałem, że to się nazywa „automatyczne wersjonowanie”. Najczęstszą metodą jest umieszczenie mtime pliku statycznego gdzieś w adresie URL i usunięcie go za pomocą programów do przepisywania lub konfesji adresów URL:

Zobacz też:

John Millikin
źródło
3
Dzięki, myślę, że to był kolejny przypadek, w którym mój pomysł został omówiony, po prostu nie wiedziałem, jak się nazywa, więc nigdy nie znalazłem go w wyszukiwarkach Google.
Kip
27

Około 30 istniejących odpowiedzi to świetna rada dla strony internetowej z około 2008 roku. Jeśli jednak chodzi o nowoczesną, jednostronicową aplikację (SPA), nadszedł czas, aby przemyśleć kilka podstawowych założeń… konkretnie pomysł, aby serwer WWW obsługiwał tylko jedną, najnowszą wersję plik.

Wyobraź sobie, że jesteś użytkownikiem, który ma w przeglądarce wersję M SPA załadowaną:

  1. Twój potok CD wdraża nową wersję N aplikacji na serwerze
  2. Nawigujesz w obrębie SPA, które wysyła XHR na serwer, aby uzyskać /some.template
    • (Twoja przeglądarka nie odświeżyła strony, więc nadal korzystasz z wersji M )
  3. Serwer odpowiada treścią /some.template- czy chcesz zwrócić wersję M lub N szablonu?

Jeśli format /some.templatezmienił się między wersjami M i N (lub nazwa pliku została zmieniona, czy cokolwiek innego) , prawdopodobnie nie chcesz, aby wersja N szablonu była wysyłana do przeglądarki, w której działa stara wersja M analizatora składni . †

W aplikacjach internetowych występuje ten problem, gdy spełnione są dwa warunki:

  • Zasoby są wymagane asynchronicznie jakiś czas po początkowym załadowaniu strony
  • Logika aplikacji zakłada rzeczy (które mogą się zmienić w przyszłych wersjach) dotyczące zawartości zasobów

Gdy aplikacja musi obsługiwać wiele wersji równolegle, rozwiązanie buforowania i „przeładowania” staje się trywialne:

  1. Zainstaluj wszystkie pliki witryny do wersjonowanymi katalogów: /v<release_tag_1>/…files…,/v<release_tag_2>/…files…
  2. Ustaw nagłówki HTTP, aby przeglądarki mogły buforować pliki na zawsze
    • (Lub jeszcze lepiej, umieść wszystko w CDN)
  3. Zaktualizuj wszystkie <script>i <link>tagi itp., Aby wskazywały ten plik w jednym z wersjonowanych katalogów

Ten ostatni krok wydaje się trudny, ponieważ może wymagać wywołania narzędzia do tworzenia adresów URL dla każdego adresu URL w kodzie serwera lub klienta. Lub możesz po prostu sprytnie użyć <base>tagu i zmienić bieżącą wersję w jednym miejscu.

† Jednym ze sposobów jest agresywne zmuszanie przeglądarki do ponownego ładowania wszystkiego po wydaniu nowej wersji. Ale ze względu na zakończenie wszelkich trwających operacji, może być nadal najłatwiej obsługiwać równolegle co najmniej dwie wersje: prąd V i prąd poprzedni.

Michael Kropat
źródło
Michael - twój komentarz jest bardzo trafny. Kameruję tutaj właśnie, próbując znaleźć rozwiązanie dla mojego SPA. Dostałem kilka wskazówek, ale sam musiałem znaleźć rozwiązanie. Ostatecznie byłem bardzo zadowolony z tego, co wymyśliłem, więc napisałem post na blogu i odpowiedź na to pytanie (w tym kod). Dzięki za wskazówki
statler
Świetny komentarz. Nie mogę zrozumieć, gdy ludzie wciąż mówią o pomijaniu pamięci podręcznej i buforowaniu HTTP jako prawdziwym rozwiązaniu problemów z buforowaniem stron internetowych bez komentowania nowych problemów SPA, jakby to był marginalny przypadek.
David Casillas
1
Doskonała reakcja i absolutnie idealna strategia! I punkty bonusowe za wzmiankę o basetagu! Jeśli chodzi o obsługę starego kodu: nie zawsze jest to możliwe, ani nie zawsze jest to dobry pomysł. Nowe wersje kodu mogą obsługiwać przełamywanie zmian w innych częściach aplikacji lub mogą obejmować naprawy awaryjne, łaty w zabezpieczeniach i tak dalej. Sam jeszcze nie wdrożyłem tej strategii, ale zawsze czułem, że ogólna architektura powinna pozwalać wdrożeniom na oznaczanie starej wersji jako obsoletei wymuszanie przeładowania przy następnym wywołaniu asynchronicznym (lub po prostu wymuszając cofnięcie autoryzacji wszystkich sesji przez WebSockets ).
Jonny Asmar,
Miło widzieć dobrze przemyślaną odpowiedź dotyczącą aplikacji na jedną stronę.
Nate I
To jest „wdrożenie niebiesko-zielone”, jeśli chcesz wyszukać więcej informacji.
Fil
15

Nie używaj wersji foo.css? = 1! Przeglądarki nie powinny buforować adresów URL zmiennymi GET. Według http://www.thinkvitamin.com/features/webapps/serving-javascript-fast , choć IE i Firefox to ignorują, Opera i Safari nie! Zamiast tego użyj foo.v1234.css i użyj reguł przepisywania, aby usunąć numer wersji.

airrob
źródło
1
Przede wszystkim przeglądarki nie buforują, to funkcja HTTP. Dlaczego http miałoby dbać o strukturę identyfikatora URI? Czy istnieje odwołanie officail do specyfikacji, która stwierdza, że ​​buforowanie HTTP powinno rozumieć semantykę identyfikatora URI, aby nie buforował elementów ciągiem zapytania?
AnthonyWJones
13
Przeglądarka internetowa z funkcją buforowania obiektów (sprawdź katalog pamięci podręcznej przeglądarki). HTTP to protokół obejmujący dyrektywy od serwerów do klientów (proxy, przeglądarki, pająki itp.) Sugerujący kontrolę pamięci podręcznej.
tzot
13

W Laravel (PHP) możemy to zrobić w następujący przejrzysty i elegancki sposób (używając znacznika czasu modyfikacji pliku):

<script src="{{ asset('/js/your.js?v='.filemtime('js/your.js')) }}"></script>

I podobnie w CSS

<link rel="stylesheet" href="{{asset('css/your.css?v='.filemtime('css/your.css'))}}">

Przykładowe dane wyjściowe HTML ( filemtimeczas powrotu jako znacznik czasu Unix )

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">
Kamil Kiełczewski
źródło
Jakie są dane wyjściowe tego polecenia w HTML? A jeśli muszę odnowić tylko wersje takie jak? V = 3,? V = 4 itd. - Nie zmusza przeglądarki do ładowania css za każdym razem, gdy użytkownik wejdzie na stronę
Gediminas
filemtime : „Ta funkcja zwraca czas, w którym zapisywane były bloki danych pliku, czyli czas, w którym zawartość pliku została zmieniona.” src: php.net/manual/en/function.filemtime.php
Kamil Kiełczewski
11

RewriteRule wymaga niewielkiej aktualizacji plików js lub css, które na końcu zawierają wersję z notacją kropkową. Np. Json-1.3.js.

Dodałem klasę negacji kropki [^.] Do wyrażenia regularnego, więc .number. jest ignorowany.

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
Nick Johnson
źródło
2
Dzięki za wkład! Odkąd napisałem ten post, też mnie to spaliło. Moim rozwiązaniem było przepisanie tylko wtedy, gdy ostatnia część nazwy pliku zawiera dokładnie dziesięć cyfr. (10 cyfr obejmuje wszystkie znaczniki czasu od 9 września 2001 r. Do 11/20/2286). Zaktualizowałem moją odpowiedź, aby uwzględnić ten regex:^(.*)\.[\d]{10}\.(css|js)$ $1.$2
Kip
Rozumiem wyrażenie regularne, ale nie rozumiem, z jakim problemem się [^.]tutaj rozwiązujesz . Poza tym pisanie \dwewnątrz klasy postaci nie ma żadnej korzyści - \d+zrobi to samo. Jak pisał, Twój wzór zostanie dopasowane dowolną liczbę znaków (łapczywie), a następnie dosłownym kropka, to non-kropka, a następnie jeden-or-więcej cyfr, a następnie kropka, a następnie csslub js, a następnie do końca pliku. Brak odpowiednika na wprowadzenie próbki: regex101.com/r/RPGC62/1
mickmackusa
10

W przypadku wersji ASP.NET 4.5 i nowszych można używać grupowania skryptów .

Żądanie http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81dotyczy pakietu AllMyScripts i zawiera parę ciągu zapytania v = r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. Ciąg zapytania v ma token wartości, który jest unikalnym identyfikatorem używanym do buforowania. Dopóki pakiet się nie zmieni, aplikacja ASP.NET będzie żądać pakietu AllMyScripts za pomocą tego tokena. Jeśli dowolny plik w pakiecie ulegnie zmianie, środowisko optymalizacji ASP.NET wygeneruje nowy token, gwarantując, że żądania przeglądarki dotyczące pakietu otrzymają najnowszy pakiet.

Istnieją inne korzyści związane z pakietowaniem, w tym zwiększona wydajność przy pierwszym ładowaniu strony przy minimalizacji.

użytkownik3738893
źródło
Pomóżcie mi, nie zmieniam pliku bundle.config, zmieniam tylko pliki css lub js. Jak mogę rozwiązać problem z buforowaniem?
vedankita kumbhar
10

Oto czyste rozwiązanie JavaScript

(function(){

    // Match this timestamp with the release of your code
    var lastVersioning = Date.UTC(2014, 11, 20, 2, 15, 10);

    var lastCacheDateTime = localStorage.getItem('lastCacheDatetime');

    if(lastCacheDateTime){
        if(lastVersioning > lastCacheDateTime){
            var reload = true;
        }
    }

    localStorage.setItem('lastCacheDatetime', Date.now());

    if(reload){
        location.reload(true);
    }

})();

Powyższe będzie szukać po raz ostatni użytkownik odwiedził twoją stronę. Jeśli ostatnia wizyta miała miejsce przed wydaniem nowego kodu, używa on location.reload(true)do wymuszenia odświeżenia strony z serwera.

Zwykle mam to jako pierwszy skrypt w ramach, <head>więc jest oceniany przed załadowaniem jakiejkolwiek innej treści. Jeśli konieczne jest ponowne załadowanie, użytkownik nie jest prawie zauważalny.

Używam pamięci lokalnej do przechowywania znacznika czasu ostatniej wizyty w przeglądarce, ale możesz dodać pliki cookie do miksu, jeśli chcesz obsługiwać starsze wersje IE.

Lloyd Banks
źródło
Próbowałem czegoś takiego, działa to tylko na przeładowanej stronie, ale jeśli strona ma wiele stron współużytkujących te same css / obrazy, inne strony nadal będą korzystać ze starych zasobów.
DeepBlue
9

Ciekawy post. Po przeczytaniu wszystkich odpowiedzi tutaj w połączeniu z faktem, że nigdy nie miałem żadnych problemów z „fałszywymi” ciągami zapytań (co nie jestem pewien, dlaczego wszyscy tak niechętnie korzystają z tego), wydaje mi się, że to rozwiązanie (które eliminuje potrzebę reguł przepisywania apache jak w zaakceptowanej odpowiedzi) polega na obliczeniu krótkiej wartości skrótu zawartości pliku CSS (zamiast godziny danych pliku) jako fałszywego zapytania.

Spowodowałoby to:

<link rel="stylesheet" href="/css/base.css?[hash-here]" type="text/css" />

Oczywiście rozwiązania datetime również wykonują zadanie w przypadku edycji pliku CSS, ale myślę, że chodzi o zawartość pliku css, a nie o datę pliku, więc po co je pomieszać?

Michiel
źródło
8

W moim rozwoju uważam, że chrom ma świetne rozwiązanie.

https://developer.chrome.com/devtools/docs/tips-and-tricks#hard-reload

Po otwarciu narzędzi programistycznych wystarczy długo kliknąć przycisk odświeżania i puścić po najechaniu myszką na „Opróżnij pamięć podręczną i uruchom ponownie ładowanie”.

To mój najlepszy przyjaciel i jest to bardzo lekki sposób na zdobycie tego, czego chcesz!

Frank Bryce
źródło
A jeśli używasz Chrome jako środowiska programistycznego, innym nieinwazyjnym rozwiązaniem jest wyłączenie pamięci podręcznej: w zębatce Ustawienia możesz unieważnić pamięć podręczną dysku, wybierając opcję „Wyłącz pamięć podręczną” (uwaga: narzędzia DevTools muszą być widoczne / otwarte aby to zadziałało).
Velojet
7

Dzięki w Kip za jego idealne rozwiązanie!

Rozszerzyłem go, aby używał go jako Zend_view_Helper. Ponieważ mój klient wyświetlał swoją stronę na wirtualnym hoście, też ją rozszerzyłem.

Mam nadzieję, że pomoże to również komuś innemu.

/**
 * Extend filepath with timestamp to force browser to
 * automatically refresh them if they are updated
 *
 * This is based on Kip's version, but now
 * also works on virtual hosts
 * @link http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
 *
 * Usage:
 * - extend your .htaccess file with
 * # Route for My_View_Helper_AutoRefreshRewriter
 * # which extends files with there timestamp so if these
 * # are updated a automatic refresh should occur
 * # RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
 * - then use it in your view script like
 * $this->headLink()->appendStylesheet( $this->autoRefreshRewriter($this->cssPath . 'default.css'));
 *
 */
class My_View_Helper_AutoRefreshRewriter extends Zend_View_Helper_Abstract {

    public function autoRefreshRewriter($filePath) {

        if (strpos($filePath, '/') !== 0) {

            // path has no leading '/'
            return $filePath;
        } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . $filePath)) {

            // file exists under normal path
            // so build path based on this
            $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $filePath);
            return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
        } else {

            // fetch directory of index.php file (file from all others are included)
            // and get only the directory
            $indexFilePath = dirname(current(get_included_files()));

            // check if file exist relativ to index file
            if (file_exists($indexFilePath . $filePath)) {

                // get timestamp based on this relativ path
                $mtime = filemtime($indexFilePath . $filePath);

                // write generated timestamp to path
                // but use old path not the relativ one
                return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
            } else {

                return $filePath;
            }
        }
    }

}

Pozdrawiam i dziękuję.

lony
źródło
7

Nie znalazłem metody DOM po stronie klienta, która dynamicznie tworzy element węzła skryptowego (lub css):

<script>
    var node = document.createElement("script"); 
    node.type = "text/javascript";
    node.src = 'test.js?'+Math.floor(Math.random()*999999999);
    document.getElementsByTagName("head")[0].appendChild(node);
</script>
GreQ
źródło
6

Google Chrome ma opcję Przeładowania twardego oraz Opróżnij pamięć podręczną i opcję Przeładowania twardego. Możesz kliknąć i przytrzymać przycisk przeładowania (W trybie inspekcji), aby go wybrać.

rev. Ajithes111
źródło
Aby wyjaśnić, przez "Zbadać Mode", odnosząc się do ich "Dev Narzędzia" aka F12, aka Ctrl + Shift + I, aka ant menu> More Tools> Developer Toolsvel right click> Inspect Element. Jest też ustawienie ukryte gdzieś w narzędziach programistycznych (zapomniałem lokalizacji), aby mocno przeładowywać przy każdym przeładowaniu.
Jonny Asmar,
5

Możesz wymusić „buforowanie dla całej sesji”, jeśli dodasz identyfikator sesji jako dokładny parametr pliku js / css:

<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
<script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>

Jeśli chcesz buforować w całej wersji, możesz dodać kod, aby wydrukować datę pliku lub podobny. Jeśli używasz Javy, możesz użyć niestandardowego tagu, aby wygenerować link w elegancki sposób.

<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>
helios
źródło
5

Załóżmy, że masz plik dostępny w:

/styles/screen.css

możesz albo dołączyć parametr zapytania z informacją o wersji do identyfikatora URI, np .:

/styles/screen.css?v=1234

lub możesz dodać informacje o wersji, np .:

/v/1234/styles/screen.css

IMHO druga metoda jest lepsza w przypadku plików CSS, ponieważ mogą one odnosić się do obrazów przy użyciu względnych adresów URL, co oznacza, że ​​jeśli podasz background-imagepodobny parametr:

body {
    background-image: url('images/happy.gif');
}

jego adres URL będzie w rzeczywistości:

/v/1234/styles/images/happy.gif

Oznacza to, że jeśli zaktualizujesz użyty numer wersji, serwer potraktuje to jako nowy zasób i nie użyje wersji buforowanej. Jeśli oprzesz swój numer wersji na Subversion / CVS / etc. Wersja oznacza, że ​​zmiany w obrazach, do których istnieją odniesienia w plikach CSS, zostaną zauważone. Nie jest to zagwarantowane w pierwszym schemacie, tzn. Adres URL w images/happy.gifstosunku do /styles/screen.css?v=1235to/styles/images/happy.gif , która nie zawiera żadnych informacji o wersji.

Zaimplementowałem rozwiązanie buforowania przy użyciu tej techniki z serwletami Java i po prostu obsługuję żądania /v/*za pomocą serwletu, który deleguje do zasobu podstawowego (tj /styles/screen.css.). W trybie rozwoju I ustawić buforowanie nagłówków, które informują klienta, aby zawsze sprawdzić świeżość zasobu z serwera (to zazwyczaj skutkuje 304 jeśli przekazać Tomcat DefaultServletoraz .css, .jsitp plik nie uległ zmianie), natomiast w trybie rozmieszczania Ustawiam nagłówki z napisem „cache forever”.

Walter Rumsby
źródło
Po prostu dodanie folderu, którego nazwę można zmienić w razie potrzeby, będzie działać, jeśli użyjesz tylko względnych adresów URL. A następnie upewnij się, aby przekierować do właściwego folderu w folderze bazowej, czyli w PHP: <?php header( 'Location: folder1/login.phtml' ); ?>.
Gruber,
1
Przy użyciu drugiej metody zmiana w CSS spowoduje unieważnienie kopii w pamięci podręcznej wszystkich obrazów, do których odnoszą się względne adresy URL, co może, ale nie musi być pożądane.
TomG
5

Możesz po prostu dodać losową liczbę z adresem URL CSS / JS jak

example.css?randomNo=Math.random()
Ponmudi VN
źródło
5

Dla ASP.NET przypuszczam, że następne rozwiązanie z zaawansowanymi opcjami (tryb debugowania / wydania, wersje):

Pliki Js lub Css zawarte w ten sposób:

<script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
<link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />

Global.JsPostfix i Global.CssPostfix jest obliczany w następujący sposób w Global.asax:

protected void Application_Start(object sender, EventArgs e)
{
    ...
    string jsVersion = ConfigurationManager.AppSettings["JsVersion"];
    bool updateEveryAppStart = Convert.ToBoolean(ConfigurationManager.AppSettings["UpdateJsEveryAppStart"]);
    int buildNumber = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Revision;
    JsPostfix = "";
#if !DEBUG
    JsPostfix += ".min";
#endif      
    JsPostfix += ".js?" + jsVersion + "_" + buildNumber;
    if (updateEveryAppStart)
    {
        Random rand = new Random();
        JsPosfix += "_" + rand.Next();
    }
    ...
}
Ivan Kochurkin
źródło
4

Ostatnio rozwiązałem to za pomocą Pythona. Tutaj kod (powinien być łatwy do dostosowania do innych języków):

def import_tag(pattern, name, **kw):
    if name[0] == "/":
        name = name[1:]
    # Additional HTML attributes
    attrs = ' '.join(['%s="%s"' % item for item in kw.items()])
    try:
        # Get the files modification time
        mtime = os.stat(os.path.join('/documentroot', name)).st_mtime
        include = "%s?%d" % (name, mtime)
        # this is the same as sprintf(pattern, attrs, include) in other
        # languages
        return pattern % (attrs, include)
    except:
        # In case of error return the include without the added query
        # parameter.
        return pattern % (attrs, name)

def script(name, **kw):
    return import_tag("""<script type="text/javascript" """ +\
        """ %s src="/%s"></script>""", name, **kw)

def stylesheet(name, **kw):
    return import_tag('<link rel="stylesheet" type="text/css" ' +\
        """%s href="/%s">', name, **kw) 

Ten kod zasadniczo dołącza znacznik czasu pliku jako parametr zapytania do adresu URL. Wywołanie następującej funkcji

script("/main.css")

spowoduje

<link rel="stylesheet" type="text/css"  href="/main.css?1221842734">

Zaletą jest oczywiście to, że nie musisz już nigdy zmieniać kodu HTML, dotknięcie pliku CSS spowoduje automatyczne unieważnienie pamięci podręcznej. Działa bardzo dobrze, a narzut nie jest zauważalny.

Liczba Pi.
źródło
czy os.stat () może stworzyć wąskie gardło?
hoju
@Richard stat może być wąskim gardłem, jeśli dysk jest bardzo wolny, a żądań jest bardzo wiele. W takim przypadku możesz buforować znacznik czasu gdzieś w pamięci i wyczyścić tę pamięć podręczną przy każdym nowym wdrożeniu. Jednak ta złożoność nie będzie konieczna w większości przypadków użycia.
pi.
4

Jeśli używasz git + PHP, możesz ponownie załadować skrypt z pamięci podręcznej za każdym razem, gdy nastąpi zmiana w repozytorium git, używając następującego kodu:

exec('git rev-parse --verify HEAD 2> /dev/null', $gitLog);
echo '  <script src="/path/to/script.js"?v='.$gitLog[0].'></script>'.PHP_EOL;
readikus
źródło
4

Jeśli jesteś programistą, który chce uniknąć buforowania, karta sieci chrom ma opcję wyłączania pamięci podręcznej. W przeciwnym razie możesz to zrobić bez szkieletu renderowania serwera za pomocą dwóch znaczników skryptu.

<script type="text/javascript">
    document.write('<script type="text/javascript" src="myfile.js?q=' + Date.now() + '">
    // can't use myfile.js stuff yet
</script>')
<script type="text/javascript">
    // do something with myfile.js
</script>
astronauty
źródło
4

To pytanie jest bardzo stare i pojawia się, gdy ktoś przejdzie do tego problemu. To nie jest odpowiedź na pytanie, jak chce tego op, ale odpowiedź dla deweloperów z tym problemem podczas opracowywania i testowania. I nie mogę opublikować nowego pytania na ten temat, ponieważ zostanie oznaczony jako duplikat.

Podobnie jak wiele innych, chciałem tylko na krótko usunąć buforowanie.

"keep caching consistent with the file" .. jego sposób jest zbyt trudny

Ogólnie rzecz biorąc, nie mam nic przeciwko ładowaniu więcej - nawet ładowaniu plików, które nie uległy zmianie - w większości projektów - jest praktycznie nieistotne. Podczas opracowywania aplikacji - przeważnie ładujemy z dysku, localhost:port więc - więc ten increase in network trafficproblem nie stanowi problemu zerwania umowy .

Większość małych projektów po prostu się bawi - nigdy nie trafiają do produkcji. więc dla nich nie potrzebujesz nic więcej ...

Jeśli korzystasz z Narzędzi Chrome dla programistów , możesz postępować zgodnie z tą metodą wyłączania buforowania, jak na obrazku poniżej: jak zmusić Chrome do ponownego ładowania buforowanych plików

A jeśli masz problemy z buforowaniem przeglądarki Firefox : jak wymusić przeładowanie zasobów na firefox

jak wyłączyć buforowanie w przeglądarce Firefox podczas programowania Zrób to tylko w fazie rozwoju, potrzebujesz również mechanizmu wymuszającego ponowne załadowanie do produkcji, ponieważ użytkownicy będą używać starych modułów unieważnionych w pamięci podręcznej, jeśli często aktualizujesz aplikację i nie zapewnisz dedykowanego mechanizmu synchronizacji pamięci podręcznej, takiego jak te opisane w odpowiedziach powyżej.

Tak, te informacje są już w poprzednich odpowiedziach, ale wciąż muszę przeprowadzić wyszukiwanie w Google, aby je znaleźć.

Mam nadzieję, że ta odpowiedź jest bardzo jasna i teraz nie musisz.

AIon
źródło
OP zapytał o coś i odpowiedział na coś innego. Nie chodzi o wymuszone ładowanie w trybie lokalnym, ale w produkcji i nie można prosić użytkowników końcowych o wykonanie powyższych czynności w celu wyłączenia pamięci podręcznej itp.
Jitendra Pancholi
3

Wydaje się, że wszystkie odpowiedzi tutaj sugerują jakąś wersjonowanie w schemacie nazewnictwa, który ma swoje wady.

Przeglądarki powinny zdawać sobie sprawę z tego, co buforować, a czego nie buforować, czytając odpowiedź serwera WWW, w szczególności nagłówki http - na jak długo ten zasób jest ważny? czy ten zasób został zaktualizowany od czasu jego ostatniego pobrania? etcetera.

Jeśli wszystko jest skonfigurowane „poprawnie”, po prostu aktualizacja plików aplikacji powinna (w pewnym momencie) odświeżyć pamięć podręczną przeglądarki. Możesz na przykład skonfigurować serwer WWW tak, aby przeglądarka nigdy nie buforowała plików (co jest złym pomysłem).

Bardziej szczegółowe wyjaśnienie tego, jak to działa, znajduje się tutaj https://www.mnot.net/cache_docs/#WORK

pospolity
źródło
3

Po prostu dodaj ten kod, w którym chcesz dokonać twardego przeładowania (wymuś, aby przeglądarka przeładowała buforowane pliki CSS / JS). Zrób to wewnątrz .load, aby nie odświeżył się jak pętla

 $( window ).load(function() {
   location.reload(true);
});
Sandeep Ranjan
źródło
Nie działa w Chrome. Wciąż ładuję zasoby z pamięci podręcznej dysku
Jason Kim
3

Wystarczy użyć kodu po stronie serwera, aby dodać datę pliku ... w ten sposób BĘDZIE on buforowany i ponownie załadowany, gdy plik się zmieni

W ASP.NET

<link rel="stylesheet" href="~/css/custom.css?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/css/custom.css")).ToString(),"[^0-9]", ""))" />

<script type="text/javascript" src="~/js/custom.js?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/js/custom.js")).ToString(),"[^0-9]", ""))"></script>    

Można to uprościć:

<script src="<%= Page.ResolveClientUrlUnique("~/js/custom.js") %>" type="text/javascript"></script>

Dodając metodę rozszerzenia do projektu w celu rozszerzenia strony:

public static class Extension_Methods
{
    public static string ResolveClientUrlUnique(this System.Web.UI.Page oPg, string sRelPath)
    {
        string sFilePath = oPg.Server.MapPath(sRelPath);
        string sLastDate = System.IO.File.GetLastWriteTime(sFilePath).ToString();
        string sDateHashed = System.Text.RegularExpressions.Regex.Replace(sLastDate, "[^0-9]", "");

        return oPg.ResolveClientUrl(sRelPath) + "?d=" + sDateHashed;
    }
}
mikrofon
źródło
2

Sugeruję wdrożenie następującego procesu:

  • wersja plików css / js przy każdym wdrożeniu, na przykład: screen.1233.css (liczba może być wersją SVN, jeśli używasz systemu kontroli wersji)

  • zminimalizuj je, aby zoptymalizować czas ładowania

Dan Burzo
źródło