web-dev-qa-db-pt.com

Como impedir o cache do navegador Angular 2 site?

No momento, estamos trabalhando em um novo projeto com atualizações regulares que estão sendo usadas diariamente por um de nossos clientes. Este projeto está sendo desenvolvido usando angular 2 e estamos enfrentando problemas de cache, ou seja, nossos clientes não estão vendo as alterações mais recentes em suas máquinas.

Principalmente os arquivos html/css para os arquivos js parecem ser atualizados corretamente sem causar muitos problemas.

52
Rikku121

Encontrou uma maneira de fazer isso, basta adicionar uma querystring para carregar seus componentes, assim:

@Component({
  selector: 'some-component',
  templateUrl: `./app/component/stuff/component.html?v=${new Date().getTime()}`,
  styleUrls: [`./app/component/stuff/component.css?v=${new Date().getTime()}`]
})

Isso deve forçar o cliente a carregar a cópia do servidor do modelo em vez do navegador. Se você gostaria de atualizá-lo somente após um determinado período de tempo, você poderia usar este ISOString:

new Date().toISOString() //2016-09-24T00:43:21.584Z

E substring alguns caracteres para que ele só mude depois de uma hora por exemplo:

new Date().toISOString().substr(0,13) //2016-09-24T00

Espero que isto ajude

28
Rikku121

angular-cli resolve isso de forma brilhante, fornecendo um sinalizador --output-hashing para o comando build . Exemplo de uso:

ng build --aot --output-hashing=all

Agrupamento e abanar a árvore fornece alguns detalhes e contexto. Executando ng help build, documenta o sinalizador:

--output-hashing=none|all|media|bundles (String) Define the output filename cache-busting hashing mode.
aliases: -oh <value>, --outputHashing <value>

Embora isso seja aplicável apenas a usuários de angular-cli , ele funciona de forma brilhante e não requer alterações de código ou ferramentas adicionais.

86
Jack

Em cada modelo de html, adiciono as seguintes metatags no topo:

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">

No meu entendimento, cada modelo é independente, portanto, ele não herda a configuração de regras sem cache no arquivo index.html. 

13
Rossco

Eu tive problema semelhante com o index.html sendo armazenado em cache pelo navegador ou mais complicado pelo meio cdn/proxies (F5 não irá ajudá-lo).

Procurei uma solução que verifica 100% que o cliente tem a última versão index.html, por sorte encontrei esta solução por Henrik Peinar:

https://blog.nodeswat.com/automagic-reload-for-clients-after-deploy-with-angular-4-8440c9fdd96c

A solução resolve também o caso em que o cliente fica com o navegador aberto por dias, o cliente verifica as atualizações nos intervalos e recarrega se a versão mais nova for implementada.

A solução é um pouco complicada, mas funciona como um encanto:

  • use o fato de que ng cli -- prod produz arquivos hash com um deles chamado main. [hash] .js
  • criar um arquivo version.json que contenha esse hash
  • crie um serviço VersionCheckService angular que verifique version.json e recarregue, se necessário. 
  • Observe que um script js em execução após a implementação cria para você o version.json e substitui o hash no serviço angular, portanto, nenhum trabalho manual é necessário, mas está executando o post-build.js

Como a solução de Henrik Peinar era para angular 4, houve pequenas alterações, eu também coloquei os scripts fixos aqui:

VersionCheckService:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class VersionCheckService {
    // this will be replaced by actual hash post-build.js
    private currentHash = '{{POST_BUILD_ENTERS_HASH_HERE}}';

    constructor(private http: HttpClient) {}

    /**
     * Checks in every set frequency the version of frontend application
     * @param url
     * @param {number} frequency - in milliseconds, defaults to 30 minutes
     */
    public initVersionCheck(url, frequency = 1000 * 60 * 30) {
        //check for first time
        this.checkVersion(url); 

        setInterval(() => {
            this.checkVersion(url);
        }, frequency);
    }

    /**
     * Will do the call and check if the hash has changed or not
     * @param url
     */
    private checkVersion(url) {
        // timestamp these requests to invalidate caches
        this.http.get(url + '?t=' + new Date().getTime())
            .subscribe(
                (response: any) => {
                    const hash = response.hash;
                    const hashChanged = this.hasHashChanged(this.currentHash, hash);

                    // If new version, do something
                    if (hashChanged) {
                        // ENTER YOUR CODE TO DO SOMETHING UPON VERSION CHANGE
                        // for an example: location.reload();
                        // or to ensure cdn miss: window.location.replace(window.location.href + '?rand=' + Math.random());
                    }
                    // store the new hash so we wouldn't trigger versionChange again
                    // only necessary in case you did not force refresh
                    this.currentHash = hash;
                },
                (err) => {
                    console.error(err, 'Could not get version');
                }
            );
    }

    /**
     * Checks if hash has changed.
     * This file has the JS hash, if it is a different one than in the version.json
     * we are dealing with version change
     * @param currentHash
     * @param newHash
     * @returns {boolean}
     */
    private hasHashChanged(currentHash, newHash) {
        if (!currentHash || currentHash === '{{POST_BUILD_ENTERS_HASH_HERE}}') {
            return false;
        }

        return currentHash !== newHash;
    }
}

mude para o AppComponent principal:

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
    constructor(private versionCheckService: VersionCheckService) {

    }

    ngOnInit() {
        console.log('AppComponent.ngOnInit() environment.versionCheckUrl=' + environment.versionCheckUrl);
        if (environment.versionCheckUrl) {
            this.versionCheckService.initVersionCheck(environment.versionCheckUrl);
        }
    }

}

O script de pós-construção que faz a mágica, post-build.js:

const path = require('path');
const fs = require('fs');
const util = require('util');

// get application version from package.json
const appVersion = require('../package.json').version;

// promisify core API's
const readDir = util.promisify(fs.readdir);
const writeFile = util.promisify(fs.writeFile);
const readFile = util.promisify(fs.readFile);

console.log('\nRunning post-build tasks');

// our version.json will be in the dist folder
const versionFilePath = path.join(__dirname + '/../dist/version.json');

let mainHash = '';
let mainBundleFile = '';

// RegExp to find main.bundle.js, even if it doesn't include a hash in it's name (dev build)
let mainBundleRegexp = /^main.?([a-z0-9]*)?.js$/;

// read the dist folder files and find the one we're looking for
readDir(path.join(__dirname, '../dist/'))
  .then(files => {
    mainBundleFile = files.find(f => mainBundleRegexp.test(f));

    if (mainBundleFile) {
      let matchHash = mainBundleFile.match(mainBundleRegexp);

      // if it has a hash in it's name, mark it down
      if (matchHash.length > 1 && !!matchHash[1]) {
        mainHash = matchHash[1];
      }
    }

    console.log(`Writing version and hash to ${versionFilePath}`);

    // write current version and hash into the version.json file
    const src = `{"version": "${appVersion}", "hash": "${mainHash}"}`;
    return writeFile(versionFilePath, src);
  }).then(() => {
    // main bundle file not found, dev build?
    if (!mainBundleFile) {
      return;
    }

    console.log(`Replacing hash in the ${mainBundleFile}`);

    // replace hash placeholder in our main.js file so the code knows it's current hash
    const mainFilepath = path.join(__dirname, '../dist/', mainBundleFile);
    return readFile(mainFilepath, 'utf8')
      .then(mainFileData => {
        const replacedFile = mainFileData.replace('{{POST_BUILD_ENTERS_HASH_HERE}}', mainHash);
        return writeFile(mainFilepath, replacedFile);
      });
  }).catch(err => {
    console.log('Error with post build:', err);
  });

simplesmente coloque o script na pasta (new) build e execute o script usando node ./build/post-build.js depois de criar a pasta dist usando ng build --prod

0
Aviko

Você pode controlar o cache do cliente com cabeçalhos HTTP. Isso funciona em qualquer estrutura da web.

Você pode definir as diretivas para que esses cabeçalhos tenham um controle refinado sobre como e quando ativar o | cache desativado:

  • Cache-Control
  • Surrogate-Control
  • Expiresname__
  • ETag(muito bom)
  • Pragma(se você quiser suportar navegadores antigos)

Bom cache é bom, mas muito complexo, em todos os sistemas de computador . Dê uma olhada em https://helmetjs.github.io/docs/nocache/#the-headers para mais informações.

0
ranieribt