[{"data":1,"prerenderedAt":1698},["ShallowReactive",2],{"post-\u002Ffr\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin":3},{"id":4,"title":5,"body":6,"contentId":1681,"date":1682,"description":1683,"extension":1684,"meta":1685,"navigation":421,"path":1686,"rawbody":1687,"robots":1688,"schemaOrg":1688,"seo":1689,"sitemap":1690,"stem":1696,"__hash__":1697},"posts\u002Ffr\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin.md","Laravel et Vite : le cross-origin ruine l'histoire d'amour",{"type":7,"value":8,"toc":1674},"minimark",[9,16,24,42,47,55,61,70,78,81,88,91,101,160,169,175,178,182,194,200,203,206,209,224,227,382,385,530,541,544,559,563,566,569,577,587,595,598,642,645,689,692,708,711,803,806,809,812,1006,1009,1022,1033,1037,1040,1043,1054,1061,1165,1177,1180,1189,1195,1198,1207,1210,1213,1220,1487,1500,1503,1506,1509,1512,1527,1548,1553,1629,1636,1639,1645,1648,1651,1657,1661,1664,1670],[10,11,12],"p",{},[13,14,15],"em",{},"Ce titre peut sembler dramatique, mais croyez-moi, cela vaut la peine d'être lu. Plongez-y !",[10,17,18,19,23],{},"C'est formidable de revenir sur mon blog. J'ai été absent en raison de nombreuses préparations pour des conférences et d'une mise à jour majeure de ce site web. Bien que ce ne soient pas les sujets du jour, je les aborderai certainement dans de futurs articles. De plus, c'est mon premier article sur ",[20,21,22],"strong",{},"Laravel",", et ce ne sera pas le dernier !",[10,25,26,27,29,30,33,34,41],{},"Aujourd'hui, je souhaite partager une expérience que j'ai vécue avec ",[20,28,22],{}," et ",[20,31,32],{},"Vite"," en raison de la rigueur du ",[35,36,40],"a",{"href":37,"rel":38},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FUsing_web_workers",[39],"nofollow","Web Worker API",".",[43,44,46],"h2",{"id":45},"lintégration-de-laravel-et-vite","L'intégration de Laravel et Vite",[10,48,49,50,52,53,41],{},"Avant de plonger dans le problème, il est crucial de comprendre comment ",[20,51,32],{}," fonctionne dans un projet ",[20,54,22],{},[10,56,57,58,60],{},"D'abord, qu'est-ce que ",[20,59,32],{}," ? C'est un outil de construction frontend rapide qui propulse les applications web de nouvelle génération. Il vous permet de construire vos actifs frontend (JavaScript, CSS, etc.) efficacement, en particulier durant le développement. Le cœur de Vite est un pipeline de plugins qui transforme les fichiers source en sorties requises, un point que nous allons revisiter bientôt.",[10,62,63,64,66,67],{},"Et ",[20,65,22],{}," ? C'est un framework d'application web connu pour sa syntaxe expressive et élégante. Laravel facilite les tâches courantes comme l'authentification, le routage, les sessions et la mise en cache, visant à rendre le développement agréable sans compromettre la fonctionnalité. ",[13,68,69],{},"Et il excelle à ce jeu !",[10,71,72,73,29,75,77],{},"Lorsque vous adoptez ",[20,74,22],{},[20,76,32],{},", vous obtenez une pile redoutable pour le développement d'applications web.",[10,79,80],{},"Ça sonne bien, n'est-ce pas ? Mais comment cela fonctionne-t-il en pratique ? Permettez-moi d'illustrer.",[10,82,83],{},[84,85],"img",{"alt":86,"src":87},"Intégration de Laravel et Vite","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flaravel-vite-request-response.png",[10,89,90],{},"Cette image offre un aperçu de haut niveau de l'architecture de Laravel et Vite. Pendant le développement, deux serveurs fonctionnent : un pour Laravel et un pour Vite.",[92,93,94,98],"ol",{},[95,96,97],"li",{},"Un utilisateur demande une page au serveur Laravel.",[95,99,100],{},"Le serveur Laravel envoie la page HTML à l'utilisateur. Avant d'envoyer la réponse, Laravel ajoute une balise lien au script d'entrée servi par le serveur Vite. Par exemple, vous pourriez trouver cette ligne dans le HTML retourné par le serveur Laravel :",[102,103,108],"pre",{"className":104,"code":105,"language":106,"meta":107,"style":107},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cscript type=\"module\" src=\"http:\u002F\u002Flocalhost:5173\u002Findex.ts\">\u003C\u002Fscript>\n","html","",[109,110,111],"code",{"__ignoreMap":107},[112,113,116,120,124,128,131,134,138,140,143,145,147,150,152,155,157],"span",{"class":114,"line":115},"line",1,[112,117,119],{"class":118},"sMK4o","\u003C",[112,121,123],{"class":122},"swJcz","script",[112,125,127],{"class":126},"spNyl"," type",[112,129,130],{"class":118},"=",[112,132,133],{"class":118},"\"",[112,135,137],{"class":136},"sfazB","module",[112,139,133],{"class":118},[112,141,142],{"class":126}," src",[112,144,130],{"class":118},[112,146,133],{"class":118},[112,148,149],{"class":136},"http:\u002F\u002Flocalhost:5173\u002Findex.ts",[112,151,133],{"class":118},[112,153,154],{"class":118},">\u003C\u002F",[112,156,123],{"class":122},[112,158,159],{"class":118},">\n",[92,161,163,166],{"start":162},3,[95,164,165],{},"Le navigateur de l'utilisateur charge l'HTML et détecte la balise script pointant vers le serveur Vite, puis demande le script au serveur Vite.",[95,167,168],{},"Le serveur Vite livre le script au navigateur de l'utilisateur.",[10,170,171,172],{},"Ce processus est simple et très efficace durant le développement. Il vous donne accès à la pleine puissance de Vite avec le pipeline de plugins et le remplacement à chaud des modules (HMR). ",[13,173,174],{},"De nombreux développeurs pourraient ne pas apprécier pleinement à quel point cette intégration fonctionne sans couture par rapport à d'autres outils.",[10,176,177],{},"Cependant, parfois, les choses se compliquent. Et c'est là que notre histoire commence.",[43,179,181],{"id":180},"le-contexte","Le Contexte",[10,183,184,185,190,191],{},"Pour un projet, j'ai dû utiliser le ",[35,186,189],{"href":187,"rel":188},"https:\u002F\u002Fmicrosoft.github.io\u002Fmonaco-editor\u002F",[39],"Monaco Editor",", un éditeur de code basé sur le navigateur et le noyau de Visual Studio Code. L'objectif était d'afficher un JSON dans l'éditeur de code et de le valider par rapport à un schéma JSON. Les utilisateurs peuvent modifier ce JSON, et l'éditeur Monaco, avec la validation du schéma JSON, affichera en temps réel si le JSON est valide ou suggérera des corrections. Étonnamment efficace quand ça fonctionne ! ",[13,192,193],{},"Et cela s'est avéré beaucoup plus complexe que prévu.",[10,195,196],{},[84,197],{"alt":198,"src":199},"Monaco Editor avec une interface familière si vous utilisez Visual Studio Code","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Fmonaco-editor.png",[10,201,202],{},"Pour parvenir à cela, l'éditeur Monaco nécessite de démarrer quelques Web Workers. Pourquoi et qu'est-ce que les Web Workers ?",[10,204,205],{},"JavaScript, en particulier dans les environnements de navigateur, fonctionne sur un seul fil, ce qui signifie qu'il exécute une tâche à la fois. Donc, si une partie du code prend du temps à s'exécuter, le navigateur devient non réactif, ce qui dégrade l'expérience utilisateur. C'est pourquoi les développeurs web conseillent d'éviter de bloquer le fil principal.",[10,207,208],{},"Pour gérer les tâches longues sans affecter les performances, nous avons deux options :",[210,211,212,218],"ul",{},[95,213,214,217],{},[20,215,216],{},"JavaScript asynchrone"," : Cela implique de décomposer les tâches en morceaux plus petits et de les exécuter de manière asynchrone. De cette façon, le fil principal reste libre pour gérer d'autres tâches.",[95,219,220,223],{},[20,221,222],{},"Web Workers"," : Pour des tâches plus complexes, en particulier celles nécessitant de lourdes calculs, nous utilisons des Web Workers. Un Web Worker est essentiellement un fichier JavaScript s'exécutant en arrière-plan, séparé des autres scripts, avec lequel il peut communiquer via des messages.",[10,225,226],{},"Voici un exemple simple d'un script de Web Worker :",[102,228,233],{"className":229,"code":230,"filename":231,"language":232,"meta":107,"style":107},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","globThis.onmessage = function (e) {\n  console.log('Message reçu du script principal')\n  const workerResult = `Résultat : ${e.data[0] * e.data[1]}`\n  console.log('Envoi du message de retour au script principal')\n  postMessage(workerResult)\n}\n","worker.js","ts",[109,234,235,266,291,343,363,376],{"__ignoreMap":107},[112,236,237,241,243,247,250,253,256,260,263],{"class":114,"line":115},[112,238,240],{"class":239},"sTEyZ","globThis",[112,242,41],{"class":118},[112,244,246],{"class":245},"s2Zo4","onmessage",[112,248,249],{"class":118}," =",[112,251,252],{"class":126}," function",[112,254,255],{"class":118}," (",[112,257,259],{"class":258},"sHdIc","e",[112,261,262],{"class":118},")",[112,264,265],{"class":118}," {\n",[112,267,269,272,274,277,280,283,286,288],{"class":114,"line":268},2,[112,270,271],{"class":239},"  console",[112,273,41],{"class":118},[112,275,276],{"class":245},"log",[112,278,279],{"class":122},"(",[112,281,282],{"class":118},"'",[112,284,285],{"class":136},"Message reçu du script principal",[112,287,282],{"class":118},[112,289,290],{"class":122},")\n",[112,292,293,296,299,301,304,307,310,312,314,317,321,324,327,330,332,334,337,340],{"class":114,"line":162},[112,294,295],{"class":126},"  const",[112,297,298],{"class":239}," workerResult",[112,300,249],{"class":118},[112,302,303],{"class":118}," `",[112,305,306],{"class":136},"Résultat : ",[112,308,309],{"class":118},"${",[112,311,259],{"class":239},[112,313,41],{"class":118},[112,315,316],{"class":239},"data[",[112,318,320],{"class":319},"sbssI","0",[112,322,323],{"class":239},"] ",[112,325,326],{"class":118},"*",[112,328,329],{"class":239}," e",[112,331,41],{"class":118},[112,333,316],{"class":239},[112,335,336],{"class":319},"1",[112,338,339],{"class":239},"]",[112,341,342],{"class":118},"}`\n",[112,344,346,348,350,352,354,356,359,361],{"class":114,"line":345},4,[112,347,271],{"class":239},[112,349,41],{"class":118},[112,351,276],{"class":245},[112,353,279],{"class":122},[112,355,282],{"class":118},[112,357,358],{"class":136},"Envoi du message de retour au script principal",[112,360,282],{"class":118},[112,362,290],{"class":122},[112,364,366,369,371,374],{"class":114,"line":365},5,[112,367,368],{"class":245},"  postMessage",[112,370,279],{"class":122},[112,372,373],{"class":239},"workerResult",[112,375,290],{"class":122},[112,377,379],{"class":114,"line":378},6,[112,380,381],{"class":118},"}\n",[10,383,384],{},"Avec son script principal :",[102,386,389],{"className":229,"code":387,"filename":388,"language":232,"meta":107,"style":107},"const worker = new Worker('worker.js')\n\nworker.addEventListener('message', (e) => {\n  console.log('Message reçu du worker')\n  console.log(e.data)\n})\n\nworker.postMessage([2, 3])\n","main.js",[109,390,391,417,423,456,475,494,501,506],{"__ignoreMap":107},[112,392,393,396,399,401,404,407,409,411,413,415],{"class":114,"line":115},[112,394,395],{"class":126},"const",[112,397,398],{"class":239}," worker ",[112,400,130],{"class":118},[112,402,403],{"class":118}," new",[112,405,406],{"class":245}," Worker",[112,408,279],{"class":239},[112,410,282],{"class":118},[112,412,231],{"class":136},[112,414,282],{"class":118},[112,416,290],{"class":239},[112,418,419],{"class":114,"line":268},[112,420,422],{"emptyLinePlaceholder":421},true,"\n",[112,424,425,428,430,433,435,437,440,442,445,447,449,451,454],{"class":114,"line":162},[112,426,427],{"class":239},"worker",[112,429,41],{"class":118},[112,431,432],{"class":245},"addEventListener",[112,434,279],{"class":239},[112,436,282],{"class":118},[112,438,439],{"class":136},"message",[112,441,282],{"class":118},[112,443,444],{"class":118},",",[112,446,255],{"class":118},[112,448,259],{"class":258},[112,450,262],{"class":118},[112,452,453],{"class":126}," =>",[112,455,265],{"class":118},[112,457,458,460,462,464,466,468,471,473],{"class":114,"line":345},[112,459,271],{"class":239},[112,461,41],{"class":118},[112,463,276],{"class":245},[112,465,279],{"class":122},[112,467,282],{"class":118},[112,469,470],{"class":136},"Message reçu du worker",[112,472,282],{"class":118},[112,474,290],{"class":122},[112,476,477,479,481,483,485,487,489,492],{"class":114,"line":365},[112,478,271],{"class":239},[112,480,41],{"class":118},[112,482,276],{"class":245},[112,484,279],{"class":122},[112,486,259],{"class":239},[112,488,41],{"class":118},[112,490,491],{"class":239},"data",[112,493,290],{"class":122},[112,495,496,499],{"class":114,"line":378},[112,497,498],{"class":118},"}",[112,500,290],{"class":239},[112,502,504],{"class":114,"line":503},7,[112,505,422],{"emptyLinePlaceholder":421},[112,507,509,511,513,516,519,522,524,527],{"class":114,"line":508},8,[112,510,427],{"class":239},[112,512,41],{"class":118},[112,514,515],{"class":245},"postMessage",[112,517,518],{"class":239},"([",[112,520,521],{"class":319},"2",[112,523,444],{"class":118},[112,525,526],{"class":319}," 3",[112,528,529],{"class":239},"])\n",[10,531,532,533,535,536,538,539,41],{},"Dans cet exemple, le script principal crée un nouveau worker et lui envoie un message en utilisant ",[109,534,515],{},". Le worker reçoit et répond avec ",[109,537,515],{},". En raison de la nature événementielle de JavaScript, le script principal écoute le message du worker en utilisant ",[109,540,432],{},[10,542,543],{},"Dans notre cas, l'éditeur Monaco exploite les Web Workers pour certaines fonctionnalités, y compris la validation du schéma JSON.",[545,546,547],"blockquote",{},[10,548,549,552,553,558],{},[112,550,551],{},"!NOTE","\nL'utilisation d'un Web Worker permet à l'éditeur Monaco d'exécuter un serveur ",[35,554,557],{"href":555,"rel":556},"https:\u002F\u002Fmicrosoft.github.io\u002Flanguage-server-protocol\u002F",[39],"LSP"," en arrière-plan pour chaque langage, fournissant des fonctionnalités plus avancées sans impacter le fil principal et l'expérience utilisateur.",[43,560,562],{"id":561},"le-problème","Le Problème",[10,564,565],{},"Mais voici le hic :",[10,567,568],{},"Le script passé au Web Worker doit être accessible depuis la même origine que le script principal. C'est là que le problème surgit.",[545,570,571],{},[10,572,573,576],{},[112,574,575],{},"!WARNING","\nC'est une mesure de sécurité pour prévenir les attaques d'origine croisée, impossible à contourner.",[10,578,579,580,29,583,586],{},"Une origine est définie par le schéma, l'hôte et le port d'une URL. Cela signifie que ",[109,581,582],{},"https:\u002F\u002Fexample.com",[109,584,585],{},"https:\u002F\u002Fexample.com:1024"," sont considérés comme des origines différentes.",[10,588,589,590,592,593,41],{},"Imaginez que nous sommes sur la page ",[109,591,582],{}," et que vous souhaitez créer un Web Worker. L'URL du script passée au Web Worker doit être accessible depuis ",[109,594,582],{},[10,596,597],{},"Cela signifie que cela fonctionne :",[102,599,601],{"className":104,"code":600,"language":106,"meta":107,"style":107},"\u003Cscript>\n  const worker = new Worker('worker.js')\n\u003C\u002Fscript>\n",[109,602,603,611,633],{"__ignoreMap":107},[112,604,605,607,609],{"class":114,"line":115},[112,606,119],{"class":118},[112,608,123],{"class":122},[112,610,159],{"class":118},[112,612,613,615,617,619,621,623,625,627,629,631],{"class":114,"line":268},[112,614,295],{"class":126},[112,616,398],{"class":239},[112,618,130],{"class":118},[112,620,403],{"class":118},[112,622,406],{"class":245},[112,624,279],{"class":239},[112,626,282],{"class":118},[112,628,231],{"class":136},[112,630,282],{"class":118},[112,632,290],{"class":239},[112,634,635,638,640],{"class":114,"line":162},[112,636,637],{"class":118},"\u003C\u002F",[112,639,123],{"class":122},[112,641,159],{"class":118},[10,643,644],{},"Mais cela ne fonctionne pas :",[102,646,648],{"className":104,"code":647,"language":106,"meta":107,"style":107},"\u003Cscript>\n  const worker = new Worker('https:\u002F\u002Fanother-origin.example.com\u002Fworker.js')\n\u003C\u002Fscript>\n",[109,649,650,658,681],{"__ignoreMap":107},[112,651,652,654,656],{"class":114,"line":115},[112,653,119],{"class":118},[112,655,123],{"class":122},[112,657,159],{"class":118},[112,659,660,662,664,666,668,670,672,674,677,679],{"class":114,"line":268},[112,661,295],{"class":126},[112,663,398],{"class":239},[112,665,130],{"class":118},[112,667,403],{"class":118},[112,669,406],{"class":245},[112,671,279],{"class":239},[112,673,282],{"class":118},[112,675,676],{"class":136},"https:\u002F\u002Fanother-origin.example.com\u002Fworker.js",[112,678,282],{"class":118},[112,680,290],{"class":239},[112,682,683,685,687],{"class":114,"line":162},[112,684,637],{"class":118},[112,686,123],{"class":122},[112,688,159],{"class":118},[10,690,691],{},"Voyez-vous maintenant le problème ?",[10,693,694,695,698,699,702,703,29,705,707],{},"Notre serveur Laravel fonctionne sur ",[109,696,697],{},"http:\u002F\u002Flocalhost:8000",", tandis que Vite est sur ",[109,700,701],{},"http:\u002F\u002Flocalhost:5173",". ",[109,704,697],{},[109,706,701],{}," sont deux origines différentes. Cela signifie que le navigateur ne chargera pas le script Web Worker depuis le serveur Vite, entraînant un problème d'origine croisée dû à l'exécution de deux serveurs.",[10,709,710],{},"Dans le navigateur, nous pouvons facilement repérer cette erreur avec le message d'avertissement suivant :",[102,712,716],{"className":713,"code":714,"language":715,"meta":107,"style":107},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","app.js:21 Échec de la construction de 'Worker' : le script à 'http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Flanguage\u002Fjson\u002Fjson.worker.js?worker_file&type=module' ne peut pas être accédé depuis l'origine 'http:\u002F\u002Flocalhost :'.\n","sh",[109,717,718],{"__ignoreMap":107},[112,719,720,724,727,730,733,736,738,741,744,746,749,752,755,758,760,763,765,768,771,774,777,780,783,786,788,791,793,796,798,800],{"class":114,"line":115},[112,721,723],{"class":722},"sBMFI","app.js:21",[112,725,726],{"class":136}," Échec",[112,728,729],{"class":136}," de",[112,731,732],{"class":136}," la",[112,734,735],{"class":136}," construction",[112,737,729],{"class":136},[112,739,740],{"class":118}," '",[112,742,743],{"class":136},"Worker",[112,745,282],{"class":118},[112,747,748],{"class":136}," :",[112,750,751],{"class":136}," le",[112,753,754],{"class":136}," script",[112,756,757],{"class":136}," à",[112,759,740],{"class":118},[112,761,762],{"class":136},"http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Flanguage\u002Fjson\u002Fjson.worker.js?worker_file&type=module",[112,764,282],{"class":118},[112,766,767],{"class":136}," ne",[112,769,770],{"class":136}," peut",[112,772,773],{"class":136}," pas",[112,775,776],{"class":136}," être",[112,778,779],{"class":136}," accédé",[112,781,782],{"class":136}," depuis",[112,784,785],{"class":136}," l",[112,787,282],{"class":118},[112,789,790],{"class":136},"origine ",[112,792,282],{"class":118},[112,794,795],{"class":136},"http:\u002F\u002Flocalhost",[112,797,748],{"class":136},[112,799,282],{"class":118},[112,801,802],{"class":136},".\n",[10,804,805],{},"Cette erreur est un frein. L'éditeur Monaco ne peut pas fonctionner correctement sans le script Web Worker. Cherchons une solution.",[10,807,808],{},"Alerte spoiler, j'ai cherché haut et bas en ligne pour trouver une solution fonctionnelle. Malheureusement, je n'ai rien trouvé. J'ai donc essayé de m'attaquer au problème moi-même. Après de nombreuses recherches et essais, j'ai trouvé une solution que je partage avec vous dans cet article.",[10,810,811],{},"Le code problématique ressemble à cela :",[102,813,815],{"className":229,"code":814,"language":232,"meta":107,"style":107},"import * as monaco from 'monaco-editor'\nimport EditorWorker from 'monaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker'\nimport JsonWorker from 'monaco-editor\u002Fesm\u002Fvs\u002Flanguage\u002Fjson\u002Fjson.worker?worker'\n\nglobalThis.MonacoEnvironment = {\n  getWorker(workerId, label) {\n    switch (label) {\n      case 'json':\n        return new JsonWorker()\n      default:\n        return new EditorWorker()\n    }\n  },\n}\n\n\u002F\u002F Initialisation de l'éditeur Monaco et fonctionnalité de diagnostic\n",[109,816,817,843,859,875,879,893,912,928,943,957,965,977,983,989,994,999],{"__ignoreMap":107},[112,818,819,823,826,829,832,835,837,840],{"class":114,"line":115},[112,820,822],{"class":821},"s7zQu","import",[112,824,825],{"class":118}," *",[112,827,828],{"class":821}," as",[112,830,831],{"class":239}," monaco ",[112,833,834],{"class":821},"from",[112,836,740],{"class":118},[112,838,839],{"class":136},"monaco-editor",[112,841,842],{"class":118},"'\n",[112,844,845,847,850,852,854,857],{"class":114,"line":268},[112,846,822],{"class":821},[112,848,849],{"class":239}," EditorWorker ",[112,851,834],{"class":821},[112,853,740],{"class":118},[112,855,856],{"class":136},"monaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker",[112,858,842],{"class":118},[112,860,861,863,866,868,870,873],{"class":114,"line":162},[112,862,822],{"class":821},[112,864,865],{"class":239}," JsonWorker ",[112,867,834],{"class":821},[112,869,740],{"class":118},[112,871,872],{"class":136},"monaco-editor\u002Fesm\u002Fvs\u002Flanguage\u002Fjson\u002Fjson.worker?worker",[112,874,842],{"class":118},[112,876,877],{"class":114,"line":345},[112,878,422],{"emptyLinePlaceholder":421},[112,880,881,884,886,889,891],{"class":114,"line":365},[112,882,883],{"class":239},"globalThis",[112,885,41],{"class":118},[112,887,888],{"class":239},"MonacoEnvironment ",[112,890,130],{"class":118},[112,892,265],{"class":118},[112,894,895,898,900,903,905,908,910],{"class":114,"line":378},[112,896,897],{"class":122},"  getWorker",[112,899,279],{"class":118},[112,901,902],{"class":258},"workerId",[112,904,444],{"class":118},[112,906,907],{"class":258}," label",[112,909,262],{"class":118},[112,911,265],{"class":118},[112,913,914,917,919,922,925],{"class":114,"line":503},[112,915,916],{"class":821},"    switch",[112,918,255],{"class":122},[112,920,921],{"class":239},"label",[112,923,924],{"class":122},") ",[112,926,927],{"class":118},"{\n",[112,929,930,933,935,938,940],{"class":114,"line":508},[112,931,932],{"class":821},"      case",[112,934,740],{"class":118},[112,936,937],{"class":136},"json",[112,939,282],{"class":118},[112,941,942],{"class":118},":\n",[112,944,946,949,951,954],{"class":114,"line":945},9,[112,947,948],{"class":821},"        return",[112,950,403],{"class":118},[112,952,953],{"class":245}," JsonWorker",[112,955,956],{"class":122},"()\n",[112,958,960,963],{"class":114,"line":959},10,[112,961,962],{"class":821},"      default",[112,964,942],{"class":118},[112,966,968,970,972,975],{"class":114,"line":967},11,[112,969,948],{"class":821},[112,971,403],{"class":118},[112,973,974],{"class":245}," EditorWorker",[112,976,956],{"class":122},[112,978,980],{"class":114,"line":979},12,[112,981,982],{"class":118},"    }\n",[112,984,986],{"class":114,"line":985},13,[112,987,988],{"class":118},"  },\n",[112,990,992],{"class":114,"line":991},14,[112,993,381],{"class":118},[112,995,997],{"class":114,"line":996},15,[112,998,422],{"emptyLinePlaceholder":421},[112,1000,1002],{"class":114,"line":1001},16,[112,1003,1005],{"class":1004},"sHwdD","\u002F\u002F Initialisation de l'éditeur Monaco et fonctionnalité de diagnostic\n",[10,1007,1008],{},"Lorsque Monaco est initialisé, il essaie de charger le script Web Worker à partir du serveur Vite. C'est là que surgit le problème d'origine croisée.",[545,1010,1011],{},[10,1012,1013,1015,1016,1021],{},[112,1014,551],{},"\nDans des frameworks comme ",[35,1017,1020],{"href":1018,"rel":1019},"https:\u002F\u002Fadonisjs.com\u002F",[39],"Adonis",", où Vite sert de middleware, ce problème ne se produit pas car Vite fonctionne au sein de la même application que le serveur. Mais dans Laravel, avec deux serveurs distincts, nous avions besoin d'un contournement.",[545,1023,1024],{},[10,1025,1026,1028,1029,1032],{},[112,1027,551],{},"\nCe problème n'existe pas si les actifs sont construits et chargés depuis ",[109,1030,1031],{},"\u002Fpublic\u002Fbuild",", car tout est servi par le serveur Laravel. Cependant, cela impacte considérablement l'expérience du développeur, nécessitant la reconstruction des actifs et le rafraîchissement de la page pour chaque changement. Cela n'est pas en accord avec l'expérience de Vite ou sa philosophie à la demande.",[43,1034,1036],{"id":1035},"la-solution","La Solution",[10,1038,1039],{},"Nous avons maintenant une compréhension claire du problème et de l'origine de celui-ci. Plongeons dans la solution.",[10,1041,1042],{},"Tout d'abord, examinons l'URL du script worker :",[102,1044,1048],{"className":1045,"code":1046,"language":1047,"meta":107,"style":107},"language-txt shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","monaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker\n","txt",[109,1049,1050],{"__ignoreMap":107},[112,1051,1052],{"class":114,"line":115},[112,1053,1046],{},[10,1055,1056,1057,1060],{},"C'est un script worker - pas destiné à un chargement direct dans le navigateur. Remarquez le paramètre de requête ",[109,1058,1059],{},"?worker",", une indication de Vite pour encapsuler le script pour un usage facile. Nous pouvons inspecter la réponse pour mieux comprendre.",[102,1062,1066],{"className":1063,"code":1064,"language":1065,"meta":107,"style":107},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export default function WorkerWrapper(options) {\n  return new Worker(\n    'http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker.js?worker_file&type=module',\n    {\n      type: 'module',\n      name: options?.name\n    }\n  )\n}\n","js",[109,1067,1068,1090,1102,1115,1120,1136,1152,1156,1161],{"__ignoreMap":107},[112,1069,1070,1073,1076,1078,1081,1083,1086,1088],{"class":114,"line":115},[112,1071,1072],{"class":821},"export",[112,1074,1075],{"class":821}," default",[112,1077,252],{"class":126},[112,1079,1080],{"class":245}," WorkerWrapper",[112,1082,279],{"class":118},[112,1084,1085],{"class":258},"options",[112,1087,262],{"class":118},[112,1089,265],{"class":118},[112,1091,1092,1095,1097,1099],{"class":114,"line":268},[112,1093,1094],{"class":821},"  return",[112,1096,403],{"class":118},[112,1098,406],{"class":245},[112,1100,1101],{"class":122},"(\n",[112,1103,1104,1107,1110,1112],{"class":114,"line":162},[112,1105,1106],{"class":118},"    '",[112,1108,1109],{"class":136},"http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker.js?worker_file&type=module",[112,1111,282],{"class":118},[112,1113,1114],{"class":118},",\n",[112,1116,1117],{"class":114,"line":345},[112,1118,1119],{"class":118},"    {\n",[112,1121,1122,1125,1128,1130,1132,1134],{"class":114,"line":365},[112,1123,1124],{"class":122},"      type",[112,1126,1127],{"class":118},":",[112,1129,740],{"class":118},[112,1131,137],{"class":136},[112,1133,282],{"class":118},[112,1135,1114],{"class":118},[112,1137,1138,1141,1143,1146,1149],{"class":114,"line":378},[112,1139,1140],{"class":122},"      name",[112,1142,1127],{"class":118},[112,1144,1145],{"class":239}," options",[112,1147,1148],{"class":118},"?.",[112,1150,1151],{"class":239},"name\n",[112,1153,1154],{"class":114,"line":503},[112,1155,982],{"class":118},[112,1157,1158],{"class":114,"line":508},[112,1159,1160],{"class":122},"  )\n",[112,1162,1163],{"class":114,"line":945},[112,1164,381],{"class":118},[10,1166,1167,1168,1170,1171,1173,1174,1176],{},"Grâce au paramètre de requête ",[109,1169,1059],{},", Vite encapsule automatiquement le script dans une fonction qui retourne une nouvelle instance de ",[109,1172,743],{},". Donc, lorsque nous importons le script, nous pouvons facilement invoquer la fonction pour démarrer le worker sans avoir à créer manuellement une nouvelle instance de ",[109,1175,743],{}," avec l'URL et les options correctes.",[10,1178,1179],{},"Le worker utilise le script suivant :",[102,1181,1183],{"className":1045,"code":1182,"language":1047,"meta":107,"style":107},"http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker.js?worker_file&type=module\n",[109,1184,1185],{"__ignoreMap":107},[112,1186,1187],{"class":114,"line":115},[112,1188,1182],{},[10,1190,1191,1192,1194],{},"L'origine ",[109,1193,701],{}," appartient au serveur Vite et c'est la racine du problème.",[10,1196,1197],{},"Pour réussir, l'URL du script doit correspondre à l'origine de notre serveur Laravel :",[102,1199,1201],{"className":1045,"code":1200,"language":1047,"meta":107,"style":107},"http:\u002F\u002Flocalhost:8000\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker.js?worker_file&type=module\n",[109,1202,1203],{"__ignoreMap":107},[112,1204,1205],{"class":114,"line":115},[112,1206,1200],{},[10,1208,1209],{},"Mais comment modifier l'URL dynamiquement pour qu'elle corresponde à l'origine du serveur Laravel ?",[10,1211,1212],{},"Avec un plugin Vite ! C'est le cœur de Vite de transformer les fichiers source à la volée. Avec un plugin, nous pourrions intercepter la demande de script worker et ajuster l'URL pour qu'elle corresponde à l'origine du serveur Laravel.",[10,1214,1215,1216,1219],{},"Dans notre ",[109,1217,1218],{},"vite.config.ts",", nous pouvons définir un plugin pour réécrire l'URL du script worker :",[102,1221,1223],{"className":229,"code":1222,"language":232,"meta":107,"style":107},"import { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [\n    (() => {\n      return {\n        name: 'monaco-editor:rewrite-worker',\n        transform(code, id) {\n          if (this.environment.mode !== 'dev') {\n            return\n          }\n\n          if (id.includes('worker')) {\n            return code.replace(\n              '__laravel_vite_placeholder__.test',\n              'localhost:8000',\n            )\n          }\n        },\n      }\n    })(),\n  ],\n})\n",[109,1224,1225,1248,1252,1264,1274,1286,1293,1309,1327,1359,1364,1369,1373,1400,1415,1427,1438,1444,1449,1455,1461,1472,1480],{"__ignoreMap":107},[112,1226,1227,1229,1232,1235,1238,1241,1243,1246],{"class":114,"line":115},[112,1228,822],{"class":821},[112,1230,1231],{"class":118}," {",[112,1233,1234],{"class":239}," defineConfig",[112,1236,1237],{"class":118}," }",[112,1239,1240],{"class":821}," from",[112,1242,740],{"class":118},[112,1244,1245],{"class":136},"vite",[112,1247,842],{"class":118},[112,1249,1250],{"class":114,"line":268},[112,1251,422],{"emptyLinePlaceholder":421},[112,1253,1254,1256,1258,1260,1262],{"class":114,"line":162},[112,1255,1072],{"class":821},[112,1257,1075],{"class":821},[112,1259,1234],{"class":245},[112,1261,279],{"class":239},[112,1263,927],{"class":118},[112,1265,1266,1269,1271],{"class":114,"line":345},[112,1267,1268],{"class":122},"  plugins",[112,1270,1127],{"class":118},[112,1272,1273],{"class":239}," [\n",[112,1275,1276,1279,1282,1284],{"class":114,"line":365},[112,1277,1278],{"class":239},"    (",[112,1280,1281],{"class":118},"()",[112,1283,453],{"class":126},[112,1285,265],{"class":118},[112,1287,1288,1291],{"class":114,"line":378},[112,1289,1290],{"class":821},"      return",[112,1292,265],{"class":118},[112,1294,1295,1298,1300,1302,1305,1307],{"class":114,"line":503},[112,1296,1297],{"class":122},"        name",[112,1299,1127],{"class":118},[112,1301,740],{"class":118},[112,1303,1304],{"class":136},"monaco-editor:rewrite-worker",[112,1306,282],{"class":118},[112,1308,1114],{"class":118},[112,1310,1311,1314,1316,1318,1320,1323,1325],{"class":114,"line":508},[112,1312,1313],{"class":122},"        transform",[112,1315,279],{"class":118},[112,1317,109],{"class":258},[112,1319,444],{"class":118},[112,1321,1322],{"class":258}," id",[112,1324,262],{"class":118},[112,1326,265],{"class":118},[112,1328,1329,1332,1334,1337,1340,1342,1345,1348,1350,1353,1355,1357],{"class":114,"line":945},[112,1330,1331],{"class":821},"          if",[112,1333,255],{"class":122},[112,1335,1336],{"class":118},"this.",[112,1338,1339],{"class":239},"environment",[112,1341,41],{"class":118},[112,1343,1344],{"class":239},"mode",[112,1346,1347],{"class":118}," !==",[112,1349,740],{"class":118},[112,1351,1352],{"class":136},"dev",[112,1354,282],{"class":118},[112,1356,924],{"class":122},[112,1358,927],{"class":118},[112,1360,1361],{"class":114,"line":959},[112,1362,1363],{"class":821},"            return\n",[112,1365,1366],{"class":114,"line":967},[112,1367,1368],{"class":118},"          }\n",[112,1370,1371],{"class":114,"line":979},[112,1372,422],{"emptyLinePlaceholder":421},[112,1374,1375,1377,1379,1382,1384,1387,1389,1391,1393,1395,1398],{"class":114,"line":985},[112,1376,1331],{"class":821},[112,1378,255],{"class":122},[112,1380,1381],{"class":239},"id",[112,1383,41],{"class":118},[112,1385,1386],{"class":245},"includes",[112,1388,279],{"class":122},[112,1390,282],{"class":118},[112,1392,427],{"class":136},[112,1394,282],{"class":118},[112,1396,1397],{"class":122},")) ",[112,1399,927],{"class":118},[112,1401,1402,1405,1408,1410,1413],{"class":114,"line":991},[112,1403,1404],{"class":821},"            return",[112,1406,1407],{"class":239}," code",[112,1409,41],{"class":118},[112,1411,1412],{"class":245},"replace",[112,1414,1101],{"class":122},[112,1416,1417,1420,1423,1425],{"class":114,"line":996},[112,1418,1419],{"class":118},"              '",[112,1421,1422],{"class":136},"__laravel_vite_placeholder__.test",[112,1424,282],{"class":118},[112,1426,1114],{"class":118},[112,1428,1429,1431,1434,1436],{"class":114,"line":1001},[112,1430,1419],{"class":118},[112,1432,1433],{"class":136},"localhost:8000",[112,1435,282],{"class":118},[112,1437,1114],{"class":118},[112,1439,1441],{"class":114,"line":1440},17,[112,1442,1443],{"class":122},"            )\n",[112,1445,1447],{"class":114,"line":1446},18,[112,1448,1368],{"class":118},[112,1450,1452],{"class":114,"line":1451},19,[112,1453,1454],{"class":118},"        },\n",[112,1456,1458],{"class":114,"line":1457},20,[112,1459,1460],{"class":118},"      }\n",[112,1462,1464,1467,1470],{"class":114,"line":1463},21,[112,1465,1466],{"class":118},"    }",[112,1468,1469],{"class":239},")()",[112,1471,1114],{"class":118},[112,1473,1475,1478],{"class":114,"line":1474},22,[112,1476,1477],{"class":239},"  ]",[112,1479,1114],{"class":118},[112,1481,1483,1485],{"class":114,"line":1482},23,[112,1484,498],{"class":118},[112,1486,290],{"class":239},[545,1488,1489],{},[10,1490,1491,1493,1494,1496,1497,1499],{},[112,1492,551],{},"\nIci, nous remplaçons ",[109,1495,1422],{}," au lieu de ",[109,1498,701],{},", car cette URL est généralement gérée par le plugin Laravel Vite.",[10,1501,1502],{},"Avec ce plugin, nous pouvons maintenant charger le script worker de l'éditeur Monaco puisque l'origine correspond à celle du serveur Laravel. En production, l'URL du script sera correcte et fonctionnera comme prévu, donc nous n'avons pas besoin de réécrire l'URL. Les fichiers de construction sont servis depuis la même origine à l'aide d'un seul serveur comme Nginx, Apache ou Caddy.",[10,1504,1505],{},"Cependant, cela pose un autre défi : le script worker est chargé depuis le serveur Laravel, mais seul Vite peut le servir - le serveur Laravel n'est pas au courant de ce script et la demande renverra simplement une erreur 404.",[10,1507,1508],{},"Mais nous avons un contrôle total sur le serveur Laravel. Nous pouvons créer une route pour proxy les demandes vers le serveur Vite, permettant au serveur Laravel de servir le script worker.",[10,1510,1511],{},"Cela semble viable, faisable et suffisamment élégant. Implémentons-le.",[10,1513,1514,1515,1518,1519,1522,1523,1526],{},"Nous pouvons créer une route Laravel, disponible uniquement en développement, pour proxy les demandes vers le serveur Vite. Créons un fichier ",[109,1516,1517],{},"proxy.dev.php"," dans le répertoire ",[109,1520,1521],{},"routes"," de Laravel et incluons-le dans ",[109,1524,1525],{},"routes\u002Fweb.php"," uniquement durant le développement.",[102,1528,1532],{"className":1529,"code":1530,"language":1531,"meta":107,"style":107},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","if (!app()->isProduction()) {\n    require __DIR__ . '\u002Fproxy.dev.php';\n}\n","php",[109,1533,1534,1539,1544],{"__ignoreMap":107},[112,1535,1536],{"class":114,"line":115},[112,1537,1538],{},"if (!app()->isProduction()) {\n",[112,1540,1541],{"class":114,"line":268},[112,1542,1543],{},"    require __DIR__ . '\u002Fproxy.dev.php';\n",[112,1545,1546],{"class":114,"line":162},[112,1547,381],{},[10,1549,1550,1551,748],{},"Ensuite, définissons notre route proxy dans ",[109,1552,1517],{},[102,1554,1556],{"className":1529,"code":1555,"language":1531,"meta":107,"style":107},"\u003C?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\nRoute::get('\u002Fnode_modules\u002F{any}', function ($any) {\n    $url = \"http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F{$any}\";\n    return response()->stream(function () use ($url) {\n        \u002F\u002F Directly stream the remote content without loading it fully into memory\n        readfile($url);\n    }, 200, [\n      'Content-Type' => 'text\u002Fjavascript'\n      'Cache-Control' => 'public, max-age=86400' \u002F\u002F Cache for 1 day\n    ]);\n})->where('any', '(.*)');\n",[109,1557,1558,1563,1567,1572,1576,1581,1586,1591,1596,1601,1606,1611,1619,1624],{"__ignoreMap":107},[112,1559,1560],{"class":114,"line":115},[112,1561,1562],{},"\u003C?php\n",[112,1564,1565],{"class":114,"line":268},[112,1566,422],{"emptyLinePlaceholder":421},[112,1568,1569],{"class":114,"line":162},[112,1570,1571],{},"use Illuminate\\Support\\Facades\\Route;\n",[112,1573,1574],{"class":114,"line":345},[112,1575,422],{"emptyLinePlaceholder":421},[112,1577,1578],{"class":114,"line":365},[112,1579,1580],{},"Route::get('\u002Fnode_modules\u002F{any}', function ($any) {\n",[112,1582,1583],{"class":114,"line":378},[112,1584,1585],{},"    $url = \"http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F{$any}\";\n",[112,1587,1588],{"class":114,"line":503},[112,1589,1590],{},"    return response()->stream(function () use ($url) {\n",[112,1592,1593],{"class":114,"line":508},[112,1594,1595],{},"        \u002F\u002F Directly stream the remote content without loading it fully into memory\n",[112,1597,1598],{"class":114,"line":945},[112,1599,1600],{},"        readfile($url);\n",[112,1602,1603],{"class":114,"line":959},[112,1604,1605],{},"    }, 200, [\n",[112,1607,1608],{"class":114,"line":967},[112,1609,1610],{},"      'Content-Type' => 'text\u002Fjavascript'\n",[112,1612,1613,1616],{"class":114,"line":979},[112,1614,1615],{},"      'Cache-Control' => 'public, max-age=86400'",[112,1617,1618],{}," \u002F\u002F Cache for 1 day\n",[112,1620,1621],{"class":114,"line":985},[112,1622,1623],{},"    ]);\n",[112,1625,1626],{"class":114,"line":991},[112,1627,1628],{},"})->where('any', '(.*)');\n",[545,1630,1631],{},[10,1632,1633,1635],{},[112,1634,551],{},"\nJ'ai fait de mon mieux pour optimiser le proxy afin de diffuser efficacement le contenu sans ajouter trop de surcharge. Si vous pensez à une meilleure façon de le faire, n'hésitez pas à la partager avec moi dans les commentaires.",[10,1637,1638],{},"Et voilà ! Le script worker se charge, et l'éditeur Monaco fonctionne parfaitement.",[10,1640,1641],{},[84,1642],{"alt":1643,"src":1644},"Monaco Editor avec une validation du schéma JSON fonctionnelle","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Fworking-monaco-editor-worker.png",[10,1646,1647],{},"Un défi a été de trouver une solution qui fonctionne bien en développement, lors de la construction, et en production. Cette approche semble être un compromis élégant,simple, efficace et propre. Jusqu'à présent, cela fonctionne parfaitement.",[10,1649,1650],{},"Voici un aperçu de la solution :",[10,1652,1653],{},[84,1654],{"alt":1655,"src":1656},"Architecture de la solution","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flarave-proxying-to-vite.png",[43,1658,1660],{"id":1659},"conclusion","Conclusion",[10,1662,1663],{},"Ce problème a effectivement été un défi. L'intégration de l'éditeur Monaco avec la validation du schéma JSON était essentielle pour notre projet. Malheureusement, le casse-tête des origines croisées et les ressources en ligne rares m'ont contraint à créer ma propre solution. Mais j'en ai trouvé une !",[10,1665,1666,1667,1669],{},"J'espère que cet article aidera ceux qui font face à des problèmes similaires. La clé à retenir : maîtriser vos outils est crucial. Bien que les plugins ",[20,1668,32],{}," puissent sembler intimidants, les comprendre supprime toutes les limites vous permettant de résoudre des problèmes complexes.",[1671,1672,1673],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":107,"searchDepth":268,"depth":268,"links":1675},[1676,1677,1678,1679,1680],{"id":45,"depth":268,"text":46},{"id":180,"depth":268,"text":181},{"id":561,"depth":268,"text":562},{"id":1035,"depth":268,"text":1036},{"id":1659,"depth":268,"text":1660},"98b9e72c-7ae6-4db7-a23a-da2412fbfc38","2025-02-06","Explorez un problème d'intégration entre Laravel et Vite, axé sur les problèmes de cross-origin et résolu grâce à une connaissance approfondie des outils.","md",{},"\u002Ffr\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin","---\ncontentId: 98b9e72c-7ae6-4db7-a23a-da2412fbfc38\ntitle: \"Laravel et Vite : le cross-origin ruine l'histoire d'amour\"\ndescription: \"Explorez un problème d'intégration entre Laravel et Vite, axé sur les problèmes de cross-origin et résolu grâce à une connaissance approfondie des outils.\"\ndate: 2025-02-06\n---\n\n_Ce titre peut sembler dramatique, mais croyez-moi, cela vaut la peine d'être lu. Plongez-y !_\n\nC'est formidable de revenir sur mon blog. J'ai été absent en raison de nombreuses préparations pour des conférences et d'une mise à jour majeure de ce site web. Bien que ce ne soient pas les sujets du jour, je les aborderai certainement dans de futurs articles. De plus, c'est mon premier article sur **Laravel**, et ce ne sera pas le dernier !\n\nAujourd'hui, je souhaite partager une expérience que j'ai vécue avec **Laravel** et **Vite** en raison de la rigueur du [Web Worker API](https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FUsing_web_workers).\n\n## L'intégration de Laravel et Vite\n\nAvant de plonger dans le problème, il est crucial de comprendre comment **Vite** fonctionne dans un projet **Laravel**.\n\nD'abord, qu'est-ce que **Vite** ? C'est un outil de construction frontend rapide qui propulse les applications web de nouvelle génération. Il vous permet de construire vos actifs frontend (JavaScript, CSS, etc.) efficacement, en particulier durant le développement. Le cœur de Vite est un pipeline de plugins qui transforme les fichiers source en sorties requises, un point que nous allons revisiter bientôt.\n\nEt **Laravel** ? C'est un framework d'application web connu pour sa syntaxe expressive et élégante. Laravel facilite les tâches courantes comme l'authentification, le routage, les sessions et la mise en cache, visant à rendre le développement agréable sans compromettre la fonctionnalité. _Et il excelle à ce jeu !_\n\nLorsque vous adoptez **Laravel** et **Vite**, vous obtenez une pile redoutable pour le développement d'applications web.\n\nÇa sonne bien, n'est-ce pas ? Mais comment cela fonctionne-t-il en pratique ? Permettez-moi d'illustrer.\n\n![Intégration de Laravel et Vite](\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flaravel-vite-request-response.png)\n\nCette image offre un aperçu de haut niveau de l'architecture de Laravel et Vite. Pendant le développement, deux serveurs fonctionnent : un pour Laravel et un pour Vite.\n\n1. Un utilisateur demande une page au serveur Laravel.\n2. Le serveur Laravel envoie la page HTML à l'utilisateur. Avant d'envoyer la réponse, Laravel ajoute une balise lien au script d'entrée servi par le serveur Vite. Par exemple, vous pourriez trouver cette ligne dans le HTML retourné par le serveur Laravel :\n\n```html\n\u003Cscript type=\"module\" src=\"http:\u002F\u002Flocalhost:5173\u002Findex.ts\">\u003C\u002Fscript>\n```\n\n3. Le navigateur de l'utilisateur charge l'HTML et détecte la balise script pointant vers le serveur Vite, puis demande le script au serveur Vite.\n4. Le serveur Vite livre le script au navigateur de l'utilisateur.\n\nCe processus est simple et très efficace durant le développement. Il vous donne accès à la pleine puissance de Vite avec le pipeline de plugins et le remplacement à chaud des modules (HMR). _De nombreux développeurs pourraient ne pas apprécier pleinement à quel point cette intégration fonctionne sans couture par rapport à d'autres outils._\n\nCependant, parfois, les choses se compliquent. Et c'est là que notre histoire commence.\n\n## Le Contexte\n\nPour un projet, j'ai dû utiliser le [Monaco Editor](https:\u002F\u002Fmicrosoft.github.io\u002Fmonaco-editor\u002F), un éditeur de code basé sur le navigateur et le noyau de Visual Studio Code. L'objectif était d'afficher un JSON dans l'éditeur de code et de le valider par rapport à un schéma JSON. Les utilisateurs peuvent modifier ce JSON, et l'éditeur Monaco, avec la validation du schéma JSON, affichera en temps réel si le JSON est valide ou suggérera des corrections. Étonnamment efficace quand ça fonctionne ! _Et cela s'est avéré beaucoup plus complexe que prévu._\n\n![Monaco Editor avec une interface familière si vous utilisez Visual Studio Code](\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Fmonaco-editor.png)\n\nPour parvenir à cela, l'éditeur Monaco nécessite de démarrer quelques Web Workers. Pourquoi et qu'est-ce que les Web Workers ?\n\nJavaScript, en particulier dans les environnements de navigateur, fonctionne sur un seul fil, ce qui signifie qu'il exécute une tâche à la fois. Donc, si une partie du code prend du temps à s'exécuter, le navigateur devient non réactif, ce qui dégrade l'expérience utilisateur. C'est pourquoi les développeurs web conseillent d'éviter de bloquer le fil principal.\n\nPour gérer les tâches longues sans affecter les performances, nous avons deux options :\n\n- **JavaScript asynchrone** : Cela implique de décomposer les tâches en morceaux plus petits et de les exécuter de manière asynchrone. De cette façon, le fil principal reste libre pour gérer d'autres tâches.\n\n- **Web Workers** : Pour des tâches plus complexes, en particulier celles nécessitant de lourdes calculs, nous utilisons des Web Workers. Un Web Worker est essentiellement un fichier JavaScript s'exécutant en arrière-plan, séparé des autres scripts, avec lequel il peut communiquer via des messages.\n\nVoici un exemple simple d'un script de Web Worker :\n\n```ts [worker.js]\nglobThis.onmessage = function (e) {\n  console.log('Message reçu du script principal')\n  const workerResult = `Résultat : ${e.data[0] * e.data[1]}`\n  console.log('Envoi du message de retour au script principal')\n  postMessage(workerResult)\n}\n```\n\nAvec son script principal :\n\n```ts [main.js]\nconst worker = new Worker('worker.js')\n\nworker.addEventListener('message', (e) => {\n  console.log('Message reçu du worker')\n  console.log(e.data)\n})\n\nworker.postMessage([2, 3])\n```\n\nDans cet exemple, le script principal crée un nouveau worker et lui envoie un message en utilisant `postMessage`. Le worker reçoit et répond avec `postMessage`. En raison de la nature événementielle de JavaScript, le script principal écoute le message du worker en utilisant `addEventListener`.\n\nDans notre cas, l'éditeur Monaco exploite les Web Workers pour certaines fonctionnalités, y compris la validation du schéma JSON.\n\n> [!NOTE]\n> L'utilisation d'un Web Worker permet à l'éditeur Monaco d'exécuter un serveur [LSP](https:\u002F\u002Fmicrosoft.github.io\u002Flanguage-server-protocol\u002F) en arrière-plan pour chaque langage, fournissant des fonctionnalités plus avancées sans impacter le fil principal et l'expérience utilisateur.\n\n## Le Problème\n\nMais voici le hic :\n\nLe script passé au Web Worker doit être accessible depuis la même origine que le script principal. C'est là que le problème surgit.\n\n> [!WARNING]\n> C'est une mesure de sécurité pour prévenir les attaques d'origine croisée, impossible à contourner.\n\nUne origine est définie par le schéma, l'hôte et le port d'une URL. Cela signifie que `https:\u002F\u002Fexample.com` et `https:\u002F\u002Fexample.com:1024` sont considérés comme des origines différentes.\n\nImaginez que nous sommes sur la page `https:\u002F\u002Fexample.com` et que vous souhaitez créer un Web Worker. L'URL du script passée au Web Worker doit être accessible depuis `https:\u002F\u002Fexample.com`.\n\nCela signifie que cela fonctionne :\n\n```html\n\u003Cscript>\n  const worker = new Worker('worker.js')\n\u003C\u002Fscript>\n```\n\nMais cela ne fonctionne pas :\n\n```html\n\u003Cscript>\n  const worker = new Worker('https:\u002F\u002Fanother-origin.example.com\u002Fworker.js')\n\u003C\u002Fscript>\n```\n\nVoyez-vous maintenant le problème ?\n\nNotre serveur Laravel fonctionne sur `http:\u002F\u002Flocalhost:8000`, tandis que Vite est sur `http:\u002F\u002Flocalhost:5173`. `http:\u002F\u002Flocalhost:8000` et `http:\u002F\u002Flocalhost:5173` sont deux origines différentes. Cela signifie que le navigateur ne chargera pas le script Web Worker depuis le serveur Vite, entraînant un problème d'origine croisée dû à l'exécution de deux serveurs.\n\nDans le navigateur, nous pouvons facilement repérer cette erreur avec le message d'avertissement suivant :\n\n```sh\napp.js:21 Échec de la construction de 'Worker' : le script à 'http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Flanguage\u002Fjson\u002Fjson.worker.js?worker_file&type=module' ne peut pas être accédé depuis l'origine 'http:\u002F\u002Flocalhost :'.\n```\n\nCette erreur est un frein. L'éditeur Monaco ne peut pas fonctionner correctement sans le script Web Worker. Cherchons une solution.\n\nAlerte spoiler, j'ai cherché haut et bas en ligne pour trouver une solution fonctionnelle. Malheureusement, je n'ai rien trouvé. J'ai donc essayé de m'attaquer au problème moi-même. Après de nombreuses recherches et essais, j'ai trouvé une solution que je partage avec vous dans cet article.\n\nLe code problématique ressemble à cela :\n\n```ts\nimport * as monaco from 'monaco-editor'\nimport EditorWorker from 'monaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker'\nimport JsonWorker from 'monaco-editor\u002Fesm\u002Fvs\u002Flanguage\u002Fjson\u002Fjson.worker?worker'\n\nglobalThis.MonacoEnvironment = {\n  getWorker(workerId, label) {\n    switch (label) {\n      case 'json':\n        return new JsonWorker()\n      default:\n        return new EditorWorker()\n    }\n  },\n}\n\n\u002F\u002F Initialisation de l'éditeur Monaco et fonctionnalité de diagnostic\n```\n\nLorsque Monaco est initialisé, il essaie de charger le script Web Worker à partir du serveur Vite. C'est là que surgit le problème d'origine croisée.\n\n> [!NOTE]\n> Dans des frameworks comme [Adonis](https:\u002F\u002Fadonisjs.com\u002F), où Vite sert de middleware, ce problème ne se produit pas car Vite fonctionne au sein de la même application que le serveur. Mais dans Laravel, avec deux serveurs distincts, nous avions besoin d'un contournement.\n\n> [!NOTE]\n> Ce problème n'existe pas si les actifs sont construits et chargés depuis `\u002Fpublic\u002Fbuild`, car tout est servi par le serveur Laravel. Cependant, cela impacte considérablement l'expérience du développeur, nécessitant la reconstruction des actifs et le rafraîchissement de la page pour chaque changement. Cela n'est pas en accord avec l'expérience de Vite ou sa philosophie à la demande.\n\n## La Solution\n\nNous avons maintenant une compréhension claire du problème et de l'origine de celui-ci. Plongeons dans la solution.\n\nTout d'abord, examinons l'URL du script worker :\n\n```txt\nmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker\n```\n\nC'est un script worker - pas destiné à un chargement direct dans le navigateur. Remarquez le paramètre de requête `?worker`, une indication de Vite pour encapsuler le script pour un usage facile. Nous pouvons inspecter la réponse pour mieux comprendre.\n\n```js\nexport default function WorkerWrapper(options) {\n  return new Worker(\n    'http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker.js?worker_file&type=module',\n    {\n      type: 'module',\n      name: options?.name\n    }\n  )\n}\n```\n\nGrâce au paramètre de requête `?worker`, Vite encapsule automatiquement le script dans une fonction qui retourne une nouvelle instance de `Worker`. Donc, lorsque nous importons le script, nous pouvons facilement invoquer la fonction pour démarrer le worker sans avoir à créer manuellement une nouvelle instance de `Worker` avec l'URL et les options correctes.\n\nLe worker utilise le script suivant :\n\n```txt\nhttp:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker.js?worker_file&type=module\n```\n\nL'origine `http:\u002F\u002Flocalhost:5173` appartient au serveur Vite et c'est la racine du problème.\n\nPour réussir, l'URL du script doit correspondre à l'origine de notre serveur Laravel :\n\n```txt\nhttp:\u002F\u002Flocalhost:8000\u002Fnode_modules\u002F.pnpm\u002Fmonaco-editor@0.52.2\u002Fnode_modules\u002Fmonaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker.js?worker_file&type=module\n```\n\nMais comment modifier l'URL dynamiquement pour qu'elle corresponde à l'origine du serveur Laravel ?\n\nAvec un plugin Vite ! C'est le cœur de Vite de transformer les fichiers source à la volée. Avec un plugin, nous pourrions intercepter la demande de script worker et ajuster l'URL pour qu'elle corresponde à l'origine du serveur Laravel.\n\nDans notre `vite.config.ts`, nous pouvons définir un plugin pour réécrire l'URL du script worker :\n\n```ts\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [\n    (() => {\n      return {\n        name: 'monaco-editor:rewrite-worker',\n        transform(code, id) {\n          if (this.environment.mode !== 'dev') {\n            return\n          }\n\n          if (id.includes('worker')) {\n            return code.replace(\n              '__laravel_vite_placeholder__.test',\n              'localhost:8000',\n            )\n          }\n        },\n      }\n    })(),\n  ],\n})\n```\n\n> [!NOTE]\n> Ici, nous remplaçons `__laravel_vite_placeholder__.test` au lieu de `http:\u002F\u002Flocalhost:5173`, car cette URL est généralement gérée par le plugin Laravel Vite.\n\nAvec ce plugin, nous pouvons maintenant charger le script worker de l'éditeur Monaco puisque l'origine correspond à celle du serveur Laravel. En production, l'URL du script sera correcte et fonctionnera comme prévu, donc nous n'avons pas besoin de réécrire l'URL. Les fichiers de construction sont servis depuis la même origine à l'aide d'un seul serveur comme Nginx, Apache ou Caddy.\n\nCependant, cela pose un autre défi : le script worker est chargé depuis le serveur Laravel, mais seul Vite peut le servir - le serveur Laravel n'est pas au courant de ce script et la demande renverra simplement une erreur 404.\n\nMais nous avons un contrôle total sur le serveur Laravel. Nous pouvons créer une route pour proxy les demandes vers le serveur Vite, permettant au serveur Laravel de servir le script worker.\n\nCela semble viable, faisable et suffisamment élégant. Implémentons-le.\n\nNous pouvons créer une route Laravel, disponible uniquement en développement, pour proxy les demandes vers le serveur Vite. Créons un fichier `proxy.dev.php` dans le répertoire `routes` de Laravel et incluons-le dans `routes\u002Fweb.php` uniquement durant le développement.\n\n```php\nif (!app()->isProduction()) {\n    require __DIR__ . '\u002Fproxy.dev.php';\n}\n```\n\nEnsuite, définissons notre route proxy dans `proxy.dev.php` :\n\n```php\n\u003C?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\nRoute::get('\u002Fnode_modules\u002F{any}', function ($any) {\n    $url = \"http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F{$any}\";\n    return response()->stream(function () use ($url) {\n        \u002F\u002F Directly stream the remote content without loading it fully into memory\n        readfile($url);\n    }, 200, [\n      'Content-Type' => 'text\u002Fjavascript'\n      'Cache-Control' => 'public, max-age=86400' \u002F\u002F Cache for 1 day\n    ]);\n})->where('any', '(.*)');\n```\n\n> [!NOTE]\n> J'ai fait de mon mieux pour optimiser le proxy afin de diffuser efficacement le contenu sans ajouter trop de surcharge. Si vous pensez à une meilleure façon de le faire, n'hésitez pas à la partager avec moi dans les commentaires.\n\nEt voilà ! Le script worker se charge, et l'éditeur Monaco fonctionne parfaitement.\n\n![Monaco Editor avec une validation du schéma JSON fonctionnelle](\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Fworking-monaco-editor-worker.png)\n\nUn défi a été de trouver une solution qui fonctionne bien en développement, lors de la construction, et en production. Cette approche semble être un compromis élégant,simple, efficace et propre. Jusqu'à présent, cela fonctionne parfaitement.\n\nVoici un aperçu de la solution :\n\n![Architecture de la solution](\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flarave-proxying-to-vite.png)\n\n## Conclusion\n\nCe problème a effectivement été un défi. L'intégration de l'éditeur Monaco avec la validation du schéma JSON était essentielle pour notre projet. Malheureusement, le casse-tête des origines croisées et les ressources en ligne rares m'ont contraint à créer ma propre solution. Mais j'en ai trouvé une !\n\nJ'espère que cet article aidera ceux qui font face à des problèmes similaires. La clé à retenir : maîtriser vos outils est crucial. Bien que les plugins **Vite** puissent sembler intimidants, les comprendre supprime toutes les limites vous permettant de résoudre des problèmes complexes.\n",null,{"title":5,"description":1683},{"loc":1686,"images":1691},[1692,1693,1694,1695],{"loc":87},{"loc":199},{"loc":1644},{"loc":1656},"fr\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin","n2DUfpuXwLM9qHcN-X1xUdh2kw0i4npVTuO94dDhDQ4",1790086903679]