Nous travaillons actuellement sur un nouveau projet avec des mises à jour régulières, utilisé quotidiennement par l'un de nos clients. Ce projet est développé en utilisant angular 2 et nous sommes confrontés à des problèmes de cache. Nos clients ne voient pas les dernières modifications apportées à leurs machines.
Principalement, les fichiers html/css pour les fichiers js semblent être correctement mis à jour sans trop de problèmes.
Si vous avez trouvé un moyen de le faire, ajoutez simplement une chaîne de requête pour charger vos composants, comme ceci:
@Component({
selector: 'some-component',
templateUrl: `./app/component/stuff/component.html?v=${new Date().getTime()}`,
styleUrls: [`./app/component/stuff/component.css?v=${new Date().getTime()}`]
})
Cela devrait obliger le client à charger la copie du modèle du serveur au lieu de la version ..__ du navigateur. Si vous souhaitez l'actualiser uniquement après un certain temps, vous pouvez utiliser cet ISOString à la place:
new Date().toISOString() //2016-09-24T00:43:21.584Z
Et sous-chaîne de caractères pour que cela ne change qu'après une heure, par exemple:
new Date().toISOString().substr(0,13) //2016-09-24T00
J'espère que cela t'aides
angular-cli résout cela brillamment en fournissant un drapeau --output-hashing
pour la commande build . Exemple d'utilisation:
ng build --aot --output-hashing=all
Bundling & Tree-Shaking fournit des détails et un contexte. En cours d'exécution ng help build
, documente le drapeau:
--output-hashing=none|all|media|bundles (String) Define the output filename cache-busting hashing mode.
aliases: -oh <value>, --outputHashing <value>
Bien que cela ne soit applicable qu'aux utilisateurs de angular-cli , cela fonctionne brillamment et ne nécessite aucune modification du code ni outillage supplémentaire.
Dans chaque modèle html, je viens d'ajouter les balises méta suivantes en haut:
<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">
À ma connaissance, chaque modèle est autonome et n’hérite donc pas des règles de mise en cache Meta No dans le fichier index.html.
J'ai eu un problème similaire avec l'index.html étant mis en cache par le navigateur ou plus délicat par cdn moyen/proxies (F5 ne vous aidera pas).
J'ai cherché une solution qui vérifie à 100% que le client dispose de la dernière version de index.html. Heureusement, j'ai trouvé cette solution de Henrik Peinar:
https://blog.nodeswat.com/automagic-reload-for-clients-after-deploy-with-angular-4-8440c9fdd96c
La solution résout également le cas où le client reste avec le navigateur ouvert pendant des jours, le client vérifie les mises à jour à intervalles et recharge si la nouvelle version est déployée.
La solution est un peu délicate mais fonctionne à merveille:
ng cli -- prod
produit des fichiers hachés avec l'un d'eux appelé main. [hash] .jsComme la solution de Henrik Peinar était pour angular 4, il y avait des changements mineurs, je place également les scripts fixes ici:
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;
}
}
passer à 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);
}
}
}
Le script post-build qui fait la magie, 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);
});
placez simplement le script dans le (nouveau) dossier build et exécutez-le à l'aide de node ./build/post-build.js
après avoir créé le dossier dist à l'aide de ng build --prod
Vous pouvez contrôler le cache du client avec des en-têtes HTTP. Cela fonctionne dans n'importe quel framework web.
Vous pouvez définir les directives pour que ces en-têtes contrôlent avec précision quand et comment activer | désactiver le cache:
Cache-Control
Surrogate-Control
Expires
ETag
(très bon)Pragma
(si vous voulez supporter les anciens navigateurs)Une bonne mise en cache est bonne, mais très complexe, dans tous les systèmes informatiques . Jetez un coup d'œil à https://helmetjs.github.io/docs/nocache/#the-headers pour plus d'informations.