Table des matières

PWA (Progressive Web App)

Documentation Vite-PWA

Une Progressive Web App (PWA) est une application qui combine le meilleur du web et du mobile. Elle s'installe sur l'écran d'accueil, fonctionne hors connexion et offre une expérience fluide proche d'une app native.

Configuration Vite

Configuration complète

// vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { VitePWA } from "vite-plugin-pwa";
import path from "path";

export default defineConfig({
  // Réduire les logs en dev
  logLevel: 'warn',

  // Optimiser les dépendances
  optimizeDeps: {
    include: [
      'prop-types',
      'react-signature-canvas',
      // Autres dépendances si nécessaire
    ],
  },

  // Alias pour les imports
  resolve: {
    alias: {
      'src': path.resolve(__dirname, './src'),
      'lib': path.resolve(__dirname, './src/lib'),
    },
  },

  // Plugins
  plugins: [
    react(),
    tailwindcss(),
    VitePWA({
      // Mise à jour automatique du Service Worker
      registerType: 'autoUpdate',

      // Configuration Workbox (cache)
      workbox: {
        // Fichiers à mettre en cache
        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'],
        // Nettoyer les anciens caches
        cleanupOutdatedCaches: true,
        // Activer immédiatement le nouveau SW
        skipWaiting: true,
      },

      // Injection automatique du SW
      injectRegister: "auto",

      // Assets à inclure
      includeAssets: [
        "favicon.ico",
        "favicon.png",
        "apple-touch-icon.png",
        "assets/*"
      ],

      // Manifest de l'application
      manifest: {
        name: "Mon Application SmartMaker",
        short_name: "MonApp",
        description: "Description de mon application",
        start_url: "/",
        display: "standalone",
        background_color: "#ffffff",
        theme_color: "#5fbabf",
        icons: [
          {
            src: 'images/pwa-64x64.png',
            sizes: '64x64',
            type: 'image/png'
          },
          {
            src: 'images/pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'images/pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'any'
          },
          {
            src: 'images/maskable-icon-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'maskable'
          }
        ],
      },
    }),
  ]
});

Structure des fichiers

public/
  favicon.ico
  favicon.png
  apple-touch-icon.png
  images/
    pwa-64x64.png
    pwa-192x192.png
    pwa-512x512.png
    maskable-icon-512x512.png

<note important>Les images doivent être dans le dossier public/ et les chemins dans le manifest sont relatifs à ce dossier.</note>

Icônes PWA

Tailles requises

Taille Usage
64×64 Favicon
192×192 Icône Android/Chrome
512×512 Splash screen, installation
180×180 Apple Touch Icon (iOS)

Icône maskable

L'icône maskable est adaptée aux différentes formes d'icônes (ronde, carrée, etc.) sur Android. La zone de sécurité est un cercle de 80% centré.

Outils pour générer les icônes :

Manifest

Options principales

Option Description
name Nom complet de l'application
shortname | Nom court (affiché sous l'icône) | | description | Description de l'application | | starturl URL de démarrage (généralement /)
display Mode d'affichage (voir ci-dessous)
backgroundcolor | Couleur de fond au lancement | | themecolor Couleur de la barre d'état
icons Tableau des icônes

Modes d'affichage

Mode Description
standalone Comme une app native (recommandé)
fullscreen Plein écran (jeux)
minimal-ui Avec contrôles de navigation minimaux
browser Dans le navigateur normal

Service Worker et Cache

Stratégies de cache Workbox

workbox: {
  // Cache-first pour les assets statiques
  runtimeCaching: [
    {
      urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
      handler: 'CacheFirst',
      options: {
        cacheName: 'google-fonts-cache',
        expiration: {
          maxEntries: 10,
          maxAgeSeconds: 60 * 60 * 24 * 365 // 1 an
        },
        cacheableResponse: {
          statuses: [0, 200]
        }
      }
    },
    {
      // Network-first pour l'API
      urlPattern: /^https:\/\/api\.example\.com\/.*/i,
      handler: 'NetworkFirst',
      options: {
        cacheName: 'api-cache',
        expiration: {
          maxEntries: 100,
          maxAgeSeconds: 60 * 60 // 1 heure
        },
        networkTimeoutSeconds: 10
      }
    }
  ]
}

Stratégies disponibles

Stratégie Usage
CacheFirst Assets statiques (fonts, images)
NetworkFirst API, données dynamiques
StaleWhileRevalidate Contenu qui peut être légèrement obsolète
NetworkOnly Toujours réseau (authentification)
CacheOnly Uniquement cache (assets embarqués)

Mode Offline

Détecter la connexion

import { useEffect, useState } from 'react';

export const useOnlineStatus = () => {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline;
};

Afficher un indicateur offline

import { useOnlineStatus } from './hooks/useOnlineStatus';

const App = () => {
  const isOnline = useOnlineStatus();

  return (
    <div>
      {!isOnline && (
        <div className="bg-yellow-500 text-white p-2 text-center">
          Mode hors connexion
        </div>
      )}
      {/* ... */}
    </div>
  );
};

Synchronisation avec useDb

Utilisez useDb pour stocker les données localement et synchroniser quand la connexion revient :

import { useDb } from '@cap-rel/smartcommon';

const db = useDb({
  name: 'myApp',
  stores: {
    pendingSync: 'id++, action, data, createdAt'
  }
});

// Stocker une action en attente
const saveOffline = async (action, data) => {
  await db.pendingSync.add({
    action,
    data,
    createdAt: Date.now()
  });
};

// Synchroniser quand en ligne
const syncPending = async (api) => {
  const pending = await db.pendingSync.toArray();

  for (const item of pending) {
    try {
      await api.private.post(item.action, { json: item.data });
      await db.pendingSync.delete(item.id);
    } catch (error) {
      console.error('Sync failed:', error);
    }
  }
};

Build et déploiement

Commandes

# Build de production
npm run build

# Prévisualiser le build
npm run preview

# Le build génère:
# - dist/index.html
# - dist/assets/*.js
# - dist/assets/*.css
# - dist/sw.js (Service Worker)
# - dist/manifest.webmanifest

Vérification

Après le build, vérifiez dans les DevTools :

Déployer sur Dolibarr

Copiez le contenu de dist/ dans le dossier pwa/ de votre module Dolibarr :

# Depuis le dossier mobile/
npm run build
cp -r dist/* ../pwa/

Ou utilisez le Makefile fourni par SmartBoot :

make pwa

Tester la PWA

Lighthouse

  1. Ouvrez les DevTools (F12)
  2. Allez dans l'onglet “Lighthouse”
  3. Sélectionnez “Progressive Web App”
  4. Lancez l'audit

Critères de validation

Voir aussi