layout: true --- class: middle, center, cover background-image: url(../_images_bateau/mouillage_rocher.jpeg) .glazed[ # Développer pour le mode hors-ligne avec le web natif ] --- class: middle
Aleth Gueguen conseil IT
Consultante indépendante depuis 2006
Je développe des logiciels sur mesure pour les PME
--- class: middle, center # Qui fait des app mobiles pro ? -- # Qui fait des app mobiles pro qui fonctionnent en mode hors-ligne ? --- class: its-web # Le Web mobile -- ## C'est du web .its-web[.absolute.screenshot[].absolute.html-css[]] -- ## C'est du mobile .noop[.contraintes[.absolute.imply[] très fortes contraintes] .maitrise[ maitrise sur le terminal dans lequel s'éxécute l'app ] ] --- class: center # Web mobile .dflex[ .text-left.dflex.flexcol[ Limité en ressources machines peu puissantes pb de perf Autonomie batterie ] .flex-1.max90[ .small[infrequently.org/2024/01/performance-inequality-gap-2024/#mobile] ]] ??? dès qu'il y a bcp de code JS Il faut parser et exécuter --- class: middle, center # Accès réseau ## La 5G partout, tout le temps ça n'éxiste pas --- class: center, middle,bkg-vertical-left background-image: url(../_images_bateau/bateau+PV.jpg) .mon-lab.absolute[ # Mon lab R&D ] .absolute.pv100w[Paneau solaire 100W] .absolute.pv-arrow[ ] .absolute.pas-partout[Réseau = pas partout] .absolute.carte-manche[ ] --- class: middle, center # Ressources limités .relative.imply-1[] HTML first -- .absolute.ami[ ## HTML est ton ami] ## Déclaratif -- Décrit le métier -- / UX inclus -- “The UX of HTML” .mauto.w87[ > a well considered user experience starts with well considere HTML vasilis.nl/nerd/the-ux-of-html/ ] ??? Le navigateur est un logiciel énorme (mm si en mobile il n'ya pas les devtools) mais incroyablement bien optimisé pour principalement afficher du HTML --- class: img72  .absolute.nav[⬅️ Navigateur optimisé pour parser et afficher du HTML] .absolute.arrow.resized[] .absolute.mille[1000 × < tr >] --- class: middle, center, audity # HTML first = Performances  .absolute.inputs[] ??? Foudroyant ex Audity perf était déterminant/mandatory --- class: middle, center  # Ce qui a tout changé ??? Rappel comment ça fonctionne ? --- class: center .absolute.dom[ ] -- .absolute.SW[ ] -- .absolute.cache[ ] -- .absolute.internet[ ] -- .absolute.arrow-cache[ ] -- .absolute.no-cache[ ] --- class: cover background-image: url(../_images_bateau/dock_santander.jpg) ## Un service Worker intercepte les requêtes HTTP -- DONC ... ??? Une fois qu'on a compris ce que ça fait, comment ça fonctionne, --- .mtvb.relative[.noop[### On peut servir une URL en local] .absolute.imply[] .absolute.bye[] .absolute.router[router] ] -- .mtb.relative[ .noop[### Si on peut naviquer et changer de page sans faire un A/R vers un serveur distant].absolute.bye-SPA[].absolute.hello-MPA[] ] --- .mtvb.recharge[ ### Si on change/recharge une nouvelle page par action .context[on réinitialise le contexte .imply[] bye bye memory leaks ] ] -- .mtb.relative[ .noop[### Si on peut changer de page pour chaque action .absolute.bye-state[] .state[maintenir l’état]]] ??? Le state est représenté par le HTML --- class: center # Offline first = Mini backend pour 1 personne  .absolute.arrow[⬇️] .absolute.idb[IndexedDB → github.com/jakearchibald/idb] ??? “IndexedDB with usability” --- class: bkg-marche background-image: url(../_images_bateau/scilly-2023.jpg) .noop[ ] .text-left.font-big[ 1. Export CSV 2. local → distant (one way) (json, binary) 3. Résolution conflit manuelle 2. optimistic locking 3. json-p 4. DexieCloud 4. intent based → Replicache, Kinto 5. CRDT / OT ] .text-left.smaller.sw[SW Background-sync (pas toujours fiable)] ??? SW background-sync, pas trop fiable --- .dflex.fluid[ 
] .check-list[ - check existence sur le serveur avant sync - Envoi de tous les fichiers dans le même cycle → Promise.allSettled - formdata.append('aPhoto', File, 'my-photo.jpg') SW/iOS 🚫 ]
---
↠
## JS : langage événementiel
--- class: center # data- attribute pour gérer les events
--- class: center, phare background-image: url(../_images_bateau/combo-mini-phare-ilot.jpg) # Light DOM custom element .m0[ .small[meyerweb.com/eric/thoughts/2023/11/01/blinded-by-the-light-dom] ] .dflex[ .text-left[ ```html
``` ] .text-left[ ```js export default class importPhoto extends HTMLElement { connectedCallback() { this.displayElt = document.getElementById( this.getAttribute('display-elt')) this.fileInput = document.querySelector( 'input[name=photos']) this.addEventListener('change', this) } handleEvent(event) { // do stuff } } ``` ] ] --- # ES Module
↠
Controller js .dflex[ .text-left[ ```js //public/js/pages/myPage.js // 1. imports import * as iDBStorage from './libs/iDBStorage.js' import importPhoto from './components/importPhoto.js' // 2. cache DOM refs const elts = { myEl: _d.qs('[data-someting]'), // ... } // 3. const, let + ‘theThing’ // ... // 4. Initialize web components customElements.define('import-photo', importPhoto) ``` ] .text-left[ ```js // 5. Templates function function entryRowTmpl(row) { return `
${row.col1}
${row.col2}
${row.col3}
Menu
` } // 6. eventHandlers // 7. eventListener // 8. init() function init() { // get theThing from iDBStorage // doStuff } // 10. local utility functions ``` ] ] ??? // const, let y compris theThing --- class: middle, center, bkg-bateau, color-white background-image: url(../_images_bateau/bateau-en-marche.jpg) # Ça marche ## Ça va vite ### No framework => Les bugs sont les tiens :D --- .mt0[ ] .noop[ .and[Android] .absolute.pwa[]] .mtb[ .ios[iOS] .absolute.hswa[] ] ??? Différence de traitement entre Android et iOS --- .right[ # exemple de Manifest] .dflex[ .w50.smaller.mt-b[ ```json { "name": "My PWA", "background_color": "#ECEFF1", "display": "standalone", "short_name": "My PWA", "start_url": "/", "theme_color": "#37474F", "icons": [{ "src": "images/splash.jpg", "sizes": "512x512", "type": "image/jpg" }, { "purpose": "maskable", "src": "images/splash.jpg", "sizes": "512x512", "type": "image/jpg" }, { "src": "images/icon.png", "sizes": "192x192", "type": "image/png" }], "scope": "/", "share_target": { "action": "/_share-target", "enctype": "multipart/form-data", "method": "POST", "params": { "files": [{"name": "media", "accept": [ "audio/*", "image/*", "video/*"] }] } }, "file_handlers": [ { "action": "/", "accept": {"text/*": [".txt"]} } ] } ``` ] .w50.right[ ## Installation intégrée pour Android ## Manuelle pour iOS .mtb[### Android uniquement : .mls.smaller.right[ App comme cible de partage Support : Chrome , Samsung, Edge ]] ]] ??? Pas forcé que l'app soit "installée" pour qu'elle marche pas besoin d'avoir un manifest sauf pour déclarer l’app comme cible de partage ---
Quelques enseignements tirés de mes projets
-- .smaller[ Les utilisateurs sont très souvent hors ligne ] -- .smaller[ _Tout_ le monde est inquiet pour sa batterie] -- .smaller[ Le cas d'usage universel : .smaller.mls[attacher des photos, de l'audio, des docs (indexedDB)] ] -- .smaller[ Bien réflechir à l’interface. .smaller.mls[Les boutons sont toujours trop petit]] -- .smaller[ Synchro : ça peut être relativement simple .smaller.mls[Bien gérer les cas où le serveur ne répond pas]] -- .smaller[ Appli business : on maitrise la flotte de devices] -- .smaller[ iOS est devenu le vilain du web] ??? hors ligne pour plein de raison BDD du Web : indexedDB. on peut y stocker des gros fichiers bien penser à l'orga des stores et des index Grosse réflexion sur l'interface. Les gens font des tas de trucs compliqués. Il ont besoin de se servir de l'app poour une action ponctuelle. Faut vraiment pas que ça soit compliqué Auto save 1 must On peut faire de la synchro basique sans mettre en œuvre des technos de ouf Mais faut gérer les cas où le serveur répond pas Testé en conditions pas simple : chantiers de canalisation (=boue) En mer; Pas de réseau pendant 2 à jours voir plus. Upload en 4G lente, ça prend du temps. bcp Dans le cas business app, on a la maitrise sur la flotte de device SW top pour MPA --- class: center, middle, bkg-cover, color-white background-image: url(../_images_bateau/rubber-duck.jpg) .mtvb[ #Questions ?] --- class: middle, bkg-cover background-image: url(../_images_bateau/scilly-2023.jpg) .glazed.form.mauto[ .contact-p[Être accompagné] ] .glazed.w25.mauto[ .contact-p[Contact] .contact-p[Aleth Gueguen conseil@alethgueguen.com +33 (0)675 90 86 06 ] ]