vue init pwa - qu'avons nous pour 10 lettres ?

publié le

Je n’ai pas encore accordé aux Google I/O 17 le temps de visionnage qu’elles méritent, mais dans mon flux twitter j’ai vu passé cet extrait de la conférence PWA d’Addy Osmani :

Annonce vue init pwa

Je remercie encore @Ladyleet pour avoir transmis l’info en live, car depuis 10 jours j’en rêvais la nuit. Il est grand temps de voir ce que cette commande a dans le ventre, car elle pourrait devenir un sérieux challenger au tout récemment annoncé Polymer2.

Options de la ligne de commande

On part du template webpack et il n’y a aucune surprise, les options sont exactement les mêmes (router, linter, tests unitaires et E2E). D’ailleurs la documentation renvoi au github de webpack et non à celui de PWA, petite coquille qui sera rapidement fixée.

Différences entre les package.json

Outre le titre du projet, il n’y a qu’une différence entre les 2 fichiers package.json :

{
    /*...*/
    },
    "dependencies": {
        "vue": "^2.3.3",
        "vue-router": "^2.3.1"
    },
    "devDependencies": {
        "sw-precache-webpack-plugin": "^0.9.1"
    }
}

J’avoue être un peu déçu sur le principe, une PWA c’est un peu plus que le pre-caching, alors je vous rassure, il y a d’autres différences dans les 2 projets, mais la couleur est annoncée ici : elles vont principalement concerner le pre-caching.

Templates

loader webpack

La partie la plus utile de ce template est selon moi dans la configuration webpack qui n’est certes en rien spécifique à Vue, mais toujours bonne à prendre :

//webpack.prod.conf.js
var webpackConfig = merge(baseWebpackConfig, {
    //...
    plugins: [
        //...
        // service worker caching
        new SWPrecacheWebpackPlugin({
            cacheId: 'my-vue-app',
            filename: 'service-worker.js',
            staticFileGlobs: ['dist/**/*.{js,html,css}'],
            minify: true,
            stripPrefix: 'dist/'
        })
    ]
})

manifest.json

La petite différence qui ne change rien mais qui est indispensable pour obtenir le titre de PWA, l’inclusion :

  • du manifest.json qu’il sera inutile de modifier la plupart du temps
  • des icônes pour les différents devices qu’il faudra bien évidemment remplacer par celles de votre application

La liste des icônes est exhaustive et cela évitera les oublis.

Arborescence manifest.json

index.html

Dernière différence entre les 2 projets, il faut bien évidemment programmer le service worker de pre-caching, et inclure les icônes :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>vueinit</title>
        <link rel="icon" type="image/png" sizes="32x32" href="/static/img/icons/favicon-32x32.png">
        <link rel="icon" type="image/png" sizes="16x16" href="/static/img/icons/favicon-16x16.png">
        <!--[if IE]><link rel="shortcut icon" href="/static/img/icons/favicon.ico"><![endif]-->
        <!-- Add to home screen for Android and modern mobile browsers -->
        <link rel="manifest" href="/static/manifest.json">
        <meta name="theme-color" content="#4DBA87">

        <!-- Add to home screen for Safari on iOS -->
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        <meta name="apple-mobile-web-app-title" content="vueinit">
        <link rel="apple-touch-icon" href="/static/img/icons/apple-touch-icon-152x152.png">
        <!-- Add to home screen for Windows -->
        <meta name="msapplication-TileImage" content="/static/img/icons/msapplication-icon-144x144.png">
        <meta name="msapplication-TileColor" content="#000000">
        <% for (var chunk of webpack.chunks) {
                for (var file of chunk.files) {
                    if (file.match(/\.(js|css)$/)) { %>
        <link rel="<%= chunk.initial?'preload':'prefetch' %>" href="<%= htmlWebpackPlugin.files.publicPath + file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
    </head>
    <body>
        <div id="app">This is your fallback content in case JavaScript fails to load.</div>
        <!-- Todo: only include in production -->
        <script>
            (function() {
                'use strict';

                // Check to make sure service workers are supported in the current browser,
                // and that the current page is accessed from a secure origin. Using a
                // service worker from an insecure origin will trigger JS console errors.
                const isLocalhost = Boolean(window.location.hostname === 'localhost' ||
                        // [::1] is the IPv6 localhost address.
                        window.location.hostname === '[::1]' ||
                        // 127.0.0.1/8 is considered localhost for IPv4.
                        window.location.hostname.match(
                            /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
                        )
                    );

                window.addEventListener('load', function() {
                        if ('serviceWorker' in navigator &&
                                (window.location.protocol === 'https:' || isLocalhost)) {
                            navigator.serviceWorker.register('service-worker.js')
                            .then(function(registration) {
                                // updatefound is fired if service-worker.js changes.
                                registration.onupdatefound = function() {
                                    // updatefound is also fired the very first time the SW is installed,
                                    // and there's no need to prompt for a reload at that point.
                                    // So check here to see if the page is already controlled,
                                    // i.e. whether there's an existing service worker.
                                    if (navigator.serviceWorker.controller) {
                                        // The updatefound event implies that registration.installing is set
                                        const installingWorker = registration.installing;

                                        installingWorker.onstatechange = function() {
                                            switch (installingWorker.state) {
                                                case 'installed':
                                                    // At this point, the old content will have been purged and the
                                                    // fresh content will have been added to the cache.
                                                    // It's the perfect time to display a "New content is
                                                    // available; please refresh." message in the page's interface.
                                                    break;

                                                case 'redundant':
                                                    throw new Error('The installing ' +
                                                                                    'service worker became redundant.');

                                                default:
                                                    // Ignore
                                            }
                                        };
                                    }
                                };
                            }).catch(function(e) {
                                console.error('Error during service worker registration:', e);
                            });
                        }
                });
            })();
        </script>
        <!-- built files will be auto injected -->
    </body>
</html>

Quelques parties sont intéressantes, notamment vis-à-vis de Webpack et la partie gestion localhost. À noter que le script d’enregistrement du worker n’est pas minifiée au build.

Conclusion

Franchement une petite déception, ce template fournit le minimum syndical de la PWA en rendant totalement transparent l’utilisation du pre-cache avec webpack et en donnant un exemple de manifest. Aucun script pour demander l’installation en standalone au bout de la deuxième visite, aucun script pour proposer la gestion des notifications avec par exemple firebase.

Néanmoins, ce minimum syndical rend service, votre pre-cache sera bien plus vite configuré avec ce template que sans, donc si vous n’avez pas besoin des notifications, vous pouvez foncer. Si vous en avez besoin, prenez le webpack de base, et intégrer vous-même sw-toolbox ou autre.