Le moteur JavaScript de Safari présente une faille cachée : lorsqu'un script d'entrée de Web Worker de type module est importé ailleurs dans le bundle, Safari exécute ce script d'entrée une seconde fois. Cette exécution en double rompt l'état des singletons, sabotant silencieusement les workers qui s'appuient sur des caches partagés ou des objets à instance unique.
Le problème est apparu lors du développement d'une application de traitement vidéo basée sur le navigateur qui utilise des Web Workers pour décoder des fichiers ProRes. Chrome et Firefox géraient le code sans incident, mais Safari échouait systématiquement à charger la vidéo. La console ne rapportait qu'une erreur générique « cannot read video », alors que le véritable coupable était le code d'initialisation du worker qui s'exécutait deux fois, laissant deux copies indépendantes du même module en mémoire.
Comment le bug se manifeste
Les bundlers modernes (Vite, Rollup, etc.) extraient souvent des utilitaires partagés dans le fichier d'entrée du worker afin que les chunks chargés en différé puissent réimporter ce code depuis le point d'entrée. Dans les navigateurs qui respectent le comportement standard du chargeur de modules, une fois le module d'entrée instancié, le chargeur renvoie le même objet module à tout import ultérieur, empêchant une seconde exécution.
Safari s'écarte de cette attente. Lorsqu'un chunk chargé en différé importe le fichier d'entrée du worker, Safari traite l'import comme une nouvelle requête de module et réexécute le script d'entrée. Le résultat est deux instances distinctes de chaque variable, classe ou singleton défini à cet endroit.
Ce qui casse lorsque l'entrée s'exécute deux fois
- Les singletons et les caches ne partagent plus de données ; une copie voit un cache vide tandis que l'autre le remplit.
- Les registres (par exemple, une liste de gestionnaires de messages) se retrouvent divisés entre les deux instances, laissant un côté pratiquement vide.
- Les écouteurs d'événements sont attachés deux fois, ce qui peut entraîner un traitement en double ou une explosion de la mémoire.
- Les modules WebAssembly (WASM) sont chargés deux fois, gaspillant ainsi la bande passante et le temps d'initialisation.
- L'échec est silencieux : aucune exception non capturée n'est levée, seule la logique en aval qui dépend de l'état manquant se comporte de manière erratique.
Détecter le problème dans un projet
Un rapide grep sur les assets compilés peut révéler si un chunk importe le fichier d'entrée du worker :
grep -l 'from"./your.worker-' dist/assets/*.js
Si la commande liste des fichiers, ces imports sont probablement à l'origine du bug de double exécution dans Safari.
Solutions de contournement pratiques
Extraire le code partagé de l'entrée du worker.
Configurez le bundler pour placer les bibliothèques communes dans leur propre chunk (par exemple, en utilisantmanualChunksde Rollup). Le worker et tous les modules chargés en différé importeront alors la bibliothèque depuis ce troisième fichier, éliminant ainsi le besoin d'importer le point d'entrée du worker.Utiliser un fichier d'entrée léger.
Réduisez le script d'entrée du worker à une seule ligne qui réexporte la véritable implémentation :// worker-entry.js import("./main.js");Tant qu'aucun autre bundle n'importe
worker-entry.js, Safari ne voit jamais de seconde requête d'import, et l'entrée ne s'exécute donc qu'une seule fois.
Ces deux approches permettent de maintenir l'unicité de l'initialisation du worker dans l'ensemble de l'application.
Pourquoi ce bug est important
Les Web Workers sont un modèle courant pour décharger les calculs lourds — encodage vidéo, traitement d'image, cryptographie — du thread principal. Une division silencieuse de l'état peut transformer une fonctionnalité parfaitement opérationnelle en une défaillance intermittente qui n'apparaît que sur Safari, le navigateur par défaut sur une grande partie des appareils de bureau et mobiles. Comme l'erreur se manifeste par un échec générique de chargement de média, les développeurs peuvent passer des heures à traquer le mauvais symptôme.
Le bug met également en évidence un risque plus large : s'appuyer sur une sémantique de chargeur de modules qui n'est pas implémentée uniformément entre les navigateurs. Lorsqu'une stratégie d'optimisation de bundler suppose une instance de module partagée unique, toute déviation peut briser cette supposition.
Contre-argument et questions ouvertes
Le comportement de Safari s'aligne sur ses propres règles de résolution de modules, qui diffèrent subtilement de la spécification dans des cas limites impliquant des workers. Certains développeurs soutiennent que les bundlers devraient éviter d'inclure du code partagé dans le fichier d'entrée d'un worker, faisant de ce problème une question de discipline lors de la construction plutôt qu'un défaut du navigateur. D'autres soulignent que l'écart de Safari n'est pas documenté, laissant les développeurs sans moyen fiable de l'anticiper.
Apple n'a pas publiquement reconnu le problème, et il n'y a pas de calendrier connu pour un correctif. Tant que Safari ne change pas son chargeur, il incombe aux développeurs de restructurer leurs bundles ou d'ajouter une logique de détection à leurs pipelines CI.
À surveiller ensuite
- Mises à jour des navigateurs – Surveillez les notes de version de Safari pour toute mention de la gestion des module-workers.
- Correctifs de la communauté des bundlers – Vite, Rollup et d'autres pourraient introduire des avertissements ou des stratégies de chunking automatique pour éviter le modèle qui déclenche le bug.
- Pratiques de test – L'intégration de fichiers média réels et de tests de workers full-stack sur Safari avant la mise en production peut permettre de détecter précocement l'échec silencieux.
À retenir
Si vos utilisateurs Safari rencontrent des échecs inexpliqués liés aux workers, vérifiez si un bundle non lié au worker importe le script d'entrée du worker. Le bug de double exécution brise silencieusement l'état du singleton, mais le fait de déplacer le code partagé hors du point d'entrée ou de réduire l'entrée à une simple réexportation restaure le comportement correct sans attendre un correctif du navigateur.
