Impedir que RequireJS armazene em cache os scripts necessários

302

O RequireJS parece fazer algo internamente que armazena em cache os arquivos javascript necessários. Se eu fizer uma alteração em um dos arquivos necessários, tenho que renomear o arquivo para que as alterações sejam aplicadas.

O truque comum de acrescentar um número de versão como um parâmetro de string de consulta ao final do nome do arquivo não funciona com requirejs <script src="jsfile.js?v2"></script>

O que estou procurando é uma maneira de impedir esse cache interno dos scripts necessários do RequireJS sem precisar renomear meus arquivos de script toda vez que eles forem atualizados.

Solução multiplataforma:

Agora, estou usando o urlArgs: "bust=" + (new Date()).getTime()bloqueio automático de cache durante o desenvolvimento e urlArgs: "bust=v2"a produção, onde incremento o número da versão codificada depois de lançar um script necessário atualizado.

Nota:

O @Dustin Getz mencionou em uma resposta recente que as Ferramentas para Desenvolvedor do Chrome eliminarão pontos de interrupção durante a depuração quando os arquivos Javascript forem atualizados continuamente dessa maneira. Uma solução alternativa é escrever um debugger;código para acionar um ponto de interrupção na maioria dos depuradores Javascript.

Soluções específicas de servidor:

Para soluções específicas que podem funcionar melhor no seu ambiente de servidor, como Node ou Apache, consulte algumas das respostas abaixo.

BumbleB2na
fonte
Você tem certeza de que este não é o servidor ou o cliente que está fazendo o cache? (utilizo js necessários há alguns meses e não notamos nada parecido) O IE foi capturado em cache os resultados das ações do MVC, o chrome estava em cache nossos modelos de html, mas todos os arquivos js parecem atualizados quando o cache do navegador é redefinido. Suponho que se você estava procurando fazer uso do cache, mas não pode fazer o habitual, porque os pedidos dos js necessários estavam removendo a cadeia de consulta que poderia causar o problema?
PJUK
Não sei se o RequireJS remove os números de versão anexados assim. Pode ter sido o meu servidor. No entanto, é interessante saber como o RequireJS possui uma configuração de cache-buster, portanto, você pode estar certo ao remover minha versão anexada nums nos arquivos necessários.
BumbleB2na
eu atualizei a minha resposta com uma solução potencial caching
Dustin Getz
Agora posso adicionar o seguinte à litania que publiquei em meu blog esta manhã: codrspace.com/dexygen/… E isto é, não apenas preciso adicionar o bloqueio de cache, mas o require.js ignora uma atualização dura.
Dexygen
Estou confuso sobre o caso de uso disso ... Isso é para recarregar os módulos AMD no front-end ou o quê?
Alexander Mills

Respostas:

457

O RequireJS pode ser configurado para anexar um valor a cada um dos URLs de script para impedir o cache.

Na documentação do RequireJS ( http://requirejs.org/docs/api.html#config ):

urlArgs : argumentos de sequência de consulta adicionais anexados a URLs que o RequireJS usa para buscar recursos. Mais útil para armazenar em cache o busto quando o navegador ou o servidor não está configurado corretamente.

Exemplo, anexando "v2" a todos os scripts:

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

Para fins de desenvolvimento, você pode forçar o RequireJS a ignorar o cache anexando um carimbo de data / hora:

require.config({
    urlArgs: "bust=" + (new Date()).getTime()
});
Phil McCullick
fonte
46
Muito útil, obrigado. Estou usando o urlArgs: "bust=" + (new Date()).getTime()bloqueio automático de cache durante o desenvolvimento e urlArgs: "bust=v2"a produção, onde incremento o número da versão codificada depois de lançar um script necessário atualizado.
BumbleB2na
9
... também como otimizador de desempenho, você pode usar Math.random () em vez de (new Date ()). getTime (). É mais bonito , não cria objetos e um pouco mais rápido jsperf.com/speedcomparison .
Vlad Tsepelev
2
Você pode obtê-lo um pouco menor:urlArgs: "bust=" + (+new Date)
mrzmyr
11
Acho que rebentar o cache todas as vezes é uma péssima idéia. Infelizmente, o RequireJS não oferece outra alternativa. Usamos urlArgs, mas não usamos um carimbo aleatório ou de data e hora para isso. Em vez disso, usamos nosso Git SHA atual, dessa maneira só muda quando implantamos um novo código.
Ivan Torres
5
Como essa variante "v2" pode funcionar na produção, se o arquivo que fornece a cadeia de caracteres "v2" é armazenado em cache? Se eu lançar um novo aplicativo em produção, adicionar "v3" não fará nada, pois o aplicativo continua trabalhando com os arquivos v2 em cache, incluindo a antiga configuração com v2 urlArgs.
Benny Bottema
54

Não use urlArgs para isso!

Exigir que as cargas de script respeitem os cabeçalhos de cache http. (Os scripts são carregados com uma inserção dinâmica <script>, o que significa que a solicitação se parece com qualquer ativo antigo sendo carregado.)

Sirva seus recursos javascript com os cabeçalhos HTTP adequados para desativar o cache durante o desenvolvimento.

O uso de urlArgs do require significa que quaisquer pontos de interrupção definidos por você não serão preservados entre as atualizações; você acaba precisando colocar debuggerinstruções em todo lugar no seu código. Ruim. Eu uso urlArgspara ativos de bloqueio de cache durante as atualizações de produção com o git sha; então, posso definir meus recursos para que sejam armazenados em cache para sempre e garantir que eles nunca terão recursos obsoletos.

No desenvolvimento, eu simulei todas as solicitações de ajax com uma configuração complexa de mockjax e posso servir meu aplicativo no modo somente javascript com um servidor http python de 10 linhas com todo o cache desativado . Isso foi ampliado para um aplicativo "corporativo" bastante grande, com centenas de pontos de extremidade de serviço da web repousantes. Temos até um designer contratado que pode trabalhar com nossa base de código de produção real sem dar a ele acesso ao nosso código de back-end.

Dustin Getz
fonte
8
@ JamesP.Wright, porque (pelo menos no Chrome) quando você define um ponto de interrupção para algo que acontece no carregamento da página e clica em Atualizar, o ponto de interrupção não é atingido porque o URL mudou e o Chrome abandonou o ponto de interrupção. Eu adoraria conhecer uma solução alternativa apenas para o cliente.
de Drew Noakes
1
Obrigado, Dustin. Se você encontrar uma maneira de contornar isso, poste. Enquanto isso, você pode usar debugger;em seu código sempre que quiser que um ponto de interrupção persista.
precisa saber é o seguinte
2
Para quem usa o servidor http no nó (npm instale o servidor http). Você também pode desativar o cache com -c-1 (ou seja, http-server -c-1).
precisa saber é o seguinte
5
Você pode desativar o cache no Chrome para contornar o problema de depuração durante o desenvolvimento: stackoverflow.com/questions/5690269/...
Deepak Joy
5
+1 a !!! NÃO USE urlArgs NA PRODUÇÃO !!! . Imagine o caso em que seu site possui 1000 arquivos JS (sim, é possível!) E a carga deles é controlada pelo JS necessário. Agora você libera a v2 ou seu site, onde apenas alguns arquivos JS são alterados! mas adicionando urlArgs = v2, você força a recarregar todos os 1000 arquivos JS! você pagará muito tráfego! somente os arquivos modificados devem ser recarregados, todos os outros devem ser respondidos com o status 304 (Não modificado).
walv
24

A solução urlArgs tem problemas. Infelizmente, você não pode controlar todos os servidores proxy que possam estar entre você e o navegador da web do usuário. Infelizmente, alguns desses servidores proxy podem ser configurados para ignorar os parâmetros de URL ao armazenar arquivos em cache. Se isso acontecer, a versão errada do seu arquivo JS será entregue ao seu usuário.

Finalmente desisti e implementei minha própria correção diretamente no require.js. Se você estiver disposto a modificar sua versão da biblioteca requirejs, esta solução poderá funcionar para você.

Você pode ver o patch aqui:

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

Uma vez adicionado, você pode fazer algo assim em sua configuração de requisição:

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

Use o sistema de construção ou o ambiente do servidor para substituir buildNumber por um ID de revisão / versão do software / cor favorita.

Usando exigem assim:

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

Causará exigir para solicitar este arquivo:

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

Em nosso ambiente de servidor, usamos regras de reescrita de URL para remover o buildNumber e servir o arquivo JS correto. Dessa forma, não precisamos nos preocupar em renomear todos os nossos arquivos JS.

O patch ignorará qualquer script que especifique um protocolo e não afetará nenhum arquivo não-JS.

Isso funciona bem para o meu ambiente, mas eu sei que alguns usuários preferem um prefixo ao invés de um sufixo. Deve ser fácil modificar meu commit para atender às suas necessidades.

Atualizar:

Na discussão da solicitação de recebimento, o autor do requirejs sugere que isso pode funcionar como uma solução para prefixar o número da revisão:

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

Eu não tentei isso, mas a implicação é que isso solicitaria o seguinte URL:

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

O que pode funcionar muito bem para muitas pessoas que podem usar um prefixo.

Aqui estão algumas perguntas duplicadas possíveis:

RequireJS e cache de proxy

require.js - Como posso definir uma versão nos módulos necessários como parte da URL?

JBCP
fonte
1
Eu realmente gostaria que sua atualização chegasse à versão oficial do requirejs. O autor principal do requirejs também pode estar interessado (consulte a resposta de Louis acima).
BumbleB2na
@ BumbleB2na - fique à vontade para comentar sobre o PullRequest ( github.com/jrburke/requirejs/pull/1017 ), o jrburke não parecia interessado. Ele sugere uma solução usando um prefixo de nome de arquivo, atualizarei minha resposta para incluir isso.
JBCP 10/02
1
Boa atualização. Eu acho que eu gosto de esta sugestão pelo autor, mas isso é só porque eu tenho usado essa convenção de nomenclatura recentemente: /scripts/myLib/v1.1/. Tentei adicionar postfix (ou prefixo) aos meus nomes de arquivos, provavelmente porque é isso que o jquery faz, mas depois de um tempo eu fiquei preguiçoso e comecei a incrementar um número de versão na pasta pai. Eu acho que facilitou a manutenção para mim em um site grande, mas agora você me preocupa com pesadelos de reescrita de URL.
BumbleB2na
1
Os sistemas modernos de front-end apenas reescrevem os arquivos JS e com uma soma MD5 no nome do arquivo e, em seguida, reescrevem os arquivos HTML para usar os novos nomes de arquivos na criação, mas isso fica complicado nos sistemas legados nos quais o código de front-end é servido pelo lado do servidor.
JBCP
é isso funciona quando eu precisar de alguns js dentro do arquivo jspx ?, como este<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

Inspirados no cache Expirar em require.js data-main , atualizamos nosso script de implantação com a seguinte tarefa 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>

Como é o início do main.js:

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

Em produção

urlArgs pode causar problemas!

O principal autor do requirejs prefere não usarurlArgs :

Para ativos implementados, prefiro colocar a versão ou o hash para toda a compilação como um diretório de compilação, depois modifique a baseUrlconfiguração usada para o projeto para usar esse diretório com versão como baseUrl. Em seguida, nenhum outro arquivo é alterado e ajuda a evitar alguns problemas de proxy nos quais eles não podem armazenar em cache um URL com uma string de consulta.

[Styling mine.]

Eu sigo este conselho.

Em desenvolvimento

Prefiro usar um servidor que armazene em cache de maneira inteligente os arquivos que podem mudar com freqüência: um servidor que emite Last-Modifiede responde If-Modified-Sincecom 304 quando apropriado. Mesmo um servidor baseado no conjunto expresso do Node para servir arquivos estáticos faz isso imediatamente. Ele não requer nada no meu navegador e não atrapalha os pontos de interrupção.

Louis
fonte
Bons pontos, mas sua resposta é específica para o ambiente do servidor. Talvez uma boa alternativa para quem tropeça nessa seja a recomendação recente para adicionar um número de versão ao nome do arquivo em vez de um parâmetro da string de consulta. Aqui está mais informações sobre esse assunto: stevesouders.com/blog/2008/08/23/...
BumbleB2na
Estamos enfrentando esse problema específico em um sistema de produção. Eu recomendo revisar seus nomes de arquivos em vez de usar um parâmetro.
JBCP
7

Peguei esse trecho do AskApache e coloquei em um arquivo .conf separado do meu servidor web Apache local (no meu caso /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>

Para o desenvolvimento, isso funciona bem, sem a necessidade de alterar o código. Quanto à produção, eu poderia usar a abordagem do @ dvtoever.

myrho
fonte
6

Correção rápida para desenvolvimento

Para o desenvolvimento, você pode simplesmente desativar o cache nas Ferramentas de Desenvolvimento do Chrome ( Desativando o cache do Chrome para desenvolvimento de sites) ). A desativação do cache ocorre apenas se a caixa de diálogo dev tools estiver aberta; portanto, você não precisa se preocupar em alternar essa opção sempre que fizer uma navegação regular.

Nota: Usar ' urlArgs ' é a solução adequada na produção, para que os usuários obtenham o código mais recente. Mas isso dificulta a depuração porque o chrome invalida pontos de interrupção a cada atualização (porque é um arquivo 'novo' sendo veiculado toda vez).

Deepak Joy
fonte
3

Não recomendo usar ' urlArgs ' para armazenamento em cache com RequireJS. Como isso não resolve o problema completamente. A atualização de uma versão no resultará no download de todos os recursos, mesmo que você tenha alterado apenas um único recurso.

Para lidar com esse problema, recomendo o uso de módulos Grunt como 'filerev' para criar a revisão no. Além disso, escrevi uma tarefa personalizada no Gruntfile para atualizar a revisão sempre que necessário.

Se necessário, posso compartilhar o trecho de código para esta tarefa.

Amit Sagar
fonte
Eu uso uma combinação de grunt-filerev e grunt-cache-buster para reescrever arquivos Javascript.
11265 Ian
2

É assim que eu faço no Django / Flask (pode ser facilmente adaptado a outros idiomas / sistemas VCS):

No seu config.py(eu uso isso em python3, então você pode precisar ajustar a codificação em python2)

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

Em seguida, no seu modelo:

{% if config.DEBUG %}
     require.config({urlArgs: "bust=" + (new Date().getTime())});
{% else %}
    require.config({urlArgs: "bust=" + {{ config.GIT_HASH|tojson }}});
{% endif %}
  • Não requer processo de construção manual
  • Só é executado git rev-parse HEADuma vez quando o aplicativo é iniciado e o armazena no configobjeto
Stephen Fuhry
fonte
0

Solução dinâmica (sem urlArgs)

Existe uma solução simples para esse problema, para que você possa carregar um número de revisão exclusivo para cada módulo.

Você pode salvar a função requirejs.load original, substituí-la por sua própria função e analisar seu URL modificado para o requirejs.load original novamente:

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

Em nosso processo de construção, usei "gulp-rev" para criar um arquivo de manifesto com todas as revisões de todos os módulos que estão sendo usados. Versão simplificada da minha tarefa 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('./'));
});

isso gerará um módulo AMD com números de revisão para moduleNames, que está incluído como 'oRevision' no main.js., onde você substitui a função requirejs.load como mostrado anteriormente.

esteira
fonte
-1

Isso é uma adição à resposta aceita por @phil mccull.

Eu uso o método dele, mas também automatizo o processo criando um modelo T4 para ser executado antes da compilação.

Comandos de pré-compilação:

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"

insira a descrição da imagem aqui

Modelo T4:

insira a descrição da imagem aqui

Arquivo gerado: insira a descrição da imagem aqui

Armazene na variável antes que o require.config.js seja carregado: insira a descrição da imagem aqui

Referência em require.config.js:

insira a descrição da imagem aqui

Zach Painter
fonte
2
Provavelmente porque sua solução para um problema de JavaScript é um monte de código C #. Também é um monte de trabalho e código extra para fazer algo que é feito exatamente da mesma maneira que a resposta aceita.
maAAdhaTTah
@mAAdhaTTah Você pode fazer isso com qualquer idioma do servidor ... não precisa ser c #. Além disso, isso automatiza o processo, atualizando a quebra de cache quando você cria uma nova versão do projeto, garantindo que o cliente esteja sempre armazenando em cache a versão mais recente dos scripts. Eu não acho que isso mereça uma redução negativa. Ele está apenas estendendo a resposta, oferecendo uma abordagem automatizada à solução.
Zach Painter
Basicamente, criei isso porque, ao manter um aplicativo que usava o require.js, achei bastante irritante ter que comentar manualmente o "(new Date ()). GetTime ()) e descomentar o cachebuster estático toda vez que atualizava o aplicativo . Fácil de esquecer de repente o cliente está verificando as mudanças e vendo roteiro em cache para que eles pensam nada mudou .. Tudo porque você simplesmente se esqueceu de mudar o cachebuster este pouco de código extra apaga a chance de isso acontecer...
Zach Painter
2
Não anotei, não sei o que realmente aconteceu. Estou apenas sugerindo o código que não é apenas js, mas uma linguagem de modelagem em C #, não será tão útil para desenvolvedores de JS tentando obter uma resposta para o problema deles.
MAAdhaTTah
-2

No meu caso, eu queria carregar o mesmo formulário cada vez que clicar, não queria que as alterações feitas no arquivo permanecessem. Pode não ser relevante para esta postagem exatamente, mas pode ser uma solução em potencial no lado do cliente sem definir a configuração para require. Em vez de enviar o conteúdo diretamente, você pode fazer uma cópia do arquivo necessário e manter o arquivo real intacto.

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