Le contexte

Maintenant que j'ai installé les outils indispensable au développement HTML5 (node.js, npm, bower et yeoman actuellement), j'ai commencé à me faire la main en utilisant le générateur FountainJs et son exemple d'application "todo list".

Après avoir ajouté une sauvegarde en utilisant le stockage local, il me fallait rendre l'application disponible hors connexion (pas de réseau sous un tunnel dans le RER par exemple...) afin de pouvoir utiliser ce pense-bête sans contrainte de connectivité.

J'allais partir sur les "appcache" quand je suis tombé sur les pages de Mozilla indiquant que cette technique était considérée comme "dépréciée" et qu'il fallait plutôt utiliser les Service Workers pour les nouveaux projets. En dehors de la complexité plus élevée que appcache, la contrainte d'avoir un site en HTTPS n'est pas un problème pour moi, puisque le serveur sur lequel je déploie l'application —un Raspberry Pi— répond exclusivement à ce genre de requête.

Donc le seul obstacle qu'il reste, c'est la mise en œuvre, et pour ça, j'ai simplement cherché un plugin utilisable avec gulp (l'outil de fabrique et d'empaquetage mis en place par le modèle de FountainJs) pour faire le gros du travail. J'ai finalement choisi sw-precache (Software Worker Precache) qui m'a l'air assez poussé, bien documenté, tout en restant simple pour mon petit projets : je mets tous mes fichiers en cache.

Générer le Service Worker

Il n'y a pas de difficulté particulière, mais il va falloir exclure le fichier généré de la tâche de vérification du code avec eslint. Le service worker sera généré dans le fichier sw-app-cache.js.

Récupérer sw-precache

npm install --save-dev sw-precache

Créer les tâches gulp

// variables de configurations (gulp.conf.js)
/**
 * precache global conf values
 */
exports.precacheSpecs = {
  target: 'sw-app-cache.js',
  staticFilesGlob: '/**/*.{js,html,css,png,jpg,gif,svg,eot,ttf,woff}'
};



// définition des tâches (nouveau fichier : precache.js)
// noter la distinction entre l'environnement de développement et l'environnement d'utilisation
const gulp = require('gulp');
const path = require('path');
const swPrecache = require('sw-precache');

const conf = require('../conf/gulp.conf');

// gulp tasks declaration
gulp.task('precache', precache);
gulp.task('precache:dist', precacheDist);


function precache(done) {
  doPrecache(conf, conf.path.src()+'/', '/');
  done();
}

function precacheDist(done) {
  doPrecache(conf, conf.path.dist()+'/', conf.baseHref.default);
  done();
}

function doPrecache(conf, rootDir, appPrefix)
{
  swPrecache.write(path.join(rootDir, conf.precacheSpecs.target), {
    staticFileGlobs: [rootDir + conf.precacheSpecs.staticFilesGlob],
    stripPrefix: rootDir,
    replacePrefix: appPrefix
  });
}


// Inscription des tâches dans la fabrique (gulpfile.js)
// on met à jour les tâche "build" et "serve"
gulp.task('build', gulp.series('partials', gulp.parallel('inject', 'other'), 'build', 'precache:dist'));
gulp.task('serve', gulp.series('inject', 'precache', 'watch', 'browsersync'));

Exclure le fichier de la vérification de code

Cette étape est indispensable pour ne pas bloquer les tâches gulp. Dans mon cas, j'ai donc patché ma tâche scripts comme suit :

diff --git a/gulp_tasks/scripts.js b/gulp_tasks/scripts.js
index f3d2482..5044c2b 100644
--- a/gulp_tasks/scripts.js
+++ b/gulp_tasks/scripts.js
@@ -1,14 +1,20 @@
 const gulp = require('gulp');
 const eslint = require('gulp-eslint');
+const filter = require('gulp-filter');
 
 const conf = require('../conf/gulp.conf');
 
 gulp.task('scripts', scripts);
 
 function scripts() {
+  // do not lint the generated service worker
+  const jsFilter = filter(conf.path.tmp('**/*.js','!sw-app-cache.js'), {restore: true});
+
   return gulp.src(conf.path.src('**/*.js'))
+    .pipe(jsFilter)
     .pipe(eslint())
     .pipe(eslint.format())
+    .pipe(jsFilter.restore)
 
     .pipe(gulp.dest(conf.path.tmp()));
 }

Utiliser le Service Worker généré

Maintenant que la génération du service worker est en ordre de marche, on doit écrire le code qui le référence pour que le navigateur puisse l'utiliser.

J'ai repris le code de démonstration disponible, auquel j'ai rajouté les directives suivantes pour désactiver les plaintes de eslint (on n'est pas dans le code angular de mon application, donc je me permets de le faire) :

/* eslint default-case: "off", angular/log: "off" */