[{"data":1,"prerenderedAt":122855},["ShallowReactive",2],{"posts-page-\u002Ffr\u002Fposts":3,"posts-schema-\u002Ffr\u002Fposts":28,"posts-list-fr":66785},{"id":4,"title":5,"body":6,"contentId":16,"description":17,"extension":18,"meta":19,"navigation":20,"path":21,"rawbody":22,"robots":23,"schemaOrg":23,"seo":24,"sitemap":25,"stem":26,"__hash__":27},"pages\u002Ffr\u002Fposts.md","Articles",{"type":7,"value":8,"toc":12},"minimark",[9],[10,11],"posts-list",{},{"title":13,"searchDepth":14,"depth":14,"links":15},"",2,[],"c2513d0b-5cfb-42b0-a8eb-9fbfd2ff9526","Mes articles de blog sur ma vie, mes projets et mes réflexions.","md",{},true,"\u002Ffr\u002Fposts","---\ncontentId: c2513d0b-5cfb-42b0-a8eb-9fbfd2ff9526\ntitle: Articles\ndescription: Mes articles de blog sur ma vie, mes projets et mes réflexions.\n---\n\n::posts-list\n::\n",null,{"title":5,"description":17},{"loc":21},"fr\u002Fposts","BCG-viINZNzc67FUahde-70GU-IVQv5bWYG8L-zYDj8",[29,1190,1633,1868,3019,5640,5752,6803,7485,8189,8779,9436,9666,11309,13791,14129,16377,16871,17231,17551,17749,17853,18194,18607,19390,20439,20846,21379,24213,28936,30461,30955,33234,33640,33842,34061,34294,36307,36698,37857,38107,38276,38648,40574,45818,60256,60705,60904,63453,63746,63972],{"id":30,"title":31,"body":32,"contentId":1180,"date":1181,"description":1182,"extension":18,"meta":1183,"navigation":20,"path":1184,"rawbody":1185,"robots":23,"schemaOrg":23,"seo":1186,"sitemap":1187,"stem":1188,"__hash__":1189},"posts\u002Ffr\u002Fposts\u002Ffrom-browsing-to-asking-the-web-learns-to-take-action.md","De la navigation à la demande : le Web passe à l'action",{"type":7,"value":33,"toc":1173},[34,44,47,50,55,58,61,64,67,88,91,95,98,113,116,120,129,146,149,158,161,164,167,171,174,180,188,191,417,434,467,482,491,585,588,937,951,954,963,1130,1141,1145,1148,1151,1154,1157,1160,1169],[35,36,37,38,43],"p",{},"Il y a quelques mois, j'ai écrit un article intitulé ",[39,40,42],"a",{"href":41},".\u002Fai-agents-will-deeply-transform-our-experience-with-the-web","Les agents IA transformeront profondément notre expérience avec le Web",". J'y partageais ma vision de la manière dont les agents IA allaient radicalement changer notre expérience du Web. Je concluais en disant qu'un nouveau paradigme allait émerger : « Site web en tant que barre de recherche ». Je n'ai jamais vraiment expliqué ce que je voulais dire, car il s'agissait davantage d'une intuition, fondée sur ma lecture de l'écosystème, que de quelque chose de concret.",[35,45,46],{},"Depuis, beaucoup de choses ont changé, et cette idée est désormais une réalité. Je ne m'attendais pas à ce que le futur arrive aussi vite. Ces derniers temps, tout évolue à une vitesse folle.",[35,48,49],{},"Dans ce futur, qui est déjà là, nous demanderons aux agents d'agir, et j'adore déjà ça.",[51,52,54],"h2",{"id":53},"site-web-en-tant-que-barre-de-recherche","Site web en tant que barre de recherche",[35,56,57],{},"Aujourd'hui, nous naviguons sur le Web. Nous cliquons sur des liens, faisons défiler des pages, remplissons des formulaires et lisons du contenu. Il en allait de même pour mon travail d'ingénieur logiciel. Depuis que j'utilise des agents IA, ce n'est plus le cas. Désormais, je leur demande plus souvent d'agir que je n'effectue moi-même ces actions. Ce nouveau comportement arrive sur le Web.",[35,59,60],{},"L'idée derrière « Site web en tant que barre de recherche » est que la page principale d'un site web sera simplement une barre de recherche : un champ dans lequel nous pourrons demander tout ce que nous souhaitons faire sur le site. En coulisses, le site disposera d'un agent qui agira pour nous. Lorsque j'y ai pensé pour la première fois, j'imaginais un serveur MCP et un agent côté serveur. L'agent appellerait le serveur MCP pour effectuer des actions, et l'interface s'adapterait ou se rafraîchirait intelligemment en fonction de la réponse du serveur. Cette expérience ressemble à celle que Notion AI propose aujourd'hui, et elle fonctionne très bien.",[35,62,63],{},"Cependant, une nouvelle approche a récemment commencé à émerger.",[35,65,66],{},"L'équipe de Chrome développe deux nouvelles API qui pourraient complètement transformer la manière dont un site web devient une barre de recherche :",[68,69,70,80],"ol",{},[71,72,73,79],"li",{},[39,74,78],{"href":75,"rel":76},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp",[77],"nofollow","WebMCP",", qui permet de créer et d'exposer des outils structurés aux agents IA ;",[71,81,82,87],{},[39,83,86],{"href":84,"rel":85},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fprompt-api",[77],"L'API Prompt",", qui permet d'envoyer des requêtes en langage naturel à un LLM dans Chrome.",[35,89,90],{},"Grâce aux deux, nous pouvons désormais avoir des agents côté client qui effectuent des actions sur le Web à notre place. C'est époustouflant.",[51,92,94],{"id":93},"le-futur-est-déjà-là","Le futur est déjà là",[35,96,97],{},"Oui, cela paraît fou, je sais, mais le futur est déjà là.",[35,99,100,101,106,107,112],{},"WebMCP et l'API Prompt sont déjà disponibles à titre expérimental dans Chrome. Ce n'est plus seulement une discussion, une idée ou une spécification : des implémentations expérimentales sont disponibles et fonctionnent. Vous pouvez maintenant créer un site web qui expose des outils WebMCP et ",[39,102,105],{"href":103,"rel":104},"https:\u002F\u002Fdeveloper.chrome.com\u002Fblog\u002Fprompt-api-with-vercel-ai",[77],"discuter avec une IA intégrée au navigateur",". ",[39,108,111],{"href":109,"rel":110},"https:\u002F\u002Fblog.cloudflare.com\u002Fwebmcp\u002F#:~:text=You%20do%20not,Copy%20link",[77],"Cloudflare expérimente également WebMCP"," au travers de son produit BrowserRun, et ChatGPT Desktop prend en charge WebMCP pour faciliter la navigation des agents sur le Web.",[35,114,115],{},"L'IA intégrée au navigateur désigne des LLM comme Gemini Nano de Chrome ou les modèles Gemma de Google, capables de fonctionner de manière privée et entièrement dans le navigateur, sur votre ordinateur. Je ne parle pas de Codex ou de Claude qui se connectent à votre navigateur et effectuent des actions en lisant le DOM ou en prenant des captures d'écran. Je parle d'un agent dont le modèle peut fonctionner hors ligne et utiliser des API dédiées pour interagir de manière fluide avec les sites web.",[51,117,119],{"id":118},"demander-des-actions","Demander des actions",[35,121,122,123,128],{},"Pour rendre cette idée plus concrète, j'ai créé une petite démo qui montre comment cela fonctionne. Au départ, mon objectif était simplement d'explorer et d'utiliser WebMCP. Cela a pris une ampleur plus importante que prévu car, hormis ",[39,124,127],{"href":125,"rel":126},"https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fwebmcp-model-context-tool\u002Fgbpdfapgefenggkahomfgkhfehlcenpd?pli=1",[77],"une extension de navigateur pour les tests",", je n'ai trouvé aucun autre outil pour le tester.",[130,131,132,133,132,142],"figure",{},"\n  ",[134,135,136,137,132],"video",{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},"\n    ",[138,139],"source",{"src":140,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ffrom-browsing-to-asking-the-web-learns-to-take-action\u002Fdemo.mp4","video\u002Fmp4",[143,144,145],"figcaption",{},"Démo de l'assistant IA intégré au navigateur effectuant des actions sur le site web.",[35,147,148],{},"Que venez-vous de voir ?",[35,150,151,152,157],{},"Nous utilisons ",[39,153,156],{"href":154,"rel":155},"https:\u002F\u002Fcode.soubiran.dev",[77],"code",", mon outil qui transforme du code brut en de belles images faciles à partager. Vous collez un extrait de code, sélectionnez le langage et la couleur d'arrière-plan, puis le capturez sous forme d'image. Vous pouvez ensuite le partager avec n'importe qui sur les réseaux sociaux.",[35,159,160],{},"Habituellement, l'utilisateur effectue manuellement toutes les actions que je viens de décrire.",[35,162,163],{},"L'agent modifie le code à partir du prompt fourni, change les paramètres et prend finalement une capture d'écran du résultat. Au fond, ce n'est rien de plus que ce qu'un humain peut faire, mais un agent local effectue toutes ces actions. C'est cela, le véritable changement, et c'est cela, le futur.",[35,165,166],{},"Je sais que cette démo est inutile. Cependant, elle vous donne une idée concrète de ce nouveau paradigme. Tout reste encore à inventer et à concevoir, de l'interface à l'expérience.",[51,168,170],{"id":169},"deux-api-pour-y-parvenir","Deux API pour y parvenir",[35,172,173],{},"Si c'est le futur, nous devrions examiner de plus près les deux API qui le rendent possible. J'ai une bonne nouvelle pour vous : en apparence, elles sont extrêmement simples. Cependant, les nouvelles possibilités qu'elles ouvrent rendent leur utilisation plus complexe qu'il n'y paraît au premier abord.",[35,175,176,179],{},[39,177,78],{"href":75,"rel":178},[77]," se présente sous deux formes :",[68,181,182,185],{},[71,183,184],{},"Une API impérative pour définir des outils avec JavaScript ;",[71,186,187],{},"Une API déclarative pour définir des outils avec des annotations HTML.",[35,189,190],{},"La première est similaire à la manière dont un serveur MCP définit des outils.",[192,193,197],"pre",{"className":194,"code":195,"language":196,"meta":13,"style":13},"language-typescript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","await document.modelContext.registerTool({\n  name: 'performAction',\n  description: 'Perform an action on the page.',\n  inputSchema: {\n    type: 'object',\n    properties: {\n      input: { type: 'string' },\n    },\n    required: ['input'],\n  },\n  execute: async ({ input }) => {\n    \u002F\u002F Add the logic to perform the action here\n  },\n})\n","typescript",[156,198,199,231,253,270,281,298,308,334,340,363,369,396,403,408],{"__ignoreMap":13},[200,201,204,208,212,216,219,221,225,228],"span",{"class":202,"line":203},"line",1,[200,205,207],{"class":206},"s7zQu","await",[200,209,211],{"class":210},"sTEyZ"," document",[200,213,215],{"class":214},"sMK4o",".",[200,217,218],{"class":210},"modelContext",[200,220,215],{"class":214},[200,222,224],{"class":223},"s2Zo4","registerTool",[200,226,227],{"class":210},"(",[200,229,230],{"class":214},"{\n",[200,232,233,237,240,243,247,250],{"class":202,"line":14},[200,234,236],{"class":235},"swJcz","  name",[200,238,239],{"class":214},":",[200,241,242],{"class":214}," '",[200,244,246],{"class":245},"sfazB","performAction",[200,248,249],{"class":214},"'",[200,251,252],{"class":214},",\n",[200,254,256,259,261,263,266,268],{"class":202,"line":255},3,[200,257,258],{"class":235},"  description",[200,260,239],{"class":214},[200,262,242],{"class":214},[200,264,265],{"class":245},"Perform an action on the page.",[200,267,249],{"class":214},[200,269,252],{"class":214},[200,271,273,276,278],{"class":202,"line":272},4,[200,274,275],{"class":235},"  inputSchema",[200,277,239],{"class":214},[200,279,280],{"class":214}," {\n",[200,282,284,287,289,291,294,296],{"class":202,"line":283},5,[200,285,286],{"class":235},"    type",[200,288,239],{"class":214},[200,290,242],{"class":214},[200,292,293],{"class":245},"object",[200,295,249],{"class":214},[200,297,252],{"class":214},[200,299,301,304,306],{"class":202,"line":300},6,[200,302,303],{"class":235},"    properties",[200,305,239],{"class":214},[200,307,280],{"class":214},[200,309,311,314,316,319,322,324,326,329,331],{"class":202,"line":310},7,[200,312,313],{"class":235},"      input",[200,315,239],{"class":214},[200,317,318],{"class":214}," {",[200,320,321],{"class":235}," type",[200,323,239],{"class":214},[200,325,242],{"class":214},[200,327,328],{"class":245},"string",[200,330,249],{"class":214},[200,332,333],{"class":214}," },\n",[200,335,337],{"class":202,"line":336},8,[200,338,339],{"class":214},"    },\n",[200,341,343,346,348,351,353,356,358,361],{"class":202,"line":342},9,[200,344,345],{"class":235},"    required",[200,347,239],{"class":214},[200,349,350],{"class":210}," [",[200,352,249],{"class":214},[200,354,355],{"class":245},"input",[200,357,249],{"class":214},[200,359,360],{"class":210},"]",[200,362,252],{"class":214},[200,364,366],{"class":202,"line":365},10,[200,367,368],{"class":214},"  },\n",[200,370,372,375,377,381,384,388,391,394],{"class":202,"line":371},11,[200,373,374],{"class":223},"  execute",[200,376,239],{"class":214},[200,378,380],{"class":379},"spNyl"," async",[200,382,383],{"class":214}," ({",[200,385,387],{"class":386},"sHdIc"," input",[200,389,390],{"class":214}," })",[200,392,393],{"class":379}," =>",[200,395,280],{"class":214},[200,397,399],{"class":202,"line":398},12,[200,400,402],{"class":401},"sHwdD","    \u002F\u002F Add the logic to perform the action here\n",[200,404,406],{"class":202,"line":405},13,[200,407,368],{"class":214},[200,409,411,414],{"class":202,"line":410},14,[200,412,413],{"class":214},"}",[200,415,416],{"class":210},")\n",[35,418,419,420,423,424,426,427,429,430,433],{},"L'objet ",[156,421,422],{},"document"," reçoit une nouvelle propriété appelée ",[156,425,218],{},", qui constitue le point d'entrée pour créer et enregistrer des outils sur la page actuelle. Les agents qui utilisent votre site doivent savoir quels outils sont disponibles. ",[156,428,218],{}," expose donc une méthode ",[156,431,432],{},"getTools()"," qui renvoie la liste des outils disponibles sur la page.",[192,435,437],{"className":194,"code":436,"language":196,"meta":13,"style":13},"const tools = await document.modelContext.getTools()\n",[156,438,439],{"__ignoreMap":13},[200,440,441,444,447,450,453,455,457,459,461,464],{"class":202,"line":203},[200,442,443],{"class":379},"const",[200,445,446],{"class":210}," tools ",[200,448,449],{"class":214},"=",[200,451,452],{"class":206}," await",[200,454,211],{"class":210},[200,456,215],{"class":214},[200,458,218],{"class":210},[200,460,215],{"class":214},[200,462,463],{"class":223},"getTools",[200,465,466],{"class":210},"()\n",[35,468,469,470,473,474,481],{},"Cela ressemble à la méthode ",[156,471,472],{},"listTools()"," exposée par ",[39,475,478],{"href":476,"rel":477},"https:\u002F\u002Fai-sdk.dev\u002Fdocs\u002Freference\u002Fai-sdk-core\u002Fcreate-mcp-client",[77],[156,479,480],{},"createMCPClient()"," dans l'AI SDK. Cela peut être un peu déroutant car, dans le navigateur, nous agissons à la fois comme le serveur en enregistrant des outils et comme le client en y accédant. La raison est que votre site web n'est pas nécessairement celui qui consomme ces outils.",[35,483,484,485,488,489,215],{},"Vous pouvez également exécuter un outil à l'aide de la méthode ",[156,486,487],{},"executeTool()"," de l'objet ",[156,490,218],{},[192,492,494],{"className":194,"code":493,"language":196,"meta":13,"style":13},"const [tool] = await document.modelContext.getTools()\n\nawait document.modelContext.executeTool(tool, JSON.stringify({\n  input: '...',\n}))\n",[156,495,496,524,529,562,578],{"__ignoreMap":13},[200,497,498,500,502,505,507,510,512,514,516,518,520,522],{"class":202,"line":203},[200,499,443],{"class":379},[200,501,350],{"class":214},[200,503,504],{"class":210},"tool",[200,506,360],{"class":214},[200,508,509],{"class":214}," =",[200,511,452],{"class":206},[200,513,211],{"class":210},[200,515,215],{"class":214},[200,517,218],{"class":210},[200,519,215],{"class":214},[200,521,463],{"class":223},[200,523,466],{"class":210},[200,525,526],{"class":202,"line":14},[200,527,528],{"emptyLinePlaceholder":20},"\n",[200,530,531,533,535,537,539,541,544,547,550,553,555,558,560],{"class":202,"line":255},[200,532,207],{"class":206},[200,534,211],{"class":210},[200,536,215],{"class":214},[200,538,218],{"class":210},[200,540,215],{"class":214},[200,542,543],{"class":223},"executeTool",[200,545,546],{"class":210},"(tool",[200,548,549],{"class":214},",",[200,551,552],{"class":210}," JSON",[200,554,215],{"class":214},[200,556,557],{"class":223},"stringify",[200,559,227],{"class":210},[200,561,230],{"class":214},[200,563,564,567,569,571,574,576],{"class":202,"line":272},[200,565,566],{"class":235},"  input",[200,568,239],{"class":214},[200,570,242],{"class":214},[200,572,573],{"class":245},"...",[200,575,249],{"class":214},[200,577,252],{"class":214},[200,579,580,582],{"class":202,"line":283},[200,581,413],{"class":214},[200,583,584],{"class":210},"))\n",[35,586,587],{},"La seconde est l'API déclarative, qui permet de définir des outils à l'aide d'annotations HTML. Elle est principalement conçue pour les formulaires :",[192,589,593],{"className":590,"code":591,"language":592,"meta":13,"style":13},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cform toolname=\"supportRequestTool\"\n  tooldescription=\"Submit a request for support.\"\n  action=\"\u002Fsubmit\">\n\n  \u003Clabel for=\"firstName\">First Name\u003C\u002Flabel>\n  \u003Cinput type=text name=firstName>\n\n  \u003Clabel for=\"lastName\">Last Name\u003C\u002Flabel>\n  \u003Cinput type=text name=lastName>\n\n  \u003Cselect name=\"select\" required\n    toolparamdescription=\"Determines what team this request is routed to.\">\n    \u003Coption value=\"Customer happiness team\">Return my purchase.\u003C\u002Foption>\n    \u003Coption value=\"Distribution team\">Check where my package is.\u003C\u002Foption>\n    \u003Coption value=\"Website support team\">Get help on the website.\u003C\u002Foption>\n  \u003C\u002Fselect>\n\n  \u003Cbutton type=submit>Submit\u003C\u002Fbutton>\n\u003C\u002Fform>\n","html",[156,594,595,617,631,648,652,685,707,711,739,759,763,783,799,830,858,887,897,902,928],{"__ignoreMap":13},[200,596,597,600,603,606,608,611,614],{"class":202,"line":203},[200,598,599],{"class":214},"\u003C",[200,601,602],{"class":235},"form",[200,604,605],{"class":379}," toolname",[200,607,449],{"class":214},[200,609,610],{"class":214},"\"",[200,612,613],{"class":245},"supportRequestTool",[200,615,616],{"class":214},"\"\n",[200,618,619,622,624,626,629],{"class":202,"line":14},[200,620,621],{"class":379},"  tooldescription",[200,623,449],{"class":214},[200,625,610],{"class":214},[200,627,628],{"class":245},"Submit a request for support.",[200,630,616],{"class":214},[200,632,633,636,638,640,643,645],{"class":202,"line":255},[200,634,635],{"class":379},"  action",[200,637,449],{"class":214},[200,639,610],{"class":214},[200,641,642],{"class":245},"\u002Fsubmit",[200,644,610],{"class":214},[200,646,647],{"class":214},">\n",[200,649,650],{"class":202,"line":272},[200,651,528],{"emptyLinePlaceholder":20},[200,653,654,657,660,663,665,667,670,672,675,678,681,683],{"class":202,"line":283},[200,655,656],{"class":214},"  \u003C",[200,658,659],{"class":235},"label",[200,661,662],{"class":379}," for",[200,664,449],{"class":214},[200,666,610],{"class":214},[200,668,669],{"class":245},"firstName",[200,671,610],{"class":214},[200,673,674],{"class":214},">",[200,676,677],{"class":210},"First Name",[200,679,680],{"class":214},"\u003C\u002F",[200,682,659],{"class":235},[200,684,647],{"class":214},[200,686,687,689,691,693,695,698,701,703,705],{"class":202,"line":300},[200,688,656],{"class":214},[200,690,355],{"class":235},[200,692,321],{"class":379},[200,694,449],{"class":214},[200,696,697],{"class":245},"text",[200,699,700],{"class":379}," name",[200,702,449],{"class":214},[200,704,669],{"class":245},[200,706,647],{"class":214},[200,708,709],{"class":202,"line":310},[200,710,528],{"emptyLinePlaceholder":20},[200,712,713,715,717,719,721,723,726,728,730,733,735,737],{"class":202,"line":336},[200,714,656],{"class":214},[200,716,659],{"class":235},[200,718,662],{"class":379},[200,720,449],{"class":214},[200,722,610],{"class":214},[200,724,725],{"class":245},"lastName",[200,727,610],{"class":214},[200,729,674],{"class":214},[200,731,732],{"class":210},"Last Name",[200,734,680],{"class":214},[200,736,659],{"class":235},[200,738,647],{"class":214},[200,740,741,743,745,747,749,751,753,755,757],{"class":202,"line":342},[200,742,656],{"class":214},[200,744,355],{"class":235},[200,746,321],{"class":379},[200,748,449],{"class":214},[200,750,697],{"class":245},[200,752,700],{"class":379},[200,754,449],{"class":214},[200,756,725],{"class":245},[200,758,647],{"class":214},[200,760,761],{"class":202,"line":365},[200,762,528],{"emptyLinePlaceholder":20},[200,764,765,767,770,772,774,776,778,780],{"class":202,"line":371},[200,766,656],{"class":214},[200,768,769],{"class":235},"select",[200,771,700],{"class":379},[200,773,449],{"class":214},[200,775,610],{"class":214},[200,777,769],{"class":245},[200,779,610],{"class":214},[200,781,782],{"class":379}," required\n",[200,784,785,788,790,792,795,797],{"class":202,"line":398},[200,786,787],{"class":379},"    toolparamdescription",[200,789,449],{"class":214},[200,791,610],{"class":214},[200,793,794],{"class":245},"Determines what team this request is routed to.",[200,796,610],{"class":214},[200,798,647],{"class":214},[200,800,801,804,807,810,812,814,817,819,821,824,826,828],{"class":202,"line":405},[200,802,803],{"class":214},"    \u003C",[200,805,806],{"class":235},"option",[200,808,809],{"class":379}," value",[200,811,449],{"class":214},[200,813,610],{"class":214},[200,815,816],{"class":245},"Customer happiness team",[200,818,610],{"class":214},[200,820,674],{"class":214},[200,822,823],{"class":210},"Return my purchase.",[200,825,680],{"class":214},[200,827,806],{"class":235},[200,829,647],{"class":214},[200,831,832,834,836,838,840,842,845,847,849,852,854,856],{"class":202,"line":410},[200,833,803],{"class":214},[200,835,806],{"class":235},[200,837,809],{"class":379},[200,839,449],{"class":214},[200,841,610],{"class":214},[200,843,844],{"class":245},"Distribution team",[200,846,610],{"class":214},[200,848,674],{"class":214},[200,850,851],{"class":210},"Check where my package is.",[200,853,680],{"class":214},[200,855,806],{"class":235},[200,857,647],{"class":214},[200,859,861,863,865,867,869,871,874,876,878,881,883,885],{"class":202,"line":860},15,[200,862,803],{"class":214},[200,864,806],{"class":235},[200,866,809],{"class":379},[200,868,449],{"class":214},[200,870,610],{"class":214},[200,872,873],{"class":245},"Website support team",[200,875,610],{"class":214},[200,877,674],{"class":214},[200,879,880],{"class":210},"Get help on the website.",[200,882,680],{"class":214},[200,884,806],{"class":235},[200,886,647],{"class":214},[200,888,890,893,895],{"class":202,"line":889},16,[200,891,892],{"class":214},"  \u003C\u002F",[200,894,769],{"class":235},[200,896,647],{"class":214},[200,898,900],{"class":202,"line":899},17,[200,901,528],{"emptyLinePlaceholder":20},[200,903,905,907,910,912,914,917,919,922,924,926],{"class":202,"line":904},18,[200,906,656],{"class":214},[200,908,909],{"class":235},"button",[200,911,321],{"class":379},[200,913,449],{"class":214},[200,915,916],{"class":245},"submit",[200,918,674],{"class":214},[200,920,921],{"class":210},"Submit",[200,923,680],{"class":214},[200,925,909],{"class":235},[200,927,647],{"class":214},[200,929,931,933,935],{"class":202,"line":930},19,[200,932,680],{"class":214},[200,934,602],{"class":235},[200,936,647],{"class":214},[938,939,940],"blockquote",{},[35,941,942,945,946,215],{},[200,943,944],{},"!NOTE","\nExemple tiré de la ",[39,947,950],{"href":948,"rel":949},"https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp\u002Fdeclarative-api",[77],"documentation de l'API déclarative de WebMCP",[35,952,953],{},"Vient ensuite l'API Prompt. Elle est plus complexe, car elle nécessite davantage de gestion de la disponibilité et du cycle de vie. Par exemple, vous devez détecter si le LLM intégré au navigateur est disponible. S'il ne l'est pas, vous devez en connaître la raison. L'appareil est peut-être incompatible, ou le LLM n'a peut-être pas encore été téléchargé. S'il peut l'être, vous devez demander au navigateur de le télécharger, ce qui peut prendre du temps. Vous devez donc informer l'utilisateur que quelque chose se passe en arrière-plan. De plus, l'API expose des primitives de bas niveau que vous ne voudrez probablement pas manipuler directement.",[35,955,956,957,962],{},"Cela demande plus de travail que d'appeler une API. La bonne nouvelle, c'est qu'un provider communautaire appelé ",[39,958,961],{"href":959,"rel":960},"https:\u002F\u002Fai-sdk.dev\u002Fproviders\u002Fcommunity-providers\u002Fbrowser-ai",[77],"Browser AI"," simplifie l'utilisation de l'API Prompt avec l'AI SDK.",[192,964,966],{"className":194,"code":965,"language":196,"meta":13,"style":13},"import { browserAI } from '@browser-ai\u002Fcore'\nimport { streamText } from 'ai'\n\nconst model = browserAI()\n\nconst { textStream } = streamText({\n  model,\n  prompt: 'Describe the future of the web in five sentences.',\n})\n\nfor await (const textPart of textStream) {\n  console.log(textPart)\n}\n",[156,967,968,992,1012,1016,1029,1033,1052,1059,1075,1081,1085,1108,1125],{"__ignoreMap":13},[200,969,970,973,975,978,981,984,986,989],{"class":202,"line":203},[200,971,972],{"class":206},"import",[200,974,318],{"class":214},[200,976,977],{"class":210}," browserAI",[200,979,980],{"class":214}," }",[200,982,983],{"class":206}," from",[200,985,242],{"class":214},[200,987,988],{"class":245},"@browser-ai\u002Fcore",[200,990,991],{"class":214},"'\n",[200,993,994,996,998,1001,1003,1005,1007,1010],{"class":202,"line":14},[200,995,972],{"class":206},[200,997,318],{"class":214},[200,999,1000],{"class":210}," streamText",[200,1002,980],{"class":214},[200,1004,983],{"class":206},[200,1006,242],{"class":214},[200,1008,1009],{"class":245},"ai",[200,1011,991],{"class":214},[200,1013,1014],{"class":202,"line":255},[200,1015,528],{"emptyLinePlaceholder":20},[200,1017,1018,1020,1023,1025,1027],{"class":202,"line":272},[200,1019,443],{"class":379},[200,1021,1022],{"class":210}," model ",[200,1024,449],{"class":214},[200,1026,977],{"class":223},[200,1028,466],{"class":210},[200,1030,1031],{"class":202,"line":283},[200,1032,528],{"emptyLinePlaceholder":20},[200,1034,1035,1037,1039,1042,1044,1046,1048,1050],{"class":202,"line":300},[200,1036,443],{"class":379},[200,1038,318],{"class":214},[200,1040,1041],{"class":210}," textStream ",[200,1043,413],{"class":214},[200,1045,509],{"class":214},[200,1047,1000],{"class":223},[200,1049,227],{"class":210},[200,1051,230],{"class":214},[200,1053,1054,1057],{"class":202,"line":310},[200,1055,1056],{"class":210},"  model",[200,1058,252],{"class":214},[200,1060,1061,1064,1066,1068,1071,1073],{"class":202,"line":336},[200,1062,1063],{"class":235},"  prompt",[200,1065,239],{"class":214},[200,1067,242],{"class":214},[200,1069,1070],{"class":245},"Describe the future of the web in five sentences.",[200,1072,249],{"class":214},[200,1074,252],{"class":214},[200,1076,1077,1079],{"class":202,"line":342},[200,1078,413],{"class":214},[200,1080,416],{"class":210},[200,1082,1083],{"class":202,"line":365},[200,1084,528],{"emptyLinePlaceholder":20},[200,1086,1087,1090,1092,1095,1097,1100,1103,1106],{"class":202,"line":371},[200,1088,1089],{"class":206},"for",[200,1091,452],{"class":206},[200,1093,1094],{"class":210}," (",[200,1096,443],{"class":379},[200,1098,1099],{"class":210}," textPart ",[200,1101,1102],{"class":214},"of",[200,1104,1105],{"class":210}," textStream) ",[200,1107,230],{"class":214},[200,1109,1110,1113,1115,1118,1120,1123],{"class":202,"line":398},[200,1111,1112],{"class":210},"  console",[200,1114,215],{"class":214},[200,1116,1117],{"class":223},"log",[200,1119,227],{"class":235},[200,1121,1122],{"class":210},"textPart",[200,1124,416],{"class":235},[200,1126,1127],{"class":202,"line":405},[200,1128,1129],{"class":214},"}\n",[938,1131,1132],{},[35,1133,1134,1136,1137,215],{},[200,1135,944],{},"\nJe vous recommande vivement de lire l'article ",[39,1138,1140],{"href":103,"rel":1139},[77],"Utiliser l'API Prompt intégrée avec le Vercel AI SDK",[51,1142,1144],{"id":1143},"ce-nest-que-le-début","Ce n'est que le début",[35,1146,1147],{},"Pour l'instant, même si vous pouvez déjà tester les deux API, elles restent expérimentales et leur prise en charge par les navigateurs est limitée. Cela signifie que la plupart des gens ne pourront pas les utiliser. Elles doivent encore être standardisées et adoptées par d'autres éditeurs de navigateurs, comme Mozilla et Apple.",[35,1149,1150],{},"De plus, les modèles intégrés au navigateur sont limités, particulièrement en comparaison avec les modèles de pointe. Ils peuvent répondre à des questions et appeler des outils, mais vous devez garder les instructions et les paramètres des outils simples, vraiment simples.",[35,1152,1153],{},"Ils deviendront inévitablement de plus en plus intelligents. Récemment, Qwen a publié Qwen3.8-27B, un modèle capable de fonctionner sur certains appareils grand public. Google fait également des progrès importants avec sa gamme de modèles Gemma, conçus pour fonctionner directement sur les appareils. Les appareils deviennent plus puissants et plus aptes à exécuter ces modèles. Les SoC de la série M d'Apple intègrent un Neural Engine, ou NPU, et Google a fait de même avec ses SoC Tensor. La dernière version de Siri effectue également des traitements d'IA directement sur l'appareil, et elle semble vraiment puissante. Ce n'est désormais plus qu'une question de temps.",[35,1155,1156],{},"Enfin, lorsque tout cela sera standardisé et mature, disposer de tout ce qu'il faut, prêt à l'emploi, vous donnera un avantage concurrentiel.",[1158,1159],"hr",{},[35,1161,1162,1163,1168],{},"Pendant que j'écrivais cet article, OpenAI a annoncé un ",[39,1164,1167],{"href":1165,"rel":1166},"https:\u002F\u002Fopenai.com\u002Fwebmcp-challenge\u002F",[77],"défi WebMCP"," pour explorer ce qui est possible. Il semble que je sois sur la bonne voie.",[1170,1171,1172],"style",{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .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}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);}",{"title":13,"searchDepth":14,"depth":14,"links":1174},[1175,1176,1177,1178,1179],{"id":53,"depth":14,"text":54},{"id":93,"depth":14,"text":94},{"id":118,"depth":14,"text":119},{"id":169,"depth":14,"text":170},{"id":1143,"depth":14,"text":1144},"442424b1-6547-40a4-93ee-a0bd8b3dfa87","2026-08-28","WebMCP et l'API Prompt de Chrome font entrer les agents IA côté client sur le Web et leur permettent d'agir sur les sites à notre place.",{},"\u002Ffr\u002Fposts\u002Ffrom-browsing-to-asking-the-web-learns-to-take-action","---\ncontentId: 442424b1-6547-40a4-93ee-a0bd8b3dfa87\ntitle: \"De la navigation à la demande : le Web passe à l'action\"\ndescription: \"WebMCP et l'API Prompt de Chrome font entrer les agents IA côté client sur le Web et leur permettent d'agir sur les sites à notre place.\"\ndate: 2026-08-28\n---\n\nIl y a quelques mois, j'ai écrit un article intitulé [Les agents IA transformeront profondément notre expérience avec le Web](.\u002Fai-agents-will-deeply-transform-our-experience-with-the-web.md). J'y partageais ma vision de la manière dont les agents IA allaient radicalement changer notre expérience du Web. Je concluais en disant qu'un nouveau paradigme allait émerger : « Site web en tant que barre de recherche ». Je n'ai jamais vraiment expliqué ce que je voulais dire, car il s'agissait davantage d'une intuition, fondée sur ma lecture de l'écosystème, que de quelque chose de concret.\n\nDepuis, beaucoup de choses ont changé, et cette idée est désormais une réalité. Je ne m'attendais pas à ce que le futur arrive aussi vite. Ces derniers temps, tout évolue à une vitesse folle.\n\nDans ce futur, qui est déjà là, nous demanderons aux agents d'agir, et j'adore déjà ça.\n\n## Site web en tant que barre de recherche\n\nAujourd'hui, nous naviguons sur le Web. Nous cliquons sur des liens, faisons défiler des pages, remplissons des formulaires et lisons du contenu. Il en allait de même pour mon travail d'ingénieur logiciel. Depuis que j'utilise des agents IA, ce n'est plus le cas. Désormais, je leur demande plus souvent d'agir que je n'effectue moi-même ces actions. Ce nouveau comportement arrive sur le Web.\n\nL'idée derrière « Site web en tant que barre de recherche » est que la page principale d'un site web sera simplement une barre de recherche : un champ dans lequel nous pourrons demander tout ce que nous souhaitons faire sur le site. En coulisses, le site disposera d'un agent qui agira pour nous. Lorsque j'y ai pensé pour la première fois, j'imaginais un serveur MCP et un agent côté serveur. L'agent appellerait le serveur MCP pour effectuer des actions, et l'interface s'adapterait ou se rafraîchirait intelligemment en fonction de la réponse du serveur. Cette expérience ressemble à celle que Notion AI propose aujourd'hui, et elle fonctionne très bien.\n\nCependant, une nouvelle approche a récemment commencé à émerger.\n\nL'équipe de Chrome développe deux nouvelles API qui pourraient complètement transformer la manière dont un site web devient une barre de recherche :\n\n1. [WebMCP](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp), qui permet de créer et d'exposer des outils structurés aux agents IA ;\n2. [L'API Prompt](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fprompt-api), qui permet d'envoyer des requêtes en langage naturel à un LLM dans Chrome.\n\nGrâce aux deux, nous pouvons désormais avoir des agents côté client qui effectuent des actions sur le Web à notre place. C'est époustouflant.\n\n## Le futur est déjà là\n\nOui, cela paraît fou, je sais, mais le futur est déjà là.\n\nWebMCP et l'API Prompt sont déjà disponibles à titre expérimental dans Chrome. Ce n'est plus seulement une discussion, une idée ou une spécification : des implémentations expérimentales sont disponibles et fonctionnent. Vous pouvez maintenant créer un site web qui expose des outils WebMCP et [discuter avec une IA intégrée au navigateur](https:\u002F\u002Fdeveloper.chrome.com\u002Fblog\u002Fprompt-api-with-vercel-ai). [Cloudflare expérimente également WebMCP](https:\u002F\u002Fblog.cloudflare.com\u002Fwebmcp\u002F#:~:text=You%20do%20not,Copy%20link) au travers de son produit BrowserRun, et ChatGPT Desktop prend en charge WebMCP pour faciliter la navigation des agents sur le Web.\n\nL'IA intégrée au navigateur désigne des LLM comme Gemini Nano de Chrome ou les modèles Gemma de Google, capables de fonctionner de manière privée et entièrement dans le navigateur, sur votre ordinateur. Je ne parle pas de Codex ou de Claude qui se connectent à votre navigateur et effectuent des actions en lisant le DOM ou en prenant des captures d'écran. Je parle d'un agent dont le modèle peut fonctionner hors ligne et utiliser des API dédiées pour interagir de manière fluide avec les sites web.\n\n## Demander des actions\n\nPour rendre cette idée plus concrète, j'ai créé une petite démo qui montre comment cela fonctionne. Au départ, mon objectif était simplement d'explorer et d'utiliser WebMCP. Cela a pris une ampleur plus importante que prévu car, hormis [une extension de navigateur pour les tests](https:\u002F\u002Fchromewebstore.google.com\u002Fdetail\u002Fwebmcp-model-context-tool\u002Fgbpdfapgefenggkahomfgkhfehlcenpd?pli=1), je n'ai trouvé aucun autre outil pour le tester.\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ffrom-browsing-to-asking-the-web-learns-to-take-action\u002Fdemo.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Démo de l'assistant IA intégré au navigateur effectuant des actions sur le site web.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nQue venez-vous de voir ?\n\nNous utilisons [code](https:\u002F\u002Fcode.soubiran.dev), mon outil qui transforme du code brut en de belles images faciles à partager. Vous collez un extrait de code, sélectionnez le langage et la couleur d'arrière-plan, puis le capturez sous forme d'image. Vous pouvez ensuite le partager avec n'importe qui sur les réseaux sociaux.\n\nHabituellement, l'utilisateur effectue manuellement toutes les actions que je viens de décrire.\n\nL'agent modifie le code à partir du prompt fourni, change les paramètres et prend finalement une capture d'écran du résultat. Au fond, ce n'est rien de plus que ce qu'un humain peut faire, mais un agent local effectue toutes ces actions. C'est cela, le véritable changement, et c'est cela, le futur.\n\nJe sais que cette démo est inutile. Cependant, elle vous donne une idée concrète de ce nouveau paradigme. Tout reste encore à inventer et à concevoir, de l'interface à l'expérience.\n\n## Deux API pour y parvenir\n\nSi c'est le futur, nous devrions examiner de plus près les deux API qui le rendent possible. J'ai une bonne nouvelle pour vous : en apparence, elles sont extrêmement simples. Cependant, les nouvelles possibilités qu'elles ouvrent rendent leur utilisation plus complexe qu'il n'y paraît au premier abord.\n\n[WebMCP](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp) se présente sous deux formes :\n\n1. Une API impérative pour définir des outils avec JavaScript ;\n2. Une API déclarative pour définir des outils avec des annotations HTML.\n\nLa première est similaire à la manière dont un serveur MCP définit des outils.\n\n```typescript\nawait document.modelContext.registerTool({\n  name: 'performAction',\n  description: 'Perform an action on the page.',\n  inputSchema: {\n    type: 'object',\n    properties: {\n      input: { type: 'string' },\n    },\n    required: ['input'],\n  },\n  execute: async ({ input }) => {\n    \u002F\u002F Add the logic to perform the action here\n  },\n})\n```\n\nL'objet `document` reçoit une nouvelle propriété appelée `modelContext`, qui constitue le point d'entrée pour créer et enregistrer des outils sur la page actuelle. Les agents qui utilisent votre site doivent savoir quels outils sont disponibles. `modelContext` expose donc une méthode `getTools()` qui renvoie la liste des outils disponibles sur la page.\n\n```typescript\nconst tools = await document.modelContext.getTools()\n```\n\nCela ressemble à la méthode `listTools()` exposée par [`createMCPClient()`](https:\u002F\u002Fai-sdk.dev\u002Fdocs\u002Freference\u002Fai-sdk-core\u002Fcreate-mcp-client) dans l'AI SDK. Cela peut être un peu déroutant car, dans le navigateur, nous agissons à la fois comme le serveur en enregistrant des outils et comme le client en y accédant. La raison est que votre site web n'est pas nécessairement celui qui consomme ces outils.\n\nVous pouvez également exécuter un outil à l'aide de la méthode `executeTool()` de l'objet `modelContext`.\n\n```typescript\nconst [tool] = await document.modelContext.getTools()\n\nawait document.modelContext.executeTool(tool, JSON.stringify({\n  input: '...',\n}))\n```\n\nLa seconde est l'API déclarative, qui permet de définir des outils à l'aide d'annotations HTML. Elle est principalement conçue pour les formulaires :\n\n```html\n\u003Cform toolname=\"supportRequestTool\"\n  tooldescription=\"Submit a request for support.\"\n  action=\"\u002Fsubmit\">\n\n  \u003Clabel for=\"firstName\">First Name\u003C\u002Flabel>\n  \u003Cinput type=text name=firstName>\n\n  \u003Clabel for=\"lastName\">Last Name\u003C\u002Flabel>\n  \u003Cinput type=text name=lastName>\n\n  \u003Cselect name=\"select\" required\n    toolparamdescription=\"Determines what team this request is routed to.\">\n    \u003Coption value=\"Customer happiness team\">Return my purchase.\u003C\u002Foption>\n    \u003Coption value=\"Distribution team\">Check where my package is.\u003C\u002Foption>\n    \u003Coption value=\"Website support team\">Get help on the website.\u003C\u002Foption>\n  \u003C\u002Fselect>\n\n  \u003Cbutton type=submit>Submit\u003C\u002Fbutton>\n\u003C\u002Fform>\n```\n\n> [!NOTE]\n> Exemple tiré de la [documentation de l'API déclarative de WebMCP](https:\u002F\u002Fdeveloper.chrome.com\u002Fdocs\u002Fai\u002Fwebmcp\u002Fdeclarative-api).\n\nVient ensuite l'API Prompt. Elle est plus complexe, car elle nécessite davantage de gestion de la disponibilité et du cycle de vie. Par exemple, vous devez détecter si le LLM intégré au navigateur est disponible. S'il ne l'est pas, vous devez en connaître la raison. L'appareil est peut-être incompatible, ou le LLM n'a peut-être pas encore été téléchargé. S'il peut l'être, vous devez demander au navigateur de le télécharger, ce qui peut prendre du temps. Vous devez donc informer l'utilisateur que quelque chose se passe en arrière-plan. De plus, l'API expose des primitives de bas niveau que vous ne voudrez probablement pas manipuler directement.\n\nCela demande plus de travail que d'appeler une API. La bonne nouvelle, c'est qu'un provider communautaire appelé [Browser AI](https:\u002F\u002Fai-sdk.dev\u002Fproviders\u002Fcommunity-providers\u002Fbrowser-ai) simplifie l'utilisation de l'API Prompt avec l'AI SDK.\n\n```typescript\nimport { browserAI } from '@browser-ai\u002Fcore'\nimport { streamText } from 'ai'\n\nconst model = browserAI()\n\nconst { textStream } = streamText({\n  model,\n  prompt: 'Describe the future of the web in five sentences.',\n})\n\nfor await (const textPart of textStream) {\n  console.log(textPart)\n}\n```\n\n> [!NOTE]\n> Je vous recommande vivement de lire l'article [Utiliser l'API Prompt intégrée avec le Vercel AI SDK](https:\u002F\u002Fdeveloper.chrome.com\u002Fblog\u002Fprompt-api-with-vercel-ai).\n\n## Ce n'est que le début\n\nPour l'instant, même si vous pouvez déjà tester les deux API, elles restent expérimentales et leur prise en charge par les navigateurs est limitée. Cela signifie que la plupart des gens ne pourront pas les utiliser. Elles doivent encore être standardisées et adoptées par d'autres éditeurs de navigateurs, comme Mozilla et Apple.\n\nDe plus, les modèles intégrés au navigateur sont limités, particulièrement en comparaison avec les modèles de pointe. Ils peuvent répondre à des questions et appeler des outils, mais vous devez garder les instructions et les paramètres des outils simples, vraiment simples.\n\nIls deviendront inévitablement de plus en plus intelligents. Récemment, Qwen a publié Qwen3.8-27B, un modèle capable de fonctionner sur certains appareils grand public. Google fait également des progrès importants avec sa gamme de modèles Gemma, conçus pour fonctionner directement sur les appareils. Les appareils deviennent plus puissants et plus aptes à exécuter ces modèles. Les SoC de la série M d'Apple intègrent un Neural Engine, ou NPU, et Google a fait de même avec ses SoC Tensor. La dernière version de Siri effectue également des traitements d'IA directement sur l'appareil, et elle semble vraiment puissante. Ce n'est désormais plus qu'une question de temps.\n\nEnfin, lorsque tout cela sera standardisé et mature, disposer de tout ce qu'il faut, prêt à l'emploi, vous donnera un avantage concurrentiel.\n\n---\n\nPendant que j'écrivais cet article, OpenAI a annoncé un [défi WebMCP](https:\u002F\u002Fopenai.com\u002Fwebmcp-challenge\u002F) pour explorer ce qui est possible. Il semble que je sois sur la bonne voie.\n",{"title":31,"description":1182},{"loc":1184},"fr\u002Fposts\u002Ffrom-browsing-to-asking-the-web-learns-to-take-action","5KJ6HxIhI2sMdTPTD_HRBShbZKxMIMvwZ9YRopfvR3w",{"id":1191,"title":1192,"body":1193,"contentId":1623,"date":1624,"description":1625,"extension":18,"meta":1626,"navigation":20,"path":1627,"rawbody":1628,"robots":23,"schemaOrg":23,"seo":1629,"sitemap":1630,"stem":1631,"__hash__":1632},"posts\u002Ffr\u002Fposts\u002Fagent-skills-will-replace-side-projects-in-portfolios.md","Les skills d’agents remplaceront les projets personnels",{"type":7,"value":1194,"toc":1617},[1195,1198,1201,1204,1207,1210,1213,1219,1223,1226,1229,1240,1243,1252,1255,1262,1265,1272,1275,1278,1282,1285,1288,1291,1294,1307,1319,1322,1330,1333,1337,1340,1349,1352,1375,1378,1436,1439,1449,1452,1455,1471,1474,1564,1570,1574,1589,1592,1608,1614],[35,1196,1197],{},"J’ai souvent entendu des personnes, de mes professeurs à d’autres développeurs, dire que les projets personnels étaient un bon moyen de se démarquer parmi une pile de CV, de se vendre lors d’un entretien et de prouver sa compétence sans avoir à l’affirmer.",[35,1199,1200],{},"Je le pense toujours. En fait, j’irais même plus loin : les projets personnels sont l’une des manières les plus convaincantes de démontrer ce dont vous êtes capable. Mais le paysage évolue. Depuis l’essor de l’IA et des LLMs, des plateformes comme Lovable et v0 permettent à bien plus de personnes de transformer une idée en projet fonctionnel.",[35,1202,1203],{},"Bien sûr, tout le monde ne construira pas. Mais davantage de personnes le feront, ce qui accroît la pression concurrentielle et rend la différenciation plus difficile.",[35,1205,1206],{},"Cela ne rend pas les projets inutiles. Cela les rend moins rares. Un projet montrera toujours ce que vous avez construit, mais il pourrait ne plus suffire à montrer votre façon de réfléchir, de prendre des décisions et d’assumer la responsabilité du résultat.",[35,1208,1209],{},"Nous sommes développeurs, pas de simples constructeurs. Construire un logiciel demande de l’ingénierie, et c’est bien plus difficile qu’il n’y paraît. Par exemple, mon père expérimente le vibe coding, mais sans mon aide pour choisir les outils, comprendre l’architecture et sélectionner la plateforme, il lui serait presque impossible de créer des projets à la hauteur de ses ambitions.",[35,1211,1212],{},"J’adore voir mon père faire du vibe coding sur Cloudflare avec Antigravity, et je ne m’attendais jamais à voir cela. Mais cette histoire amusante porte aussi une leçon : nous devons relever le niveau et élargir notre état d’esprit. Ce que nous appelions autrefois un projet personnel peut désormais être créé avec quelques prompts, et une idée de startup peut maintenant devenir un projet personnel. Nos ambitions doivent être plus grandes, mais les preuves que nous apportons de nos capacités d’ingénierie aussi. C’est de là que viendra la différenciation.",[35,1214,1215],{},[1216,1217,1218],"em",{},"Faites simplement les choses honnêtement. Vous resterez très loin devant la plupart des gens, bien plus que vous ne le pensez.",[51,1220,1222],{"id":1221},"vous-nêtes-plus-seul","Vous n’êtes plus seul",[35,1224,1225],{},"Jusqu’à récemment, construire un logiciel signifiait compter sur ses propres mains et son propre temps. Il fallait consacrer des jours et des nuits à apprendre les outils, choisir l’architecture et écrire le code. Aujourd’hui, quelques prompts peuvent vous séparer d’un projet fonctionnel.",[35,1227,1228],{},"Avec les agents, vous pouvez consacrer davantage de temps aux choses importantes : comprendre le problème, réfléchir à l’architecture et aux fonctionnalités utiles, écrire le contenu et vérifier le résultat. L’agent peut prendre en charge une plus grande partie du travail de production.",[35,1230,1231,1232],{},"Vous n’êtes plus seul. Vous avez un partenaire, un coéquipier, auquel déléguer une partie du travail. ",[1216,1233,1234,1235],{},"S’il vous plaît, déléguez avec discernement. ",[39,1236,1239],{"href":1237,"rel":1238},"https:\u002F\u002Fromainlanz.com\u002Farticles\u002Fdeleguer-le-clavier-pas-la-reflexion",[77],"Déléguez le clavier, pas la réflexion.",[35,1241,1242],{},"Pendant un temps, nous avons entendu que l’IA était une bulle, qu’il s’agissait d’un effet de mode qui allait bientôt s’essouffler. Les produits individuels peuvent aller et venir, mais les capacités sous-jacentes ne disparaîtront pas. Avec la sortie de Qwen3.8 27B, nous pouvons désormais faire tourner de puissants LLMs sur du matériel grand public. Certes, c’est lent, le matériel est coûteux et ce n’est pas au niveau des modèles de pointe. Mais ChatGPT est sorti en novembre 2022 : regardez où nous en sommes aujourd’hui.",[35,1244,1245,1246,1251],{},"Les LLMs et les agents sont là pour rester. Nous devons, en tant que marché, nous adapter, évoluer et élargir notre état d’esprit. Comme je l’explique dans ",[39,1247,1250],{"href":1248,"rel":1249},"https:\u002F\u002Fsoubiran.dev\u002Ftalks\u002F2026-04-17\u002Fmixit",[77],"mon talk à MiXiT",", le métier de développeur évolue plutôt qu’il ne disparaît. Les entreprises devront adapter leurs processus de recrutement, et les candidats devront adapter leurs portfolios.",[35,1253,1254],{},"Jusqu’à récemment, un test technique ressemblait souvent à ceci : \"Voici le problème. Expliquez comment vous le résoudriez, puis écrivez le code.\" Avec les agents, écrire du code comme unique critère d’évaluation devient moins pertinent. Une évaluation plus solide pourrait mesurer la manière dont un candidat emmène un agent d’une idée à un produit fonctionnel, ou d’un produit existant à une nouvelle fonctionnalité, dans des contextes greenfield comme brownfield. Le candidat devrait toujours expliquer ses décisions, relire le résultat et en rester responsable.",[35,1256,1257,1258,1261],{},"Cela a d’énormes conséquences sur la manière dont nous nous préparons en tant que candidats. Nous devons comprendre ce qu’est un fichier ",[156,1259,1260],{},"AGENTS.md"," et ce qu’est un skill d’agent, mais aussi savoir les écrire et les évaluer. Nous devons être capables de reconnaître, à leur seule lecture, ce qui est bon, ce qui est mauvais et ce qui pourrait échouer. Nous devons améliorer notre ingénierie de prompts et de contexte.",[35,1263,1264],{},"Cela ne signifie pas que les fondamentaux deviennent moins pertinents. Bien au contraire : moins nous écrivons de code nous-mêmes, plus nous avons besoin des connaissances et du jugement nécessaires pour évaluer ce que produit un agent. Architecture, bases de données, réseau, sécurité, tests et maintenabilité comptent toujours. Tout comme l’esprit critique, la curiosité et la capacité à agir.",[35,1266,1267,1268,1271],{},"Lorsque j’emploie le mot ",[1216,1269,1270],{},"skills"," dans cet article, je parle de deux choses liées, mais distinctes. D’abord, il y a les compétences humaines : fondamentaux de l’ingénierie, jugement, esprit critique, ingénierie de prompts, ingénierie de contexte et capacité à guider puis relire des agents. Ensuite, il y a les skills d’agents réutilisables : des instructions, ressources et workflows documentés que nous fournissons aux agents pour leur permettre d’accomplir une tâche de façon cohérente.",[35,1273,1274],{},"C’est ce deuxième type qui constitue le nouvel artefact de portfolio sur lequel je veux me concentrer. Les skills d’agents réutilisables ne remplacent pas les compétences humaines, ni les projets. Ils les complètent. Les projets montrent ce que vous avez construit, tandis que les skills d’agents peuvent montrer votre façon de travailler et ce que vous avez appris à rendre réutilisable.",[35,1276,1277],{},"Nous pourrions aller encore plus loin en publiant des ressources prêtes à l’emploi qui aideraient les autres à démarrer plus vite.",[51,1279,1281],{"id":1280},"montrer-des-skills-à-côté-des-projets","Montrer des skills à côté des projets",[35,1283,1284],{},"Si vous n’avez pas encore de nom de domaine, achetez-en un. Vraiment.",[35,1286,1287],{},"Un site web est la meilleure vitrine. Depuis des années, nous utilisons des sites web pour présenter notre expérience professionnelle, nos projets personnels et nos contributions open source. Nous pouvons maintenant aussi les utiliser pour publier les skills que nous avons créés pour nos agents.",[35,1289,1290],{},"Ces skills ne remplacent pas les projets. Ce sont un nouveau type d’artefact qui peut les accompagner, ainsi qu’une nouvelle manière de vous différencier de tous les autres.",[35,1292,1293],{},"Les skills d’agents sont faits pour être réutilisés entre les projets, facilement installés et facilement découverts. Nous avons donc besoin d’un moyen de les exposer sur nos sites web.",[35,1295,1296,1297,1302,1303,1306],{},"C’est pourquoi Cloudflare a publié la RFC ",[39,1298,1301],{"href":1299,"rel":1300},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fagent-skills-discovery-rfc",[77],"Agent Skills Discovery via Well-Known URIs",". L’idée est de standardiser la façon dont les skills sont exposés sur un site web, afin que chacun puisse les découvrir et les utiliser dans ses propres projets. Un index central sera disponible à l’adresse ",[156,1304,1305],{},"https:\u002F\u002F\u003Cvotre-site>\u002F.well-known\u002Fagent-skills\u002Findex.json"," et listera tous les skills disponibles sur votre site web. En suivant chaque entrée, les personnes pourront accéder au skill et aux fichiers qui lui correspondent.",[35,1308,1309,1310,1314,1315,1318],{},"Aujourd’hui, des outils comme ",[39,1311,1270],{"href":1312,"rel":1313},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fskills",[77]," permettent d’ajouter des skills depuis un site web à votre projet. Par exemple, pour ajouter mes skills à votre projet, il vous suffit d’exécuter ",[156,1316,1317],{},"npx skills add https:\u002F\u002Fsoubiran.dev",". À l’avenir, lors d’un test technique, vous pourrez importer des skills pour aider votre agent à travailler comme prévu, ou en discuter avec la personne qui vous fait passer l’entretien. Cela pourrait devenir une manière supplémentaire d’évaluer un candidat.",[35,1320,1321],{},"Lorsque vous embauchez un plombier, vous ne lui fournissez ni clé à molette, ni tournevis, ni perceuse. Il vient avec ses propres outils. Les développeurs apporteront eux aussi de plus en plus leurs propres outils réutilisables et leurs façons de travailler. Un portfolio montrera non seulement ce qu’ils ont construit, mais aussi leur manière d’aborder la construction de logiciels.",[35,1323,1324,1325,215],{},"C’est ma vision de l’avenir du recrutement et des portfolios de candidats. Je me trompe peut-être sur les détails, mais les choses vont changer, et ",[39,1326,1329],{"href":1327,"rel":1328},"https:\u002F\u002Fwww.epicweb.dev\u002Ftalks\u002Fepic-bets-practical-tips-for-betting-on-yourself",[77],"nous devrons nous adapter",[35,1331,1332],{},"Si les skills réutilisables doivent devenir des artefacts de portfolio, ils doivent être faciles à publier, à découvrir et à réutiliser. Ce qui m’amène à la partie pratique de cet article.",[51,1334,1336],{"id":1335},"rendre-vos-skills-disponibles-pour-le-monde-entier","Rendre vos skills disponibles pour le monde entier",[35,1338,1339],{},"La RFC est prometteuse, mais cela signifie-t-il que nous devons créer et maintenir manuellement un fichier de skills d’agents well-known ? Honnêtement, ce serait fastidieux. Personne n’a envie de ça.",[35,1341,1342,1343,1345,1346,215],{},"J’ai donc créé un module Nuxt qui intègre automatiquement à votre site les skills stockés dans le dossier ",[156,1344,1270],{}," à la racine de votre projet, ainsi que le fichier de skills d’agents well-known à l’adresse ",[156,1347,1348],{},".well-known\u002Fagent-skills\u002Findex.json",[35,1350,1351],{},"Il n’y a aucune configuration à écrire. Installez simplement le module, et il s’occupe du reste.",[192,1353,1358],{"className":1354,"code":1355,"filename":1356,"language":1357,"meta":13,"style":13},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm install -D nuxt-skills\n","terminal","sh",[156,1359,1360],{"__ignoreMap":13},[200,1361,1362,1366,1369,1372],{"class":202,"line":203},[200,1363,1365],{"class":1364},"sBMFI","pnpm",[200,1367,1368],{"class":245}," install",[200,1370,1371],{"class":245}," -D",[200,1373,1374],{"class":245}," nuxt-skills\n",[35,1376,1377],{},"Ajoutez ensuite le module à votre configuration Nuxt :",[192,1379,1384],{"className":1380,"code":1381,"filename":1382,"language":1383,"meta":13,"style":13},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export default defineNuxtConfig({\n  modules: [\n    'nuxt-skills',\n  ],\n})\n","nuxt.config.ts","ts",[156,1385,1386,1401,1411,1423,1430],{"__ignoreMap":13},[200,1387,1388,1391,1394,1397,1399],{"class":202,"line":203},[200,1389,1390],{"class":206},"export",[200,1392,1393],{"class":206}," default",[200,1395,1396],{"class":223}," defineNuxtConfig",[200,1398,227],{"class":210},[200,1400,230],{"class":214},[200,1402,1403,1406,1408],{"class":202,"line":14},[200,1404,1405],{"class":235},"  modules",[200,1407,239],{"class":214},[200,1409,1410],{"class":210}," [\n",[200,1412,1413,1416,1419,1421],{"class":202,"line":255},[200,1414,1415],{"class":214},"    '",[200,1417,1418],{"class":245},"nuxt-skills",[200,1420,249],{"class":214},[200,1422,252],{"class":214},[200,1424,1425,1428],{"class":202,"line":272},[200,1426,1427],{"class":210},"  ]",[200,1429,252],{"class":214},[200,1431,1432,1434],{"class":202,"line":283},[200,1433,413],{"class":214},[200,1435,416],{"class":210},[35,1437,1438],{},"C’est tout. Le module Nuxt s’occupe du reste.",[35,1440,1441,1442,1445,1446,1448],{},"Vos visiteurs peuvent ensuite utiliser ",[156,1443,1444],{},"npx skills add https:\u002F\u002F\u003Cvotre-site>"," pour ajouter vos skills à leurs projets. Vous pourriez être l’un de ces visiteurs. Pour ajouter mes skills à un projet, j’utilise ",[156,1447,1317],{},". C’est facile à retenir, simple à utiliser, et cela fonctionne tout simplement.",[35,1450,1451],{},"Vous n’utilisez peut-être pas Nuxt. Aucun problème : j’ai aussi créé un plugin Vite.",[35,1453,1454],{},"Installez d’abord le plugin :",[192,1456,1458],{"className":1354,"code":1457,"filename":1356,"language":1357,"meta":13,"style":13},"pnpm install -D unplugin-skills\n",[156,1459,1460],{"__ignoreMap":13},[200,1461,1462,1464,1466,1468],{"class":202,"line":203},[200,1463,1365],{"class":1364},[200,1465,1368],{"class":245},[200,1467,1371],{"class":245},[200,1469,1470],{"class":245}," unplugin-skills\n",[35,1472,1473],{},"Ajoutez ensuite le plugin à votre configuration Vite :",[192,1475,1478],{"className":1380,"code":1476,"filename":1477,"language":1383,"meta":13,"style":13},"import skills from 'unplugin-skills\u002Fvite'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [\n    skills(),\n  ],\n})\n","vite.config.ts",[156,1479,1480,1497,1517,1521,1533,1542,1552,1558],{"__ignoreMap":13},[200,1481,1482,1484,1487,1490,1492,1495],{"class":202,"line":203},[200,1483,972],{"class":206},[200,1485,1486],{"class":210}," skills ",[200,1488,1489],{"class":206},"from",[200,1491,242],{"class":214},[200,1493,1494],{"class":245},"unplugin-skills\u002Fvite",[200,1496,991],{"class":214},[200,1498,1499,1501,1503,1506,1508,1510,1512,1515],{"class":202,"line":14},[200,1500,972],{"class":206},[200,1502,318],{"class":214},[200,1504,1505],{"class":210}," defineConfig",[200,1507,980],{"class":214},[200,1509,983],{"class":206},[200,1511,242],{"class":214},[200,1513,1514],{"class":245},"vite",[200,1516,991],{"class":214},[200,1518,1519],{"class":202,"line":255},[200,1520,528],{"emptyLinePlaceholder":20},[200,1522,1523,1525,1527,1529,1531],{"class":202,"line":272},[200,1524,1390],{"class":206},[200,1526,1393],{"class":206},[200,1528,1505],{"class":223},[200,1530,227],{"class":210},[200,1532,230],{"class":214},[200,1534,1535,1538,1540],{"class":202,"line":283},[200,1536,1537],{"class":235},"  plugins",[200,1539,239],{"class":214},[200,1541,1410],{"class":210},[200,1543,1544,1547,1550],{"class":202,"line":300},[200,1545,1546],{"class":223},"    skills",[200,1548,1549],{"class":210},"()",[200,1551,252],{"class":214},[200,1553,1554,1556],{"class":202,"line":310},[200,1555,1427],{"class":210},[200,1557,252],{"class":214},[200,1559,1560,1562],{"class":202,"line":336},[200,1561,413],{"class":214},[200,1563,416],{"class":210},[35,1565,1566,1567,1569],{},"Sans configuration supplémentaire, le plugin intègre automatiquement à votre site les skills disponibles à la racine du projet, avec le fichier de skills d’agents well-known (",[156,1568,1348],{},").",[51,1571,1573],{"id":1572},"dans-la-continuité-du-travail-de-léquipe-docus","Dans la continuité du travail de l’équipe Docus",[35,1575,1576,1577,1582,1583,1588],{},"Ce travail n’aurait pas été possible sans l’incroyable talent de l’équipe ",[39,1578,1581],{"href":1579,"rel":1580},"https:\u002F\u002Fdocus.dev\u002F",[77],"Docus",". Le 31 mars 2026, Hugo a ouvert une pull request pour ",[39,1584,1587],{"href":1585,"rel":1586},"https:\u002F\u002Fgithub.com\u002Fnuxt-content\u002Fdocus\u002Fpull\u002F1297",[77],"ajouter la prise en charge de la découverte des skills d’agents via well-known",". L’équipe l’a ajoutée sous forme de module à la couche Docus.",[35,1590,1591],{},"Cette pull request a inspiré mon travail. Au départ, j’avais besoin d’un plugin Vite pour intégrer mes skills à mon site. Puis, j’ai réalisé que créer un module Nuxt utilisable par tout le monde ne serait pas très difficile si je réutilisais la logique centrale du plugin Vite. C’est donc ce que j’ai fait, et vous pouvez maintenant l’utiliser vous aussi.",[1593,1594,1595,1602],"ul",{},[71,1596,1597,1598],{},"Plugin Vite : ",[1599,1600],"git-hub-link",{"repo":1601},"barbapapazes\u002Funplugin-skills",[71,1603,1604,1605],{},"Module Nuxt : ",[1599,1606],{"repo":1607},"barbapapazes\u002Fnuxt-skills",[35,1609,1610,1611],{},"C’est toute la puissance de l’open source. ",[1216,1612,1613],{},"Merci, Hugo, de m’avoir inspiré.",[1170,1615,1616],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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}",{"title":13,"searchDepth":14,"depth":14,"links":1618},[1619,1620,1621,1622],{"id":1221,"depth":14,"text":1222},{"id":1280,"depth":14,"text":1281},{"id":1335,"depth":14,"text":1336},{"id":1572,"depth":14,"text":1573},"6e1166e4-27e7-40c0-a623-78d2606e7f27","2026-08-25","À l’heure où l’IA facilite le développement logiciel, les portfolios mettent en avant compétences d’ingénierie, projets et skills d’agents réutilisables.",{},"\u002Ffr\u002Fposts\u002Fagent-skills-will-replace-side-projects-in-portfolios","---\ncontentId: 6e1166e4-27e7-40c0-a623-78d2606e7f27\ntitle: \"Les skills d’agents remplaceront les projets personnels\"\ndescription: \"À l’heure où l’IA facilite le développement logiciel, les portfolios mettent en avant compétences d’ingénierie, projets et skills d’agents réutilisables.\"\ndate: 2026-08-25\n---\n\nJ’ai souvent entendu des personnes, de mes professeurs à d’autres développeurs, dire que les projets personnels étaient un bon moyen de se démarquer parmi une pile de CV, de se vendre lors d’un entretien et de prouver sa compétence sans avoir à l’affirmer.\n\nJe le pense toujours. En fait, j’irais même plus loin : les projets personnels sont l’une des manières les plus convaincantes de démontrer ce dont vous êtes capable. Mais le paysage évolue. Depuis l’essor de l’IA et des LLMs, des plateformes comme Lovable et v0 permettent à bien plus de personnes de transformer une idée en projet fonctionnel.\n\nBien sûr, tout le monde ne construira pas. Mais davantage de personnes le feront, ce qui accroît la pression concurrentielle et rend la différenciation plus difficile.\n\nCela ne rend pas les projets inutiles. Cela les rend moins rares. Un projet montrera toujours ce que vous avez construit, mais il pourrait ne plus suffire à montrer votre façon de réfléchir, de prendre des décisions et d’assumer la responsabilité du résultat.\n\nNous sommes développeurs, pas de simples constructeurs. Construire un logiciel demande de l’ingénierie, et c’est bien plus difficile qu’il n’y paraît. Par exemple, mon père expérimente le vibe coding, mais sans mon aide pour choisir les outils, comprendre l’architecture et sélectionner la plateforme, il lui serait presque impossible de créer des projets à la hauteur de ses ambitions.\n\nJ’adore voir mon père faire du vibe coding sur Cloudflare avec Antigravity, et je ne m’attendais jamais à voir cela. Mais cette histoire amusante porte aussi une leçon : nous devons relever le niveau et élargir notre état d’esprit. Ce que nous appelions autrefois un projet personnel peut désormais être créé avec quelques prompts, et une idée de startup peut maintenant devenir un projet personnel. Nos ambitions doivent être plus grandes, mais les preuves que nous apportons de nos capacités d’ingénierie aussi. C’est de là que viendra la différenciation.\n\n_Faites simplement les choses honnêtement. Vous resterez très loin devant la plupart des gens, bien plus que vous ne le pensez._\n\n## Vous n’êtes plus seul\n\nJusqu’à récemment, construire un logiciel signifiait compter sur ses propres mains et son propre temps. Il fallait consacrer des jours et des nuits à apprendre les outils, choisir l’architecture et écrire le code. Aujourd’hui, quelques prompts peuvent vous séparer d’un projet fonctionnel.\n\nAvec les agents, vous pouvez consacrer davantage de temps aux choses importantes : comprendre le problème, réfléchir à l’architecture et aux fonctionnalités utiles, écrire le contenu et vérifier le résultat. L’agent peut prendre en charge une plus grande partie du travail de production.\n\nVous n’êtes plus seul. Vous avez un partenaire, un coéquipier, auquel déléguer une partie du travail. _S’il vous plaît, déléguez avec discernement. [Déléguez le clavier, pas la réflexion.](https:\u002F\u002Fromainlanz.com\u002Farticles\u002Fdeleguer-le-clavier-pas-la-reflexion)_\n\nPendant un temps, nous avons entendu que l’IA était une bulle, qu’il s’agissait d’un effet de mode qui allait bientôt s’essouffler. Les produits individuels peuvent aller et venir, mais les capacités sous-jacentes ne disparaîtront pas. Avec la sortie de Qwen3.8 27B, nous pouvons désormais faire tourner de puissants LLMs sur du matériel grand public. Certes, c’est lent, le matériel est coûteux et ce n’est pas au niveau des modèles de pointe. Mais ChatGPT est sorti en novembre 2022 : regardez où nous en sommes aujourd’hui.\n\nLes LLMs et les agents sont là pour rester. Nous devons, en tant que marché, nous adapter, évoluer et élargir notre état d’esprit. Comme je l’explique dans [mon talk à MiXiT](https:\u002F\u002Fsoubiran.dev\u002Ftalks\u002F2026-04-17\u002Fmixit), le métier de développeur évolue plutôt qu’il ne disparaît. Les entreprises devront adapter leurs processus de recrutement, et les candidats devront adapter leurs portfolios.\n\nJusqu’à récemment, un test technique ressemblait souvent à ceci : \"Voici le problème. Expliquez comment vous le résoudriez, puis écrivez le code.\" Avec les agents, écrire du code comme unique critère d’évaluation devient moins pertinent. Une évaluation plus solide pourrait mesurer la manière dont un candidat emmène un agent d’une idée à un produit fonctionnel, ou d’un produit existant à une nouvelle fonctionnalité, dans des contextes greenfield comme brownfield. Le candidat devrait toujours expliquer ses décisions, relire le résultat et en rester responsable.\n\nCela a d’énormes conséquences sur la manière dont nous nous préparons en tant que candidats. Nous devons comprendre ce qu’est un fichier `AGENTS.md` et ce qu’est un skill d’agent, mais aussi savoir les écrire et les évaluer. Nous devons être capables de reconnaître, à leur seule lecture, ce qui est bon, ce qui est mauvais et ce qui pourrait échouer. Nous devons améliorer notre ingénierie de prompts et de contexte.\n\nCela ne signifie pas que les fondamentaux deviennent moins pertinents. Bien au contraire : moins nous écrivons de code nous-mêmes, plus nous avons besoin des connaissances et du jugement nécessaires pour évaluer ce que produit un agent. Architecture, bases de données, réseau, sécurité, tests et maintenabilité comptent toujours. Tout comme l’esprit critique, la curiosité et la capacité à agir.\n\nLorsque j’emploie le mot _skills_ dans cet article, je parle de deux choses liées, mais distinctes. D’abord, il y a les compétences humaines : fondamentaux de l’ingénierie, jugement, esprit critique, ingénierie de prompts, ingénierie de contexte et capacité à guider puis relire des agents. Ensuite, il y a les skills d’agents réutilisables : des instructions, ressources et workflows documentés que nous fournissons aux agents pour leur permettre d’accomplir une tâche de façon cohérente.\n\nC’est ce deuxième type qui constitue le nouvel artefact de portfolio sur lequel je veux me concentrer. Les skills d’agents réutilisables ne remplacent pas les compétences humaines, ni les projets. Ils les complètent. Les projets montrent ce que vous avez construit, tandis que les skills d’agents peuvent montrer votre façon de travailler et ce que vous avez appris à rendre réutilisable.\n\nNous pourrions aller encore plus loin en publiant des ressources prêtes à l’emploi qui aideraient les autres à démarrer plus vite.\n\n## Montrer des skills à côté des projets\n\nSi vous n’avez pas encore de nom de domaine, achetez-en un. Vraiment.\n\nUn site web est la meilleure vitrine. Depuis des années, nous utilisons des sites web pour présenter notre expérience professionnelle, nos projets personnels et nos contributions open source. Nous pouvons maintenant aussi les utiliser pour publier les skills que nous avons créés pour nos agents.\n\nCes skills ne remplacent pas les projets. Ce sont un nouveau type d’artefact qui peut les accompagner, ainsi qu’une nouvelle manière de vous différencier de tous les autres.\n\nLes skills d’agents sont faits pour être réutilisés entre les projets, facilement installés et facilement découverts. Nous avons donc besoin d’un moyen de les exposer sur nos sites web.\n\nC’est pourquoi Cloudflare a publié la RFC [Agent Skills Discovery via Well-Known URIs](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fagent-skills-discovery-rfc). L’idée est de standardiser la façon dont les skills sont exposés sur un site web, afin que chacun puisse les découvrir et les utiliser dans ses propres projets. Un index central sera disponible à l’adresse `https:\u002F\u002F\u003Cvotre-site>\u002F.well-known\u002Fagent-skills\u002Findex.json` et listera tous les skills disponibles sur votre site web. En suivant chaque entrée, les personnes pourront accéder au skill et aux fichiers qui lui correspondent.\n\nAujourd’hui, des outils comme [skills](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fskills) permettent d’ajouter des skills depuis un site web à votre projet. Par exemple, pour ajouter mes skills à votre projet, il vous suffit d’exécuter `npx skills add https:\u002F\u002Fsoubiran.dev`. À l’avenir, lors d’un test technique, vous pourrez importer des skills pour aider votre agent à travailler comme prévu, ou en discuter avec la personne qui vous fait passer l’entretien. Cela pourrait devenir une manière supplémentaire d’évaluer un candidat.\n\nLorsque vous embauchez un plombier, vous ne lui fournissez ni clé à molette, ni tournevis, ni perceuse. Il vient avec ses propres outils. Les développeurs apporteront eux aussi de plus en plus leurs propres outils réutilisables et leurs façons de travailler. Un portfolio montrera non seulement ce qu’ils ont construit, mais aussi leur manière d’aborder la construction de logiciels.\n\nC’est ma vision de l’avenir du recrutement et des portfolios de candidats. Je me trompe peut-être sur les détails, mais les choses vont changer, et [nous devrons nous adapter](https:\u002F\u002Fwww.epicweb.dev\u002Ftalks\u002Fepic-bets-practical-tips-for-betting-on-yourself).\n\nSi les skills réutilisables doivent devenir des artefacts de portfolio, ils doivent être faciles à publier, à découvrir et à réutiliser. Ce qui m’amène à la partie pratique de cet article.\n\n## Rendre vos skills disponibles pour le monde entier\n\nLa RFC est prometteuse, mais cela signifie-t-il que nous devons créer et maintenir manuellement un fichier de skills d’agents well-known ? Honnêtement, ce serait fastidieux. Personne n’a envie de ça.\n\nJ’ai donc créé un module Nuxt qui intègre automatiquement à votre site les skills stockés dans le dossier `skills` à la racine de votre projet, ainsi que le fichier de skills d’agents well-known à l’adresse `.well-known\u002Fagent-skills\u002Findex.json`.\n\nIl n’y a aucune configuration à écrire. Installez simplement le module, et il s’occupe du reste.\n\n```sh [terminal]\npnpm install -D nuxt-skills\n```\n\nAjoutez ensuite le module à votre configuration Nuxt :\n\n```ts [nuxt.config.ts]\nexport default defineNuxtConfig({\n  modules: [\n    'nuxt-skills',\n  ],\n})\n```\n\nC’est tout. Le module Nuxt s’occupe du reste.\n\nVos visiteurs peuvent ensuite utiliser `npx skills add https:\u002F\u002F\u003Cvotre-site>` pour ajouter vos skills à leurs projets. Vous pourriez être l’un de ces visiteurs. Pour ajouter mes skills à un projet, j’utilise `npx skills add https:\u002F\u002Fsoubiran.dev`. C’est facile à retenir, simple à utiliser, et cela fonctionne tout simplement.\n\nVous n’utilisez peut-être pas Nuxt. Aucun problème : j’ai aussi créé un plugin Vite.\n\nInstallez d’abord le plugin :\n\n```sh [terminal]\npnpm install -D unplugin-skills\n```\n\nAjoutez ensuite le plugin à votre configuration Vite :\n\n```ts [vite.config.ts]\nimport skills from 'unplugin-skills\u002Fvite'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [\n    skills(),\n  ],\n})\n```\n\nSans configuration supplémentaire, le plugin intègre automatiquement à votre site les skills disponibles à la racine du projet, avec le fichier de skills d’agents well-known (`.well-known\u002Fagent-skills\u002Findex.json`).\n\n## Dans la continuité du travail de l’équipe Docus\n\nCe travail n’aurait pas été possible sans l’incroyable talent de l’équipe [Docus](https:\u002F\u002Fdocus.dev\u002F). Le 31 mars 2026, Hugo a ouvert une pull request pour [ajouter la prise en charge de la découverte des skills d’agents via well-known](https:\u002F\u002Fgithub.com\u002Fnuxt-content\u002Fdocus\u002Fpull\u002F1297). L’équipe l’a ajoutée sous forme de module à la couche Docus.\n\nCette pull request a inspiré mon travail. Au départ, j’avais besoin d’un plugin Vite pour intégrer mes skills à mon site. Puis, j’ai réalisé que créer un module Nuxt utilisable par tout le monde ne serait pas très difficile si je réutilisais la logique centrale du plugin Vite. C’est donc ce que j’ai fait, et vous pouvez maintenant l’utiliser vous aussi.\n\n- Plugin Vite : \u003CGitHubLink repo=\"barbapapazes\u002Funplugin-skills\" \u002F>\n- Module Nuxt : \u003CGitHubLink repo=\"barbapapazes\u002Fnuxt-skills\" \u002F>\n\nC’est toute la puissance de l’open source. _Merci, Hugo, de m’avoir inspiré._\n",{"title":1192,"description":1625},{"loc":1627},"fr\u002Fposts\u002Fagent-skills-will-replace-side-projects-in-portfolios","o0HJU3-7fXycupOHjKEsdK1oFgQZkHlts3ON43Rzgi4",{"id":1634,"title":1635,"body":1636,"contentId":1852,"date":1624,"description":1853,"extension":18,"meta":1854,"navigation":20,"path":1855,"rawbody":1856,"robots":23,"schemaOrg":23,"seo":1857,"sitemap":1858,"stem":1866,"__hash__":1867},"posts\u002Ffr\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder.md","L'IA change le monde : les hackathons sont pour tous",{"type":7,"value":1637,"toc":1847},[1638,1641,1644,1647,1662,1665,1669,1672,1675,1678,1681,1695,1698,1701,1704,1711,1715,1718,1721,1724,1733,1736,1739,1742,1745,1758,1764,1773,1779,1785,1794,1800,1806,1810,1813,1816,1819,1822,1825,1828,1836,1839,1842,1844],[35,1639,1640],{},"Dans l'article d'aujourd'hui, j'ai envie d'essayer quelque chose de nouveau.",[35,1642,1643],{},"Je veux écrire pour tout le monde. Absolument tout le monde. Que tu sois développeur expérimenté ou que tu connaisses seulement les bases de l'informatique, je vais faire de mon mieux pour que cet article te soit accessible.",[35,1645,1646],{},"Tu te demandes peut-être pourquoi je fais ce choix, surtout dans un article sur les hackathons. On associe généralement ces événements aux développeurs et aux personnes qui programment. Mais depuis quelque temps, les choses commencent à changer.",[35,1648,1649,1650,1655,1656,1661],{},"Depuis que les outils de code assisté par IA se sont démocratisés, écrire du code est devenu beaucoup plus accessible. Des services comme ",[39,1651,1654],{"href":1652,"rel":1653},"https:\u002F\u002Fbolt.new\u002F",[77],"Bolt"," et ",[39,1657,1660],{"href":1658,"rel":1659},"https:\u002F\u002Flovable.dev\u002F",[77],"Lovable"," peuvent aider à transformer une simple idée en prototype fonctionnel en quelques minutes. Même mon père, qui n'est pas ingénieur logiciel, a commencé à créer des logiciels personnels pour répondre à ses propres besoins.",[35,1663,1664],{},"Pour les hackathons, cette barrière à l'entrée plus basse change les personnes qui peuvent participer et ce qu'elles peuvent construire. Et ce n'est peut-être que la partie émergée de l'iceberg.",[51,1666,1668],{"id":1667},"quest-ce-quun-hackathon","Qu'est-ce qu'un hackathon ?",[35,1670,1671],{},"Commençons par les bases.",[35,1673,1674],{},"Un hackathon est un événement court pendant lequel des personnes transforment des idées en prototypes fonctionnels. Les participants travaillent seuls ou en équipe, généralement autour d'un thème choisi par les organisateurs. Ce thème est souvent assez large pour laisser de la place à la créativité. L'événement peut durer de quelques heures à plusieurs jours, et chaque équipe présente ce qu'elle a construit à la fin. Selon ce que chacun vient y chercher, un hackathon peut mêler compétition, rencontres, apprentissage, collaboration ou simplement l'occasion de faire connaissance avec de nouvelles personnes.",[35,1676,1677],{},"Cette définition est large parce que les organisateurs adaptent le format à leurs objectifs. Une entreprise qui organise un hackathon en interne peut demander à ses employés de créer de nouvelles fonctionnalités ou de sortir de leur routine pour explorer une technologie comme l'IA. Pour un hackathon public, elle peut inviter des personnes à expérimenter avec ses produits ou à leur imaginer de nouveaux usages. Un hackathon peut servir au marketing, au recrutement, à la recherche produit ou simplement à réunir une communauté.",[35,1679,1680],{},"De leur côté, les participants viennent pour de nombreuses raisons :",[1593,1682,1683,1686,1689,1692],{},[71,1684,1685],{},"apprendre de nouvelles compétences ;",[71,1687,1688],{},"rencontrer des personnes qui partagent leurs centres d'intérêt ;",[71,1690,1691],{},"sortir de leur routine et s'amuser ;",[71,1693,1694],{},"participer à une compétition et peut-être gagner un prix.",[35,1696,1697],{},"Un hackathon te donne une raison de créer, d'apprendre et de rencontrer de nouvelles personnes. Chacun peut y trouver sa propre motivation.",[35,1699,1700],{},"Pendant un hackathon, le temps semble se déformer. Chaque heure est tellement dense en apprentissage, en création et en discussions qu'elle peut donner l'impression de durer une semaine entière. Les hackathons peuvent être épuisants et frustrants, mais j'en ai toujours retiré quelque chose.",[35,1702,1703],{},"Je t'encourage vraiment à en essayer un, même si tu n'as aucune compétence technique. Tu pourrais être surpris par ce que tu es capable d'accomplir.",[35,1705,1706],{},[1707,1708],"img",{"alt":1709,"src":1710},"Les participants du Mistral Worldwide Hackathon à Paris forment le logo de Mistral","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fparticipants-of-the-mistral-worldwide-hackathon.webp",[51,1712,1714],{"id":1713},"huit-ans-de-hackathons","Huit ans de hackathons",[35,1716,1717],{},"Je participe à des hackathons depuis 2018.",[35,1719,1720],{},"J'ai commencé cette année-là avec la Nuit de l'Info. Elle rassemble des milliers d'étudiants sur différents sites en France pour développer une application pendant la nuit. Organisée chaque année dans la nuit du premier jeudi au vendredi de décembre, elle a réuni plus de 4 000 étudiants en 2018.",[35,1722,1723],{},"À l'époque, j'étais étudiant et je ne connaissais rien au développement web. Une association de mon campus avait organisé un cours avant le hackathon. On nous avait appris les bases du HTML et du CSS, ainsi que juste assez de Git pour travailler en équipe. C'est tout.",[35,1725,1726,1727,1732],{},"Le thème était ",[39,1728,1731],{"href":1729,"rel":1730},"https:\u002F\u002Fwww.nuitdelinfo.com\u002Fnuitinfo\u002F_media\u002Fsujets:sujet_n2i_2018.pdf",[77],"\"Accompagnement numérique d'un explorateur de l'extrême\"",". Mon équipe a créé un tableau de bord pour une mission d'exploration de Mars. Il affichait dans le navigateur un modèle 3D interactif de la base martienne. Quand les utilisateurs cliquaient sur un bâtiment, la vue zoomait dessus et affichait son état. J'étais comme un enfant, émerveillé par tout ce que je voyais. Je n'oublierai jamais ce que j'ai ressenti la première fois que je l'ai vu fonctionner.",[35,1734,1735],{},"Honnêtement, je n'ai pas fait grand-chose pendant ce hackathon. Pourtant, ça a été une révélation. À ce moment-là, j'ai compris que je voulais créer sur le web. Je suis tombé amoureux de l'idée d'avoir une toile blanche sur laquelle je pouvais créer tout ce que je voulais. J'étais accro.",[35,1737,1738],{},"J'ai ensuite participé aux éditions 2019, 2020 et 2021, chaque fois avec une équipe différente. Au début, j'apprenais auprès des personnes qui m'entouraient. Plus tard, c'est moi qui aidais mes coéquipiers à se lancer.",[35,1740,1741],{},"Tout cela s'est passé avant que des outils comme ChatGPT puissent générer et expliquer du code à la demande.",[35,1743,1744],{},"En 2026, j'ai participé à deux hackathons organisés par Mistral.",[35,1746,1747,1748,1753,1754,215],{},"Le premier était le ",[39,1749,1752],{"href":1750,"rel":1751},"https:\u002F\u002Fworldwide-hackathon.mistral.ai\u002F",[77],"Mistral Worldwide Hackathon"," en février. Il s'est déroulé dans sept villes à travers le monde : Paris, Londres, New York, San Francisco, Singapour, Sydney et Tokyo. Chaque ville a accueilli 100 participants pendant deux jours, tous en compétition autour du même thème. Mon équipe et moi avons remporté l'événement parisien. Si tu veux en savoir plus, j'ai écrit un article à ce sujet : ",[39,1755,1757],{"href":1756},".\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon","De l'idée à la victoire : Hackathon Mistral AI Paris 2026",[35,1759,1760],{},[1707,1761],{"alt":1762,"src":1763},"L'équipe gagnante du Hackathon Mistral AI à Paris","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fthe-winning-team-of-the-mistral-ai-hackathon-in-paris.webp",[35,1765,1766,1767,1772],{},"Le second était le ",[39,1768,1771],{"href":1769,"rel":1770},"https:\u002F\u002Fluma.com\u002Fmistral-summer-vibe-hackathon-paris",[77],"Mistral Vibe Hackathon"," en juillet. Mistral l'avait organisé autour de Vibe, le nom donné à la fois à son chat et à son assistant de code avec IA. Le hackathon proposait deux catégories. Les participants pouvaient soit construire quelque chose avec le chat, soit ajouter de nouvelles fonctionnalités au code source public de son application en ligne de commande.",[35,1774,1775],{},[1707,1776],{"alt":1777,"src":1778},"La salle principale du Mistral Vibe Hackathon","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fthe-main-room-of-the-mistral-vibe-hackathon.webp",[35,1780,1781],{},[1707,1782],{"alt":1783,"src":1784},"Marion travaille sur notre projet pendant le Mistral Vibe Hackathon","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fmarion-working-on-our-project.webp",[35,1786,1787,1788,1793],{},"J'ai participé à la première catégorie avec ma coéquipière ",[39,1789,1792],{"href":1790,"rel":1791},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F",[77],"Marion",". Nous voulions aider les lecteurs à évaluer des publications politiques potentiellement trompeuses sur les réseaux sociaux. Publier une fausse affirmation prend quelques secondes, mais la vérifier et la corriger peut demander beaucoup plus de temps. Notre outil examinait les sources d'une publication, retraçait l'historique de ses affirmations, vérifiait leur cohérence factuelle et analysait le langage employé. C'était très sympa de découvrir Mistral Vibe et de travailler avec Marion. Nous avons beaucoup appris en seulement huit heures.",[35,1795,1796],{},[1707,1797],{"alt":1798,"src":1799},"Marion et Estéban avant de présenter notre projet devant le jury","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fmarion-and-esteban-before-pitching-our-project.webp",[35,1801,1802],{},[1707,1803],{"alt":1804,"src":1805},"Les gagnants du Mistral Vibe Hackathon","\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fwinners-of-the-mistral-vibe-hackathon.webp",[51,1807,1809],{"id":1808},"les-hackathons-sont-désormais-ouverts-à-tous","Les hackathons sont désormais ouverts à tous",[35,1811,1812],{},"J'ai partagé cette histoire parce que les hackathons auxquels j'ai participé en 2026 étaient très différents de ceux que je connaissais en 2018. Ma première équipe comptait 10 personnes, principalement passionnées par le web. Après avoir travaillé ensemble toute la nuit, nous avons rendu notre projet sans préparer de pitch ni de slides pour un jury. Pendant les événements Mistral, les équipes comptaient entre une et quatre personnes. En seulement huit heures, elles ont livré un produit fonctionnel, un pitch, des slides et une démonstration en direct.",[35,1814,1815],{},"Le contraste est saisissant. Les événements que je connaissais s'adressaient surtout aux développeurs. Ceux auxquels j'ai participé en 2026 accueillaient un public plus large, composé de personnes qui voulaient construire quelque chose. Pour beaucoup de participants, la première question porte de moins en moins sur leur expertise technique et de plus en plus sur leur envie d'essayer.",[35,1817,1818],{},"Tu peux être designer, product manager, étudiant ou en train d'écrire une thèse, que tu travailles ou non dans la tech. Ce qui compte, c'est l'envie de transformer une idée en quelque chose de réel et d'apprendre au passage. Certains hackathons restent très techniques, mais d'autres s'ouvrent à un public plus large. Ils peuvent durer de quelques heures à deux jours, et les participants réussissent tout de même à construire des projets impressionnants.",[35,1820,1821],{},"L'IA joue un rôle croissant dans cette ouverture. Les participants peuvent demander à un assistant de code avec IA de créer une première version, d'expliquer du code qu'ils ne connaissent pas ou de les aider à corriger un problème. Pendant les hackathons, tu vas même plus loin : tu fais du vibe coding.",[35,1823,1824],{},"C'est un changement majeur. Tu n'as plus besoin d'être développeur pour commencer à construire un logiciel, et les hackathons rendent cette évolution très visible.",[35,1826,1827],{},"Rendre le code plus accessible ne supprime pas l'importance de l'expertise en développement logiciel. Construire un prototype est une chose. Créer un logiciel qui reste fiable, sécurisé et utile dans le temps en est une autre. Malgré tout, le métier de développeur évolue rapidement. Nous devons continuer à apprendre et à nous adapter, ce qui peut être à la fois stimulant et déstabilisant.",[35,1829,1830,1831,1835],{},"J'ai récemment donné ",[39,1832,1834],{"href":1833},"\u002Ffr\u002Ftalks","un talk sur l'agency",", que je définis comme le pouvoir d'agir. L'agency t'aide à te lancer avant d'avoir toutes les réponses, à découvrir ce dont un projet a besoin et à changer d'approche quand la première ne fonctionne pas. C'est important lorsque les outils et les compétences autour de nous évoluent sans cesse.",[35,1837,1838],{},"Les hackathons sont un excellent moyen de développer cette capacité. Ils t'imposent une échéance, te donnent des personnes auprès desquelles apprendre et t'autorisent à expérimenter, échouer, puis réessayer.",[35,1840,1841],{},"Tu arrives avec une idée. Quelques heures plus tard, tu repars après avoir construit quelque chose, même si ce n'est pas parfait. Pour moi, c'est cela, l'agency en pratique.",[1158,1843],{},[35,1845,1846],{},"Merci, Marion, d'avoir participé au Mistral Vibe Hackathon !",{"title":13,"searchDepth":14,"depth":14,"links":1848},[1849,1850,1851],{"id":1667,"depth":14,"text":1668},{"id":1713,"depth":14,"text":1714},{"id":1808,"depth":14,"text":1809},"352d15c1-5d9c-42ad-962e-4c00430d510c","L'IA rend les hackathons accessibles au-delà des développeurs et permet à davantage de personnes de transformer leurs idées en projets concrets.",{},"\u002Ffr\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder","---\ncontentId: 352d15c1-5d9c-42ad-962e-4c00430d510c\ntitle: \"L'IA change le monde : les hackathons sont pour tous\"\ndescription: \"L'IA rend les hackathons accessibles au-delà des développeurs et permet à davantage de personnes de transformer leurs idées en projets concrets.\"\ndate: 2026-08-25\n---\n\nDans l'article d'aujourd'hui, j'ai envie d'essayer quelque chose de nouveau.\n\nJe veux écrire pour tout le monde. Absolument tout le monde. Que tu sois développeur expérimenté ou que tu connaisses seulement les bases de l'informatique, je vais faire de mon mieux pour que cet article te soit accessible.\n\nTu te demandes peut-être pourquoi je fais ce choix, surtout dans un article sur les hackathons. On associe généralement ces événements aux développeurs et aux personnes qui programment. Mais depuis quelque temps, les choses commencent à changer.\n\nDepuis que les outils de code assisté par IA se sont démocratisés, écrire du code est devenu beaucoup plus accessible. Des services comme [Bolt](https:\u002F\u002Fbolt.new\u002F) et [Lovable](https:\u002F\u002Flovable.dev\u002F) peuvent aider à transformer une simple idée en prototype fonctionnel en quelques minutes. Même mon père, qui n'est pas ingénieur logiciel, a commencé à créer des logiciels personnels pour répondre à ses propres besoins.\n\nPour les hackathons, cette barrière à l'entrée plus basse change les personnes qui peuvent participer et ce qu'elles peuvent construire. Et ce n'est peut-être que la partie émergée de l'iceberg.\n\n## Qu'est-ce qu'un hackathon ?\n\nCommençons par les bases.\n\nUn hackathon est un événement court pendant lequel des personnes transforment des idées en prototypes fonctionnels. Les participants travaillent seuls ou en équipe, généralement autour d'un thème choisi par les organisateurs. Ce thème est souvent assez large pour laisser de la place à la créativité. L'événement peut durer de quelques heures à plusieurs jours, et chaque équipe présente ce qu'elle a construit à la fin. Selon ce que chacun vient y chercher, un hackathon peut mêler compétition, rencontres, apprentissage, collaboration ou simplement l'occasion de faire connaissance avec de nouvelles personnes.\n\nCette définition est large parce que les organisateurs adaptent le format à leurs objectifs. Une entreprise qui organise un hackathon en interne peut demander à ses employés de créer de nouvelles fonctionnalités ou de sortir de leur routine pour explorer une technologie comme l'IA. Pour un hackathon public, elle peut inviter des personnes à expérimenter avec ses produits ou à leur imaginer de nouveaux usages. Un hackathon peut servir au marketing, au recrutement, à la recherche produit ou simplement à réunir une communauté.\n\nDe leur côté, les participants viennent pour de nombreuses raisons :\n\n- apprendre de nouvelles compétences ;\n- rencontrer des personnes qui partagent leurs centres d'intérêt ;\n- sortir de leur routine et s'amuser ;\n- participer à une compétition et peut-être gagner un prix.\n\nUn hackathon te donne une raison de créer, d'apprendre et de rencontrer de nouvelles personnes. Chacun peut y trouver sa propre motivation.\n\nPendant un hackathon, le temps semble se déformer. Chaque heure est tellement dense en apprentissage, en création et en discussions qu'elle peut donner l'impression de durer une semaine entière. Les hackathons peuvent être épuisants et frustrants, mais j'en ai toujours retiré quelque chose.\n\nJe t'encourage vraiment à en essayer un, même si tu n'as aucune compétence technique. Tu pourrais être surpris par ce que tu es capable d'accomplir.\n\n![Les participants du Mistral Worldwide Hackathon à Paris forment le logo de Mistral](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fparticipants-of-the-mistral-worldwide-hackathon.webp)\n\n## Huit ans de hackathons\n\nJe participe à des hackathons depuis 2018.\n\nJ'ai commencé cette année-là avec la Nuit de l'Info. Elle rassemble des milliers d'étudiants sur différents sites en France pour développer une application pendant la nuit. Organisée chaque année dans la nuit du premier jeudi au vendredi de décembre, elle a réuni plus de 4 000 étudiants en 2018.\n\nÀ l'époque, j'étais étudiant et je ne connaissais rien au développement web. Une association de mon campus avait organisé un cours avant le hackathon. On nous avait appris les bases du HTML et du CSS, ainsi que juste assez de Git pour travailler en équipe. C'est tout.\n\nLe thème était [\"Accompagnement numérique d'un explorateur de l'extrême\"](https:\u002F\u002Fwww.nuitdelinfo.com\u002Fnuitinfo\u002F_media\u002Fsujets:sujet_n2i_2018.pdf). Mon équipe a créé un tableau de bord pour une mission d'exploration de Mars. Il affichait dans le navigateur un modèle 3D interactif de la base martienne. Quand les utilisateurs cliquaient sur un bâtiment, la vue zoomait dessus et affichait son état. J'étais comme un enfant, émerveillé par tout ce que je voyais. Je n'oublierai jamais ce que j'ai ressenti la première fois que je l'ai vu fonctionner.\n\nHonnêtement, je n'ai pas fait grand-chose pendant ce hackathon. Pourtant, ça a été une révélation. À ce moment-là, j'ai compris que je voulais créer sur le web. Je suis tombé amoureux de l'idée d'avoir une toile blanche sur laquelle je pouvais créer tout ce que je voulais. J'étais accro.\n\nJ'ai ensuite participé aux éditions 2019, 2020 et 2021, chaque fois avec une équipe différente. Au début, j'apprenais auprès des personnes qui m'entouraient. Plus tard, c'est moi qui aidais mes coéquipiers à se lancer.\n\nTout cela s'est passé avant que des outils comme ChatGPT puissent générer et expliquer du code à la demande.\n\nEn 2026, j'ai participé à deux hackathons organisés par Mistral.\n\nLe premier était le [Mistral Worldwide Hackathon](https:\u002F\u002Fworldwide-hackathon.mistral.ai\u002F) en février. Il s'est déroulé dans sept villes à travers le monde : Paris, Londres, New York, San Francisco, Singapour, Sydney et Tokyo. Chaque ville a accueilli 100 participants pendant deux jours, tous en compétition autour du même thème. Mon équipe et moi avons remporté l'événement parisien. Si tu veux en savoir plus, j'ai écrit un article à ce sujet : [De l'idée à la victoire : Hackathon Mistral AI Paris 2026](.\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon.md).\n\n![L'équipe gagnante du Hackathon Mistral AI à Paris](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fthe-winning-team-of-the-mistral-ai-hackathon-in-paris.webp)\n\nLe second était le [Mistral Vibe Hackathon](https:\u002F\u002Fluma.com\u002Fmistral-summer-vibe-hackathon-paris) en juillet. Mistral l'avait organisé autour de Vibe, le nom donné à la fois à son chat et à son assistant de code avec IA. Le hackathon proposait deux catégories. Les participants pouvaient soit construire quelque chose avec le chat, soit ajouter de nouvelles fonctionnalités au code source public de son application en ligne de commande.\n\n![La salle principale du Mistral Vibe Hackathon](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fthe-main-room-of-the-mistral-vibe-hackathon.webp)\n\n![Marion travaille sur notre projet pendant le Mistral Vibe Hackathon](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fmarion-working-on-our-project.webp)\n\nJ'ai participé à la première catégorie avec ma coéquipière [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F). Nous voulions aider les lecteurs à évaluer des publications politiques potentiellement trompeuses sur les réseaux sociaux. Publier une fausse affirmation prend quelques secondes, mais la vérifier et la corriger peut demander beaucoup plus de temps. Notre outil examinait les sources d'une publication, retraçait l'historique de ses affirmations, vérifiait leur cohérence factuelle et analysait le langage employé. C'était très sympa de découvrir Mistral Vibe et de travailler avec Marion. Nous avons beaucoup appris en seulement huit heures.\n\n![Marion et Estéban avant de présenter notre projet devant le jury](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fmarion-and-esteban-before-pitching-our-project.webp)\n\n![Les gagnants du Mistral Vibe Hackathon](\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder\u002Fwinners-of-the-mistral-vibe-hackathon.webp)\n\n## Les hackathons sont désormais ouverts à tous\n\nJ'ai partagé cette histoire parce que les hackathons auxquels j'ai participé en 2026 étaient très différents de ceux que je connaissais en 2018. Ma première équipe comptait 10 personnes, principalement passionnées par le web. Après avoir travaillé ensemble toute la nuit, nous avons rendu notre projet sans préparer de pitch ni de slides pour un jury. Pendant les événements Mistral, les équipes comptaient entre une et quatre personnes. En seulement huit heures, elles ont livré un produit fonctionnel, un pitch, des slides et une démonstration en direct.\n\nLe contraste est saisissant. Les événements que je connaissais s'adressaient surtout aux développeurs. Ceux auxquels j'ai participé en 2026 accueillaient un public plus large, composé de personnes qui voulaient construire quelque chose. Pour beaucoup de participants, la première question porte de moins en moins sur leur expertise technique et de plus en plus sur leur envie d'essayer.\n\nTu peux être designer, product manager, étudiant ou en train d'écrire une thèse, que tu travailles ou non dans la tech. Ce qui compte, c'est l'envie de transformer une idée en quelque chose de réel et d'apprendre au passage. Certains hackathons restent très techniques, mais d'autres s'ouvrent à un public plus large. Ils peuvent durer de quelques heures à deux jours, et les participants réussissent tout de même à construire des projets impressionnants.\n\nL'IA joue un rôle croissant dans cette ouverture. Les participants peuvent demander à un assistant de code avec IA de créer une première version, d'expliquer du code qu'ils ne connaissent pas ou de les aider à corriger un problème. Pendant les hackathons, tu vas même plus loin : tu fais du vibe coding.\n\nC'est un changement majeur. Tu n'as plus besoin d'être développeur pour commencer à construire un logiciel, et les hackathons rendent cette évolution très visible.\n\nRendre le code plus accessible ne supprime pas l'importance de l'expertise en développement logiciel. Construire un prototype est une chose. Créer un logiciel qui reste fiable, sécurisé et utile dans le temps en est une autre. Malgré tout, le métier de développeur évolue rapidement. Nous devons continuer à apprendre et à nous adapter, ce qui peut être à la fois stimulant et déstabilisant.\n\nJ'ai récemment donné [un talk sur l'agency](\u002Ffr\u002Ftalks), que je définis comme le pouvoir d'agir. L'agency t'aide à te lancer avant d'avoir toutes les réponses, à découvrir ce dont un projet a besoin et à changer d'approche quand la première ne fonctionne pas. C'est important lorsque les outils et les compétences autour de nous évoluent sans cesse.\n\nLes hackathons sont un excellent moyen de développer cette capacité. Ils t'imposent une échéance, te donnent des personnes auprès desquelles apprendre et t'autorisent à expérimenter, échouer, puis réessayer.\n\nTu arrives avec une idée. Quelques heures plus tard, tu repars après avoir construit quelque chose, même si ce n'est pas parfait. Pour moi, c'est cela, l'agency en pratique.\n\n---\n\nMerci, Marion, d'avoir participé au Mistral Vibe Hackathon !\n",{"title":1635,"description":1853},{"loc":1855,"images":1859},[1860,1861,1862,1863,1864,1865],{"loc":1710},{"loc":1763},{"loc":1778},{"loc":1784},{"loc":1799},{"loc":1805},"fr\u002Fposts\u002Fai-is-changing-the-world-hackathons-are-for-every-builder","MtGSMNkcvuXsecBoe0Gq6LQNHk-NCNofAWGTE2oo5MY",{"id":1869,"title":1870,"body":1871,"contentId":3009,"date":3010,"description":3011,"extension":18,"meta":3012,"navigation":20,"path":3013,"rawbody":3014,"robots":23,"schemaOrg":23,"seo":3015,"sitemap":3016,"stem":3017,"__hash__":3018},"posts\u002Ffr\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress.md","Intégrer Cloudflare AI Search à Docus et VitePress",{"type":7,"value":1872,"toc":2998},[1873,1885,1889,1903,1906,1912,1916,1919,1922,1925,1928,1933,1936,1943,2124,2134,2137,2147,2161,2164,2181,2187,2294,2297,2309,2313,2316,2327,2334,2337,2345,2348,2362,2367,2452,2455,2458,2462,2471,2478,2481,2484,2488,2491,2494,2506,2513,2516,2530,2536,2669,2676,2678,2690,2695,2787,2797,2800,2812,2816,2825,2828,2838,2855,2864,2867,2889,2904,2907,2910,2995],[35,1874,1875,1876,1881,1882],{},"En juillet 2025, ",[39,1877,1880],{"href":1878,"rel":1879},"https:\u002F\u002Fvercel.com\u002Fblog\u002Fnuxtlabs-joins-vercel",[77],"NuxtLabs a rejoint Vercel",". En tant que grand fan du framework et de l'équipe, j'étais vraiment heureux de les voir rejoindre une entreprise comme Vercel. Le travail open source est extrêmement exigeant et l'équipe consacrait beaucoup d'énergie à construire une base financière durable. Rejoindre Vercel lui donne plus de liberté pour se concentrer sur le framework et son écosystème. Elle fait un travail formidable. ",[1216,1883,1884],{},"Merci à vous !",[51,1886,1888],{"id":1887},"faire-entrer-cloudflare-dans-lunivers-nuxt","Faire Entrer Cloudflare dans l'Univers Nuxt",[35,1890,1891,1892,1897,1898,215],{},"Depuis que NuxtLabs a rejoint Vercel, les services de Vercel sont naturellement devenus plus visibles dans l'écosystème qui l'entoure. Cette préférence ne s'étend pas à Nuxt lui-même, qui reste indépendant des plateformes grâce en grande partie au travail de ",[39,1893,1896],{"href":1894,"rel":1895},"https:\u002F\u002Fgithub.com\u002Fdanielroe",[77],"Daniel"," sur le framework. Il en va de même pour les primitives UnJS sous-jacentes, grâce au travail de ",[39,1899,1902],{"href":1900,"rel":1901},"https:\u002F\u002Fgithub.com\u002Fpi0",[77],"Pooya",[35,1904,1905],{},"Pour ma part, je suis un utilisateur de Cloudflare. J'utilise beaucoup de ses services pour développeurs depuis des années et, parfois, la documentation ne couvre que Vercel.",[35,1907,1908,1909],{},"Je veux changer cela. Je veux renforcer la présence de Cloudflare dans l'écosystème Nuxt pour en faire davantage un acteur de premier plan. Heureusement, l'équipe Nuxt est ouverte aux contributions et accueille vraiment bien les changements qui rendent son travail compatible avec d'autres plateformes que Vercel. ",[1216,1910,1911],{},"Honnêtement, il est tout à fait compréhensible qu'elle optimise d'abord pour Vercel. Je suis simplement ce type qui a décidé d'utiliser Cloudflare.",[51,1913,1915],{"id":1914},"docus-le-framework-de-documentation-pour-nuxt","Docus, le Framework de Documentation pour Nuxt",[35,1917,1918],{},"L'aventure commence avec Docus.",[35,1920,1921],{},"Docus est un framework de documentation construit sur Nuxt Content. Il permet de créer un site de documentation en quelques secondes avec de nombreuses fonctionnalités prêtes à l'emploi. Il inclut un moteur de recherche, un assistant, un serveur MCP, la distribution de skills pour agents et bien d'autres fonctionnalités qui facilitent la création d'un site de documentation. Ah, et il est magnifique.",[35,1923,1924],{},"Pendant longtemps, j'ai utilisé VitePress parce qu'il était beaucoup plus simple. Mais depuis l'essor de l'IA, Docus est devenu bien mieux adapté à des besoins tels que les assistants, l'accès pour les agents et la distribution de skills. J'utilise donc désormais Docus partout où j'ai besoin d'un site de documentation.",[35,1926,1927],{},"Mais si vous consultez la documentation, vous verrez que l'assistant intégré est réservé à Vercel et qu'il n'existe aucune intégration avec Cloudflare AI Search pour remplacer la recherche plein texte intégrée.",[1929,1930,1932],"h3",{"id":1931},"le-module-nuxt-pour-docus","Le Module Nuxt pour Docus",[35,1934,1935],{},"Docus repose sur Nuxt. Cela signifie que nous pouvons créer un module Nuxt pour l'étendre ou en surcharger le fonctionnement.",[35,1937,1938,1939,1942],{},"En réalité, la recherche de Docus est propulsée par un composant nommé ",[156,1940,1941],{},"AppSearch",". Nous pouvons modifier son comportement en le remplaçant simplement par notre propre composant. Dans un module Nuxt, il suffit d'enregistrer un composant avec une priorité supérieure à celle du composant intégré pour que notre implémentation prenne le dessus.",[192,1944,1946],{"className":1380,"code":1945,"language":1383,"meta":13,"style":13},"import { addComponent, createResolver, defineNuxtModule } from 'nuxt\u002Fkit'\n\nexport default defineNuxtModule\u003CAiSearchOptions>({\n  setup() {\n    const resolver = createResolver(import.meta.url)\n\n    addComponent({\n      priority: 100,\n      name: 'AppSearch',\n      filePath: resolver.resolve('.\u002Fruntime\u002Fcomponents\u002FAppSearch.vue'),\n    })\n  },\n})\n",[156,1947,1948,1978,1982,2001,2010,2038,2042,2051,2064,2079,2107,2114,2118],{"__ignoreMap":13},[200,1949,1950,1952,1954,1957,1959,1962,1964,1967,1969,1971,1973,1976],{"class":202,"line":203},[200,1951,972],{"class":206},[200,1953,318],{"class":214},[200,1955,1956],{"class":210}," addComponent",[200,1958,549],{"class":214},[200,1960,1961],{"class":210}," createResolver",[200,1963,549],{"class":214},[200,1965,1966],{"class":210}," defineNuxtModule",[200,1968,980],{"class":214},[200,1970,983],{"class":206},[200,1972,242],{"class":214},[200,1974,1975],{"class":245},"nuxt\u002Fkit",[200,1977,991],{"class":214},[200,1979,1980],{"class":202,"line":14},[200,1981,528],{"emptyLinePlaceholder":20},[200,1983,1984,1986,1988,1990,1992,1995,1997,1999],{"class":202,"line":255},[200,1985,1390],{"class":206},[200,1987,1393],{"class":206},[200,1989,1966],{"class":223},[200,1991,599],{"class":214},[200,1993,1994],{"class":1364},"AiSearchOptions",[200,1996,674],{"class":214},[200,1998,227],{"class":210},[200,2000,230],{"class":214},[200,2002,2003,2006,2008],{"class":202,"line":272},[200,2004,2005],{"class":235},"  setup",[200,2007,1549],{"class":214},[200,2009,280],{"class":214},[200,2011,2012,2015,2018,2020,2022,2024,2026,2028,2031,2033,2036],{"class":202,"line":283},[200,2013,2014],{"class":379},"    const",[200,2016,2017],{"class":210}," resolver",[200,2019,509],{"class":214},[200,2021,1961],{"class":223},[200,2023,227],{"class":235},[200,2025,972],{"class":206},[200,2027,215],{"class":214},[200,2029,2030],{"class":210},"meta",[200,2032,215],{"class":214},[200,2034,2035],{"class":210},"url",[200,2037,416],{"class":235},[200,2039,2040],{"class":202,"line":300},[200,2041,528],{"emptyLinePlaceholder":20},[200,2043,2044,2047,2049],{"class":202,"line":310},[200,2045,2046],{"class":223},"    addComponent",[200,2048,227],{"class":235},[200,2050,230],{"class":214},[200,2052,2053,2056,2058,2062],{"class":202,"line":336},[200,2054,2055],{"class":235},"      priority",[200,2057,239],{"class":214},[200,2059,2061],{"class":2060},"sbssI"," 100",[200,2063,252],{"class":214},[200,2065,2066,2069,2071,2073,2075,2077],{"class":202,"line":342},[200,2067,2068],{"class":235},"      name",[200,2070,239],{"class":214},[200,2072,242],{"class":214},[200,2074,1941],{"class":245},[200,2076,249],{"class":214},[200,2078,252],{"class":214},[200,2080,2081,2084,2086,2088,2090,2093,2095,2097,2100,2102,2105],{"class":202,"line":365},[200,2082,2083],{"class":235},"      filePath",[200,2085,239],{"class":214},[200,2087,2017],{"class":210},[200,2089,215],{"class":214},[200,2091,2092],{"class":223},"resolve",[200,2094,227],{"class":235},[200,2096,249],{"class":214},[200,2098,2099],{"class":245},".\u002Fruntime\u002Fcomponents\u002FAppSearch.vue",[200,2101,249],{"class":214},[200,2103,2104],{"class":235},")",[200,2106,252],{"class":214},[200,2108,2109,2112],{"class":202,"line":371},[200,2110,2111],{"class":214},"    }",[200,2113,416],{"class":235},[200,2115,2116],{"class":202,"line":398},[200,2117,368],{"class":214},[200,2119,2120,2122],{"class":202,"line":405},[200,2121,413],{"class":214},[200,2123,416],{"class":210},[35,2125,2126,2127,2129,2130,2133],{},"Nous avons alors un contrôle total sur le composant ",[156,2128,1941],{},". Nous pouvons réutiliser le composant ",[156,2131,2132],{},"ContentSearch"," de Nuxt UI et lui transmettre les résultats de l'endpoint public de Cloudflare AI Search.",[35,2135,2136],{},"Et ça fonctionne à merveille !",[130,2138,132,2139,132,2144],{},[134,2140,136,2141,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,2142],{"src":2143,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress\u002Fdocus.mp4",[143,2145,2146],{},"Intégration de Cloudflare AI Search dans Docus",[35,2148,2149,2150,2155,2156,215],{},"Je suis vraiment satisfait du résultat et je l'utiliserai sans aucun doute dans mes prochains projets. Vous pouvez consulter la démo sur ",[39,2151,2154],{"href":2152,"rel":2153},"https:\u002F\u002Fdocus-cloudflare-ai-search.barbapapazes.dev",[77],"docus-cloudflare-ai-search.barbapapazes.dev"," ou le ",[39,2157,2160],{"href":2158,"rel":2159},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fdocus-cloudflare-ai-search",[77],"dépôt GitHub",[35,2162,2163],{},"Pour utiliser le module, vous devez l'installer :",[192,2165,2169],{"className":2166,"code":2167,"language":2168,"meta":13,"style":13},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pnpm add docus-cloudflare-ai-search\n","bash",[156,2170,2171],{"__ignoreMap":13},[200,2172,2173,2175,2178],{"class":202,"line":203},[200,2174,1365],{"class":1364},[200,2176,2177],{"class":245}," add",[200,2179,2180],{"class":245}," docus-cloudflare-ai-search\n",[35,2182,2183,2184,2186],{},"Vous devez ensuite ajouter le module à votre fichier ",[156,2185,1382],{}," :",[192,2188,2190],{"className":1380,"code":2189,"language":1383,"meta":13,"style":13},"import { defineNuxtConfig } from 'nuxt'\n\nexport default defineNuxtConfig({\n  modules: ['docus-cloudflare-ai-search'],\n  docus: {\n    aiSearch: {\n      endpoint: 'https:\u002F\u002F\u003Cyour-domain>',\n    },\n  },\n})\n",[156,2191,2192,2211,2215,2227,2246,2255,2264,2280,2284,2288],{"__ignoreMap":13},[200,2193,2194,2196,2198,2200,2202,2204,2206,2209],{"class":202,"line":203},[200,2195,972],{"class":206},[200,2197,318],{"class":214},[200,2199,1396],{"class":210},[200,2201,980],{"class":214},[200,2203,983],{"class":206},[200,2205,242],{"class":214},[200,2207,2208],{"class":245},"nuxt",[200,2210,991],{"class":214},[200,2212,2213],{"class":202,"line":14},[200,2214,528],{"emptyLinePlaceholder":20},[200,2216,2217,2219,2221,2223,2225],{"class":202,"line":255},[200,2218,1390],{"class":206},[200,2220,1393],{"class":206},[200,2222,1396],{"class":223},[200,2224,227],{"class":210},[200,2226,230],{"class":214},[200,2228,2229,2231,2233,2235,2237,2240,2242,2244],{"class":202,"line":272},[200,2230,1405],{"class":235},[200,2232,239],{"class":214},[200,2234,350],{"class":210},[200,2236,249],{"class":214},[200,2238,2239],{"class":245},"docus-cloudflare-ai-search",[200,2241,249],{"class":214},[200,2243,360],{"class":210},[200,2245,252],{"class":214},[200,2247,2248,2251,2253],{"class":202,"line":283},[200,2249,2250],{"class":235},"  docus",[200,2252,239],{"class":214},[200,2254,280],{"class":214},[200,2256,2257,2260,2262],{"class":202,"line":300},[200,2258,2259],{"class":235},"    aiSearch",[200,2261,239],{"class":214},[200,2263,280],{"class":214},[200,2265,2266,2269,2271,2273,2276,2278],{"class":202,"line":310},[200,2267,2268],{"class":235},"      endpoint",[200,2270,239],{"class":214},[200,2272,242],{"class":214},[200,2274,2275],{"class":245},"https:\u002F\u002F\u003Cyour-domain>",[200,2277,249],{"class":214},[200,2279,252],{"class":214},[200,2281,2282],{"class":202,"line":336},[200,2283,339],{"class":214},[200,2285,2286],{"class":202,"line":342},[200,2287,368],{"class":214},[200,2289,2290,2292],{"class":202,"line":365},[200,2291,413],{"class":214},[200,2293,416],{"class":210},[35,2295,2296],{},"En travaillant sur ce module, j'ai réalisé que l'assistant intégré n'était disponible qu'avec Vercel. Dommage, car disposer de Cloudflare AI Search sans pouvoir exécuter l'assistant avec Cloudflare était bien loin de ce que j'appellerais une expérience Cloudflare native.",[35,2298,2299,2300,2305,2306],{},"L'assistant utilise une passerelle d'IA pour acheminer les requêtes vers son modèle. Docus prenait en charge Vercel AI Gateway, mais n'exposait pas la configuration nécessaire pour utiliser Cloudflare AI Gateway à la place. J'ai donc ouvert une PR pour ajouter les options de configuration du fournisseur, de la passerelle et du modèle requises pour Cloudflare : ",[39,2301,2304],{"href":2302,"rel":2303},"https:\u002F\u002Fgithub.com\u002Fnuxt-content\u002Fdocus\u002Fpull\u002F1421",[77],"feat(assistant): can use cloudflare for assistant",". La PR conserve Vercel comme option par défaut et fait de Cloudflare un fournisseur facultatif configuré par des variables d'environnement. ",[1216,2307,2308],{},"Comme toujours dans l'open source, il suffit d'ouvrir une PR pour concrétiser une idée.",[51,2310,2312],{"id":2311},"il-faut-du-contenu-pour-effectuer-une-recherche","Il Faut du Contenu pour Effectuer une Recherche",[35,2314,2315],{},"L'intégration fonctionne, ce qui est une belle première étape. Mais pour le moment, notre index de recherche est vide. Cloudflare AI Search propose trois façons de le remplir :",[68,2317,2318,2321,2324],{},[71,2319,2320],{},"Utiliser le stockage intégré, alimenté depuis le tableau de bord ou l'API Items",[71,2322,2323],{},"Connecter un bucket R2",[71,2325,2326],{},"Explorer un site web public sur un domaine qui vous appartient",[35,2328,2329,2330,2333],{},"Les téléversements manuels depuis le tableau de bord ne conviennent pas, car le contenu doit être synchronisé à chaque déploiement en production. Un bucket R2 est une solution viable, mais téléverser le contenu n'est pas aussi simple que d'utiliser rclone, car chaque fichier a également besoin de métadonnées personnalisées. Cela nécessiterait un outil supplémentaire. L'exploration d'un site web peut extraire des métadonnées personnalisées depuis les balises HTML ",[156,2331,2332],{},"\u003Cmeta>",", mais ajouter ces balises à chaque page générée nécessiterait aussi une intégration sur mesure.",[35,2335,2336],{},"La meilleure solution consiste donc à alimenter le stockage intégré via l'API Items, qui permet d'associer des métadonnées personnalisées lors du téléversement de chaque fichier. Cela nécessite toujours une intégration sur mesure, mais avec la bonne intégration, nous pouvons vraiment simplifier l'expérience des développeurs.",[35,2338,2339,2340,2186],{},"Pour Nuxt, vous pouvez utiliser le package ",[39,2341,2344],{"href":2342,"rel":2343},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync",[77],"cloudflare-ai-search-sync",[35,2346,2347],{},"Installez le package :",[192,2349,2351],{"className":2166,"code":2350,"language":2168,"meta":13,"style":13},"pnpm add cloudflare-ai-search-sync\n",[156,2352,2353],{"__ignoreMap":13},[200,2354,2355,2357,2359],{"class":202,"line":203},[200,2356,1365],{"class":1364},[200,2358,2177],{"class":245},[200,2360,2361],{"class":245}," cloudflare-ai-search-sync\n",[35,2363,2364,2365,2186],{},"Ajoutez ensuite le module à votre fichier ",[156,2366,1382],{},[192,2368,2370],{"className":1380,"code":2369,"language":1383,"meta":13,"style":13},"export default defineNuxtConfig({\n  modules: [\n    '@nuxt\u002Fcontent',\n    'cloudflare-ai-search-sync\u002Fnuxt',\n  ],\n  cloudflareAISearchSync: {\n    enabled: true,\n  },\n})\n",[156,2371,2372,2384,2392,2403,2414,2420,2429,2442,2446],{"__ignoreMap":13},[200,2373,2374,2376,2378,2380,2382],{"class":202,"line":203},[200,2375,1390],{"class":206},[200,2377,1393],{"class":206},[200,2379,1396],{"class":223},[200,2381,227],{"class":210},[200,2383,230],{"class":214},[200,2385,2386,2388,2390],{"class":202,"line":14},[200,2387,1405],{"class":235},[200,2389,239],{"class":214},[200,2391,1410],{"class":210},[200,2393,2394,2396,2399,2401],{"class":202,"line":255},[200,2395,1415],{"class":214},[200,2397,2398],{"class":245},"@nuxt\u002Fcontent",[200,2400,249],{"class":214},[200,2402,252],{"class":214},[200,2404,2405,2407,2410,2412],{"class":202,"line":272},[200,2406,1415],{"class":214},[200,2408,2409],{"class":245},"cloudflare-ai-search-sync\u002Fnuxt",[200,2411,249],{"class":214},[200,2413,252],{"class":214},[200,2415,2416,2418],{"class":202,"line":283},[200,2417,1427],{"class":210},[200,2419,252],{"class":214},[200,2421,2422,2425,2427],{"class":202,"line":300},[200,2423,2424],{"class":235},"  cloudflareAISearchSync",[200,2426,239],{"class":214},[200,2428,280],{"class":214},[200,2430,2431,2434,2436,2440],{"class":202,"line":310},[200,2432,2433],{"class":235},"    enabled",[200,2435,239],{"class":214},[200,2437,2439],{"class":2438},"sfNiH"," true",[200,2441,252],{"class":214},[200,2443,2444],{"class":202,"line":336},[200,2445,368],{"class":214},[200,2447,2448,2450],{"class":202,"line":342},[200,2449,413],{"class":214},[200,2451,416],{"class":210},[35,2453,2454],{},"Désormais, chaque fois que vous compilez votre projet Nuxt, les fichiers Markdown traités par Nuxt Content sont téléversés vers Cloudflare AI Search avec les bonnes métadonnées. On dirait de la magie !",[35,2456,2457],{},"Cette intégration n'est pas obligatoire tant que vous fournissez les métadonnées appropriées pour votre contenu.",[51,2459,2461],{"id":2460},"aller-plus-loin","Aller Plus Loin",[35,2463,2464,2465,2470],{},"Ajouter une nouvelle fonctionnalité à l'écosystème Nuxt a été très simple grâce au système de modules de Nuxt. Il fournit des points d'extension pour un large éventail de cas d'usage. Il suffit de consulter la page des ",[39,2466,2469],{"href":2467,"rel":2468},"https:\u002F\u002Fnuxt.com\u002Fmodules",[77],"modules Nuxt"," pour découvrir toutes les possibilités.",[35,2472,2473,2474,2477],{},"Pour essayer une idée en local, les modules sont parfaitement adaptés. Créez un projet Nuxt, ajoutez un dossier ",[156,2475,2476],{},"modules"," et commencez à bricoler votre idée. Si vous souhaitez publier le module, le starter officiel de modules Nuxt fournit la structure complète du projet. Donnez la documentation des modules Nuxt à votre assistant IA et vous pourrez obtenir un prototype fonctionnel étonnamment vite. Il n'y a jamais eu de meilleur moment pour essayer.",[35,2479,2480],{},"Mais tout l'écosystème Vue ne repose pas sur Nuxt. Nous avons VitePress et une multitude de plugins Vite pour créer nos propres systèmes. Les plugins Vite sont d'ailleurs un bon moyen de toucher un public plus large. Un plugin Vite peut souvent servir des frameworks tels qu'Astro, Svelte, React et même Nuxt, même si chaque intégration peut encore nécessiter un travail spécifique au framework. Cependant, les plugins Vite sont beaucoup plus difficiles à créer. Commencer à un haut niveau avec un module Nuxt, vérifier que l'idée fonctionne vraiment, puis descendre dans la pile pour créer un plugin Vite est donc une bonne façon de procéder.",[35,2482,2483],{},"Et c'est exactement ce que j'ai fait avec l'intégration de Cloudflare AI Search.",[1929,2485,2487],{"id":2486},"vitepress-était-le-premier-choix-évident","VitePress Était le Premier Choix Évident",[35,2489,2490],{},"Une fois la preuve de concept validée pour Docus, j'ai commencé à réfléchir à la manière de la rendre disponible pour VitePress. J'ai commencé en tant qu'utilisateur de VitePress, mon portfolio l'utilise toujours et le framework dispose d'une vaste communauté autour de la documentation.",[35,2492,2493],{},"VitePress prend également déjà en charge nativement la recherche locale et Algolia, ainsi que plusieurs plugins tiers pour d'autres moteurs de recherche. Intégrer Cloudflare AI Search à VitePress était donc un choix naturel et je pouvais tirer parti du travail déjà réalisé pour l'intégration à Docus.",[35,2495,2496,2497,2502,2503],{},"Grâce au ",[39,2498,2501],{"href":2499,"rel":2500},"https:\u002F\u002Fsearch.ai.cloudflare.com\u002F",[77],"snippet Cloudflare AI Search",", j'ai pu intégrer rapidement le moteur de recherche à VitePress. Une fois la preuve de concept validée, j'ai créé un plugin VitePress dédié : ",[1599,2504,215],{"repo":2505},"barbapapazes\u002Fvitepress-plugin-cloudflare-ai-search",[35,2507,2508,2509,2512],{},"Sous le capot, le plugin est assez simple. Il remplace le composant intégré ",[156,2510,2511],{},"VPNavBarSearch"," par un composant personnalisé qui intègre le snippet de Cloudflare AI Search et reçoit sa configuration via un module virtuel.",[35,2514,2515],{},"Pour commencer, vous devez installer le plugin :",[192,2517,2519],{"className":2166,"code":2518,"language":2168,"meta":13,"style":13},"pnpm add vitepress-plugin-cloudflare-ai-search\n",[156,2520,2521],{"__ignoreMap":13},[200,2522,2523,2525,2527],{"class":202,"line":203},[200,2524,1365],{"class":1364},[200,2526,2177],{"class":245},[200,2528,2529],{"class":245}," vitepress-plugin-cloudflare-ai-search\n",[35,2531,2532,2533,2186],{},"Vous devez ensuite ajouter le plugin à votre fichier ",[156,2534,2535],{},"config.ts",[192,2537,2539],{"className":1380,"code":2538,"language":1383,"meta":13,"style":13},"\u002F\u002F .vitepress\u002Fconfig.ts\nimport { defineConfig } from 'vitepress'\nimport { cloudflareAISearch } from 'vitepress-plugin-cloudflare-ai-search'\n\nexport default defineConfig({\n  vite: {\n    plugins: [\n      cloudflareAISearch({\n        endpoint: 'https:\u002F\u002F\u003Cyour-domain>',\n      }),\n    ],\n  },\n})\n",[156,2540,2541,2546,2565,2585,2589,2601,2610,2619,2628,2643,2652,2659,2663],{"__ignoreMap":13},[200,2542,2543],{"class":202,"line":203},[200,2544,2545],{"class":401},"\u002F\u002F .vitepress\u002Fconfig.ts\n",[200,2547,2548,2550,2552,2554,2556,2558,2560,2563],{"class":202,"line":14},[200,2549,972],{"class":206},[200,2551,318],{"class":214},[200,2553,1505],{"class":210},[200,2555,980],{"class":214},[200,2557,983],{"class":206},[200,2559,242],{"class":214},[200,2561,2562],{"class":245},"vitepress",[200,2564,991],{"class":214},[200,2566,2567,2569,2571,2574,2576,2578,2580,2583],{"class":202,"line":255},[200,2568,972],{"class":206},[200,2570,318],{"class":214},[200,2572,2573],{"class":210}," cloudflareAISearch",[200,2575,980],{"class":214},[200,2577,983],{"class":206},[200,2579,242],{"class":214},[200,2581,2582],{"class":245},"vitepress-plugin-cloudflare-ai-search",[200,2584,991],{"class":214},[200,2586,2587],{"class":202,"line":272},[200,2588,528],{"emptyLinePlaceholder":20},[200,2590,2591,2593,2595,2597,2599],{"class":202,"line":283},[200,2592,1390],{"class":206},[200,2594,1393],{"class":206},[200,2596,1505],{"class":223},[200,2598,227],{"class":210},[200,2600,230],{"class":214},[200,2602,2603,2606,2608],{"class":202,"line":300},[200,2604,2605],{"class":235},"  vite",[200,2607,239],{"class":214},[200,2609,280],{"class":214},[200,2611,2612,2615,2617],{"class":202,"line":310},[200,2613,2614],{"class":235},"    plugins",[200,2616,239],{"class":214},[200,2618,1410],{"class":210},[200,2620,2621,2624,2626],{"class":202,"line":336},[200,2622,2623],{"class":223},"      cloudflareAISearch",[200,2625,227],{"class":210},[200,2627,230],{"class":214},[200,2629,2630,2633,2635,2637,2639,2641],{"class":202,"line":342},[200,2631,2632],{"class":235},"        endpoint",[200,2634,239],{"class":214},[200,2636,242],{"class":214},[200,2638,2275],{"class":245},[200,2640,249],{"class":214},[200,2642,252],{"class":214},[200,2644,2645,2648,2650],{"class":202,"line":365},[200,2646,2647],{"class":214},"      }",[200,2649,2104],{"class":210},[200,2651,252],{"class":214},[200,2653,2654,2657],{"class":202,"line":371},[200,2655,2656],{"class":210},"    ]",[200,2658,252],{"class":214},[200,2660,2661],{"class":202,"line":398},[200,2662,368],{"class":214},[200,2664,2665,2667],{"class":202,"line":405},[200,2666,413],{"class":214},[200,2668,416],{"class":210},[35,2670,2671,2672,2675],{},"Vous voudrez peut-être aussi téléverser votre contenu vers le moteur AI Search afin de le rendre consultable. Pour cela, vous pouvez également utiliser le package ",[39,2673,2344],{"href":2342,"rel":2674},[77],", qui s'en chargera automatiquement.",[35,2677,2347],{},[192,2679,2680],{"className":2166,"code":2350,"language":2168,"meta":13,"style":13},[156,2681,2682],{"__ignoreMap":13},[200,2683,2684,2686,2688],{"class":202,"line":203},[200,2685,1365],{"class":1364},[200,2687,2177],{"class":245},[200,2689,2361],{"class":245},[35,2691,2692,2693,2186],{},"Ajoutez ensuite le plugin à votre fichier ",[156,2694,2535],{},[192,2696,2698],{"className":1380,"code":2697,"language":1383,"meta":13,"style":13},"import { cloudflareAISearchSync } from 'cloudflare-ai-search-sync\u002Fvitepress'\nimport { defineConfig } from 'vitepress'\n\nexport default defineConfig({\n  buildEnd: cloudflareAISearchSync({ enabled: true }),\n})\n",[156,2699,2700,2720,2738,2742,2754,2781],{"__ignoreMap":13},[200,2701,2702,2704,2706,2709,2711,2713,2715,2718],{"class":202,"line":203},[200,2703,972],{"class":206},[200,2705,318],{"class":214},[200,2707,2708],{"class":210}," cloudflareAISearchSync",[200,2710,980],{"class":214},[200,2712,983],{"class":206},[200,2714,242],{"class":214},[200,2716,2717],{"class":245},"cloudflare-ai-search-sync\u002Fvitepress",[200,2719,991],{"class":214},[200,2721,2722,2724,2726,2728,2730,2732,2734,2736],{"class":202,"line":14},[200,2723,972],{"class":206},[200,2725,318],{"class":214},[200,2727,1505],{"class":210},[200,2729,980],{"class":214},[200,2731,983],{"class":206},[200,2733,242],{"class":214},[200,2735,2562],{"class":245},[200,2737,991],{"class":214},[200,2739,2740],{"class":202,"line":255},[200,2741,528],{"emptyLinePlaceholder":20},[200,2743,2744,2746,2748,2750,2752],{"class":202,"line":272},[200,2745,1390],{"class":206},[200,2747,1393],{"class":206},[200,2749,1505],{"class":223},[200,2751,227],{"class":210},[200,2753,230],{"class":214},[200,2755,2756,2759,2761,2763,2765,2768,2771,2773,2775,2777,2779],{"class":202,"line":283},[200,2757,2758],{"class":235},"  buildEnd",[200,2760,239],{"class":214},[200,2762,2708],{"class":223},[200,2764,227],{"class":210},[200,2766,2767],{"class":214},"{",[200,2769,2770],{"class":235}," enabled",[200,2772,239],{"class":214},[200,2774,2439],{"class":2438},[200,2776,980],{"class":214},[200,2778,2104],{"class":210},[200,2780,252],{"class":214},[200,2782,2783,2785],{"class":202,"line":300},[200,2784,413],{"class":214},[200,2786,416],{"class":210},[130,2788,132,2789,132,2794],{},[134,2790,136,2791,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,2792],{"src":2793,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress\u002Fvitepress.mp4",[143,2795,2796],{},"Intégration de Cloudflare AI Search dans VitePress",[35,2798,2799],{},"Vous pouvez désormais compiler votre projet VitePress et son contenu sera synchronisé avec AI Search. On dirait de la magie !",[35,2801,2802,2803,2155,2808,215],{},"Si vous préférez simplement l'essayer, vous pouvez consulter la démo sur ",[39,2804,2807],{"href":2805,"rel":2806},"https:\u002F\u002Fvitepress-plugin-cloudflare-ai-search.barbapapazes.dev",[77],"vitepress-plugin-cloudflare-ai-search.barbapapazes.dev",[39,2809,2160],{"href":2810,"rel":2811},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fvitepress-plugin-cloudflare-ai-search",[77],[51,2813,2815],{"id":2814},"tout-ne-sest-pas-passé-comme-prévu","Tout ne s'est pas Passé Comme Prévu",[35,2817,2818,2819,2824],{},"Vous pensez peut-être : « Super, ça fonctionne bien ! » Mais ce n'est pas ce qui s'est passé au départ et je l'ai appris à mes dépens. À ce moment-là, la ",[39,2820,2823],{"href":2821,"rel":2822},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002Fconfiguration\u002Fretrieval\u002Fpublic-endpoint\u002Fembed-search-snippets\u002F",[77],"documentation de Cloudflare"," expliquait comment intégrer le snippet d'interface, mais elle n'établissait pas clairement le lien entre ce processus et l'indexation du contenu avec les métadonnées attendues par le composant. Sans contenu indexé au format attendu, le snippet d'interface ne fonctionne pas.",[35,2826,2827],{},"Comme mentionné précédemment, il existe trois façons de rendre du contenu accessible au moteur de recherche :",[68,2829,2830,2833,2835],{},[71,2831,2832],{},"Utiliser le stockage intégré via le tableau de bord ou l'API Items",[71,2834,2323],{},[71,2836,2837],{},"Explorer un site web public",[35,2839,2840,2841,2846,2847,106,2852],{},"Aucune d'entre elles ne fonctionnait directement avec le snippet. Pire encore, après les avoir ajustées et avoir essayé de modifier les clés ou les métadonnées des éléments, rien ne fonctionnait. J'ai donc fait ce que je fais le mieux : je suis allé consulter le code source de l'",[39,2842,2845],{"href":2843,"rel":2844},"https:\u002F\u002Fdocs.emdashcms.com\u002Fdeployment\u002Fcloudflare\u002F#cloudflare-ai-search",[77],"intégration EmDash"," et du ",[39,2848,2851],{"href":2849,"rel":2850},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet",[77],"snippet d'interface",[1216,2853,2854],{},"Heureusement pour moi, il est open source.",[35,2856,2857,2858,2863],{},"J'ai découvert qu'EmDash n'interroge pas directement l'endpoint public. Son intégration expose plutôt ",[39,2859,2862],{"href":2860,"rel":2861},"https:\u002F\u002Fgithub.com\u002Femdash-cms\u002Femdash\u002Fblob\u002Fe7978d3da8884a1d829e2f64fc7cc7a534ca1f40\u002Fpackages\u002Fcloudflare\u002Fsrc\u002Fplugins\u002Fai-search.ts#L767-L780",[77],"un endpoint dédié qui réécrit les métadonnées"," dans la réponse d'AI Search afin qu'elles correspondent au format attendu par le snippet. Mauvaise nouvelle. J'ai également inspecté les réponses réseau de la recherche du blog de Cloudflare pour comprendre le format attendu, ce qui a confirmé ma découverte.",[35,2865,2866],{},"Je ne vais pas me laisser abattre. Trouvons d'abord un moyen de le faire fonctionner. Ouvrons ensuite quelques PR pour améliorer la situation. Enfin, publions une démo pour montrer à quel point le produit est génial lorsqu'il fonctionne.",[35,2868,2869,2870,2873,2874,2877,2878,2881,2882,2885,2886,215],{},"J'ai fouillé dans le code source du snippet, en particulier dans celui de la modale, et j'ai découvert qu'il utilisait la clé de l'élément comme URL lorsqu'un utilisateur cliquait sur un résultat de recherche. Le problème est qu'une clé AI Search n'est pas nécessairement une URL publique. C'est un chemin vers l'élément dans le stockage. Ainsi, si vous téléversez un fichier nommé ",[156,2871,2872],{},"my-file.md"," dans le répertoire ",[156,2875,2876],{},"docs",", la clé sera ",[156,2879,2880],{},"docs\u002Fmy-file.md",". Mais le snippet utilise cette clé comme lien. Cliquer sur le résultat ouvre donc ",[156,2883,2884],{},"https:\u002F\u002F\u003Cyour-domain>\u002Fdocs\u002Fmy-file.md",", ce qui n'est pas la bonne URL. Je voulais qu'il ouvre ",[156,2887,2888],{},"https:\u002F\u002F\u003Cyour-domain>\u002Fdocs\u002Fmy-file",[35,2890,2891,2892,2895,2896,2899,2900,2903],{},"Mais pourquoi ne pas simplement envoyer ",[156,2893,2894],{},"\u002Fdocs\u002Fmy-file"," comme clé ? Parce que ce n'est pas une clé d'élément AI Search valide. Les clés d'éléments ne peuvent pas commencer par ",[156,2897,2898],{},"\u002F"," et doivent inclure une extension de fichier. Le snippet peut également différencier les pages et les sections pour améliorer l'expérience de recherche. C'est une bonne idée jusqu'à ce que vous réalisiez qu'un lien vers une section nécessite un fragment ",[156,2901,2902],{},"#"," dans l'URL publique, qui ne peut pas être représenté dans la clé de l'élément.",[35,2905,2906],{},"Je ne peux pas réécrire la clé à la volée, car la promesse du snippet est de l'utiliser avec l'endpoint public et de le faire fonctionner immédiatement, sans configuration ni serveur.",[35,2908,2909],{},"Au lieu d'utiliser la clé, j'ai donc décidé d'utiliser un champ de métadonnées personnalisé pour stocker l'URL. AI Search nous permet de définir des champs de métadonnées supplémentaires et d'associer leurs valeurs aux éléments téléversés. Cependant, cela nécessite également une PR sur le snippet pour fonctionner :",[2911,2912,2915,2918],"pull-request",{"title":2913,"href":2914},"feat: use metadata url","https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F44",[35,2916,2917],{},"J'ai donc ouvert la PR. Entre-temps, j'en ai également ouvert une autre pour mettre à jour le README obsolète :",[2911,2919,2922,2936,2944,2947,2950,2962,2972,2975,2978,2982,2989,2992],{"title":2920,"href":2921},"docs: update readme","https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F45",[35,2923,2924,2925,1655,2930,2935],{},"Dans les adaptateurs ",[39,2926,2929],{"href":2927,"rel":2928},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync\u002Fblob\u002F08055be4ca257cd618064f9e9aa03d96b220ce6f\u002Fsrc\u002Fvitepress\u002Futils.ts#L13",[77],"VitePress",[39,2931,2934],{"href":2932,"rel":2933},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync\u002Fblob\u002F08055be4ca257cd618064f9e9aa03d96b220ce6f\u002Fsrc\u002Fnuxt\u002Futils.ts#L26",[77],"Nuxt",", je déduis l'URL publique de chaque page à partir de son chemin source et je stocke cette URL dans les métadonnées personnalisées de l'élément.",[35,2937,2938,2939,2943],{},"J'ai également dû créer les ",[39,2940,2942],{"href":2342,"rel":2941},[77],"adaptateurs qui synchronisent le contenu"," avec AI Search. Nous avions besoin de métadonnées personnalisées et voulions l'expérience de développement la plus simple possible, où il suffit d'installer l'intégration pour que tout fonctionne. R2 nécessitait des outils de téléversement supplémentaires et l'exploration impliquait d'ajouter des métadonnées à chaque page générée du site web. La meilleure option était d'utiliser l'API Items pour téléverser directement les fichiers Markdown et leurs métadonnées vers le stockage intégré.",[35,2945,2946],{},"Enfin, quelque chose fonctionnait ! Énorme !",[35,2948,2949],{},"Mais cela ne fonctionnait que dans mon environnement local. Dommage !",[35,2951,2952,2953,2957,2958,2961],{},"Tant que la ",[39,2954,2956],{"href":2914,"rel":2955},[77],"PR #44"," reste ouverte, le snippet en amont continue d'utiliser la clé de l'élément comme URL. Mon environnement local utilisait la dépendance corrigée, mais le plugin publié chargeait dynamiquement le package en amont à la place. Pour que cela fonctionne aujourd'hui, j'ai dû intégrer le snippet corrigé au plugin. C'était un peu bricolé, car le module est chargé dynamiquement dans un composant Vue en dehors du pipeline de bundling habituel. L'ajouter à ",[156,2959,2960],{},"alwaysBundle"," dans la configuration de tsdown ne suffisait donc pas. J'ai dû faire preuve de plus de créativité.",[35,2963,2964,2965,2968,2969,215],{},"Au final, cela fonctionne. Découvrez la démo VitePress sur ",[39,2966,2807],{"href":2805,"rel":2967},[77]," et la démo du module Nuxt sur ",[39,2970,2154],{"href":2152,"rel":2971},[77],[35,2973,2974],{},"L'expérience de développement de Cloudflare ne cesse de s'améliorer, mais les produits les plus récents peuvent encore nécessiter de fouiller dans le code source et de tester le système à plusieurs reprises lorsque la documentation et les intégrations évoluent à des rythmes différents.",[35,2976,2977],{},"Au final, cela fonctionne et je suis certain que l'équipe corrigera rapidement ces problèmes ! Ses chefs de produit sont vraiment ouverts aux retours de la communauté.",[51,2979,2981],{"id":2980},"ce-quil-faut-retenir","Ce qu'il Faut Retenir",[35,2983,2984,2985,215],{},"Premièrement, l'open source est formidable. Créez des projets open source et contribuez à l'open source. C'est l'une des meilleures façons d'apprendre et de progresser en tant que développeur. Mais n'oubliez pas qu'",[39,2986,2988],{"href":2987},".\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong","il ne s'agit que d'un effet secondaire du fait de créer des choses",[35,2990,2991],{},"Deuxièmement, commencez par un périmètre restreint avant de l'élargir. J'ai commencé avec un module Nuxt, car c'était le moyen le plus simple de valider l'idée. Après avoir partagé une courte vidéo sur X et recueilli des retours, j'ai extrait le processus de synchronisation dans un package dédié et étendu l'intégration à VitePress. Fait intéressant, la version VitePress a été prête en premier et le module Nuxt a suivi plus tard.",[35,2993,2994],{},"Troisièmement, soyez persévérant et patient. Parfois, les choses ne fonctionnent pas comme prévu et sont plus difficiles qu'elles n'en ont l'air. Cela ne signifie toutefois pas qu'elles sont impossibles. En persévérant et en allant un peu plus loin, vous pouvez les faire fonctionner et la récompense en vaut toujours la peine.",[1170,2996,2997],{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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":13,"searchDepth":14,"depth":14,"links":2999},[3000,3001,3004,3005,3008],{"id":1887,"depth":14,"text":1888},{"id":1914,"depth":14,"text":1915,"children":3002},[3003],{"id":1931,"depth":255,"text":1932},{"id":2311,"depth":14,"text":2312},{"id":2460,"depth":14,"text":2461,"children":3006},[3007],{"id":2486,"depth":255,"text":2487},{"id":2814,"depth":14,"text":2815},"28eecb1d-69ce-435d-b21d-977628bd65ff","2026-08-24","Découvrez comment j'ai intégré Cloudflare AI Search à Docus et VitePress, synchronisé leur contenu et contribué à la prise en charge d'AI Gateway.",{},"\u002Ffr\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress","---\ncontentId: 28eecb1d-69ce-435d-b21d-977628bd65ff\ntitle: Intégrer Cloudflare AI Search à Docus et VitePress\ndescription: Découvrez comment j'ai intégré Cloudflare AI Search à Docus et VitePress, synchronisé leur contenu et contribué à la prise en charge d'AI Gateway.\ndate: 2026-08-24\n---\n\nEn juillet 2025, [NuxtLabs a rejoint Vercel](https:\u002F\u002Fvercel.com\u002Fblog\u002Fnuxtlabs-joins-vercel). En tant que grand fan du framework et de l'équipe, j'étais vraiment heureux de les voir rejoindre une entreprise comme Vercel. Le travail open source est extrêmement exigeant et l'équipe consacrait beaucoup d'énergie à construire une base financière durable. Rejoindre Vercel lui donne plus de liberté pour se concentrer sur le framework et son écosystème. Elle fait un travail formidable. _Merci à vous !_\n\n## Faire Entrer Cloudflare dans l'Univers Nuxt\n\nDepuis que NuxtLabs a rejoint Vercel, les services de Vercel sont naturellement devenus plus visibles dans l'écosystème qui l'entoure. Cette préférence ne s'étend pas à Nuxt lui-même, qui reste indépendant des plateformes grâce en grande partie au travail de [Daniel](https:\u002F\u002Fgithub.com\u002Fdanielroe) sur le framework. Il en va de même pour les primitives UnJS sous-jacentes, grâce au travail de [Pooya](https:\u002F\u002Fgithub.com\u002Fpi0).\n\nPour ma part, je suis un utilisateur de Cloudflare. J'utilise beaucoup de ses services pour développeurs depuis des années et, parfois, la documentation ne couvre que Vercel.\n\nJe veux changer cela. Je veux renforcer la présence de Cloudflare dans l'écosystème Nuxt pour en faire davantage un acteur de premier plan. Heureusement, l'équipe Nuxt est ouverte aux contributions et accueille vraiment bien les changements qui rendent son travail compatible avec d'autres plateformes que Vercel. _Honnêtement, il est tout à fait compréhensible qu'elle optimise d'abord pour Vercel. Je suis simplement ce type qui a décidé d'utiliser Cloudflare._\n\n## Docus, le Framework de Documentation pour Nuxt\n\nL'aventure commence avec Docus.\n\nDocus est un framework de documentation construit sur Nuxt Content. Il permet de créer un site de documentation en quelques secondes avec de nombreuses fonctionnalités prêtes à l'emploi. Il inclut un moteur de recherche, un assistant, un serveur MCP, la distribution de skills pour agents et bien d'autres fonctionnalités qui facilitent la création d'un site de documentation. Ah, et il est magnifique.\n\nPendant longtemps, j'ai utilisé VitePress parce qu'il était beaucoup plus simple. Mais depuis l'essor de l'IA, Docus est devenu bien mieux adapté à des besoins tels que les assistants, l'accès pour les agents et la distribution de skills. J'utilise donc désormais Docus partout où j'ai besoin d'un site de documentation.\n\nMais si vous consultez la documentation, vous verrez que l'assistant intégré est réservé à Vercel et qu'il n'existe aucune intégration avec Cloudflare AI Search pour remplacer la recherche plein texte intégrée.\n\n### Le Module Nuxt pour Docus\n\nDocus repose sur Nuxt. Cela signifie que nous pouvons créer un module Nuxt pour l'étendre ou en surcharger le fonctionnement.\n\nEn réalité, la recherche de Docus est propulsée par un composant nommé `AppSearch`. Nous pouvons modifier son comportement en le remplaçant simplement par notre propre composant. Dans un module Nuxt, il suffit d'enregistrer un composant avec une priorité supérieure à celle du composant intégré pour que notre implémentation prenne le dessus.\n\n```ts\nimport { addComponent, createResolver, defineNuxtModule } from 'nuxt\u002Fkit'\n\nexport default defineNuxtModule\u003CAiSearchOptions>({\n  setup() {\n    const resolver = createResolver(import.meta.url)\n\n    addComponent({\n      priority: 100,\n      name: 'AppSearch',\n      filePath: resolver.resolve('.\u002Fruntime\u002Fcomponents\u002FAppSearch.vue'),\n    })\n  },\n})\n```\n\nNous avons alors un contrôle total sur le composant `AppSearch`. Nous pouvons réutiliser le composant `ContentSearch` de Nuxt UI et lui transmettre les résultats de l'endpoint public de Cloudflare AI Search.\n\nEt ça fonctionne à merveille !\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress\u002Fdocus.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Intégration de Cloudflare AI Search dans Docus\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nJe suis vraiment satisfait du résultat et je l'utiliserai sans aucun doute dans mes prochains projets. Vous pouvez consulter la démo sur [docus-cloudflare-ai-search.barbapapazes.dev](https:\u002F\u002Fdocus-cloudflare-ai-search.barbapapazes.dev) ou le [dépôt GitHub](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fdocus-cloudflare-ai-search).\n\nPour utiliser le module, vous devez l'installer :\n\n```bash\npnpm add docus-cloudflare-ai-search\n```\n\nVous devez ensuite ajouter le module à votre fichier `nuxt.config.ts` :\n\n```ts\nimport { defineNuxtConfig } from 'nuxt'\n\nexport default defineNuxtConfig({\n  modules: ['docus-cloudflare-ai-search'],\n  docus: {\n    aiSearch: {\n      endpoint: 'https:\u002F\u002F\u003Cyour-domain>',\n    },\n  },\n})\n```\n\nEn travaillant sur ce module, j'ai réalisé que l'assistant intégré n'était disponible qu'avec Vercel. Dommage, car disposer de Cloudflare AI Search sans pouvoir exécuter l'assistant avec Cloudflare était bien loin de ce que j'appellerais une expérience Cloudflare native.\n\nL'assistant utilise une passerelle d'IA pour acheminer les requêtes vers son modèle. Docus prenait en charge Vercel AI Gateway, mais n'exposait pas la configuration nécessaire pour utiliser Cloudflare AI Gateway à la place. J'ai donc ouvert une PR pour ajouter les options de configuration du fournisseur, de la passerelle et du modèle requises pour Cloudflare : [feat(assistant): can use cloudflare for assistant](https:\u002F\u002Fgithub.com\u002Fnuxt-content\u002Fdocus\u002Fpull\u002F1421). La PR conserve Vercel comme option par défaut et fait de Cloudflare un fournisseur facultatif configuré par des variables d'environnement. _Comme toujours dans l'open source, il suffit d'ouvrir une PR pour concrétiser une idée._\n\n## Il Faut du Contenu pour Effectuer une Recherche\n\nL'intégration fonctionne, ce qui est une belle première étape. Mais pour le moment, notre index de recherche est vide. Cloudflare AI Search propose trois façons de le remplir :\n\n1. Utiliser le stockage intégré, alimenté depuis le tableau de bord ou l'API Items\n2. Connecter un bucket R2\n3. Explorer un site web public sur un domaine qui vous appartient\n\nLes téléversements manuels depuis le tableau de bord ne conviennent pas, car le contenu doit être synchronisé à chaque déploiement en production. Un bucket R2 est une solution viable, mais téléverser le contenu n'est pas aussi simple que d'utiliser rclone, car chaque fichier a également besoin de métadonnées personnalisées. Cela nécessiterait un outil supplémentaire. L'exploration d'un site web peut extraire des métadonnées personnalisées depuis les balises HTML `\u003Cmeta>`, mais ajouter ces balises à chaque page générée nécessiterait aussi une intégration sur mesure.\n\nLa meilleure solution consiste donc à alimenter le stockage intégré via l'API Items, qui permet d'associer des métadonnées personnalisées lors du téléversement de chaque fichier. Cela nécessite toujours une intégration sur mesure, mais avec la bonne intégration, nous pouvons vraiment simplifier l'expérience des développeurs.\n\nPour Nuxt, vous pouvez utiliser le package [cloudflare-ai-search-sync](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync) :\n\nInstallez le package :\n\n```bash\npnpm add cloudflare-ai-search-sync\n```\n\nAjoutez ensuite le module à votre fichier `nuxt.config.ts` :\n\n```ts\nexport default defineNuxtConfig({\n  modules: [\n    '@nuxt\u002Fcontent',\n    'cloudflare-ai-search-sync\u002Fnuxt',\n  ],\n  cloudflareAISearchSync: {\n    enabled: true,\n  },\n})\n```\n\nDésormais, chaque fois que vous compilez votre projet Nuxt, les fichiers Markdown traités par Nuxt Content sont téléversés vers Cloudflare AI Search avec les bonnes métadonnées. On dirait de la magie !\n\nCette intégration n'est pas obligatoire tant que vous fournissez les métadonnées appropriées pour votre contenu.\n\n## Aller Plus Loin\n\nAjouter une nouvelle fonctionnalité à l'écosystème Nuxt a été très simple grâce au système de modules de Nuxt. Il fournit des points d'extension pour un large éventail de cas d'usage. Il suffit de consulter la page des [modules Nuxt](https:\u002F\u002Fnuxt.com\u002Fmodules) pour découvrir toutes les possibilités.\n\nPour essayer une idée en local, les modules sont parfaitement adaptés. Créez un projet Nuxt, ajoutez un dossier `modules` et commencez à bricoler votre idée. Si vous souhaitez publier le module, le starter officiel de modules Nuxt fournit la structure complète du projet. Donnez la documentation des modules Nuxt à votre assistant IA et vous pourrez obtenir un prototype fonctionnel étonnamment vite. Il n'y a jamais eu de meilleur moment pour essayer.\n\nMais tout l'écosystème Vue ne repose pas sur Nuxt. Nous avons VitePress et une multitude de plugins Vite pour créer nos propres systèmes. Les plugins Vite sont d'ailleurs un bon moyen de toucher un public plus large. Un plugin Vite peut souvent servir des frameworks tels qu'Astro, Svelte, React et même Nuxt, même si chaque intégration peut encore nécessiter un travail spécifique au framework. Cependant, les plugins Vite sont beaucoup plus difficiles à créer. Commencer à un haut niveau avec un module Nuxt, vérifier que l'idée fonctionne vraiment, puis descendre dans la pile pour créer un plugin Vite est donc une bonne façon de procéder.\n\nEt c'est exactement ce que j'ai fait avec l'intégration de Cloudflare AI Search.\n\n### VitePress Était le Premier Choix Évident\n\nUne fois la preuve de concept validée pour Docus, j'ai commencé à réfléchir à la manière de la rendre disponible pour VitePress. J'ai commencé en tant qu'utilisateur de VitePress, mon portfolio l'utilise toujours et le framework dispose d'une vaste communauté autour de la documentation.\n\nVitePress prend également déjà en charge nativement la recherche locale et Algolia, ainsi que plusieurs plugins tiers pour d'autres moteurs de recherche. Intégrer Cloudflare AI Search à VitePress était donc un choix naturel et je pouvais tirer parti du travail déjà réalisé pour l'intégration à Docus.\n\nGrâce au [snippet Cloudflare AI Search](https:\u002F\u002Fsearch.ai.cloudflare.com\u002F), j'ai pu intégrer rapidement le moteur de recherche à VitePress. Une fois la preuve de concept validée, j'ai créé un plugin VitePress dédié : \u003CGitHubLink repo=\"barbapapazes\u002Fvitepress-plugin-cloudflare-ai-search\" \u002F>.\n\nSous le capot, le plugin est assez simple. Il remplace le composant intégré `VPNavBarSearch` par un composant personnalisé qui intègre le snippet de Cloudflare AI Search et reçoit sa configuration via un module virtuel.\n\nPour commencer, vous devez installer le plugin :\n\n```bash\npnpm add vitepress-plugin-cloudflare-ai-search\n```\n\nVous devez ensuite ajouter le plugin à votre fichier `config.ts` :\n\n```ts\n\u002F\u002F .vitepress\u002Fconfig.ts\nimport { defineConfig } from 'vitepress'\nimport { cloudflareAISearch } from 'vitepress-plugin-cloudflare-ai-search'\n\nexport default defineConfig({\n  vite: {\n    plugins: [\n      cloudflareAISearch({\n        endpoint: 'https:\u002F\u002F\u003Cyour-domain>',\n      }),\n    ],\n  },\n})\n```\n\nVous voudrez peut-être aussi téléverser votre contenu vers le moteur AI Search afin de le rendre consultable. Pour cela, vous pouvez également utiliser le package [cloudflare-ai-search-sync](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync), qui s'en chargera automatiquement.\n\nInstallez le package :\n\n```bash\npnpm add cloudflare-ai-search-sync\n```\n\nAjoutez ensuite le plugin à votre fichier `config.ts` :\n\n```ts\nimport { cloudflareAISearchSync } from 'cloudflare-ai-search-sync\u002Fvitepress'\nimport { defineConfig } from 'vitepress'\n\nexport default defineConfig({\n  buildEnd: cloudflareAISearchSync({ enabled: true }),\n})\n```\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress\u002Fvitepress.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Intégration de Cloudflare AI Search dans VitePress\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nVous pouvez désormais compiler votre projet VitePress et son contenu sera synchronisé avec AI Search. On dirait de la magie !\n\nSi vous préférez simplement l'essayer, vous pouvez consulter la démo sur [vitepress-plugin-cloudflare-ai-search.barbapapazes.dev](https:\u002F\u002Fvitepress-plugin-cloudflare-ai-search.barbapapazes.dev) ou le [dépôt GitHub](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fvitepress-plugin-cloudflare-ai-search).\n\n## Tout ne s'est pas Passé Comme Prévu\n\nVous pensez peut-être : « Super, ça fonctionne bien ! » Mais ce n'est pas ce qui s'est passé au départ et je l'ai appris à mes dépens. À ce moment-là, la [documentation de Cloudflare](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002Fconfiguration\u002Fretrieval\u002Fpublic-endpoint\u002Fembed-search-snippets\u002F) expliquait comment intégrer le snippet d'interface, mais elle n'établissait pas clairement le lien entre ce processus et l'indexation du contenu avec les métadonnées attendues par le composant. Sans contenu indexé au format attendu, le snippet d'interface ne fonctionne pas.\n\nComme mentionné précédemment, il existe trois façons de rendre du contenu accessible au moteur de recherche :\n\n1. Utiliser le stockage intégré via le tableau de bord ou l'API Items\n2. Connecter un bucket R2\n3. Explorer un site web public\n\nAucune d'entre elles ne fonctionnait directement avec le snippet. Pire encore, après les avoir ajustées et avoir essayé de modifier les clés ou les métadonnées des éléments, rien ne fonctionnait. J'ai donc fait ce que je fais le mieux : je suis allé consulter le code source de l'[intégration EmDash](https:\u002F\u002Fdocs.emdashcms.com\u002Fdeployment\u002Fcloudflare\u002F#cloudflare-ai-search) et du [snippet d'interface](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet). _Heureusement pour moi, il est open source._\n\nJ'ai découvert qu'EmDash n'interroge pas directement l'endpoint public. Son intégration expose plutôt [un endpoint dédié qui réécrit les métadonnées](https:\u002F\u002Fgithub.com\u002Femdash-cms\u002Femdash\u002Fblob\u002Fe7978d3da8884a1d829e2f64fc7cc7a534ca1f40\u002Fpackages\u002Fcloudflare\u002Fsrc\u002Fplugins\u002Fai-search.ts#L767-L780) dans la réponse d'AI Search afin qu'elles correspondent au format attendu par le snippet. Mauvaise nouvelle. J'ai également inspecté les réponses réseau de la recherche du blog de Cloudflare pour comprendre le format attendu, ce qui a confirmé ma découverte.\n\nJe ne vais pas me laisser abattre. Trouvons d'abord un moyen de le faire fonctionner. Ouvrons ensuite quelques PR pour améliorer la situation. Enfin, publions une démo pour montrer à quel point le produit est génial lorsqu'il fonctionne.\n\nJ'ai fouillé dans le code source du snippet, en particulier dans celui de la modale, et j'ai découvert qu'il utilisait la clé de l'élément comme URL lorsqu'un utilisateur cliquait sur un résultat de recherche. Le problème est qu'une clé AI Search n'est pas nécessairement une URL publique. C'est un chemin vers l'élément dans le stockage. Ainsi, si vous téléversez un fichier nommé `my-file.md` dans le répertoire `docs`, la clé sera `docs\u002Fmy-file.md`. Mais le snippet utilise cette clé comme lien. Cliquer sur le résultat ouvre donc `https:\u002F\u002F\u003Cyour-domain>\u002Fdocs\u002Fmy-file.md`, ce qui n'est pas la bonne URL. Je voulais qu'il ouvre `https:\u002F\u002F\u003Cyour-domain>\u002Fdocs\u002Fmy-file`.\n\nMais pourquoi ne pas simplement envoyer `\u002Fdocs\u002Fmy-file` comme clé ? Parce que ce n'est pas une clé d'élément AI Search valide. Les clés d'éléments ne peuvent pas commencer par `\u002F` et doivent inclure une extension de fichier. Le snippet peut également différencier les pages et les sections pour améliorer l'expérience de recherche. C'est une bonne idée jusqu'à ce que vous réalisiez qu'un lien vers une section nécessite un fragment `#` dans l'URL publique, qui ne peut pas être représenté dans la clé de l'élément.\n\nJe ne peux pas réécrire la clé à la volée, car la promesse du snippet est de l'utiliser avec l'endpoint public et de le faire fonctionner immédiatement, sans configuration ni serveur.\n\nAu lieu d'utiliser la clé, j'ai donc décidé d'utiliser un champ de métadonnées personnalisé pour stocker l'URL. AI Search nous permet de définir des champs de métadonnées supplémentaires et d'associer leurs valeurs aux éléments téléversés. Cependant, cela nécessite également une PR sur le snippet pour fonctionner :\n\n\u003CPullRequest title=\"feat: use metadata url\" href=\"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F44\" \u002F>\n\nJ'ai donc ouvert la PR. Entre-temps, j'en ai également ouvert une autre pour mettre à jour le README obsolète :\n\n\u003CPullRequest title=\"docs: update readme\" href=\"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F45\" \u002F>\n\nDans les adaptateurs [VitePress](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync\u002Fblob\u002F08055be4ca257cd618064f9e9aa03d96b220ce6f\u002Fsrc\u002Fvitepress\u002Futils.ts#L13) et [Nuxt](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync\u002Fblob\u002F08055be4ca257cd618064f9e9aa03d96b220ce6f\u002Fsrc\u002Fnuxt\u002Futils.ts#L26), je déduis l'URL publique de chaque page à partir de son chemin source et je stocke cette URL dans les métadonnées personnalisées de l'élément.\n\nJ'ai également dû créer les [adaptateurs qui synchronisent le contenu](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcloudflare-ai-search-sync) avec AI Search. Nous avions besoin de métadonnées personnalisées et voulions l'expérience de développement la plus simple possible, où il suffit d'installer l'intégration pour que tout fonctionne. R2 nécessitait des outils de téléversement supplémentaires et l'exploration impliquait d'ajouter des métadonnées à chaque page générée du site web. La meilleure option était d'utiliser l'API Items pour téléverser directement les fichiers Markdown et leurs métadonnées vers le stockage intégré.\n\nEnfin, quelque chose fonctionnait ! Énorme !\n\nMais cela ne fonctionnait que dans mon environnement local. Dommage !\n\nTant que la [PR #44](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fai-search-snippet\u002Fpull\u002F44) reste ouverte, le snippet en amont continue d'utiliser la clé de l'élément comme URL. Mon environnement local utilisait la dépendance corrigée, mais le plugin publié chargeait dynamiquement le package en amont à la place. Pour que cela fonctionne aujourd'hui, j'ai dû intégrer le snippet corrigé au plugin. C'était un peu bricolé, car le module est chargé dynamiquement dans un composant Vue en dehors du pipeline de bundling habituel. L'ajouter à `alwaysBundle` dans la configuration de tsdown ne suffisait donc pas. J'ai dû faire preuve de plus de créativité.\n\nAu final, cela fonctionne. Découvrez la démo VitePress sur [vitepress-plugin-cloudflare-ai-search.barbapapazes.dev](https:\u002F\u002Fvitepress-plugin-cloudflare-ai-search.barbapapazes.dev) et la démo du module Nuxt sur [docus-cloudflare-ai-search.barbapapazes.dev](https:\u002F\u002Fdocus-cloudflare-ai-search.barbapapazes.dev).\n\nL'expérience de développement de Cloudflare ne cesse de s'améliorer, mais les produits les plus récents peuvent encore nécessiter de fouiller dans le code source et de tester le système à plusieurs reprises lorsque la documentation et les intégrations évoluent à des rythmes différents.\n\nAu final, cela fonctionne et je suis certain que l'équipe corrigera rapidement ces problèmes ! Ses chefs de produit sont vraiment ouverts aux retours de la communauté.\n\n## Ce qu'il Faut Retenir\n\nPremièrement, l'open source est formidable. Créez des projets open source et contribuez à l'open source. C'est l'une des meilleures façons d'apprendre et de progresser en tant que développeur. Mais n'oubliez pas qu'[il ne s'agit que d'un effet secondaire du fait de créer des choses](.\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong.md).\n\nDeuxièmement, commencez par un périmètre restreint avant de l'élargir. J'ai commencé avec un module Nuxt, car c'était le moyen le plus simple de valider l'idée. Après avoir partagé une courte vidéo sur X et recueilli des retours, j'ai extrait le processus de synchronisation dans un package dédié et étendu l'intégration à VitePress. Fait intéressant, la version VitePress a été prête en premier et le module Nuxt a suivi plus tard.\n\nTroisièmement, soyez persévérant et patient. Parfois, les choses ne fonctionnent pas comme prévu et sont plus difficiles qu'elles n'en ont l'air. Cela ne signifie toutefois pas qu'elles sont impossibles. En persévérant et en allant un peu plus loin, vous pouvez les faire fonctionner et la récompense en vaut toujours la peine.\n",{"title":1870,"description":3011},{"loc":3013},"fr\u002Fposts\u002Fbringing-cloudflare-ai-search-to-docus-and-vitepress","ohbpyTiPqqi8WBf2m5D6xOjIU-DmWkGF534MA8D8XLc",{"id":3020,"title":3021,"body":3022,"contentId":5631,"date":3010,"description":5632,"extension":18,"meta":5633,"navigation":20,"path":5634,"rawbody":5635,"robots":23,"schemaOrg":23,"seo":5636,"sitemap":5637,"stem":5638,"__hash__":5639},"posts\u002Ffr\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website.md","Les trois outils MCP essentiels à un site de contenu",{"type":7,"value":3023,"toc":5625},[3024,3027,3041,3049,3052,3056,3059,3062,3079,3082,3086,3100,3105,3114,3344,3357,3360,3370,3375,3386,3663,3673,3676,3685,3690,3693,3696,3702,3714,3717,4038,4049,4052,4224,4234,4243,4246,4249,4266,4269,4273,4280,4283,4705,5622],[35,3025,3026],{},"Depuis octobre 2025, je n'arrive pas à me sortir quelques questions de la tête :",[1593,3028,3029,3032,3035,3038],{},[71,3030,3031],{},"Résume l'article sur Code Mode.",[71,3033,3034],{},"Que faut-il retenir de la série sur les agents IA ?",[71,3036,3037],{},"Y a-t-il du contenu sur Vite ?",[71,3039,3040],{},"Quel est le dernier article sur Devoxx France ?",[35,3042,3043,3044],{},"En octobre, j'ai publié l'article ",[3045,3046,3048],"link-to-article",{"id":3047},"e258d6e6-2f2f-400d-9b6f-02801a7284db",". J'y expliquais avoir construit un serveur MCP et un agent pour discuter avec le contenu de mon site. J'avais réussi à créer une preuve de concept (POC). C'était la première fois que je créais un tel système et j'étais vraiment impressionné. Cependant, il n'a jamais été prêt pour la production.",[35,3050,3051],{},"Ces questions m'ont aidé à itérer sur le MCP jusqu'à ce qu'il devienne efficace et utilisable. Aujourd'hui, je veux partager ce que j'ai appris en construisant un MCP pour un site orienté contenu.",[51,3053,3055],{"id":3054},"les-défis-pour-les-agents","Les défis pour les agents",[35,3057,3058],{},"Pour un humain, ces questions sont relativement simples. Parcourir le site et lire son contenu peut prendre du temps, mais c'est la seule difficulté. Pour un agent, c'est une autre histoire.",[35,3060,3061],{},"Un agent doit :",[68,3063,3064,3067,3070,3073,3076],{},[71,3065,3066],{},"répondre aussi vite que possible pour éviter de faire attendre l'utilisateur trop longtemps ;",[71,3068,3069],{},"répondre aussi précisément que possible pour éviter de frustrer l'utilisateur en ne trouvant pas ce qu'il cherche ;",[71,3071,3072],{},"continuer à répondre efficacement à mesure que le contenu du site augmente ;",[71,3074,3075],{},"utiliser aussi peu de tokens que possible pour éviter de faire payer trop cher une réponse aux utilisateurs ;",[71,3077,3078],{},"fonder ses réponses sur le contenu réel du site et identifier la source afin que les utilisateurs puissent les vérifier.",[35,3080,3081],{},"Cela fait beaucoup de contraintes à respecter lors de la conception d'un serveur MCP.",[51,3083,3085],{"id":3084},"lapproche-actuelle","L'approche actuelle",[35,3087,3088,3089,3092,3093,1655,3096,3099],{},"La version finale de mon MCP dispose de trois outils : ",[156,3090,3091],{},"get_page",", ",[156,3094,3095],{},"search_content",[156,3097,3098],{},"list_pages",". Chacun répond à un type de question différent. Examinons-les un par un.",[938,3101,3102],{},[35,3103,3104],{},"« Résume l'article sur Code Mode. »",[35,3106,3107,3108,3110,3111,3113],{},"Pour répondre à cette question, l'agent doit pouvoir lire le contenu de l'article. De nombreux serveurs MCP que j'ai explorés pendant la création du mien exposaient un outil ",[156,3109,3091],{}," précisément dans ce but. Mon outil ",[156,3112,3091],{}," reçoit l'identifiant d'une page et récupère en interne l'URL correspondante pour charger son contenu.",[192,3115,3117],{"className":194,"code":3116,"language":196,"meta":13,"style":13},"server.registerTool(\n  'get_page',\n  {\n    description: '...',\n    inputSchema: {\n      id: z.string().trim().min(1).describe('Identifiant de contenu exact et globalement unique renvoyé par search_content ou list_pages.')\n    },\n  },\n  async ({ id }) => {\n    const pages = await loadPages()\n\n    const page = pages.find(page => page.id === id)\n\n    return fetch(`${page.url}.md`)\n  },\n)\n",[156,3118,3119,3131,3142,3147,3162,3171,3222,3226,3230,3246,3262,3266,3303,3307,3336,3340],{"__ignoreMap":13},[200,3120,3121,3124,3126,3128],{"class":202,"line":203},[200,3122,3123],{"class":210},"server",[200,3125,215],{"class":214},[200,3127,224],{"class":223},[200,3129,3130],{"class":210},"(\n",[200,3132,3133,3136,3138,3140],{"class":202,"line":14},[200,3134,3135],{"class":214},"  '",[200,3137,3091],{"class":245},[200,3139,249],{"class":214},[200,3141,252],{"class":214},[200,3143,3144],{"class":202,"line":255},[200,3145,3146],{"class":214},"  {\n",[200,3148,3149,3152,3154,3156,3158,3160],{"class":202,"line":272},[200,3150,3151],{"class":235},"    description",[200,3153,239],{"class":214},[200,3155,242],{"class":214},[200,3157,573],{"class":245},[200,3159,249],{"class":214},[200,3161,252],{"class":214},[200,3163,3164,3167,3169],{"class":202,"line":283},[200,3165,3166],{"class":235},"    inputSchema",[200,3168,239],{"class":214},[200,3170,280],{"class":214},[200,3172,3173,3176,3178,3181,3183,3185,3187,3189,3192,3194,3196,3199,3201,3204,3206,3208,3211,3213,3215,3218,3220],{"class":202,"line":300},[200,3174,3175],{"class":235},"      id",[200,3177,239],{"class":214},[200,3179,3180],{"class":210}," z",[200,3182,215],{"class":214},[200,3184,328],{"class":223},[200,3186,1549],{"class":210},[200,3188,215],{"class":214},[200,3190,3191],{"class":223},"trim",[200,3193,1549],{"class":210},[200,3195,215],{"class":214},[200,3197,3198],{"class":223},"min",[200,3200,227],{"class":210},[200,3202,3203],{"class":2060},"1",[200,3205,2104],{"class":210},[200,3207,215],{"class":214},[200,3209,3210],{"class":223},"describe",[200,3212,227],{"class":210},[200,3214,249],{"class":214},[200,3216,3217],{"class":245},"Identifiant de contenu exact et globalement unique renvoyé par search_content ou list_pages.",[200,3219,249],{"class":214},[200,3221,416],{"class":210},[200,3223,3224],{"class":202,"line":310},[200,3225,339],{"class":214},[200,3227,3228],{"class":202,"line":336},[200,3229,368],{"class":214},[200,3231,3232,3235,3237,3240,3242,3244],{"class":202,"line":342},[200,3233,3234],{"class":379},"  async",[200,3236,383],{"class":214},[200,3238,3239],{"class":386}," id",[200,3241,390],{"class":214},[200,3243,393],{"class":379},[200,3245,280],{"class":214},[200,3247,3248,3250,3253,3255,3257,3260],{"class":202,"line":365},[200,3249,2014],{"class":379},[200,3251,3252],{"class":210}," pages",[200,3254,509],{"class":214},[200,3256,452],{"class":206},[200,3258,3259],{"class":223}," loadPages",[200,3261,466],{"class":235},[200,3263,3264],{"class":202,"line":371},[200,3265,528],{"emptyLinePlaceholder":20},[200,3267,3268,3270,3273,3275,3277,3279,3282,3284,3287,3289,3291,3293,3296,3299,3301],{"class":202,"line":398},[200,3269,2014],{"class":379},[200,3271,3272],{"class":210}," page",[200,3274,509],{"class":214},[200,3276,3252],{"class":210},[200,3278,215],{"class":214},[200,3280,3281],{"class":223},"find",[200,3283,227],{"class":235},[200,3285,3286],{"class":386},"page",[200,3288,393],{"class":379},[200,3290,3272],{"class":210},[200,3292,215],{"class":214},[200,3294,3295],{"class":210},"id",[200,3297,3298],{"class":214}," ===",[200,3300,3239],{"class":210},[200,3302,416],{"class":235},[200,3304,3305],{"class":202,"line":405},[200,3306,528],{"emptyLinePlaceholder":20},[200,3308,3309,3312,3315,3317,3320,3322,3324,3326,3328,3331,3334],{"class":202,"line":410},[200,3310,3311],{"class":206},"    return",[200,3313,3314],{"class":223}," fetch",[200,3316,227],{"class":235},[200,3318,3319],{"class":214},"`${",[200,3321,3286],{"class":210},[200,3323,215],{"class":214},[200,3325,2035],{"class":210},[200,3327,413],{"class":214},[200,3329,3330],{"class":245},".md",[200,3332,3333],{"class":214},"`",[200,3335,416],{"class":235},[200,3337,3338],{"class":202,"line":860},[200,3339,368],{"class":214},[200,3341,3342],{"class":202,"line":889},[200,3343,416],{"class":210},[938,3345,3346],{},[35,3347,3348,3350,3351,3353,3354],{},[200,3349,944],{},"\nIl ne s'agit pas de la véritable implémentation de l'outil ",[156,3352,3091],{},". C'est une version simplifiée à l'extrême pour illustrer l'idée. Pour voir l'implémentation complète, consulte ",[1599,3355,215],{"repo":3356},"barbapapazes\u002Fmcp.soubiran.dev",[35,3358,3359],{},"Cela couvre la lecture d'une page connue. Reste une question : comment l'agent connaît-il son identifiant ?",[130,3361,132,3362,132,3367],{},[134,3363,136,3364,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,3365],{"src":3366,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website\u002Fsummarize-the-article-about-code-mode.mp4",[143,3368,3369],{},"Résumé de l'article sur Code Mode",[938,3371,3372],{},[35,3373,3374],{},"« Y a-t-il du contenu sur Vite ? »",[35,3376,3377,3378,3380,3381,215],{},"Pour répondre à cette question, l'agent doit pouvoir rechercher dans l'ensemble du contenu, y compris les titres, les descriptions et le corps du texte. Nous avons donc besoin d'un outil ",[156,3379,3095],{},". Cet outil reçoit une requête en paramètre et utilise en interne la recherche sémantique et la recherche par mots-clés pour retrouver le contenu correspondant. Cela repose sur ",[39,3382,3385],{"href":3383,"rel":3384},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002F",[77],"Cloudflare AI Search",[192,3387,3389],{"className":194,"code":3388,"language":196,"meta":13,"style":13},"server.registerTool(\n  'search_content',\n  {\n    description: '...',\n    inputSchema: {\n      query: z.string().trim().min(1).describe('Requête pour rechercher du contenu.')\n    },\n  },\n  async ({ query }) => {\n    const results = await searchContent(query)\n\n    return results.map(result => ({\n      id: result.id,\n      title: result.title,\n      description: result.description,\n      content: result.chunk,\n      url: result.url,\n      date: result.date,\n    }))\n  },\n)\n",[156,3390,3391,3401,3411,3415,3429,3437,3483,3487,3491,3506,3527,3531,3553,3568,3584,3600,3616,3631,3647,3653,3658],{"__ignoreMap":13},[200,3392,3393,3395,3397,3399],{"class":202,"line":203},[200,3394,3123],{"class":210},[200,3396,215],{"class":214},[200,3398,224],{"class":223},[200,3400,3130],{"class":210},[200,3402,3403,3405,3407,3409],{"class":202,"line":14},[200,3404,3135],{"class":214},[200,3406,3095],{"class":245},[200,3408,249],{"class":214},[200,3410,252],{"class":214},[200,3412,3413],{"class":202,"line":255},[200,3414,3146],{"class":214},[200,3416,3417,3419,3421,3423,3425,3427],{"class":202,"line":272},[200,3418,3151],{"class":235},[200,3420,239],{"class":214},[200,3422,242],{"class":214},[200,3424,573],{"class":245},[200,3426,249],{"class":214},[200,3428,252],{"class":214},[200,3430,3431,3433,3435],{"class":202,"line":283},[200,3432,3166],{"class":235},[200,3434,239],{"class":214},[200,3436,280],{"class":214},[200,3438,3439,3442,3444,3446,3448,3450,3452,3454,3456,3458,3460,3462,3464,3466,3468,3470,3472,3474,3476,3479,3481],{"class":202,"line":300},[200,3440,3441],{"class":235},"      query",[200,3443,239],{"class":214},[200,3445,3180],{"class":210},[200,3447,215],{"class":214},[200,3449,328],{"class":223},[200,3451,1549],{"class":210},[200,3453,215],{"class":214},[200,3455,3191],{"class":223},[200,3457,1549],{"class":210},[200,3459,215],{"class":214},[200,3461,3198],{"class":223},[200,3463,227],{"class":210},[200,3465,3203],{"class":2060},[200,3467,2104],{"class":210},[200,3469,215],{"class":214},[200,3471,3210],{"class":223},[200,3473,227],{"class":210},[200,3475,249],{"class":214},[200,3477,3478],{"class":245},"Requête pour rechercher du contenu.",[200,3480,249],{"class":214},[200,3482,416],{"class":210},[200,3484,3485],{"class":202,"line":310},[200,3486,339],{"class":214},[200,3488,3489],{"class":202,"line":336},[200,3490,368],{"class":214},[200,3492,3493,3495,3497,3500,3502,3504],{"class":202,"line":342},[200,3494,3234],{"class":379},[200,3496,383],{"class":214},[200,3498,3499],{"class":386}," query",[200,3501,390],{"class":214},[200,3503,393],{"class":379},[200,3505,280],{"class":214},[200,3507,3508,3510,3513,3515,3517,3520,3522,3525],{"class":202,"line":365},[200,3509,2014],{"class":379},[200,3511,3512],{"class":210}," results",[200,3514,509],{"class":214},[200,3516,452],{"class":206},[200,3518,3519],{"class":223}," searchContent",[200,3521,227],{"class":235},[200,3523,3524],{"class":210},"query",[200,3526,416],{"class":235},[200,3528,3529],{"class":202,"line":371},[200,3530,528],{"emptyLinePlaceholder":20},[200,3532,3533,3535,3537,3539,3542,3544,3547,3549,3551],{"class":202,"line":398},[200,3534,3311],{"class":206},[200,3536,3512],{"class":210},[200,3538,215],{"class":214},[200,3540,3541],{"class":223},"map",[200,3543,227],{"class":235},[200,3545,3546],{"class":386},"result",[200,3548,393],{"class":379},[200,3550,1094],{"class":235},[200,3552,230],{"class":214},[200,3554,3555,3557,3559,3562,3564,3566],{"class":202,"line":405},[200,3556,3175],{"class":235},[200,3558,239],{"class":214},[200,3560,3561],{"class":210}," result",[200,3563,215],{"class":214},[200,3565,3295],{"class":210},[200,3567,252],{"class":214},[200,3569,3570,3573,3575,3577,3579,3582],{"class":202,"line":410},[200,3571,3572],{"class":235},"      title",[200,3574,239],{"class":214},[200,3576,3561],{"class":210},[200,3578,215],{"class":214},[200,3580,3581],{"class":210},"title",[200,3583,252],{"class":214},[200,3585,3586,3589,3591,3593,3595,3598],{"class":202,"line":860},[200,3587,3588],{"class":235},"      description",[200,3590,239],{"class":214},[200,3592,3561],{"class":210},[200,3594,215],{"class":214},[200,3596,3597],{"class":210},"description",[200,3599,252],{"class":214},[200,3601,3602,3605,3607,3609,3611,3614],{"class":202,"line":889},[200,3603,3604],{"class":235},"      content",[200,3606,239],{"class":214},[200,3608,3561],{"class":210},[200,3610,215],{"class":214},[200,3612,3613],{"class":210},"chunk",[200,3615,252],{"class":214},[200,3617,3618,3621,3623,3625,3627,3629],{"class":202,"line":899},[200,3619,3620],{"class":235},"      url",[200,3622,239],{"class":214},[200,3624,3561],{"class":210},[200,3626,215],{"class":214},[200,3628,2035],{"class":210},[200,3630,252],{"class":214},[200,3632,3633,3636,3638,3640,3642,3645],{"class":202,"line":904},[200,3634,3635],{"class":235},"      date",[200,3637,239],{"class":214},[200,3639,3561],{"class":210},[200,3641,215],{"class":214},[200,3643,3644],{"class":210},"date",[200,3646,252],{"class":214},[200,3648,3649,3651],{"class":202,"line":930},[200,3650,2111],{"class":214},[200,3652,584],{"class":235},[200,3654,3656],{"class":202,"line":3655},20,[200,3657,368],{"class":214},[200,3659,3661],{"class":202,"line":3660},21,[200,3662,416],{"class":210},[938,3664,3665],{},[35,3666,3667,3350,3669,3353,3671],{},[200,3668,944],{},[156,3670,3095],{},[1599,3672,215],{"repo":3356},[35,3674,3675],{},"Avec une requête bien choisie, cet outil pourrait également aider à répondre à la première question. Grâce à la recherche par mots-clés, l'agent pourrait rechercher « Code Mode » et récupérer l'identifiant de la page correspondante. Cependant, cette méthode n'est pas assez fiable à elle seule : une requête peut être ambiguë ou ne pas classer la page attendue en première position.",[130,3677,132,3678,132,3683],{},[134,3679,136,3680,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,3681],{"src":3682,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website\u002Fis-there-content-about-vite.mp4",[143,3684,3037],{},[938,3686,3687],{},[35,3688,3689],{},"« Quel est le dernier article sur Devoxx France ? »",[35,3691,3692],{},"Cette question est plus complexe. Elle nécessite de comparer les dates des articles. Pour identifier efficacement le dernier article correspondant, l'agent doit pouvoir accéder aux métadonnées du contenu et les analyser avec du code. Sans cette possibilité, il devrait récupérer la liste complète et effectuer lui-même la comparaison.",[35,3694,3695],{},"Cela pourrait fonctionner, mais ce n'est pas efficace. La liste complète du contenu de mon site compte 203 535 caractères, soit environ 60 000 tokens. Bien sûr, elle tiendrait dans la fenêtre de contexte de la plupart des modèles actuels, mais cela consommerait du temps et des tokens sans aucun bénéfice. Cela polluerait également le contexte et compliquerait la recherche d'informations pertinentes par l'agent.",[35,3697,3698,3699,3701],{},"Malgré cela, j'ai tout de même décidé de créer l'outil ",[156,3700,3098],{},". Cependant, il ne fonctionne peut-être pas comme tu l'imagines.",[35,3703,3704,3705,3707,3708,3713],{},"L'outil reçoit un paramètre ",[156,3706,156],{},". Il permet à l'agent d'écrire du JavaScript à partir de données typées pour filtrer, trier et transformer précisément les informations dont il a besoin. Les ",[39,3709,3712],{"href":3710,"rel":3711},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fdynamic-workers\u002F",[77],"Workers dynamiques de Cloudflare"," exécutent le code dans un environnement léger, sécurisé et isolé.",[35,3715,3716],{},"Par exemple, l'agent pourrait écrire le code suivant pour récupérer le dernier article sur Devoxx France :",[192,3718,3720],{"className":194,"code":3719,"language":196,"meta":13,"style":13},"async () => {\n  const query = 'devoxx france'\n\n  return pages.data\n    .filter(page => page.type === 'post')\n    .filter(page =>\n      `${page.title} ${page.description ?? ''}`\n        .toLowerCase()\n        .includes(query),\n    )\n    .sort((a, b) =>\n      (b.date ?? '').localeCompare(a.date ?? ''),\n    )\n    .slice(0, 1)\n    .map(({ id, title, description, date, url }) => ({\n      id,\n      title,\n      description,\n      date,\n      url,\n    }))\n}\n",[156,3721,3722,3734,3750,3754,3766,3798,3811,3840,3850,3865,3870,3892,3933,3937,3956,3997,4003,4009,4015,4021,4027,4033],{"__ignoreMap":13},[200,3723,3724,3727,3730,3732],{"class":202,"line":203},[200,3725,3726],{"class":379},"async",[200,3728,3729],{"class":214}," ()",[200,3731,393],{"class":379},[200,3733,280],{"class":214},[200,3735,3736,3739,3741,3743,3745,3748],{"class":202,"line":14},[200,3737,3738],{"class":379},"  const",[200,3740,3499],{"class":210},[200,3742,509],{"class":214},[200,3744,242],{"class":214},[200,3746,3747],{"class":245},"devoxx france",[200,3749,991],{"class":214},[200,3751,3752],{"class":202,"line":255},[200,3753,528],{"emptyLinePlaceholder":20},[200,3755,3756,3759,3761,3763],{"class":202,"line":272},[200,3757,3758],{"class":206},"  return",[200,3760,3252],{"class":210},[200,3762,215],{"class":214},[200,3764,3765],{"class":210},"data\n",[200,3767,3768,3771,3774,3776,3778,3780,3782,3784,3787,3789,3791,3794,3796],{"class":202,"line":283},[200,3769,3770],{"class":214},"    .",[200,3772,3773],{"class":223},"filter",[200,3775,227],{"class":235},[200,3777,3286],{"class":386},[200,3779,393],{"class":379},[200,3781,3272],{"class":210},[200,3783,215],{"class":214},[200,3785,3786],{"class":210},"type",[200,3788,3298],{"class":214},[200,3790,242],{"class":214},[200,3792,3793],{"class":245},"post",[200,3795,249],{"class":214},[200,3797,416],{"class":235},[200,3799,3800,3802,3804,3806,3808],{"class":202,"line":300},[200,3801,3770],{"class":214},[200,3803,3773],{"class":223},[200,3805,227],{"class":235},[200,3807,3286],{"class":386},[200,3809,3810],{"class":379}," =>\n",[200,3812,3813,3816,3818,3820,3822,3824,3827,3829,3831,3834,3837],{"class":202,"line":310},[200,3814,3815],{"class":214},"      `${",[200,3817,3286],{"class":210},[200,3819,215],{"class":214},[200,3821,3581],{"class":210},[200,3823,413],{"class":214},[200,3825,3826],{"class":214}," ${",[200,3828,3286],{"class":210},[200,3830,215],{"class":214},[200,3832,3833],{"class":210},"description ",[200,3835,3836],{"class":214},"??",[200,3838,3839],{"class":214}," ''}`\n",[200,3841,3842,3845,3848],{"class":202,"line":336},[200,3843,3844],{"class":214},"        .",[200,3846,3847],{"class":223},"toLowerCase",[200,3849,466],{"class":235},[200,3851,3852,3854,3857,3859,3861,3863],{"class":202,"line":342},[200,3853,3844],{"class":214},[200,3855,3856],{"class":223},"includes",[200,3858,227],{"class":235},[200,3860,3524],{"class":210},[200,3862,2104],{"class":235},[200,3864,252],{"class":214},[200,3866,3867],{"class":202,"line":365},[200,3868,3869],{"class":235},"    )\n",[200,3871,3872,3874,3877,3879,3881,3883,3885,3888,3890],{"class":202,"line":371},[200,3873,3770],{"class":214},[200,3875,3876],{"class":223},"sort",[200,3878,227],{"class":235},[200,3880,227],{"class":214},[200,3882,39],{"class":386},[200,3884,549],{"class":214},[200,3886,3887],{"class":386}," b",[200,3889,2104],{"class":214},[200,3891,3810],{"class":379},[200,3893,3894,3897,3900,3902,3904,3907,3910,3912,3914,3917,3919,3921,3923,3925,3927,3929,3931],{"class":202,"line":398},[200,3895,3896],{"class":235},"      (",[200,3898,3899],{"class":210},"b",[200,3901,215],{"class":214},[200,3903,3644],{"class":210},[200,3905,3906],{"class":214}," ??",[200,3908,3909],{"class":214}," ''",[200,3911,2104],{"class":235},[200,3913,215],{"class":214},[200,3915,3916],{"class":223},"localeCompare",[200,3918,227],{"class":235},[200,3920,39],{"class":210},[200,3922,215],{"class":214},[200,3924,3644],{"class":210},[200,3926,3906],{"class":214},[200,3928,3909],{"class":214},[200,3930,2104],{"class":235},[200,3932,252],{"class":214},[200,3934,3935],{"class":202,"line":405},[200,3936,3869],{"class":235},[200,3938,3939,3941,3944,3946,3949,3951,3954],{"class":202,"line":410},[200,3940,3770],{"class":214},[200,3942,3943],{"class":223},"slice",[200,3945,227],{"class":235},[200,3947,3948],{"class":2060},"0",[200,3950,549],{"class":214},[200,3952,3953],{"class":2060}," 1",[200,3955,416],{"class":235},[200,3957,3958,3960,3962,3964,3967,3969,3971,3974,3976,3979,3981,3984,3986,3989,3991,3993,3995],{"class":202,"line":860},[200,3959,3770],{"class":214},[200,3961,3541],{"class":223},[200,3963,227],{"class":235},[200,3965,3966],{"class":214},"({",[200,3968,3239],{"class":386},[200,3970,549],{"class":214},[200,3972,3973],{"class":386}," title",[200,3975,549],{"class":214},[200,3977,3978],{"class":386}," description",[200,3980,549],{"class":214},[200,3982,3983],{"class":386}," date",[200,3985,549],{"class":214},[200,3987,3988],{"class":386}," url",[200,3990,390],{"class":214},[200,3992,393],{"class":379},[200,3994,1094],{"class":235},[200,3996,230],{"class":214},[200,3998,3999,4001],{"class":202,"line":889},[200,4000,3175],{"class":210},[200,4002,252],{"class":214},[200,4004,4005,4007],{"class":202,"line":899},[200,4006,3572],{"class":210},[200,4008,252],{"class":214},[200,4010,4011,4013],{"class":202,"line":904},[200,4012,3588],{"class":210},[200,4014,252],{"class":214},[200,4016,4017,4019],{"class":202,"line":930},[200,4018,3635],{"class":210},[200,4020,252],{"class":214},[200,4022,4023,4025],{"class":202,"line":3655},[200,4024,3620],{"class":210},[200,4026,252],{"class":214},[200,4028,4029,4031],{"class":202,"line":3660},[200,4030,2111],{"class":214},[200,4032,584],{"class":235},[200,4034,4036],{"class":202,"line":4035},22,[200,4037,1129],{"class":214},[938,4039,4040],{},[35,4041,4042,4044,4045,215],{},[200,4043,944],{},"\nPour comprendre ce qu'est Code Mode, lis ",[39,4046,4048],{"href":4047},".\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context","Code Mode, deux outils et un MCP pour sauver le contexte de ton LLM",[35,4050,4051],{},"En coulisses, l'outil ressemble à ceci. Garde à l'esprit que la description est l'un des éléments intéressants d'un outil Code Mode.",[192,4053,4055],{"className":194,"code":4054,"language":196,"meta":13,"style":13},"server.registerTool(\n  'list_pages',\n  {\n    description: '...',\n    inputSchema: {\n      code: z.string().trim().min(1).max(20_000).describe('Une fonction fléchée JavaScript asynchrone avec des variables globales pages, talks et infra en lecture seule.'),\n    },\n  },\n  async ({ code }) => {\n    const result = await executeCode(code)\n\n    return result\n  },\n)\n",[156,4056,4057,4067,4077,4081,4095,4103,4163,4167,4171,4186,4205,4209,4216,4220],{"__ignoreMap":13},[200,4058,4059,4061,4063,4065],{"class":202,"line":203},[200,4060,3123],{"class":210},[200,4062,215],{"class":214},[200,4064,224],{"class":223},[200,4066,3130],{"class":210},[200,4068,4069,4071,4073,4075],{"class":202,"line":14},[200,4070,3135],{"class":214},[200,4072,3098],{"class":245},[200,4074,249],{"class":214},[200,4076,252],{"class":214},[200,4078,4079],{"class":202,"line":255},[200,4080,3146],{"class":214},[200,4082,4083,4085,4087,4089,4091,4093],{"class":202,"line":272},[200,4084,3151],{"class":235},[200,4086,239],{"class":214},[200,4088,242],{"class":214},[200,4090,573],{"class":245},[200,4092,249],{"class":214},[200,4094,252],{"class":214},[200,4096,4097,4099,4101],{"class":202,"line":283},[200,4098,3166],{"class":235},[200,4100,239],{"class":214},[200,4102,280],{"class":214},[200,4104,4105,4108,4110,4112,4114,4116,4118,4120,4122,4124,4126,4128,4130,4132,4134,4136,4139,4141,4144,4146,4148,4150,4152,4154,4157,4159,4161],{"class":202,"line":300},[200,4106,4107],{"class":235},"      code",[200,4109,239],{"class":214},[200,4111,3180],{"class":210},[200,4113,215],{"class":214},[200,4115,328],{"class":223},[200,4117,1549],{"class":210},[200,4119,215],{"class":214},[200,4121,3191],{"class":223},[200,4123,1549],{"class":210},[200,4125,215],{"class":214},[200,4127,3198],{"class":223},[200,4129,227],{"class":210},[200,4131,3203],{"class":2060},[200,4133,2104],{"class":210},[200,4135,215],{"class":214},[200,4137,4138],{"class":223},"max",[200,4140,227],{"class":210},[200,4142,4143],{"class":2060},"20_000",[200,4145,2104],{"class":210},[200,4147,215],{"class":214},[200,4149,3210],{"class":223},[200,4151,227],{"class":210},[200,4153,249],{"class":214},[200,4155,4156],{"class":245},"Une fonction fléchée JavaScript asynchrone avec des variables globales pages, talks et infra en lecture seule.",[200,4158,249],{"class":214},[200,4160,2104],{"class":210},[200,4162,252],{"class":214},[200,4164,4165],{"class":202,"line":310},[200,4166,339],{"class":214},[200,4168,4169],{"class":202,"line":336},[200,4170,368],{"class":214},[200,4172,4173,4175,4177,4180,4182,4184],{"class":202,"line":342},[200,4174,3234],{"class":379},[200,4176,383],{"class":214},[200,4178,4179],{"class":386}," code",[200,4181,390],{"class":214},[200,4183,393],{"class":379},[200,4185,280],{"class":214},[200,4187,4188,4190,4192,4194,4196,4199,4201,4203],{"class":202,"line":365},[200,4189,2014],{"class":379},[200,4191,3561],{"class":210},[200,4193,509],{"class":214},[200,4195,452],{"class":206},[200,4197,4198],{"class":223}," executeCode",[200,4200,227],{"class":235},[200,4202,156],{"class":210},[200,4204,416],{"class":235},[200,4206,4207],{"class":202,"line":371},[200,4208,528],{"emptyLinePlaceholder":20},[200,4210,4211,4213],{"class":202,"line":398},[200,4212,3311],{"class":206},[200,4214,4215],{"class":210}," result\n",[200,4217,4218],{"class":202,"line":405},[200,4219,368],{"class":214},[200,4221,4222],{"class":202,"line":410},[200,4223,416],{"class":210},[938,4225,4226],{},[35,4227,4228,3350,4230,3353,4232],{},[200,4229,944],{},[156,4231,3098],{},[1599,4233,215],{"repo":3356},[130,4235,132,4236,132,4241],{},[134,4237,136,4238,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,4239],{"src":4240,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website\u002Fwhat-s-the-latest-article-about-devoxx-france.mp4",[143,4242,3040],{},[35,4244,4245],{},"Cette conception respecte-t-elle les contraintes définies au début ? Oui.",[35,4247,4248],{},"J'ai beaucoup appris en concevant ce MCP.",[1593,4250,4251,4254,4257,4260,4263],{},[71,4252,4253],{},"Réduis autant que possible le nombre d'outils. Utilise des paramètres pour ajouter de la flexibilité à un outil au lieu d'en créer un nouveau. J'aime beaucoup l'approche de GitHub pour son MCP ;",[71,4255,4256],{},"Utilise tes outils manuellement pour vérifier s'ils peuvent répondre à tes questions. Si ce n'est pas le cas, améliore-les jusqu'à ce qu'ils le puissent ;",[71,4258,4259],{},"Garde des outils au périmètre suffisamment large, mais assez spécialisés pour éviter qu'ils se marchent sur les pieds ;",[71,4261,4262],{},"Réduis la quantité d'informations fournies à l'agent. Moins il a de contenu à lire, plus il sera performant ;",[71,4264,4265],{},"Parfois, l'agent doit orchestrer lui-même les outils.",[35,4267,4268],{},"Je sais que cela représente beaucoup de technologies, AI Search et Dynamic Workers, rien que pour construire un serveur MCP, mais la différence sur la qualité des réponses est réelle. Un MCP incapable de répondre aux questions des utilisateurs a une valeur limitée. Si tu veux construire un MCP pour ton site orienté contenu, j'espère que cet article t'aidera à éviter les erreurs que j'ai commises et à en construire un meilleur.",[51,4270,4272],{"id":4271},"comment-je-suis-arrivé-à-trois-outils","Comment je suis arrivé à trois outils",[35,4274,4275,4276],{},"J'ai créé la première version du MCP en octobre 2025. C'était le deuxième MCP que je créais ; j'avais utilisé le premier pour explorer le concept dans ",[3045,4277,4279],{"id":4278},"4ff51ee2-926d-4924-86f5-97cd9e378fa7",". Je n'avais aucune idée de la manière de l'architecturer, alors j'ai créé tout ce qui me passait par la tête.",[35,4281,4282],{},"Au final, j'ai créé 10 outils rien que pour le contenu de mon site principal :",[1593,4284,4285,4301,4315,4328,4342,4356,4370,4512,4636,4692],{},[71,4286,4287,4290],{},[156,4288,4289],{},"list_languages",[192,4291,4295],{"className":4292,"code":4293,"language":4294,"meta":13,"style":13},"language-txt shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","Renvoie un tableau JSON exploitable par une machine contenant toutes les langues prises en charge par le site d'Estéban. Chaque objet comprend un « code » (ISO 639-1) et un « name » (nom en anglais). Exemple de réponse : [{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"fr\",\"name\":\"French\"}].\n","txt",[156,4296,4297],{"__ignoreMap":13},[200,4298,4299],{"class":202,"line":203},[200,4300,4293],{},[71,4302,4303,4306],{},[156,4304,4305],{},"list_parts",[192,4307,4309],{"className":4292,"code":4308,"language":4294,"meta":13,"style":13},"Renvoie un tableau JSON exploitable par une machine contenant toutes les parties (sections) disponibles sur le site d'Estéban. Chaque objet comprend un « id » (chaîne de caractères), un « name » (chaîne de caractères) et une « description » (chaîne de caractères). Exemple de réponse : [{\"id\":\"pages\",\"name\":\"Pages\",\"description\":\"All website pages available.\"},{\"id\":\"blog\",\"name\":\"Blog\",\"description\":\"All blog posts available.\"}].\n",[156,4310,4311],{"__ignoreMap":13},[200,4312,4313],{"class":202,"line":203},[200,4314,4308],{},[71,4316,4317,4319],{},[156,4318,3098],{},[192,4320,4322],{"className":4292,"code":4321,"language":4294,"meta":13,"style":13},"Renvoie la liste de toutes les pages disponibles sur le site d'Estéban dans une langue donnée. Chaque page comprend son titre, sa description, son URL et sa date. Utilise le paramètre « language » pour sélectionner la langue (« en » pour l'anglais ou « fr » pour le français, par exemple). La réponse est un tableau JSON d'objets : [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n",[156,4323,4324],{"__ignoreMap":13},[200,4325,4326],{"class":202,"line":203},[200,4327,4321],{},[71,4329,4330,4333],{},[156,4331,4332],{},"list_posts",[192,4334,4336],{"className":4292,"code":4335,"language":4294,"meta":13,"style":13},"Renvoie la liste de tous les articles disponibles sur le site d'Estéban dans une langue donnée. Chaque article comprend son titre, sa description, son URL et sa date. Utilise le paramètre « language » pour sélectionner la langue (« en » pour l'anglais ou « fr » pour le français, par exemple). La réponse est un tableau JSON d'objets : [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n",[156,4337,4338],{"__ignoreMap":13},[200,4339,4340],{"class":202,"line":203},[200,4341,4335],{},[71,4343,4344,4347],{},[156,4345,4346],{},"list_series",[192,4348,4350],{"className":4292,"code":4349,"language":4294,"meta":13,"style":13},"Renvoie la liste de toutes les séries disponibles sur le site d'Estéban dans une langue donnée. Chaque série comprend son titre, sa description, son URL et sa date. Utilise le paramètre « language » pour sélectionner la langue (« en » pour l'anglais ou « fr » pour le français, par exemple). La réponse est un tableau JSON d'objets : [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n",[156,4351,4352],{"__ignoreMap":13},[200,4353,4354],{"class":202,"line":203},[200,4355,4349],{},[71,4357,4358,4361],{},[156,4359,4360],{},"list_series_articles",[192,4362,4364],{"className":4292,"code":4363,"language":4294,"meta":13,"style":13},"Renvoie la liste de tous les articles d'une série donnée sur le site d'Estéban dans une langue donnée. Chaque article comprend son titre, sa description, son URL et sa date. Utilise le paramètre « language » pour sélectionner la langue (« en » pour l'anglais ou « fr » pour le français, par exemple) et le paramètre « series » pour indiquer l'URI de la série. La réponse est un tableau JSON d'objets : [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n",[156,4365,4366],{"__ignoreMap":13},[200,4367,4368],{"class":202,"line":203},[200,4369,4363],{},[71,4371,4372,4375],{},[156,4373,4374],{},"list_projects",[192,4376,4378],{"className":4292,"code":4377,"language":4294,"meta":13,"style":13},"Renvoie un tableau JSON exploitable par une machine contenant toutes les catégories de projets d'Estéban, chacune avec un « title » (nom de la catégorie) et un tableau « projects ».\nChaque projet comprend :\n- « name » (chaîne de caractères, par exemple « barbapapazes\u002Fcode.soubiran.dev »),\n- « description » (chaîne de caractères),\n- « stars » (nombre),\n- « updatedAt » (chaîne de caractères ISO 8601),\n- « topics » (tableau de chaînes de caractères),\n- « url » (chaîne de caractères),\n- « license » (chaîne de caractères, facultative).\n\nExemple de réponse :\n[\n  {\n    \"title\": \"Ecosystem\",\n    \"projects\": [\n      {\n        \"name\": \"barbapapazes\u002Fcode.soubiran.dev\",\n        \"description\": \"Create beautiful images from code.\",\n        \"stars\": 3,\n        \"updatedAt\": \"2025-03-16T21:16:15Z\",\n        \"topics\": [\"code\", \"vue\"],\n        \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcode.soubiran.dev\"\n      }\n    ]\n  }\n]\n",[156,4379,4380,4385,4390,4395,4400,4405,4410,4415,4420,4425,4429,4434,4439,4443,4448,4453,4458,4463,4468,4473,4478,4483,4488,4494,4500,4506],{"__ignoreMap":13},[200,4381,4382],{"class":202,"line":203},[200,4383,4384],{},"Renvoie un tableau JSON exploitable par une machine contenant toutes les catégories de projets d'Estéban, chacune avec un « title » (nom de la catégorie) et un tableau « projects ».\n",[200,4386,4387],{"class":202,"line":14},[200,4388,4389],{},"Chaque projet comprend :\n",[200,4391,4392],{"class":202,"line":255},[200,4393,4394],{},"- « name » (chaîne de caractères, par exemple « barbapapazes\u002Fcode.soubiran.dev »),\n",[200,4396,4397],{"class":202,"line":272},[200,4398,4399],{},"- « description » (chaîne de caractères),\n",[200,4401,4402],{"class":202,"line":283},[200,4403,4404],{},"- « stars » (nombre),\n",[200,4406,4407],{"class":202,"line":300},[200,4408,4409],{},"- « updatedAt » (chaîne de caractères ISO 8601),\n",[200,4411,4412],{"class":202,"line":310},[200,4413,4414],{},"- « topics » (tableau de chaînes de caractères),\n",[200,4416,4417],{"class":202,"line":336},[200,4418,4419],{},"- « url » (chaîne de caractères),\n",[200,4421,4422],{"class":202,"line":342},[200,4423,4424],{},"- « license » (chaîne de caractères, facultative).\n",[200,4426,4427],{"class":202,"line":365},[200,4428,528],{"emptyLinePlaceholder":20},[200,4430,4431],{"class":202,"line":371},[200,4432,4433],{},"Exemple de réponse :\n",[200,4435,4436],{"class":202,"line":398},[200,4437,4438],{},"[\n",[200,4440,4441],{"class":202,"line":405},[200,4442,3146],{},[200,4444,4445],{"class":202,"line":410},[200,4446,4447],{},"    \"title\": \"Ecosystem\",\n",[200,4449,4450],{"class":202,"line":860},[200,4451,4452],{},"    \"projects\": [\n",[200,4454,4455],{"class":202,"line":889},[200,4456,4457],{},"      {\n",[200,4459,4460],{"class":202,"line":899},[200,4461,4462],{},"        \"name\": \"barbapapazes\u002Fcode.soubiran.dev\",\n",[200,4464,4465],{"class":202,"line":904},[200,4466,4467],{},"        \"description\": \"Create beautiful images from code.\",\n",[200,4469,4470],{"class":202,"line":930},[200,4471,4472],{},"        \"stars\": 3,\n",[200,4474,4475],{"class":202,"line":3655},[200,4476,4477],{},"        \"updatedAt\": \"2025-03-16T21:16:15Z\",\n",[200,4479,4480],{"class":202,"line":3660},[200,4481,4482],{},"        \"topics\": [\"code\", \"vue\"],\n",[200,4484,4485],{"class":202,"line":4035},[200,4486,4487],{},"        \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcode.soubiran.dev\"\n",[200,4489,4491],{"class":202,"line":4490},23,[200,4492,4493],{},"      }\n",[200,4495,4497],{"class":202,"line":4496},24,[200,4498,4499],{},"    ]\n",[200,4501,4503],{"class":202,"line":4502},25,[200,4504,4505],{},"  }\n",[200,4507,4509],{"class":202,"line":4508},26,[200,4510,4511],{},"]\n",[71,4513,4514,4517],{},[156,4515,4516],{},"list_talks",[192,4518,4520],{"className":4292,"code":4519,"language":4294,"meta":13,"style":13},"Renvoie un tableau JSON exploitable par une machine contenant toutes les conférences données par Estéban Soubiran.\nChaque conférence comprend :\n- « name » (titre de la conférence, chaîne de caractères)\n- « event » (nom de l'événement, chaîne de caractères)\n- « date » (date ISO 8601, chaîne de caractères)\n- « url » (URL principale de la conférence, chaîne de caractères)\n- « pdf_url » (URL PDF des diapositives, chaîne de caractères, facultative)\n- « thumbnail_url » (URL de la miniature, chaîne de caractères, facultative)\n- « github_url » (dépôt GitHub, chaîne de caractères, facultative)\n- « recording_url » (enregistrement vidéo, chaîne de caractères, facultative)\n\nExemple de réponse :\n[\n  {\n    \"name\": \"Unpoly pour reprendre le contrôle !\",\n    \"event\": \"Devoxx France\",\n    \"date\": \"2023-04-12\",\n    \"url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\",\n    \"pdf_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Fpdf\",\n    \"thumbnail_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Fthumbnail.png\",\n    \"github_url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ftalks\u002Ftree\u002Fmain\u002F2023-04-12\",\n    \"recording_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Frecording\"\n  }\n]\n",[156,4521,4522,4527,4532,4537,4542,4547,4552,4557,4562,4567,4572,4576,4580,4584,4588,4593,4598,4603,4608,4613,4618,4623,4628,4632],{"__ignoreMap":13},[200,4523,4524],{"class":202,"line":203},[200,4525,4526],{},"Renvoie un tableau JSON exploitable par une machine contenant toutes les conférences données par Estéban Soubiran.\n",[200,4528,4529],{"class":202,"line":14},[200,4530,4531],{},"Chaque conférence comprend :\n",[200,4533,4534],{"class":202,"line":255},[200,4535,4536],{},"- « name » (titre de la conférence, chaîne de caractères)\n",[200,4538,4539],{"class":202,"line":272},[200,4540,4541],{},"- « event » (nom de l'événement, chaîne de caractères)\n",[200,4543,4544],{"class":202,"line":283},[200,4545,4546],{},"- « date » (date ISO 8601, chaîne de caractères)\n",[200,4548,4549],{"class":202,"line":300},[200,4550,4551],{},"- « url » (URL principale de la conférence, chaîne de caractères)\n",[200,4553,4554],{"class":202,"line":310},[200,4555,4556],{},"- « pdf_url » (URL PDF des diapositives, chaîne de caractères, facultative)\n",[200,4558,4559],{"class":202,"line":336},[200,4560,4561],{},"- « thumbnail_url » (URL de la miniature, chaîne de caractères, facultative)\n",[200,4563,4564],{"class":202,"line":342},[200,4565,4566],{},"- « github_url » (dépôt GitHub, chaîne de caractères, facultative)\n",[200,4568,4569],{"class":202,"line":365},[200,4570,4571],{},"- « recording_url » (enregistrement vidéo, chaîne de caractères, facultative)\n",[200,4573,4574],{"class":202,"line":371},[200,4575,528],{"emptyLinePlaceholder":20},[200,4577,4578],{"class":202,"line":398},[200,4579,4433],{},[200,4581,4582],{"class":202,"line":405},[200,4583,4438],{},[200,4585,4586],{"class":202,"line":410},[200,4587,3146],{},[200,4589,4590],{"class":202,"line":860},[200,4591,4592],{},"    \"name\": \"Unpoly pour reprendre le contrôle !\",\n",[200,4594,4595],{"class":202,"line":889},[200,4596,4597],{},"    \"event\": \"Devoxx France\",\n",[200,4599,4600],{"class":202,"line":899},[200,4601,4602],{},"    \"date\": \"2023-04-12\",\n",[200,4604,4605],{"class":202,"line":904},[200,4606,4607],{},"    \"url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\",\n",[200,4609,4610],{"class":202,"line":930},[200,4611,4612],{},"    \"pdf_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Fpdf\",\n",[200,4614,4615],{"class":202,"line":3655},[200,4616,4617],{},"    \"thumbnail_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Fthumbnail.png\",\n",[200,4619,4620],{"class":202,"line":3660},[200,4621,4622],{},"    \"github_url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ftalks\u002Ftree\u002Fmain\u002F2023-04-12\",\n",[200,4624,4625],{"class":202,"line":4035},[200,4626,4627],{},"    \"recording_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Frecording\"\n",[200,4629,4630],{"class":202,"line":4490},[200,4631,4505],{},[200,4633,4634],{"class":202,"line":4496},[200,4635,4511],{},[71,4637,4638,4641],{},[156,4639,4640],{},"list_socials",[192,4642,4644],{"className":4292,"code":4643,"language":4294,"meta":13,"style":13},"Renvoie un tableau JSON exploitable par une machine contenant tous les profils d'Estéban sur les réseaux sociaux.\nChaque profil comprend :\n- « name » (nom de la plateforme, chaîne de caractères, par exemple « Twitter »)\n- « url » (URL du profil, chaîne de caractères)\n\nExemple de réponse :\n[\n  { \"name\": \"Twitter\", \"url\": \"https:\u002F\u002Ftwitter.com\u002Festebansoubiran\" },\n  { \"name\": \"GitHub\", \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\" }\n]\n",[156,4645,4646,4651,4656,4661,4666,4670,4674,4678,4683,4688],{"__ignoreMap":13},[200,4647,4648],{"class":202,"line":203},[200,4649,4650],{},"Renvoie un tableau JSON exploitable par une machine contenant tous les profils d'Estéban sur les réseaux sociaux.\n",[200,4652,4653],{"class":202,"line":14},[200,4654,4655],{},"Chaque profil comprend :\n",[200,4657,4658],{"class":202,"line":255},[200,4659,4660],{},"- « name » (nom de la plateforme, chaîne de caractères, par exemple « Twitter »)\n",[200,4662,4663],{"class":202,"line":272},[200,4664,4665],{},"- « url » (URL du profil, chaîne de caractères)\n",[200,4667,4668],{"class":202,"line":283},[200,4669,528],{"emptyLinePlaceholder":20},[200,4671,4672],{"class":202,"line":300},[200,4673,4433],{},[200,4675,4676],{"class":202,"line":310},[200,4677,4438],{},[200,4679,4680],{"class":202,"line":336},[200,4681,4682],{},"  { \"name\": \"Twitter\", \"url\": \"https:\u002F\u002Ftwitter.com\u002Festebansoubiran\" },\n",[200,4684,4685],{"class":202,"line":342},[200,4686,4687],{},"  { \"name\": \"GitHub\", \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\" }\n",[200,4689,4690],{"class":202,"line":365},[200,4691,4511],{},[71,4693,4694,4696],{},[156,4695,3091],{},[192,4697,4699],{"className":4292,"code":4698,"language":4294,"meta":13,"style":13},"Récupère une page donnée du site d'Estéban. La réponse contient le contenu Markdown de la page.\n",[156,4700,4701],{"__ignoreMap":13},[200,4702,4703],{"class":202,"line":203},[200,4704,4698],{},[1599,4706,4708,4711,4718,4732,4986,4989,5149,5156,5162,5477,5480,5488,5491,5494,5502,5505,5517,5533,5539,5542,5545,5548,5551,5563,5577,5581,5584,5601,5604,5607,5610,5613,5615],{"repo":4707},"barbapapazes\u002Fmcp.soubiran.dev\u002Fcommit\u002F8c544b5adde3450d18b7b6d13b5927030054be7f",[35,4709,4710],{},"La plupart de ces outils suivent le même modèle.",[35,4712,4713,4717],{},[39,4714,4716],{"href":4715},".\u002Fvitepress-for-a-personal-website-is-that-possible","Mon site repose sur VitePress"," et génère chaque page de manière statique. J'en ai profité pour générer au moment de la construction de nombreux fichiers JSON contenant les informations dont j'ai besoin. Chaque outil de liste effectue ensuite une simple requête HTTP pour récupérer le fichier JSON correspondant.",[35,4719,4720,4721,4723,4724,4727,4728,4731],{},"Par exemple, l'outil ",[156,4722,3098],{}," récupère un fichier JSON nommé ",[156,4725,4726],{},"pages.en.json",", ou ",[156,4729,4730],{},"pages.fr.json"," selon la langue demandée dans le paramètre, puis renvoie son contenu à l'agent :",[192,4733,4735],{"className":194,"code":4734,"language":196,"meta":13,"style":13},"this.server.tool(\n  'list_pages',\n  '...',\n  {\n    language: z.string().min(2).max(2).describe('Code de langue des pages de contenu (« en » ou « fr », par exemple)'),\n  },\n  async ({ language }) => {\n    const result = await ofetch(`pages.${language}.json`, {\n      baseURL: env.BASE_API_URL,\n    })\n    return {\n      content: [\n        {\n          type: 'text',\n          text: JSON.stringify(result),\n        },\n      ],\n    }\n  },\n)\n",[156,4736,4737,4750,4760,4770,4774,4827,4831,4846,4883,4900,4906,4912,4920,4925,4940,4961,4966,4973,4978,4982],{"__ignoreMap":13},[200,4738,4739,4742,4744,4746,4748],{"class":202,"line":203},[200,4740,4741],{"class":214},"this.",[200,4743,3123],{"class":210},[200,4745,215],{"class":214},[200,4747,504],{"class":223},[200,4749,3130],{"class":210},[200,4751,4752,4754,4756,4758],{"class":202,"line":14},[200,4753,3135],{"class":214},[200,4755,3098],{"class":245},[200,4757,249],{"class":214},[200,4759,252],{"class":214},[200,4761,4762,4764,4766,4768],{"class":202,"line":255},[200,4763,3135],{"class":214},[200,4765,573],{"class":245},[200,4767,249],{"class":214},[200,4769,252],{"class":214},[200,4771,4772],{"class":202,"line":272},[200,4773,3146],{"class":214},[200,4775,4776,4779,4781,4783,4785,4787,4789,4791,4793,4795,4798,4800,4802,4804,4806,4808,4810,4812,4814,4816,4818,4821,4823,4825],{"class":202,"line":283},[200,4777,4778],{"class":235},"    language",[200,4780,239],{"class":214},[200,4782,3180],{"class":210},[200,4784,215],{"class":214},[200,4786,328],{"class":223},[200,4788,1549],{"class":210},[200,4790,215],{"class":214},[200,4792,3198],{"class":223},[200,4794,227],{"class":210},[200,4796,4797],{"class":2060},"2",[200,4799,2104],{"class":210},[200,4801,215],{"class":214},[200,4803,4138],{"class":223},[200,4805,227],{"class":210},[200,4807,4797],{"class":2060},[200,4809,2104],{"class":210},[200,4811,215],{"class":214},[200,4813,3210],{"class":223},[200,4815,227],{"class":210},[200,4817,249],{"class":214},[200,4819,4820],{"class":245},"Code de langue des pages de contenu (« en » ou « fr », par exemple)",[200,4822,249],{"class":214},[200,4824,2104],{"class":210},[200,4826,252],{"class":214},[200,4828,4829],{"class":202,"line":300},[200,4830,368],{"class":214},[200,4832,4833,4835,4837,4840,4842,4844],{"class":202,"line":310},[200,4834,3234],{"class":379},[200,4836,383],{"class":214},[200,4838,4839],{"class":386}," language",[200,4841,390],{"class":214},[200,4843,393],{"class":379},[200,4845,280],{"class":214},[200,4847,4848,4850,4852,4854,4856,4859,4861,4863,4866,4869,4872,4874,4877,4879,4881],{"class":202,"line":336},[200,4849,2014],{"class":379},[200,4851,3561],{"class":210},[200,4853,509],{"class":214},[200,4855,452],{"class":206},[200,4857,4858],{"class":223}," ofetch",[200,4860,227],{"class":235},[200,4862,3333],{"class":214},[200,4864,4865],{"class":245},"pages.",[200,4867,4868],{"class":214},"${",[200,4870,4871],{"class":210},"language",[200,4873,413],{"class":214},[200,4875,4876],{"class":245},".json",[200,4878,3333],{"class":214},[200,4880,549],{"class":214},[200,4882,280],{"class":214},[200,4884,4885,4888,4890,4893,4895,4898],{"class":202,"line":342},[200,4886,4887],{"class":235},"      baseURL",[200,4889,239],{"class":214},[200,4891,4892],{"class":210}," env",[200,4894,215],{"class":214},[200,4896,4897],{"class":210},"BASE_API_URL",[200,4899,252],{"class":214},[200,4901,4902,4904],{"class":202,"line":365},[200,4903,2111],{"class":214},[200,4905,416],{"class":235},[200,4907,4908,4910],{"class":202,"line":371},[200,4909,3311],{"class":206},[200,4911,280],{"class":214},[200,4913,4914,4916,4918],{"class":202,"line":398},[200,4915,3604],{"class":235},[200,4917,239],{"class":214},[200,4919,1410],{"class":235},[200,4921,4922],{"class":202,"line":405},[200,4923,4924],{"class":214},"        {\n",[200,4926,4927,4930,4932,4934,4936,4938],{"class":202,"line":410},[200,4928,4929],{"class":235},"          type",[200,4931,239],{"class":214},[200,4933,242],{"class":214},[200,4935,697],{"class":245},[200,4937,249],{"class":214},[200,4939,252],{"class":214},[200,4941,4942,4945,4947,4949,4951,4953,4955,4957,4959],{"class":202,"line":860},[200,4943,4944],{"class":235},"          text",[200,4946,239],{"class":214},[200,4948,552],{"class":210},[200,4950,215],{"class":214},[200,4952,557],{"class":223},[200,4954,227],{"class":235},[200,4956,3546],{"class":210},[200,4958,2104],{"class":235},[200,4960,252],{"class":214},[200,4962,4963],{"class":202,"line":889},[200,4964,4965],{"class":214},"        },\n",[200,4967,4968,4971],{"class":202,"line":899},[200,4969,4970],{"class":235},"      ]",[200,4972,252],{"class":214},[200,4974,4975],{"class":202,"line":904},[200,4976,4977],{"class":214},"    }\n",[200,4979,4980],{"class":202,"line":930},[200,4981,368],{"class":214},[200,4983,4984],{"class":202,"line":3655},[200,4985,416],{"class":210},[35,4987,4988],{},"Le fichier JSON lui-même était généré au moment de la construction avec un plugin Vite qui encapsulait la logique suivante :",[192,4990,4992],{"className":194,"code":4991,"language":196,"meta":13,"style":13},"export async function listEnPages(): Promise\u003CMcpGenerator> {\n  const pages = await createContentLoader('**\u002F*.md', {\n    transform: data => data\n      .filter(isContentEn)\n      .map(contentMapper),\n  }).load()\n\n  return {\n    filename: 'pages.en.json',\n    content: pages,\n  }\n}\n",[156,4993,4994,5021,5047,5062,5076,5091,5105,5109,5115,5130,5141,5145],{"__ignoreMap":13},[200,4995,4996,4998,5000,5003,5006,5009,5012,5014,5017,5019],{"class":202,"line":203},[200,4997,1390],{"class":206},[200,4999,380],{"class":379},[200,5001,5002],{"class":379}," function",[200,5004,5005],{"class":223}," listEnPages",[200,5007,5008],{"class":214},"():",[200,5010,5011],{"class":1364}," Promise",[200,5013,599],{"class":214},[200,5015,5016],{"class":1364},"McpGenerator",[200,5018,674],{"class":214},[200,5020,280],{"class":214},[200,5022,5023,5025,5027,5029,5031,5034,5036,5038,5041,5043,5045],{"class":202,"line":14},[200,5024,3738],{"class":379},[200,5026,3252],{"class":210},[200,5028,509],{"class":214},[200,5030,452],{"class":206},[200,5032,5033],{"class":223}," createContentLoader",[200,5035,227],{"class":235},[200,5037,249],{"class":214},[200,5039,5040],{"class":245},"**\u002F*.md",[200,5042,249],{"class":214},[200,5044,549],{"class":214},[200,5046,280],{"class":214},[200,5048,5049,5052,5054,5057,5059],{"class":202,"line":255},[200,5050,5051],{"class":223},"    transform",[200,5053,239],{"class":214},[200,5055,5056],{"class":386}," data",[200,5058,393],{"class":379},[200,5060,5061],{"class":210}," data\n",[200,5063,5064,5067,5069,5071,5074],{"class":202,"line":272},[200,5065,5066],{"class":214},"      .",[200,5068,3773],{"class":223},[200,5070,227],{"class":235},[200,5072,5073],{"class":210},"isContentEn",[200,5075,416],{"class":235},[200,5077,5078,5080,5082,5084,5087,5089],{"class":202,"line":283},[200,5079,5066],{"class":214},[200,5081,3541],{"class":223},[200,5083,227],{"class":235},[200,5085,5086],{"class":210},"contentMapper",[200,5088,2104],{"class":235},[200,5090,252],{"class":214},[200,5092,5093,5096,5098,5100,5103],{"class":202,"line":300},[200,5094,5095],{"class":214},"  }",[200,5097,2104],{"class":235},[200,5099,215],{"class":214},[200,5101,5102],{"class":223},"load",[200,5104,466],{"class":235},[200,5106,5107],{"class":202,"line":310},[200,5108,528],{"emptyLinePlaceholder":20},[200,5110,5111,5113],{"class":202,"line":336},[200,5112,3758],{"class":206},[200,5114,280],{"class":214},[200,5116,5117,5120,5122,5124,5126,5128],{"class":202,"line":342},[200,5118,5119],{"class":235},"    filename",[200,5121,239],{"class":214},[200,5123,242],{"class":214},[200,5125,4726],{"class":245},[200,5127,249],{"class":214},[200,5129,252],{"class":214},[200,5131,5132,5135,5137,5139],{"class":202,"line":365},[200,5133,5134],{"class":235},"    content",[200,5136,239],{"class":214},[200,5138,3252],{"class":210},[200,5140,252],{"class":214},[200,5142,5143],{"class":202,"line":371},[200,5144,4505],{"class":214},[200,5146,5147],{"class":202,"line":398},[200,5148,1129],{"class":214},[35,5150,5151,5152,5155],{},"Je liste tous les fichiers Markdown, je les filtre pour ne conserver que ceux dont j'ai besoin et je les transforme dans la structure JSON correspondante. Le résultat est un nouveau fichier JSON dans le dossier ",[156,5153,5154],{},"dist",", servi comme n'importe quelle autre ressource statique.",[35,5157,5158,5159,5161],{},"Seul l'outil ",[156,5160,3091],{}," est différent. Il récupère le contenu Markdown brut d'une page.",[192,5163,5165],{"className":194,"code":5164,"language":196,"meta":13,"style":13},"this.server.tool(\n  'get_page',\n  '...',\n  {\n    url: z.string().min(1).describe('URL de la page à récupérer (« \u002Fabout » ou « \u002Fcontact », par exemple)'),\n  },\n  async ({ url }) => {\n    if (url === '\u002F') {\n      url = '\u002Findex'\n    }\n\n    if (url === '\u002Ffr\u002F') {\n      url = '\u002Ffr\u002Findex'\n    }\n\n    const result = await ofetch(`pages${url}.md`, {\n      baseURL: env.BASE_API_URL,\n    })\n    return {\n      content: [\n        {\n          type: 'text',\n          text: JSON.stringify(result),\n        },\n      ],\n    }\n  },\n)\n",[156,5166,5167,5179,5189,5199,5203,5245,5249,5263,5285,5298,5302,5306,5327,5340,5344,5348,5381,5395,5401,5407,5415,5419,5433,5453,5457,5463,5467,5472],{"__ignoreMap":13},[200,5168,5169,5171,5173,5175,5177],{"class":202,"line":203},[200,5170,4741],{"class":214},[200,5172,3123],{"class":210},[200,5174,215],{"class":214},[200,5176,504],{"class":223},[200,5178,3130],{"class":210},[200,5180,5181,5183,5185,5187],{"class":202,"line":14},[200,5182,3135],{"class":214},[200,5184,3091],{"class":245},[200,5186,249],{"class":214},[200,5188,252],{"class":214},[200,5190,5191,5193,5195,5197],{"class":202,"line":255},[200,5192,3135],{"class":214},[200,5194,573],{"class":245},[200,5196,249],{"class":214},[200,5198,252],{"class":214},[200,5200,5201],{"class":202,"line":272},[200,5202,3146],{"class":214},[200,5204,5205,5208,5210,5212,5214,5216,5218,5220,5222,5224,5226,5228,5230,5232,5234,5236,5239,5241,5243],{"class":202,"line":283},[200,5206,5207],{"class":235},"    url",[200,5209,239],{"class":214},[200,5211,3180],{"class":210},[200,5213,215],{"class":214},[200,5215,328],{"class":223},[200,5217,1549],{"class":210},[200,5219,215],{"class":214},[200,5221,3198],{"class":223},[200,5223,227],{"class":210},[200,5225,3203],{"class":2060},[200,5227,2104],{"class":210},[200,5229,215],{"class":214},[200,5231,3210],{"class":223},[200,5233,227],{"class":210},[200,5235,249],{"class":214},[200,5237,5238],{"class":245},"URL de la page à récupérer (« \u002Fabout » ou « \u002Fcontact », par exemple)",[200,5240,249],{"class":214},[200,5242,2104],{"class":210},[200,5244,252],{"class":214},[200,5246,5247],{"class":202,"line":300},[200,5248,368],{"class":214},[200,5250,5251,5253,5255,5257,5259,5261],{"class":202,"line":310},[200,5252,3234],{"class":379},[200,5254,383],{"class":214},[200,5256,3988],{"class":386},[200,5258,390],{"class":214},[200,5260,393],{"class":379},[200,5262,280],{"class":214},[200,5264,5265,5268,5270,5272,5274,5276,5278,5280,5283],{"class":202,"line":336},[200,5266,5267],{"class":206},"    if",[200,5269,1094],{"class":235},[200,5271,2035],{"class":210},[200,5273,3298],{"class":214},[200,5275,242],{"class":214},[200,5277,2898],{"class":245},[200,5279,249],{"class":214},[200,5281,5282],{"class":235},") ",[200,5284,230],{"class":214},[200,5286,5287,5289,5291,5293,5296],{"class":202,"line":342},[200,5288,3620],{"class":210},[200,5290,509],{"class":214},[200,5292,242],{"class":214},[200,5294,5295],{"class":245},"\u002Findex",[200,5297,991],{"class":214},[200,5299,5300],{"class":202,"line":365},[200,5301,4977],{"class":214},[200,5303,5304],{"class":202,"line":371},[200,5305,528],{"emptyLinePlaceholder":20},[200,5307,5308,5310,5312,5314,5316,5318,5321,5323,5325],{"class":202,"line":398},[200,5309,5267],{"class":206},[200,5311,1094],{"class":235},[200,5313,2035],{"class":210},[200,5315,3298],{"class":214},[200,5317,242],{"class":214},[200,5319,5320],{"class":245},"\u002Ffr\u002F",[200,5322,249],{"class":214},[200,5324,5282],{"class":235},[200,5326,230],{"class":214},[200,5328,5329,5331,5333,5335,5338],{"class":202,"line":405},[200,5330,3620],{"class":210},[200,5332,509],{"class":214},[200,5334,242],{"class":214},[200,5336,5337],{"class":245},"\u002Ffr\u002Findex",[200,5339,991],{"class":214},[200,5341,5342],{"class":202,"line":410},[200,5343,4977],{"class":214},[200,5345,5346],{"class":202,"line":860},[200,5347,528],{"emptyLinePlaceholder":20},[200,5349,5350,5352,5354,5356,5358,5360,5362,5364,5367,5369,5371,5373,5375,5377,5379],{"class":202,"line":889},[200,5351,2014],{"class":379},[200,5353,3561],{"class":210},[200,5355,509],{"class":214},[200,5357,452],{"class":206},[200,5359,4858],{"class":223},[200,5361,227],{"class":235},[200,5363,3333],{"class":214},[200,5365,5366],{"class":245},"pages",[200,5368,4868],{"class":214},[200,5370,2035],{"class":210},[200,5372,413],{"class":214},[200,5374,3330],{"class":245},[200,5376,3333],{"class":214},[200,5378,549],{"class":214},[200,5380,280],{"class":214},[200,5382,5383,5385,5387,5389,5391,5393],{"class":202,"line":899},[200,5384,4887],{"class":235},[200,5386,239],{"class":214},[200,5388,4892],{"class":210},[200,5390,215],{"class":214},[200,5392,4897],{"class":210},[200,5394,252],{"class":214},[200,5396,5397,5399],{"class":202,"line":904},[200,5398,2111],{"class":214},[200,5400,416],{"class":235},[200,5402,5403,5405],{"class":202,"line":930},[200,5404,3311],{"class":206},[200,5406,280],{"class":214},[200,5408,5409,5411,5413],{"class":202,"line":3655},[200,5410,3604],{"class":235},[200,5412,239],{"class":214},[200,5414,1410],{"class":235},[200,5416,5417],{"class":202,"line":3660},[200,5418,4924],{"class":214},[200,5420,5421,5423,5425,5427,5429,5431],{"class":202,"line":4035},[200,5422,4929],{"class":235},[200,5424,239],{"class":214},[200,5426,242],{"class":214},[200,5428,697],{"class":245},[200,5430,249],{"class":214},[200,5432,252],{"class":214},[200,5434,5435,5437,5439,5441,5443,5445,5447,5449,5451],{"class":202,"line":4490},[200,5436,4944],{"class":235},[200,5438,239],{"class":214},[200,5440,552],{"class":210},[200,5442,215],{"class":214},[200,5444,557],{"class":223},[200,5446,227],{"class":235},[200,5448,3546],{"class":210},[200,5450,2104],{"class":235},[200,5452,252],{"class":214},[200,5454,5455],{"class":202,"line":4496},[200,5456,4965],{"class":214},[200,5458,5459,5461],{"class":202,"line":4502},[200,5460,4970],{"class":235},[200,5462,252],{"class":214},[200,5464,5465],{"class":202,"line":4508},[200,5466,4977],{"class":214},[200,5468,5470],{"class":202,"line":5469},27,[200,5471,368],{"class":214},[200,5473,5475],{"class":202,"line":5474},28,[200,5476,416],{"class":210},[35,5478,5479],{},"Comme tu peux t'en douter, cela fait beaucoup trop d'outils. Il y a deux conséquences directes :",[68,5481,5482,5485],{},[71,5483,5484],{},"trop de descriptions qui polluent le contexte du LLM ;",[71,5486,5487],{},"trop d'outils à gérer efficacement.",[35,5489,5490],{},"Au quotidien, cet ensemble d'outils vaste et redondant compliquait la sélection fiable des outils et leur orchestration par l'agent.",[35,5492,5493],{},"J'ai tiré deux enseignements de cette première expérience :",[68,5495,5496,5499],{},[71,5497,5498],{},"réduire le nombre d'outils au strict minimum, car il vaut mieux ajouter des paramètres à un outil que d'en créer un nouveau ;",[71,5500,5501],{},"rendre l'orchestration des outils compréhensible uniquement à partir de leurs noms et de leurs descriptions.",[35,5503,5504],{},"En juillet 2026, j'ai continué à itérer sur le MCP. J'ai réduit le nombre d'outils à seulement deux :",[68,5506,5507,5512],{},[71,5508,5509,5511],{},[156,5510,3091],{}," ;",[71,5513,5514,215],{},[156,5515,5516],{},"search_pages",[35,5518,5519,5520,5522,5523,5525,5526,5529,5530,5532],{},"L'outil ",[156,5521,3091],{}," était semblable au précédent : il récupérait le contenu Markdown brut d'une page. En revanche, l'outil ",[156,5524,5516],{}," était complètement différent. Il utilisait ",[39,5527,3385],{"href":3383,"rel":5528},[77]," pour permettre à l'agent de rechercher le contenu de mon site en langage naturel. Le résultat comprenait à la fois le nom du fichier et l'extrait de contenu correspondant. L'agent pouvait ensuite appeler ",[156,5531,3091],{}," pour récupérer le contenu complet de la page et répondre à la question. À l'époque, AI Search ne pouvait alimenter son index de recherche qu'à partir de R2. J'ai donc utilisé une GitHub Action et rclone pour remplir un bucket avec le contenu de mon site. L'index de recherche était ainsi reconstruit automatiquement chaque fois que je publiais du nouveau contenu sur mon site.",[35,5534,5535,5536,5538],{},"La suppression de l'outil ",[156,5537,4289],{}," a soulevé beaucoup de questions. Cet outil indiquait à l'agent que le site était multilingue. Mais devons-nous vraiment fournir cette information de cette manière ? Devons-nous ajouter un paramètre à chaque outil pour permettre à l'agent de choisir la langue ? Ou devons-nous toujours fournir les deux langues dans la réponse de l'outil ?",[35,5540,5541],{},"À ce moment-là, j'ai décidé de ne fournir à l'agent que le contenu anglais. Les LLM peuvent traduire ce contenu dans d'autres langues.",[35,5543,5544],{},"J'ai également supprimé les projets et les conférences, car je ne savais pas comment les rendre accessibles à la recherche. À cette époque, mon objectif était de supprimer complètement l'idée d'un outil qui renvoie simplement une liste d'éléments.",[35,5546,5547],{},"Cependant, après quelques jours d'utilisation, j'ai réalisé que cette configuration ne pourrait jamais répondre à une question comme « Quel est le dernier article sur Devoxx France ? », et c'était un véritable problème pour moi.",[35,5549,5550],{},"Il me fallait un moyen d'analyser les métadonnées du contenu pour répondre à cette question, et un outil de liste était l'option la plus évidente. En même temps, je ne voulais pas en ajouter un à cause de tous les problèmes expliqués précédemment. J'étais donc bloqué.",[35,5552,5553,5554,5559,5560,215],{},"Puis, je me suis souvenu du problème de spécification OpenAPI rencontré par Cloudflare lors de ",[39,5555,5558],{"href":5556,"rel":5557},"https:\u002F\u002Fblog.cloudflare.com\u002Fcode-mode\u002F",[77],"l'introduction de son MCP Code Mode",". Une spécification OpenAPI est une liste d'endpoints, tout comme les métadonnées de mon contenu sont une liste de pages. Pourquoi ne pas appliquer la même solution ? Je pouvais fournir à l'agent une liste d'articles et lui permettre d'utiliser du code pour trouver ce dont il avait besoin, en ne renvoyant que les informations pertinentes. C'est ainsi qu'est né l'outil ",[156,5561,5562],{},"list_content",[35,5564,5565,5566,5569,5570,1655,5572,5569,5574,5576],{},"La dernière itération a renommé ",[156,5567,5568],{},"get_content"," en ",[156,5571,3091],{},[156,5573,5562],{},[156,5575,3098],{},". Le résultat est la conception à trois outils décrite au début de cet article.",[51,5578,5580],{"id":5579},"et-maintenant","Et maintenant ?",[35,5582,5583],{},"Aujourd'hui, le MCP ne dispose que de trois outils :",[1593,5585,5586,5591,5596],{},[71,5587,5588,5590],{},[156,5589,3091],{},", pour lire le contenu d'une page ;",[71,5592,5593,5595],{},[156,5594,3095],{},", pour découvrir du contenu en langage naturel ;",[71,5597,5598,5600],{},[156,5599,3098],{},", pour analyser par programmation le contenu disponible sur le site.",[35,5602,5603],{},"Chacun répond à un problème différent et, ensemble, ils permettent à l'agent de répondre à n'importe quelle question sur le contenu de mon site.",[35,5605,5606],{},"C'est vraiment fascinant de voir à quel point des questions qui semblent simples sur le papier peuvent devenir un véritable défi. La plupart des serveurs MCP destinés aux sites orientés contenu, comme les sites de documentation, combinent un outil de liste et un outil de lecture, ou un outil de recherche et un outil de lecture, malgré les comportements différents des outils de liste et de recherche.",[35,5608,5609],{},"Il est maintenant temps d'utiliser mon MCP dans des situations réelles. Je vais créer un chat qui y sera connecté. Il me permettra d'ajuster le prompt système et donnera à tout le monde la possibilité de poser des questions sur le contenu de mon site.",[35,5611,5612],{},"Je créerai ensuite des évaluations pour mesurer l'effet de petites modifications apportées au prompt système ou aux descriptions des outils sur la qualité des réponses.",[1158,5614],{},[35,5616,5617,5618,5621],{},"En fin de compte, pour la documentation destinée aux développeurs, j'ai constaté qu'un fichier ",[156,5619,5620],{},"llms.txt"," bien entretenu peut constituer un point de départ plus simple et plus efficace qu'un MCP.",[1170,5623,5624],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":13,"searchDepth":14,"depth":14,"links":5626},[5627,5628,5629,5630],{"id":3054,"depth":14,"text":3055},{"id":3084,"depth":14,"text":3085},{"id":4271,"depth":14,"text":4272},{"id":5579,"depth":14,"text":5580},"451cb412-c38c-403e-ab6b-049db658b227","Découvrez comment trois outils MCP ciblés permettent de rechercher, lire et analyser le contenu d’un site avec précision et efficacité.",{},"\u002Ffr\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website","---\ncontentId: 451cb412-c38c-403e-ab6b-049db658b227\ntitle: Les trois outils MCP essentiels à un site de contenu\ndescription: Découvrez comment trois outils MCP ciblés permettent de rechercher, lire et analyser le contenu d’un site avec précision et efficacité.\ndate: 2026-08-24\n---\n\nDepuis octobre 2025, je n'arrive pas à me sortir quelques questions de la tête :\n\n- Résume l'article sur Code Mode.\n- Que faut-il retenir de la série sur les agents IA ?\n- Y a-t-il du contenu sur Vite ?\n- Quel est le dernier article sur Devoxx France ?\n\nEn octobre, j'ai publié l'article \u003CLinkToArticle id=\"e258d6e6-2f2f-400d-9b6f-02801a7284db\" \u002F>. J'y expliquais avoir construit un serveur MCP et un agent pour discuter avec le contenu de mon site. J'avais réussi à créer une preuve de concept (POC). C'était la première fois que je créais un tel système et j'étais vraiment impressionné. Cependant, il n'a jamais été prêt pour la production.\n\nCes questions m'ont aidé à itérer sur le MCP jusqu'à ce qu'il devienne efficace et utilisable. Aujourd'hui, je veux partager ce que j'ai appris en construisant un MCP pour un site orienté contenu.\n\n## Les défis pour les agents\n\nPour un humain, ces questions sont relativement simples. Parcourir le site et lire son contenu peut prendre du temps, mais c'est la seule difficulté. Pour un agent, c'est une autre histoire.\n\nUn agent doit :\n\n1. répondre aussi vite que possible pour éviter de faire attendre l'utilisateur trop longtemps ;\n2. répondre aussi précisément que possible pour éviter de frustrer l'utilisateur en ne trouvant pas ce qu'il cherche ;\n3. continuer à répondre efficacement à mesure que le contenu du site augmente ;\n4. utiliser aussi peu de tokens que possible pour éviter de faire payer trop cher une réponse aux utilisateurs ;\n5. fonder ses réponses sur le contenu réel du site et identifier la source afin que les utilisateurs puissent les vérifier.\n\nCela fait beaucoup de contraintes à respecter lors de la conception d'un serveur MCP.\n\n## L'approche actuelle\n\nLa version finale de mon MCP dispose de trois outils : `get_page`, `search_content` et `list_pages`. Chacun répond à un type de question différent. Examinons-les un par un.\n\n> « Résume l'article sur Code Mode. »\n\nPour répondre à cette question, l'agent doit pouvoir lire le contenu de l'article. De nombreux serveurs MCP que j'ai explorés pendant la création du mien exposaient un outil `get_page` précisément dans ce but. Mon outil `get_page` reçoit l'identifiant d'une page et récupère en interne l'URL correspondante pour charger son contenu.\n\n```typescript\nserver.registerTool(\n  'get_page',\n  {\n    description: '...',\n    inputSchema: {\n      id: z.string().trim().min(1).describe('Identifiant de contenu exact et globalement unique renvoyé par search_content ou list_pages.')\n    },\n  },\n  async ({ id }) => {\n    const pages = await loadPages()\n\n    const page = pages.find(page => page.id === id)\n\n    return fetch(`${page.url}.md`)\n  },\n)\n```\n\n> [!NOTE]\n> Il ne s'agit pas de la véritable implémentation de l'outil `get_page`. C'est une version simplifiée à l'extrême pour illustrer l'idée. Pour voir l'implémentation complète, consulte \u003CGitHubLink repo=\"barbapapazes\u002Fmcp.soubiran.dev\" \u002F>.\n\nCela couvre la lecture d'une page connue. Reste une question : comment l'agent connaît-il son identifiant ?\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website\u002Fsummarize-the-article-about-code-mode.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Résumé de l'article sur Code Mode\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n> « Y a-t-il du contenu sur Vite ? »\n\nPour répondre à cette question, l'agent doit pouvoir rechercher dans l'ensemble du contenu, y compris les titres, les descriptions et le corps du texte. Nous avons donc besoin d'un outil `search_content`. Cet outil reçoit une requête en paramètre et utilise en interne la recherche sémantique et la recherche par mots-clés pour retrouver le contenu correspondant. Cela repose sur [Cloudflare AI Search](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002F).\n\n```typescript\nserver.registerTool(\n  'search_content',\n  {\n    description: '...',\n    inputSchema: {\n      query: z.string().trim().min(1).describe('Requête pour rechercher du contenu.')\n    },\n  },\n  async ({ query }) => {\n    const results = await searchContent(query)\n\n    return results.map(result => ({\n      id: result.id,\n      title: result.title,\n      description: result.description,\n      content: result.chunk,\n      url: result.url,\n      date: result.date,\n    }))\n  },\n)\n```\n\n> [!NOTE]\n> Il ne s'agit pas de la véritable implémentation de l'outil `search_content`. C'est une version simplifiée à l'extrême pour illustrer l'idée. Pour voir l'implémentation complète, consulte \u003CGitHubLink repo=\"barbapapazes\u002Fmcp.soubiran.dev\" \u002F>.\n\nAvec une requête bien choisie, cet outil pourrait également aider à répondre à la première question. Grâce à la recherche par mots-clés, l'agent pourrait rechercher « Code Mode » et récupérer l'identifiant de la page correspondante. Cependant, cette méthode n'est pas assez fiable à elle seule : une requête peut être ambiguë ou ne pas classer la page attendue en première position.\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website\u002Fis-there-content-about-vite.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Y a-t-il du contenu sur Vite ?\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n> « Quel est le dernier article sur Devoxx France ? »\n\nCette question est plus complexe. Elle nécessite de comparer les dates des articles. Pour identifier efficacement le dernier article correspondant, l'agent doit pouvoir accéder aux métadonnées du contenu et les analyser avec du code. Sans cette possibilité, il devrait récupérer la liste complète et effectuer lui-même la comparaison.\n\nCela pourrait fonctionner, mais ce n'est pas efficace. La liste complète du contenu de mon site compte 203 535 caractères, soit environ 60 000 tokens. Bien sûr, elle tiendrait dans la fenêtre de contexte de la plupart des modèles actuels, mais cela consommerait du temps et des tokens sans aucun bénéfice. Cela polluerait également le contexte et compliquerait la recherche d'informations pertinentes par l'agent.\n\nMalgré cela, j'ai tout de même décidé de créer l'outil `list_pages`. Cependant, il ne fonctionne peut-être pas comme tu l'imagines.\n\nL'outil reçoit un paramètre `code`. Il permet à l'agent d'écrire du JavaScript à partir de données typées pour filtrer, trier et transformer précisément les informations dont il a besoin. Les [Workers dynamiques de Cloudflare](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fdynamic-workers\u002F) exécutent le code dans un environnement léger, sécurisé et isolé.\n\nPar exemple, l'agent pourrait écrire le code suivant pour récupérer le dernier article sur Devoxx France :\n\n```typescript\nasync () => {\n  const query = 'devoxx france'\n\n  return pages.data\n    .filter(page => page.type === 'post')\n    .filter(page =>\n      `${page.title} ${page.description ?? ''}`\n        .toLowerCase()\n        .includes(query),\n    )\n    .sort((a, b) =>\n      (b.date ?? '').localeCompare(a.date ?? ''),\n    )\n    .slice(0, 1)\n    .map(({ id, title, description, date, url }) => ({\n      id,\n      title,\n      description,\n      date,\n      url,\n    }))\n}\n```\n\n> [!NOTE]\n> Pour comprendre ce qu'est Code Mode, lis [Code Mode, deux outils et un MCP pour sauver le contexte de ton LLM](.\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context.md).\n\nEn coulisses, l'outil ressemble à ceci. Garde à l'esprit que la description est l'un des éléments intéressants d'un outil Code Mode.\n\n```typescript\nserver.registerTool(\n  'list_pages',\n  {\n    description: '...',\n    inputSchema: {\n      code: z.string().trim().min(1).max(20_000).describe('Une fonction fléchée JavaScript asynchrone avec des variables globales pages, talks et infra en lecture seule.'),\n    },\n  },\n  async ({ code }) => {\n    const result = await executeCode(code)\n\n    return result\n  },\n)\n```\n\n> [!NOTE]\n> Il ne s'agit pas de la véritable implémentation de l'outil `list_pages`. C'est une version simplifiée à l'extrême pour illustrer l'idée. Pour voir l'implémentation complète, consulte \u003CGitHubLink repo=\"barbapapazes\u002Fmcp.soubiran.dev\" \u002F>.\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website\u002Fwhat-s-the-latest-article-about-devoxx-france.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Quel est le dernier article sur Devoxx France ?\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nCette conception respecte-t-elle les contraintes définies au début ? Oui.\n\nJ'ai beaucoup appris en concevant ce MCP.\n\n- Réduis autant que possible le nombre d'outils. Utilise des paramètres pour ajouter de la flexibilité à un outil au lieu d'en créer un nouveau. J'aime beaucoup l'approche de GitHub pour son MCP ;\n- Utilise tes outils manuellement pour vérifier s'ils peuvent répondre à tes questions. Si ce n'est pas le cas, améliore-les jusqu'à ce qu'ils le puissent ;\n- Garde des outils au périmètre suffisamment large, mais assez spécialisés pour éviter qu'ils se marchent sur les pieds ;\n- Réduis la quantité d'informations fournies à l'agent. Moins il a de contenu à lire, plus il sera performant ;\n- Parfois, l'agent doit orchestrer lui-même les outils.\n\nJe sais que cela représente beaucoup de technologies, AI Search et Dynamic Workers, rien que pour construire un serveur MCP, mais la différence sur la qualité des réponses est réelle. Un MCP incapable de répondre aux questions des utilisateurs a une valeur limitée. Si tu veux construire un MCP pour ton site orienté contenu, j'espère que cet article t'aidera à éviter les erreurs que j'ai commises et à en construire un meilleur.\n\n## Comment je suis arrivé à trois outils\n\nJ'ai créé la première version du MCP en octobre 2025. C'était le deuxième MCP que je créais ; j'avais utilisé le premier pour explorer le concept dans \u003CLinkToArticle id=\"4ff51ee2-926d-4924-86f5-97cd9e378fa7\" \u002F>. Je n'avais aucune idée de la manière de l'architecturer, alors j'ai créé tout ce qui me passait par la tête.\n\nAu final, j'ai créé 10 outils rien que pour le contenu de mon site principal :\n\n- `list_languages`\n\n  ```txt\n  Renvoie un tableau JSON exploitable par une machine contenant toutes les langues prises en charge par le site d'Estéban. Chaque objet comprend un « code » (ISO 639-1) et un « name » (nom en anglais). Exemple de réponse : [{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"fr\",\"name\":\"French\"}].\n  ```\n\n- `list_parts`\n\n  ```txt\n  Renvoie un tableau JSON exploitable par une machine contenant toutes les parties (sections) disponibles sur le site d'Estéban. Chaque objet comprend un « id » (chaîne de caractères), un « name » (chaîne de caractères) et une « description » (chaîne de caractères). Exemple de réponse : [{\"id\":\"pages\",\"name\":\"Pages\",\"description\":\"All website pages available.\"},{\"id\":\"blog\",\"name\":\"Blog\",\"description\":\"All blog posts available.\"}].\n  ```\n\n- `list_pages`\n\n  ```txt\n  Renvoie la liste de toutes les pages disponibles sur le site d'Estéban dans une langue donnée. Chaque page comprend son titre, sa description, son URL et sa date. Utilise le paramètre « language » pour sélectionner la langue (« en » pour l'anglais ou « fr » pour le français, par exemple). La réponse est un tableau JSON d'objets : [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n  ```\n\n- `list_posts`\n\n  ```txt\n  Renvoie la liste de tous les articles disponibles sur le site d'Estéban dans une langue donnée. Chaque article comprend son titre, sa description, son URL et sa date. Utilise le paramètre « language » pour sélectionner la langue (« en » pour l'anglais ou « fr » pour le français, par exemple). La réponse est un tableau JSON d'objets : [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n  ```\n\n- `list_series`\n\n  ```txt\n  Renvoie la liste de toutes les séries disponibles sur le site d'Estéban dans une langue donnée. Chaque série comprend son titre, sa description, son URL et sa date. Utilise le paramètre « language » pour sélectionner la langue (« en » pour l'anglais ou « fr » pour le français, par exemple). La réponse est un tableau JSON d'objets : [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n  ```\n\n- `list_series_articles`\n\n  ```txt\n  Renvoie la liste de tous les articles d'une série donnée sur le site d'Estéban dans une langue donnée. Chaque article comprend son titre, sa description, son URL et sa date. Utilise le paramètre « language » pour sélectionner la langue (« en » pour l'anglais ou « fr » pour le français, par exemple) et le paramètre « series » pour indiquer l'URI de la série. La réponse est un tableau JSON d'objets : [{ \"title\": string, \"description\": string, \"url\": string, \"uri\": string, \"date\": string }].\n  ```\n\n- `list_projects`\n\n  ```txt\n  Renvoie un tableau JSON exploitable par une machine contenant toutes les catégories de projets d'Estéban, chacune avec un « title » (nom de la catégorie) et un tableau « projects ».\n  Chaque projet comprend :\n  - « name » (chaîne de caractères, par exemple « barbapapazes\u002Fcode.soubiran.dev »),\n  - « description » (chaîne de caractères),\n  - « stars » (nombre),\n  - « updatedAt » (chaîne de caractères ISO 8601),\n  - « topics » (tableau de chaînes de caractères),\n  - « url » (chaîne de caractères),\n  - « license » (chaîne de caractères, facultative).\n\n  Exemple de réponse :\n  [\n    {\n      \"title\": \"Ecosystem\",\n      \"projects\": [\n        {\n          \"name\": \"barbapapazes\u002Fcode.soubiran.dev\",\n          \"description\": \"Create beautiful images from code.\",\n          \"stars\": 3,\n          \"updatedAt\": \"2025-03-16T21:16:15Z\",\n          \"topics\": [\"code\", \"vue\"],\n          \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcode.soubiran.dev\"\n        }\n      ]\n    }\n  ]\n  ```\n\n- `list_talks`\n\n  ```txt\n  Renvoie un tableau JSON exploitable par une machine contenant toutes les conférences données par Estéban Soubiran.\n  Chaque conférence comprend :\n  - « name » (titre de la conférence, chaîne de caractères)\n  - « event » (nom de l'événement, chaîne de caractères)\n  - « date » (date ISO 8601, chaîne de caractères)\n  - « url » (URL principale de la conférence, chaîne de caractères)\n  - « pdf_url » (URL PDF des diapositives, chaîne de caractères, facultative)\n  - « thumbnail_url » (URL de la miniature, chaîne de caractères, facultative)\n  - « github_url » (dépôt GitHub, chaîne de caractères, facultative)\n  - « recording_url » (enregistrement vidéo, chaîne de caractères, facultative)\n\n  Exemple de réponse :\n  [\n    {\n      \"name\": \"Unpoly pour reprendre le contrôle !\",\n      \"event\": \"Devoxx France\",\n      \"date\": \"2023-04-12\",\n      \"url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\",\n      \"pdf_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Fpdf\",\n      \"thumbnail_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Fthumbnail.png\",\n      \"github_url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ftalks\u002Ftree\u002Fmain\u002F2023-04-12\",\n      \"recording_url\": \"https:\u002F\u002Ftalks.soubiran.dev\u002F2023-04-12\u002Fdevoxxfr\u002Frecording\"\n    }\n  ]\n  ```\n\n- `list_socials`\n\n  ```txt\n  Renvoie un tableau JSON exploitable par une machine contenant tous les profils d'Estéban sur les réseaux sociaux.\n  Chaque profil comprend :\n  - « name » (nom de la plateforme, chaîne de caractères, par exemple « Twitter »)\n  - « url » (URL du profil, chaîne de caractères)\n\n  Exemple de réponse :\n  [\n    { \"name\": \"Twitter\", \"url\": \"https:\u002F\u002Ftwitter.com\u002Festebansoubiran\" },\n    { \"name\": \"GitHub\", \"url\": \"https:\u002F\u002Fgithub.com\u002FBarbapapazes\" }\n  ]\n  ```\n\n- `get_page`\n\n  ```txt\n  Récupère une page donnée du site d'Estéban. La réponse contient le contenu Markdown de la page.\n  ```\n\n\u003CGitHubLink repo=\"barbapapazes\u002Fmcp.soubiran.dev\u002Fcommit\u002F8c544b5adde3450d18b7b6d13b5927030054be7f\" \u002F>\n\nLa plupart de ces outils suivent le même modèle.\n\n[Mon site repose sur VitePress](.\u002Fvitepress-for-a-personal-website-is-that-possible.md) et génère chaque page de manière statique. J'en ai profité pour générer au moment de la construction de nombreux fichiers JSON contenant les informations dont j'ai besoin. Chaque outil de liste effectue ensuite une simple requête HTTP pour récupérer le fichier JSON correspondant.\n\nPar exemple, l'outil `list_pages` récupère un fichier JSON nommé `pages.en.json`, ou `pages.fr.json` selon la langue demandée dans le paramètre, puis renvoie son contenu à l'agent :\n\n```typescript\nthis.server.tool(\n  'list_pages',\n  '...',\n  {\n    language: z.string().min(2).max(2).describe('Code de langue des pages de contenu (« en » ou « fr », par exemple)'),\n  },\n  async ({ language }) => {\n    const result = await ofetch(`pages.${language}.json`, {\n      baseURL: env.BASE_API_URL,\n    })\n    return {\n      content: [\n        {\n          type: 'text',\n          text: JSON.stringify(result),\n        },\n      ],\n    }\n  },\n)\n```\n\nLe fichier JSON lui-même était généré au moment de la construction avec un plugin Vite qui encapsulait la logique suivante :\n\n```typescript\nexport async function listEnPages(): Promise\u003CMcpGenerator> {\n  const pages = await createContentLoader('**\u002F*.md', {\n    transform: data => data\n      .filter(isContentEn)\n      .map(contentMapper),\n  }).load()\n\n  return {\n    filename: 'pages.en.json',\n    content: pages,\n  }\n}\n```\n\nJe liste tous les fichiers Markdown, je les filtre pour ne conserver que ceux dont j'ai besoin et je les transforme dans la structure JSON correspondante. Le résultat est un nouveau fichier JSON dans le dossier `dist`, servi comme n'importe quelle autre ressource statique.\n\nSeul l'outil `get_page` est différent. Il récupère le contenu Markdown brut d'une page.\n\n```typescript\nthis.server.tool(\n  'get_page',\n  '...',\n  {\n    url: z.string().min(1).describe('URL de la page à récupérer (« \u002Fabout » ou « \u002Fcontact », par exemple)'),\n  },\n  async ({ url }) => {\n    if (url === '\u002F') {\n      url = '\u002Findex'\n    }\n\n    if (url === '\u002Ffr\u002F') {\n      url = '\u002Ffr\u002Findex'\n    }\n\n    const result = await ofetch(`pages${url}.md`, {\n      baseURL: env.BASE_API_URL,\n    })\n    return {\n      content: [\n        {\n          type: 'text',\n          text: JSON.stringify(result),\n        },\n      ],\n    }\n  },\n)\n```\n\nComme tu peux t'en douter, cela fait beaucoup trop d'outils. Il y a deux conséquences directes :\n\n1. trop de descriptions qui polluent le contexte du LLM ;\n2. trop d'outils à gérer efficacement.\n\nAu quotidien, cet ensemble d'outils vaste et redondant compliquait la sélection fiable des outils et leur orchestration par l'agent.\n\nJ'ai tiré deux enseignements de cette première expérience :\n\n1. réduire le nombre d'outils au strict minimum, car il vaut mieux ajouter des paramètres à un outil que d'en créer un nouveau ;\n2. rendre l'orchestration des outils compréhensible uniquement à partir de leurs noms et de leurs descriptions.\n\nEn juillet 2026, j'ai continué à itérer sur le MCP. J'ai réduit le nombre d'outils à seulement deux :\n\n1. `get_page` ;\n2. `search_pages`.\n\nL'outil `get_page` était semblable au précédent : il récupérait le contenu Markdown brut d'une page. En revanche, l'outil `search_pages` était complètement différent. Il utilisait [Cloudflare AI Search](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fai-search\u002F) pour permettre à l'agent de rechercher le contenu de mon site en langage naturel. Le résultat comprenait à la fois le nom du fichier et l'extrait de contenu correspondant. L'agent pouvait ensuite appeler `get_page` pour récupérer le contenu complet de la page et répondre à la question. À l'époque, AI Search ne pouvait alimenter son index de recherche qu'à partir de R2. J'ai donc utilisé une GitHub Action et rclone pour remplir un bucket avec le contenu de mon site. L'index de recherche était ainsi reconstruit automatiquement chaque fois que je publiais du nouveau contenu sur mon site.\n\nLa suppression de l'outil `list_languages` a soulevé beaucoup de questions. Cet outil indiquait à l'agent que le site était multilingue. Mais devons-nous vraiment fournir cette information de cette manière ? Devons-nous ajouter un paramètre à chaque outil pour permettre à l'agent de choisir la langue ? Ou devons-nous toujours fournir les deux langues dans la réponse de l'outil ?\n\nÀ ce moment-là, j'ai décidé de ne fournir à l'agent que le contenu anglais. Les LLM peuvent traduire ce contenu dans d'autres langues.\n\nJ'ai également supprimé les projets et les conférences, car je ne savais pas comment les rendre accessibles à la recherche. À cette époque, mon objectif était de supprimer complètement l'idée d'un outil qui renvoie simplement une liste d'éléments.\n\nCependant, après quelques jours d'utilisation, j'ai réalisé que cette configuration ne pourrait jamais répondre à une question comme « Quel est le dernier article sur Devoxx France ? », et c'était un véritable problème pour moi.\n\nIl me fallait un moyen d'analyser les métadonnées du contenu pour répondre à cette question, et un outil de liste était l'option la plus évidente. En même temps, je ne voulais pas en ajouter un à cause de tous les problèmes expliqués précédemment. J'étais donc bloqué.\n\nPuis, je me suis souvenu du problème de spécification OpenAPI rencontré par Cloudflare lors de [l'introduction de son MCP Code Mode](https:\u002F\u002Fblog.cloudflare.com\u002Fcode-mode\u002F). Une spécification OpenAPI est une liste d'endpoints, tout comme les métadonnées de mon contenu sont une liste de pages. Pourquoi ne pas appliquer la même solution ? Je pouvais fournir à l'agent une liste d'articles et lui permettre d'utiliser du code pour trouver ce dont il avait besoin, en ne renvoyant que les informations pertinentes. C'est ainsi qu'est né l'outil `list_content`.\n\nLa dernière itération a renommé `get_content` en `get_page` et `list_content` en `list_pages`. Le résultat est la conception à trois outils décrite au début de cet article.\n\n## Et maintenant ?\n\nAujourd'hui, le MCP ne dispose que de trois outils :\n\n- `get_page`, pour lire le contenu d'une page ;\n- `search_content`, pour découvrir du contenu en langage naturel ;\n- `list_pages`, pour analyser par programmation le contenu disponible sur le site.\n\nChacun répond à un problème différent et, ensemble, ils permettent à l'agent de répondre à n'importe quelle question sur le contenu de mon site.\n\nC'est vraiment fascinant de voir à quel point des questions qui semblent simples sur le papier peuvent devenir un véritable défi. La plupart des serveurs MCP destinés aux sites orientés contenu, comme les sites de documentation, combinent un outil de liste et un outil de lecture, ou un outil de recherche et un outil de lecture, malgré les comportements différents des outils de liste et de recherche.\n\nIl est maintenant temps d'utiliser mon MCP dans des situations réelles. Je vais créer un chat qui y sera connecté. Il me permettra d'ajuster le prompt système et donnera à tout le monde la possibilité de poser des questions sur le contenu de mon site.\n\nJe créerai ensuite des évaluations pour mesurer l'effet de petites modifications apportées au prompt système ou aux descriptions des outils sur la qualité des réponses.\n\n---\n\nEn fin de compte, pour la documentation destinée aux développeurs, j'ai constaté qu'un fichier `llms.txt` bien entretenu peut constituer un point de départ plus simple et plus efficace qu'un MCP.\n",{"title":3021,"description":5632},{"loc":5634},"fr\u002Fposts\u002Fthe-three-mcp-tools-you-need-for-a-content-oriented-website","fwRyMUGshdYXCHjnRK3JMYCj72Q8Sc31NHfGYmIfZE0",{"id":5641,"title":5642,"body":5643,"contentId":5742,"date":5743,"description":5744,"extension":18,"meta":5745,"navigation":20,"path":5746,"rawbody":5747,"robots":23,"schemaOrg":23,"seo":5748,"sitemap":5749,"stem":5750,"__hash__":5751},"posts\u002Ffr\u002Fposts\u002Fwelcome-to-you-can-just-do-things-week-we-must-ship.md","Bienvenue à la semaine où l'on peut simplement agir",{"type":7,"value":5644,"toc":5738},[5645,5648,5651,5655,5664,5671,5674,5677,5680,5683,5686,5690,5693,5702,5711,5725,5727,5730,5732,5735],[35,5646,5647],{},"Commencer quelque chose n'a jamais été aussi facile.",[35,5649,5650],{},"Cependant, terminer quelque chose et le publier, c'est une autre histoire. J'ai arrêté de compter toutes les idées, tous les projets, toutes les expérimentations et tous les textes que j'ai commencés sans jamais les terminer. Parfois, il ne manquait que le clic sur le bouton \"Publier\". D'autres fois, une nouvelle idée surgissait, déplaçait complètement mon attention, ou je ne trouvais jamais l'énergie nécessaire pour les derniers 20 % du travail.",[51,5652,5654],{"id":5653},"la-semaine-you-can-just-do-things","La semaine \"You Can Just Do Things\"",[35,5656,5657,5658,5663],{},"Du 24 au 28 août, j'ai décidé de publier quelque chose chaque jour. Attendez-vous à des modules Nuxt, des plugins Vite, des intégrations de recherche avec l'IA, quelque chose construit avec ",[39,5659,5662],{"href":5660,"rel":5661},"https:\u002F\u002Fflueframework.com\u002F",[77],"Flue",", une série d'apprentissage, des compétences pour agents, et bien d'autres choses sur lesquelles je travaille depuis ces derniers mois.",[35,5665,5666,5667],{},"J'ai récemment donné un talk sur l'agentivité, l'idée que vous n'avez pas besoin de permission pour faire quelque chose, créer et prendre le contrôle de votre vie. L'année dernière, j'en ai aussi parlé dans ",[3045,5668,5670],{"id":5669},"1cfb53a4-a252-4f64-b569-9a07dbf94e3e"," et, depuis, j'essaie de la mettre en pratique chaque jour. Cette semaine est comme la consécration de cet état d'esprit.",[35,5672,5673],{},"\"You can just do things\" est au cœur de l'agentivité. C'est désormais gravé dans mon esprit. Honnêtement, c'est comme un mode de vie.",[35,5675,5676],{},"J'ai une étoile polaire qui m'aide à choisir sur quoi travailler, mais je suis aussi attiré par les opportunités. Certaines idées n'ont de sens qu'à une période précise et, si je ne les saisis pas, je risque de manquer l'occasion. L'équilibre est difficile à trouver : rester en mouvement ne doit pas vouloir dire abandonner chaque projet, mais notre temps est limité. Cet engagement public m'aide à rester concentré, organisé et discipliné, tout en me laissant faire des projets juste pour le plaisir.",[35,5678,5679],{},"Ne vous méprenez pas, je ne veux pas tout garder secret jusqu'à avoir assez de contenu pour composer une semaine complète de lancements. Je publie tout ce que je peux, quand je trouve le temps et quand j'estime que cela en vaut la peine. Cette fois, c'est différent : j'ai commencé trop de choses, et je dois les terminer.",[35,5681,5682],{},"Je suis tellement enthousiaste à l'idée de cette semaine. J'ai tant de choses que je veux partager avec vous !",[35,5684,5685],{},"Suivez tout cela ici du 24 au 28 août. Je publierai quelque chose chaque jour, et j'espère que vous serez au rendez-vous !",[51,5687,5689],{"id":5688},"celles-et-ceux-qui-minspirent","Celles et ceux qui m'inspirent",[35,5691,5692],{},"L'idée d'une semaine d'innovation ne vient pas de moi. Je l'ai empruntée à Cloudflare.",[35,5694,5695,5696,5701],{},"Chaque année, et plusieurs fois par an, Cloudflare organise des semaines pour publier. Début août, l'entreprise a organisé une Agents Week et ",[39,5697,5700],{"href":5698,"rel":5699},"https:\u002F\u002Fblog.cloudflare.com\u002Fagents-week-review-august-2026\u002F",[77],"elle a vraiment publié",". Elle organise aussi une semaine d'anniversaire que je suis attentivement chaque année depuis quelques années, même si c'est parfois difficile à suivre. J'adore l'idée de consacrer une semaine à publier, car cela force à communiquer clairement sur ce qui est lancé et à en écrire.",[35,5703,5704,5705,5710],{},"\"We must ship\" est le mantra de Taylor Otwell, le créateur de Laravel. Il l'a dit pendant ",[39,5706,5709],{"href":5707,"rel":5708},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NTMDzGLs8pw",[77],"sa keynote de Laracon EU 2025",", avec l'idée que le fait d'avoir terminé vaut mieux que la perfection. Cela résonne beaucoup en moi et j'essaie de l'appliquer dans ma vie quotidienne, à l'image de l'état d'esprit d'agentivité.",[35,5712,5713,5714,5719,5720,5724],{},"Enfin, \"You can just do things\" est une devise qu'Aaron Francis promeut beaucoup sur Twitter et dont il a parlé pendant ",[39,5715,5718],{"href":5716,"rel":5717},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=dgr-WAUgELw",[77],"la keynote d'introduction de Laracon US 2025",", ou encore lors d'Epic Web dans un talk intitulé ",[39,5721,5723],{"href":1327,"rel":5722},[77],"Epic Bets: Practical Tips For Betting on Yourself",". J'adore cet état d'esprit et la manière dont il encourage les gens à simplement agir.",[1158,5726],{},[35,5728,5729],{},"Je suis seul à la barre de mon navire, alors cette semaine sera plus petite que celle de Cloudflare, mais pas moins personnelle. Je suis fier de ce que j'ai construit jusqu'ici, et j'espère que cela vous plaira.",[1158,5731],{},[35,5733,5734],{},"Cela pourrait être un événement unique.",[35,5736,5737],{},"Ou peut-être que je le relancerai dans 16 mois.",{"title":13,"searchDepth":14,"depth":14,"links":5739},[5740,5741],{"id":5653,"depth":14,"text":5654},{"id":5688,"depth":14,"text":5689},"6d50cc32-a294-4e17-9423-61371066bcb4","2026-08-19","Du 24 au 28 août, je publierai quelque chose chaque jour : articles, expériences, outils et projets que je souhaite partager depuis longtemps.",{},"\u002Ffr\u002Fposts\u002Fwelcome-to-you-can-just-do-things-week-we-must-ship","---\ncontentId: 6d50cc32-a294-4e17-9423-61371066bcb4\ntitle: \"Bienvenue à la semaine où l'on peut simplement agir\"\ndescription: \"Du 24 au 28 août, je publierai quelque chose chaque jour : articles, expériences, outils et projets que je souhaite partager depuis longtemps.\"\ndate: 2026-08-19\n---\n\nCommencer quelque chose n'a jamais été aussi facile.\n\nCependant, terminer quelque chose et le publier, c'est une autre histoire. J'ai arrêté de compter toutes les idées, tous les projets, toutes les expérimentations et tous les textes que j'ai commencés sans jamais les terminer. Parfois, il ne manquait que le clic sur le bouton \"Publier\". D'autres fois, une nouvelle idée surgissait, déplaçait complètement mon attention, ou je ne trouvais jamais l'énergie nécessaire pour les derniers 20 % du travail.\n\n## La semaine \"You Can Just Do Things\"\n\nDu 24 au 28 août, j'ai décidé de publier quelque chose chaque jour. Attendez-vous à des modules Nuxt, des plugins Vite, des intégrations de recherche avec l'IA, quelque chose construit avec [Flue](https:\u002F\u002Fflueframework.com\u002F), une série d'apprentissage, des compétences pour agents, et bien d'autres choses sur lesquelles je travaille depuis ces derniers mois.\n\nJ'ai récemment donné un talk sur l'agentivité, l'idée que vous n'avez pas besoin de permission pour faire quelque chose, créer et prendre le contrôle de votre vie. L'année dernière, j'en ai aussi parlé dans \u003CLinkToArticle id=\"1cfb53a4-a252-4f64-b569-9a07dbf94e3e\" \u002F> et, depuis, j'essaie de la mettre en pratique chaque jour. Cette semaine est comme la consécration de cet état d'esprit.\n\n\"You can just do things\" est au cœur de l'agentivité. C'est désormais gravé dans mon esprit. Honnêtement, c'est comme un mode de vie.\n\nJ'ai une étoile polaire qui m'aide à choisir sur quoi travailler, mais je suis aussi attiré par les opportunités. Certaines idées n'ont de sens qu'à une période précise et, si je ne les saisis pas, je risque de manquer l'occasion. L'équilibre est difficile à trouver : rester en mouvement ne doit pas vouloir dire abandonner chaque projet, mais notre temps est limité. Cet engagement public m'aide à rester concentré, organisé et discipliné, tout en me laissant faire des projets juste pour le plaisir.\n\nNe vous méprenez pas, je ne veux pas tout garder secret jusqu'à avoir assez de contenu pour composer une semaine complète de lancements. Je publie tout ce que je peux, quand je trouve le temps et quand j'estime que cela en vaut la peine. Cette fois, c'est différent : j'ai commencé trop de choses, et je dois les terminer.\n\nJe suis tellement enthousiaste à l'idée de cette semaine. J'ai tant de choses que je veux partager avec vous !\n\nSuivez tout cela ici du 24 au 28 août. Je publierai quelque chose chaque jour, et j'espère que vous serez au rendez-vous !\n\n## Celles et ceux qui m'inspirent\n\nL'idée d'une semaine d'innovation ne vient pas de moi. Je l'ai empruntée à Cloudflare.\n\nChaque année, et plusieurs fois par an, Cloudflare organise des semaines pour publier. Début août, l'entreprise a organisé une Agents Week et [elle a vraiment publié](https:\u002F\u002Fblog.cloudflare.com\u002Fagents-week-review-august-2026\u002F). Elle organise aussi une semaine d'anniversaire que je suis attentivement chaque année depuis quelques années, même si c'est parfois difficile à suivre. J'adore l'idée de consacrer une semaine à publier, car cela force à communiquer clairement sur ce qui est lancé et à en écrire.\n\n\"We must ship\" est le mantra de Taylor Otwell, le créateur de Laravel. Il l'a dit pendant [sa keynote de Laracon EU 2025](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NTMDzGLs8pw), avec l'idée que le fait d'avoir terminé vaut mieux que la perfection. Cela résonne beaucoup en moi et j'essaie de l'appliquer dans ma vie quotidienne, à l'image de l'état d'esprit d'agentivité.\n\nEnfin, \"You can just do things\" est une devise qu'Aaron Francis promeut beaucoup sur Twitter et dont il a parlé pendant [la keynote d'introduction de Laracon US 2025](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=dgr-WAUgELw), ou encore lors d'Epic Web dans un talk intitulé [Epic Bets: Practical Tips For Betting on Yourself](https:\u002F\u002Fwww.epicweb.dev\u002Ftalks\u002Fepic-bets-practical-tips-for-betting-on-yourself). J'adore cet état d'esprit et la manière dont il encourage les gens à simplement agir.\n\n---\n\nJe suis seul à la barre de mon navire, alors cette semaine sera plus petite que celle de Cloudflare, mais pas moins personnelle. Je suis fier de ce que j'ai construit jusqu'ici, et j'espère que cela vous plaira.\n\n---\n\nCela pourrait être un événement unique.\n\nOu peut-être que je le relancerai dans 16 mois.\n",{"title":5642,"description":5744},{"loc":5746},"fr\u002Fposts\u002Fwelcome-to-you-can-just-do-things-week-we-must-ship","IF2o92ZQyNRtVq4khFvfopWYTHjK-w8f6YrXXgXgs74",{"id":5753,"title":5754,"body":5755,"contentId":6793,"date":6794,"description":6795,"extension":18,"meta":6796,"navigation":20,"path":6797,"rawbody":6798,"robots":23,"schemaOrg":23,"seo":6799,"sitemap":6800,"stem":6801,"__hash__":6802},"posts\u002Ffr\u002Fposts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context.md","Code Mode, deux outils et un MCP pour sauver le contexte",{"type":7,"value":5756,"toc":6790},[5757,5760,5763,5766,5769,5773,5776,5785,5802,5805,6787],[35,5758,5759],{},"L'un des outils que je construis est un serveur MCP personnalisé, basé sur l'idée de Code Mode de Cloudflare, qui me permet d'interagir avec les services locaux que je développe via leurs API comme un client local, sans avoir à le faire moi-même.",[35,5761,5762],{},"Il fonctionne bien mieux que ce à quoi je m'attendais. J'ai été à la fois impressionné et enthousiasmé par les possibilités offertes par cette approche. Mais je me suis heurté à un mur : absolument personne parmi les personnes à qui j'en ai parlé ne partageait mon enthousiasme ou n'avait une idée de la puissance que pouvait avoir cette nouvelle approche.",[35,5764,5765],{},"J'ai donc fait ce que je sais faire de mieux : j'ai écrit à ce sujet.",[35,5767,5768],{},"Cet article est le résultat de cette réflexion.",[51,5770,5772],{"id":5771},"le-contexte-cest-tout","Le contexte, c'est tout",[35,5774,5775],{},"L'une des choses auxquelles nous devons accorder le plus d'importance lorsque nous utilisons des LLM est le contexte. Le contexte est cette fenêtre de texte que le LLM voit et utilise pour générer sa réponse, alimentée par le fournisseur, l'utilisateur et le modèle lui-même.",[35,5777,5778,5779,5784],{},"La ",[39,5780,5783],{"href":5781,"rel":5782},"https:\u002F\u002Fmodel-spec.openai.com\u002F2025-02-12.html",[77],"spécification des modèles d'OpenAI"," définit cinq rôles qui peuvent être utilisés pour remplir le contexte :",[1593,5786,5787,5790,5793,5796,5799],{},[71,5788,5789],{},"system : messages ajoutés par OpenAI",[71,5791,5792],{},"developer : messages provenant du développeur de l'application (et éventuellement d'OpenAI)",[71,5794,5795],{},"user : entrées des utilisateurs finaux, ou catégorie générale pour les données que nous voulons fournir au modèle",[71,5797,5798],{},"assistant : messages générés par le modèle de langage",[71,5800,5801],{},"tool : messages générés par un programme, comme l'exécution de code ou un appel d'API",[35,5803,5804],{},"Tous ces rôles et leurs messages sont empilés pour former ce que nous appelons le contexte. Imaginez-le comme un long historique de conversation, dans lequel le LLM peut voir tout ce qui a été dit et fait.",[5806,5807,5808,5811],"llm-context",{},[35,5809,5810],{},"Le contexte est limité par la longueur maximale de contexte du modèle, qui est généralement mesurée en tokens. Par exemple, GPT-5.5 a une longueur maximale de contexte pouvant atteindre 1M de tokens. Mais il y a un piège : le prix augmente au-delà de 272k tokens, et les performances du modèle commencent à se dégrader au-delà de 50 % de la longueur maximale du contexte (ce qui n'est pas vraiment vrai pour les modèles d'OpenAI si tu restes dans la limite de 272k tokens). Et si le contexte dépasse la longueur maximale de contexte, les anciens messages peuvent être tronqués ou supprimés, ce qui entraîne la perte d'informations précieuses que le modèle aurait pu utiliser pour générer de meilleures réponses.",[5812,5813,5814,5817],"llm-context-length",{},[35,5815,5816],{},"Bien sûr, supprimer les anciens messages n'est pas la seule façon de faire de la place. Une application peut condenser le contexte en demandant à un modèle de résumer ces messages, puis en les remplaçant par un seul message condensé.",[5818,5819,5820,5823,5826,5829,5832,5835,5839,5842,5845,5852,5860,5863,5866,5869,5872,5880,5883],"llm-context-compaction",{},[35,5821,5822],{},"Cela permet de conserver les décisions et les contraintes importantes tout en libérant de l'espace. Ce résumé omettra ou déformera certains détails, il est donc essentiel de guider le LLM lorsqu'il résume. Tu peux indiquer au LLM quelles seront les prochaines actions afin de l'aider à conserver les informations pertinentes.",[35,5824,5825],{},"Il faut donc faire attention au contexte et en prendre soin comme de la prunelle de ses yeux.",[35,5827,5828],{},"Cela signifie que tu ne peux pas simplement remplir le contexte avec une grande quantité de texte et t'attendre à ce que le modèle trouve les informations pertinentes tout en générant une bonne réponse. Tu dois lui fournir uniquement le contexte pertinent.",[35,5830,5831],{},"C'est l'une des raisons pour lesquelles le RAG, ou Retrieval-Augmented Generation, est si important.",[35,5833,5834],{},"Mais le RAG ne peut pas tout résoudre. Il est efficace pour ajouter à la demande des connaissances externes et spécifiques dans le contexte, mais il ne permet pas d'exécuter quoi que ce soit. C'est un problème.",[51,5836,5838],{"id":5837},"les-llm-connaissent-le-code","Les LLM connaissent le code",[35,5840,5841],{},"Je suis ingénieur logiciel. Je sais écrire du code et, si tu lis ceci, tu sais probablement aussi en écrire.",[35,5843,5844],{},"Depuis la fin de l'année 2025, impossible de ne pas avoir remarqué que les LLM sont désormais capables d'écrire du code, et de bien l'écrire. On en est arrivé au point où plus de 95 % du code que je mets en production ou que j'utilise est généré par des LLM avec l'aide d'un agent.",[35,5846,5847,5848,215],{},"Nous pouvons tirer parti de cela pour résoudre le problème du contexte. Cela peut sembler contre-intuitif. Mais regardons l'article sur ",[39,5849,5851],{"href":5556,"rel":5850},[77],"Cloudflare Code Mode",[938,5853,5854],{},[35,5855,5856,5857,5859],{},"Les concepteurs de serveurs MCP sont encouragés à proposer des API largement simplifiées par rapport aux API plus traditionnelles qu'ils pourraient exposer aux développeurs. ",[200,5858,573],{}," Les LLM à qui l'on demande d'écrire du code avec les API complètes et complexes normalement exposées aux développeurs ne semblent pas rencontrer trop de difficultés. Pourquoi les interfaces MCP devraient-elles alors être « simplifiées à l'extrême » ?",[35,5861,5862],{},"Que veulent-ils dire par là ?",[35,5864,5865],{},"Les LLM peuvent appeler plusieurs outils à la suite, et parfois en parallèle. Mais lorsqu'un appel dépend du résultat d'un autre, le LLM doit recevoir ce résultat au tour suivant. Imagine le scénario suivant : tu disposes d'un outil capable de lire un fichier sur un disque et d'un autre capable de créer un nouveau fichier sur ce disque. Tu veux ensuite créer un nouveau fichier contenant tous les titres d'un fichier présent sur le disque.",[35,5867,5868],{},"Que va faire le LLM ?",[35,5870,5871],{},"Pour résoudre ce problème, le LLM effectuera deux appels sur deux tours :",[68,5873,5874,5877],{},[71,5875,5876],{},"Appeler le premier outil pour lire le fichier. Cette action renverra le contenu du fichier, qui sera ajouté au contexte.",[71,5878,5879],{},"Appeler le second outil pour créer le nouveau fichier contenant tous les titres. L'appel de l'outil, qui contient les titres extraits de la sortie du premier outil, sera ajouté au contexte.",[35,5881,5882],{},"Visuellement, cela ressemble à ceci :",[5884,5885,5886,5889,5892,5895,5898,5901,6024,6027,6217,6224],"llm-context-two-tools",{},[35,5887,5888],{},"Mais c'est loin d'être optimal.",[35,5890,5891],{},"Le contexte est encombré par le contenu du fichier, que le LLM n'a pas vraiment besoin de voir. Il est possible d'extraire les titres sans que le LLM connaisse le contenu du fichier. Cela signifie également que, pour un fichier confidentiel, le LLM et, par extension, le fournisseur auront accès à son contenu, ce qui n'est pas idéal. La nature non déterministe des LLM signifie aussi que l'extraction des titres peut ne pas être parfaite. Cela pourrait causer de vrais problèmes à une entreprise. Enfin, cette approche ne passe pas à l'échelle. Si le document fait 1M de tokens, le LLM devra tout lire et le contexte sera gonflé par une grande quantité d'informations inutiles.",[35,5893,5894],{},"Même si cet exemple peut sembler simple et irréaliste, il ne l'est pas. Si nous voulons que notre IA effectue des tâches complexes, nous devons trouver une meilleure façon de procéder. C'est là que Code Mode entre en jeu.",[35,5896,5897],{},"Avec Code Mode, tu demandes au LLM d'écrire du code qui effectuera la tâche à ta place. Pour l'informer de l'API qu'il peut utiliser, tu fournis des interfaces de types dans la description de l'outil. Le LLM génère alors du code qui appellera les outils dans le bon ordre et avec les bons paramètres pour effectuer la tâche.",[35,5899,5900],{},"Dans notre exemple, la description de l'outil ressemblera à ceci :",[192,5902,5905],{"className":5903,"code":5904,"language":18,"meta":13,"style":13},"language-md shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","Exécute du code JavaScript contre l'API Drive.\n\nDisponible dans ton code :\ninterface Drive {\n  listFiles: () => Promise\u003C{ title: string, filePath: string }[]>;\n  readFile: (filePath: string) => Promise\u003Cstring>;\n  createFile: (filePath: string, content: string) => Promise\u003Cvoid>;\n}\n\ndeclare const drive: Drive;\n\nTon code doit être une fonction fléchée asynchrone.\n\nExemple :\n\n\u002F\u002F Crée une table des matières à partir de tous les fichiers du disque\nasync () => {\n  const files = await drive.listFiles();\n  const titles = files.map(file => file.title);\n  await drive.createFile('table-of-contents.txt', titles.join('\\n'));\n};\n",[156,5906,5907,5912,5916,5921,5926,5931,5945,5959,5963,5967,5972,5976,5981,5985,5990,5994,5999,6004,6009,6014,6019],{"__ignoreMap":13},[200,5908,5909],{"class":202,"line":203},[200,5910,5911],{"class":210},"Exécute du code JavaScript contre l'API Drive.\n",[200,5913,5914],{"class":202,"line":14},[200,5915,528],{"emptyLinePlaceholder":20},[200,5917,5918],{"class":202,"line":255},[200,5919,5920],{"class":210},"Disponible dans ton code :\n",[200,5922,5923],{"class":202,"line":272},[200,5924,5925],{"class":210},"interface Drive {\n",[200,5927,5928],{"class":202,"line":283},[200,5929,5930],{"class":210},"  listFiles: () => Promise\u003C{ title: string, filePath: string }[]>;\n",[200,5932,5933,5936,5938,5940,5942],{"class":202,"line":300},[200,5934,5935],{"class":210},"  readFile: (filePath: string) => Promise",[200,5937,599],{"class":214},[200,5939,328],{"class":235},[200,5941,674],{"class":214},[200,5943,5944],{"class":210},";\n",[200,5946,5947,5950,5952,5955,5957],{"class":202,"line":310},[200,5948,5949],{"class":210},"  createFile: (filePath: string, content: string) => Promise",[200,5951,599],{"class":214},[200,5953,5954],{"class":235},"void",[200,5956,674],{"class":214},[200,5958,5944],{"class":210},[200,5960,5961],{"class":202,"line":336},[200,5962,1129],{"class":210},[200,5964,5965],{"class":202,"line":342},[200,5966,528],{"emptyLinePlaceholder":20},[200,5968,5969],{"class":202,"line":365},[200,5970,5971],{"class":210},"declare const drive: Drive;\n",[200,5973,5974],{"class":202,"line":371},[200,5975,528],{"emptyLinePlaceholder":20},[200,5977,5978],{"class":202,"line":398},[200,5979,5980],{"class":210},"Ton code doit être une fonction fléchée asynchrone.\n",[200,5982,5983],{"class":202,"line":405},[200,5984,528],{"emptyLinePlaceholder":20},[200,5986,5987],{"class":202,"line":410},[200,5988,5989],{"class":210},"Exemple :\n",[200,5991,5992],{"class":202,"line":860},[200,5993,528],{"emptyLinePlaceholder":20},[200,5995,5996],{"class":202,"line":889},[200,5997,5998],{"class":210},"\u002F\u002F Crée une table des matières à partir de tous les fichiers du disque\n",[200,6000,6001],{"class":202,"line":899},[200,6002,6003],{"class":210},"async () => {\n",[200,6005,6006],{"class":202,"line":904},[200,6007,6008],{"class":210},"  const files = await drive.listFiles();\n",[200,6010,6011],{"class":202,"line":930},[200,6012,6013],{"class":210},"  const titles = files.map(file => file.title);\n",[200,6015,6016],{"class":202,"line":3655},[200,6017,6018],{"class":210},"  await drive.createFile('table-of-contents.txt', titles.join('\\n'));\n",[200,6020,6021],{"class":202,"line":3660},[200,6022,6023],{"class":210},"};\n",[35,6025,6026],{},"Le LLM générera ensuite le code suivant pour effectuer la tâche :",[192,6028,6030],{"className":1380,"code":6029,"language":1383,"meta":13,"style":13},"\u002F\u002F Crée un nouveau fichier contenant tous les titres d'un fichier présent sur le disque\nasync () => {\n  const content = await drive.readFile('my-file.md')\n  const titles = content.split('\\n').filter(line => line.startsWith('#')).map(line => line.replace(\u002F^#+\\s*\u002F, ''))\n  await drive.createFile('titles.txt', titles.join('\\n'))\n}\n",[156,6031,6032,6037,6047,6076,6173,6213],{"__ignoreMap":13},[200,6033,6034],{"class":202,"line":203},[200,6035,6036],{"class":401},"\u002F\u002F Crée un nouveau fichier contenant tous les titres d'un fichier présent sur le disque\n",[200,6038,6039,6041,6043,6045],{"class":202,"line":14},[200,6040,3726],{"class":379},[200,6042,3729],{"class":214},[200,6044,393],{"class":379},[200,6046,280],{"class":214},[200,6048,6049,6051,6054,6056,6058,6061,6063,6066,6068,6070,6072,6074],{"class":202,"line":255},[200,6050,3738],{"class":379},[200,6052,6053],{"class":210}," content",[200,6055,509],{"class":214},[200,6057,452],{"class":206},[200,6059,6060],{"class":210}," drive",[200,6062,215],{"class":214},[200,6064,6065],{"class":223},"readFile",[200,6067,227],{"class":235},[200,6069,249],{"class":214},[200,6071,2872],{"class":245},[200,6073,249],{"class":214},[200,6075,416],{"class":235},[200,6077,6078,6080,6083,6085,6087,6089,6092,6094,6096,6099,6101,6103,6105,6107,6109,6111,6113,6116,6118,6121,6123,6125,6127,6129,6132,6134,6136,6138,6140,6142,6144,6146,6149,6151,6153,6156,6158,6161,6164,6167,6169,6171],{"class":202,"line":272},[200,6079,3738],{"class":379},[200,6081,6082],{"class":210}," titles",[200,6084,509],{"class":214},[200,6086,6053],{"class":210},[200,6088,215],{"class":214},[200,6090,6091],{"class":223},"split",[200,6093,227],{"class":235},[200,6095,249],{"class":214},[200,6097,6098],{"class":210},"\\n",[200,6100,249],{"class":214},[200,6102,2104],{"class":235},[200,6104,215],{"class":214},[200,6106,3773],{"class":223},[200,6108,227],{"class":235},[200,6110,202],{"class":386},[200,6112,393],{"class":379},[200,6114,6115],{"class":210}," line",[200,6117,215],{"class":214},[200,6119,6120],{"class":223},"startsWith",[200,6122,227],{"class":235},[200,6124,249],{"class":214},[200,6126,2902],{"class":245},[200,6128,249],{"class":214},[200,6130,6131],{"class":235},"))",[200,6133,215],{"class":214},[200,6135,3541],{"class":223},[200,6137,227],{"class":235},[200,6139,202],{"class":386},[200,6141,393],{"class":379},[200,6143,6115],{"class":210},[200,6145,215],{"class":214},[200,6147,6148],{"class":223},"replace",[200,6150,227],{"class":235},[200,6152,2898],{"class":214},[200,6154,6155],{"class":206},"^",[200,6157,2902],{"class":245},[200,6159,6160],{"class":214},"+",[200,6162,6163],{"class":245},"\\s",[200,6165,6166],{"class":214},"*\u002F",[200,6168,549],{"class":214},[200,6170,3909],{"class":214},[200,6172,584],{"class":235},[200,6174,6175,6178,6180,6182,6185,6187,6189,6192,6194,6196,6198,6200,6203,6205,6207,6209,6211],{"class":202,"line":283},[200,6176,6177],{"class":206},"  await",[200,6179,6060],{"class":210},[200,6181,215],{"class":214},[200,6183,6184],{"class":223},"createFile",[200,6186,227],{"class":235},[200,6188,249],{"class":214},[200,6190,6191],{"class":245},"titles.txt",[200,6193,249],{"class":214},[200,6195,549],{"class":214},[200,6197,6082],{"class":210},[200,6199,215],{"class":214},[200,6201,6202],{"class":223},"join",[200,6204,227],{"class":235},[200,6206,249],{"class":214},[200,6208,6098],{"class":210},[200,6210,249],{"class":214},[200,6212,584],{"class":235},[200,6214,6215],{"class":202,"line":300},[200,6216,1129],{"class":214},[35,6218,6219,6220,6223],{},"L'outil renvoie ",[156,6221,6222],{},"{ success: true }",". Le contexte du LLM ne contient donc plus que l'appel de l'outil et le résultat de cet appel, ce qui est bien mieux que d'avoir tout le contenu du fichier dans le contexte. À condition que l'environnement d'exécution ne puisse pas le divulguer, le LLM n'a pas besoin d'accéder au contenu du fichier.",[6225,6226,6227,6230,6234,6237,6246,6249,6255,6259,6262,6265,6269,6272,6275,6336,6339,6342,6345,6348,6352,6355,6358,6361,6370,6375,6378,6382,6385,6393,6400,6403,6408,6647,6650,6655,6740,6743,6756,6759,6762,6766,6769,6779,6781,6784],"llm-context-code-mode",{},[35,6228,6229],{},"Le LLM peut toujours générer du mauvais code. Mais le traitement des données est désormais explicite, reproductible et testable. Une fois le code validé, l'extraction des titres n'est plus laissée au LLM. C'est énorme.",[51,6231,6233],{"id":6232},"deux-façons-de-découper-un-texte","Deux façons de découper un texte",[35,6235,6236],{},"Imaginez la situation suivante.",[35,6238,6239,6240,6245],{},"Tu récupères le contenu d'une page web pour en extraire le contenu d'un article et l'envoyer à un agrégateur d'actualités. Pour cela, tu utilises ",[39,6241,6244],{"href":6242,"rel":6243},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fbrowser-run\u002F",[77],"Cloudflare Browser Run",", qui peut récupérer une page web et renvoyer son contenu au format Markdown.",[35,6247,6248],{},"Mais voici le problème : la page web complète est renvoyée. Elle comprend le contenu de l'article, mais aussi l'en-tête, le pied de page, la barre latérale et tout le reste de ce qui n'est pas pertinent pour le contexte. Tu veux uniquement extraire le contenu de l'article.",[35,6250,6251,6252],{},"À partir de là, deux façons de procéder s'offrent à toi. ",[1216,6253,6254],{},"Trois, en réalité, si l'on considère que nous pouvons ignorer le problème et transmettre l'intégralité du contenu au LLM.",[1929,6256,6258],{"id":6257},"réécrire-le-contenu","Réécrire le contenu",[35,6260,6261],{},"La première façon, la plus simple, serait de demander au LLM de réécrire le contenu et de ne conserver que la partie pertinente. Le LLM produirait alors le contenu de l'article. Simple. Et très inefficace.",[35,6263,6264],{},"Tu as peut-être remarqué que les tokens de sortie sont beaucoup plus chers que les tokens d'entrée. Pour GPT-5.5, les tokens d'entrée coûtent 5 $ par million de tokens, tandis que les tokens de sortie coûtent 30 $ par million de tokens. Ainsi, si tu as une page web de 100k tokens et que le contenu de l'article ne fait que 10k tokens, tu paieras 0,50 $ pour les tokens d'entrée et 0,30 $ pour les tokens de sortie. Cela représente un total de 0,80 $ pour extraire 10k tokens de contenu, sans aucune certitude que le LLM réécrira parfaitement l'article.",[1929,6266,6268],{"id":6267},"découper-le-contenu","Découper le contenu",[35,6270,6271],{},"La seconde façon est un peu plus complexe, mais bien plus efficace. L'idée est de donner l'intégralité de la page web au LLM et de lui demander de générer une fonction qui en extraira le contenu de l'article. Ainsi, le LLM ne produira que la fonction, qui ne fera que quelques tokens, puis tu pourras exécuter cette fonction pour extraire le contenu de l'article.",[35,6273,6274],{},"Pour te donner une idée, la fonction pourrait ressembler à ceci :",[192,6276,6278],{"className":1380,"code":6277,"language":1383,"meta":13,"style":13},"() => content.split('#')[1].split('next steps.')[0].trim() \u002F\u002F Le contenu de l'article se trouve entre le premier H1 et le texte « next steps. ». C'est toute la puissance d'un LLM : il s'adapte au contenu de la page web et génère une fonction qui extrait le contenu de l'article sans avoir à le coder en dur.\n",[156,6279,6280],{"__ignoreMap":13},[200,6281,6282,6284,6286,6288,6290,6292,6294,6296,6298,6300,6303,6305,6307,6309,6311,6313,6315,6318,6320,6322,6324,6326,6328,6330,6333],{"class":202,"line":203},[200,6283,1549],{"class":214},[200,6285,393],{"class":379},[200,6287,6053],{"class":210},[200,6289,215],{"class":214},[200,6291,6091],{"class":223},[200,6293,227],{"class":210},[200,6295,249],{"class":214},[200,6297,2902],{"class":245},[200,6299,249],{"class":214},[200,6301,6302],{"class":210},")[",[200,6304,3203],{"class":2060},[200,6306,360],{"class":210},[200,6308,215],{"class":214},[200,6310,6091],{"class":223},[200,6312,227],{"class":210},[200,6314,249],{"class":214},[200,6316,6317],{"class":245},"next steps.",[200,6319,249],{"class":214},[200,6321,6302],{"class":210},[200,6323,3948],{"class":2060},[200,6325,360],{"class":210},[200,6327,215],{"class":214},[200,6329,3191],{"class":223},[200,6331,6332],{"class":210},"() ",[200,6334,6335],{"class":401},"\u002F\u002F Le contenu de l'article se trouve entre le premier H1 et le texte « next steps. ». C'est toute la puissance d'un LLM : il s'adapte au contenu de la page web et génère une fonction qui extrait le contenu de l'article sans avoir à le coder en dur.\n",[35,6337,6338],{},"Cette fonction ne fait que 18 tokens. En utilisant GPT-5.5 pour les deux approches, tu paieras 0,50 $ pour les tokens d'entrée et 0,00054 $ pour les tokens de sortie. Cela représente un total de 0,50054 $ pour extraire 10k tokens de contenu, avec une petite fonction que tu peux inspecter et tester.",[35,6340,6341],{},"Code Mode permet également d'utiliser des modèles moins chers pour écrire la fonction. Par exemple, GPT-5.4-nano coûte respectivement 0,20 $ et 1,25 $ par million de tokens en entrée et en sortie. Avec ce modèle, tu paieras 0,02 $ pour les tokens d'entrée et 0,0000225 $ pour les tokens de sortie. Cela représente un total de 0,0200225 $ pour extraire 10k tokens de contenu, avec une petite fonction que tu peux inspecter et tester.",[35,6343,6344],{},"0,80 $ pour une réécriture par un modèle contre 0,50054 $ pour une petite fonction avec le même modèle, ou 0,0200225 $ avec un modèle moins cher.",[35,6346,6347],{},"Le choix t'appartient, mais le mien est clair. Code Mode est la voie à suivre.",[51,6349,6351],{"id":6350},"le-problème-de-la-spécification-openapi","Le problème de la spécification OpenAPI",[35,6353,6354],{},"Imaginez une spécification OpenAPI contenant 1,7 million de caractères, soit environ 425k tokens.",[35,6356,6357],{},"C'est beaucoup trop pour être transmis au LLM, et cette approche ne peut pas passer à l'échelle. Si la spécification OpenAPI dépasse la longueur maximale du contexte, le LLM ne pourra pas voir l'intégralité de la spécification et, même s'il le peut, le contexte sera encombré par des informations inutiles.",[35,6359,6360],{},"Mais avec uniquement la spécification OpenAPI et sans aucune connaissance de la logique métier, le LLM a été capable de réaliser une tâche assez complexe en quelques minutes en utilisant moins de 100k tokens de contexte. C'est incroyable.",[35,6362,6363,6364,6369],{},"C'est une histoire vraie, résolue grâce au serveur MCP personnalisé que j'ai mentionné plus tôt. Mais c'est aussi ",[39,6365,6368],{"href":6366,"rel":6367},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fmcp",[77],"l'histoire de Cloudflare avec son serveur MCP",". La première ligne du README rend explicite le problème du contexte :",[938,6371,6372],{},[35,6373,6374],{},"Un serveur MCP économe en tokens pour l'ensemble de l'API Cloudflare. 2 500 endpoints en 1k tokens, propulsé par Code Mode.",[35,6376,6377],{},"Leur spécification OpenAPI fait environ 2M tokens. À ce stade, ce n'est pas énorme. C'est inutilisable.",[1929,6379,6381],{"id":6380},"code-mode-à-la-rescousse","Code Mode à la rescousse",[35,6383,6384],{},"Pour résoudre notre problème de spécification OpenAPI, notre solution Code Mode sera composée de deux outils :",[68,6386,6387,6390],{},[71,6388,6389],{},"Search, qui permet au LLM de rechercher les endpoints et les paramètres pertinents dans la spécification OpenAPI.",[71,6391,6392],{},"Execute, qui permet au LLM d'exécuter les endpoints pertinents avec les bons paramètres.",[35,6394,6395,6396,6399],{},"Chaque outil recevra ",[156,6397,6398],{},"{ code: string }"," en entrée.",[35,6401,6402],{},"Un déroulement nominal ressemblera à ceci :",[68,6404,6405],{},[71,6406,6407],{},"Le LLM recherchera les endpoints et les paramètres pertinents dans la spécification OpenAPI à l'aide de l'outil Search. L'outil renverra une liste d'endpoints et de paramètres pertinents, qui sera ajoutée au contexte. Le code généré ressemblera à ceci :",[192,6409,6411],{"className":1380,"code":6410,"language":1383,"meta":13,"style":13},"async () => {\n  const results = []\n  for (const [path, methods] of Object.entries(spec.paths)) {\n    for (const [method, op] of Object.entries(methods)) {\n      if (op.tags?.some(tag => tag.toLowerCase() === '\u003Ctag>')) { \u002F\u002F Dans un scénario réel, le LLM remplacera \u003Ctag> par le tag pertinent à rechercher dans la spécification OpenAPI.\n        results.push({ method: method.toUpperCase(), path, summary: op.summary })\n      }\n    }\n  }\n  return results\n}\n",[156,6412,6413,6423,6434,6481,6519,6573,6624,6628,6632,6636,6643],{"__ignoreMap":13},[200,6414,6415,6417,6419,6421],{"class":202,"line":203},[200,6416,3726],{"class":379},[200,6418,3729],{"class":214},[200,6420,393],{"class":379},[200,6422,280],{"class":214},[200,6424,6425,6427,6429,6431],{"class":202,"line":14},[200,6426,3738],{"class":379},[200,6428,3512],{"class":210},[200,6430,509],{"class":214},[200,6432,6433],{"class":235}," []\n",[200,6435,6436,6439,6441,6443,6445,6448,6450,6453,6455,6458,6461,6463,6466,6468,6471,6473,6476,6479],{"class":202,"line":255},[200,6437,6438],{"class":206},"  for",[200,6440,1094],{"class":235},[200,6442,443],{"class":379},[200,6444,350],{"class":214},[200,6446,6447],{"class":210},"path",[200,6449,549],{"class":214},[200,6451,6452],{"class":210}," methods",[200,6454,360],{"class":214},[200,6456,6457],{"class":214}," of",[200,6459,6460],{"class":210}," Object",[200,6462,215],{"class":214},[200,6464,6465],{"class":223},"entries",[200,6467,227],{"class":235},[200,6469,6470],{"class":210},"spec",[200,6472,215],{"class":214},[200,6474,6475],{"class":210},"paths",[200,6477,6478],{"class":235},")) ",[200,6480,230],{"class":214},[200,6482,6483,6486,6488,6490,6492,6495,6497,6500,6502,6504,6506,6508,6510,6512,6515,6517],{"class":202,"line":272},[200,6484,6485],{"class":206},"    for",[200,6487,1094],{"class":235},[200,6489,443],{"class":379},[200,6491,350],{"class":214},[200,6493,6494],{"class":210},"method",[200,6496,549],{"class":214},[200,6498,6499],{"class":210}," op",[200,6501,360],{"class":214},[200,6503,6457],{"class":214},[200,6505,6460],{"class":210},[200,6507,215],{"class":214},[200,6509,6465],{"class":223},[200,6511,227],{"class":235},[200,6513,6514],{"class":210},"methods",[200,6516,6478],{"class":235},[200,6518,230],{"class":214},[200,6520,6521,6524,6526,6529,6531,6534,6537,6540,6542,6545,6547,6550,6552,6554,6556,6559,6561,6564,6566,6568,6570],{"class":202,"line":283},[200,6522,6523],{"class":206},"      if",[200,6525,1094],{"class":235},[200,6527,6528],{"class":210},"op",[200,6530,215],{"class":214},[200,6532,6533],{"class":210},"tags",[200,6535,6536],{"class":214},"?.",[200,6538,6539],{"class":223},"some",[200,6541,227],{"class":235},[200,6543,6544],{"class":386},"tag",[200,6546,393],{"class":379},[200,6548,6549],{"class":210}," tag",[200,6551,215],{"class":214},[200,6553,3847],{"class":223},[200,6555,6332],{"class":235},[200,6557,6558],{"class":214},"===",[200,6560,242],{"class":214},[200,6562,6563],{"class":245},"\u003Ctag>",[200,6565,249],{"class":214},[200,6567,6478],{"class":235},[200,6569,2767],{"class":214},[200,6571,6572],{"class":401}," \u002F\u002F Dans un scénario réel, le LLM remplacera \u003Ctag> par le tag pertinent à rechercher dans la spécification OpenAPI.\n",[200,6574,6575,6578,6580,6583,6585,6587,6590,6592,6594,6596,6599,6601,6603,6606,6608,6611,6613,6615,6617,6620,6622],{"class":202,"line":300},[200,6576,6577],{"class":210},"        results",[200,6579,215],{"class":214},[200,6581,6582],{"class":223},"push",[200,6584,227],{"class":235},[200,6586,2767],{"class":214},[200,6588,6589],{"class":235}," method",[200,6591,239],{"class":214},[200,6593,6589],{"class":210},[200,6595,215],{"class":214},[200,6597,6598],{"class":223},"toUpperCase",[200,6600,1549],{"class":235},[200,6602,549],{"class":214},[200,6604,6605],{"class":210}," path",[200,6607,549],{"class":214},[200,6609,6610],{"class":235}," summary",[200,6612,239],{"class":214},[200,6614,6499],{"class":210},[200,6616,215],{"class":214},[200,6618,6619],{"class":210},"summary",[200,6621,980],{"class":214},[200,6623,416],{"class":235},[200,6625,6626],{"class":202,"line":310},[200,6627,4493],{"class":214},[200,6629,6630],{"class":202,"line":336},[200,6631,4977],{"class":214},[200,6633,6634],{"class":202,"line":342},[200,6635,4505],{"class":214},[200,6637,6638,6640],{"class":202,"line":365},[200,6639,3758],{"class":206},[200,6641,6642],{"class":210}," results\n",[200,6644,6645],{"class":202,"line":371},[200,6646,1129],{"class":214},[35,6648,6649],{},"Il s'agit simplement d'une fonction qui sera exécutée sur la spécification OpenAPI afin de trouver les endpoints et les paramètres pertinents.",[68,6651,6652],{"start":14},[71,6653,6654],{},"Le LLM générera du code pour exécuter les endpoints pertinents avec les bons paramètres à l'aide de l'outil Execute. L'outil renverra le résultat de l'exécution, qui sera ajouté au contexte. Le code généré ressemblera à ceci :",[192,6656,6658],{"className":1380,"code":6657,"language":1383,"meta":13,"style":13},"async () => api.request({\n  method: 'GET',\n  path: '\u002Fapi\u002Fv1\u002Fresources', \u002F\u002F Dans un scénario réel, le LLM remplacera ceci par l'endpoint pertinent à exécuter.\n  query: { max: 10 },\n})\n",[156,6659,6660,6680,6696,6715,6734],{"__ignoreMap":13},[200,6661,6662,6664,6666,6668,6671,6673,6676,6678],{"class":202,"line":203},[200,6663,3726],{"class":379},[200,6665,3729],{"class":214},[200,6667,393],{"class":379},[200,6669,6670],{"class":210}," api",[200,6672,215],{"class":214},[200,6674,6675],{"class":223},"request",[200,6677,227],{"class":210},[200,6679,230],{"class":214},[200,6681,6682,6685,6687,6689,6692,6694],{"class":202,"line":14},[200,6683,6684],{"class":235},"  method",[200,6686,239],{"class":214},[200,6688,242],{"class":214},[200,6690,6691],{"class":245},"GET",[200,6693,249],{"class":214},[200,6695,252],{"class":214},[200,6697,6698,6701,6703,6705,6708,6710,6712],{"class":202,"line":255},[200,6699,6700],{"class":235},"  path",[200,6702,239],{"class":214},[200,6704,242],{"class":214},[200,6706,6707],{"class":245},"\u002Fapi\u002Fv1\u002Fresources",[200,6709,249],{"class":214},[200,6711,549],{"class":214},[200,6713,6714],{"class":401}," \u002F\u002F Dans un scénario réel, le LLM remplacera ceci par l'endpoint pertinent à exécuter.\n",[200,6716,6717,6720,6722,6724,6727,6729,6732],{"class":202,"line":272},[200,6718,6719],{"class":235},"  query",[200,6721,239],{"class":214},[200,6723,318],{"class":214},[200,6725,6726],{"class":235}," max",[200,6728,239],{"class":214},[200,6730,6731],{"class":2060}," 10",[200,6733,333],{"class":214},[200,6735,6736,6738],{"class":202,"line":283},[200,6737,413],{"class":214},[200,6739,416],{"class":210},[35,6741,6742],{},"Une fois encore, il s'agit simplement d'une fonction qui sera exécutée au sein du serveur MCP afin d'appeler l'endpoint pertinent avec les bons paramètres. C'est tout.",[35,6744,6745,6746,6749,6750,6755],{},"En coulisses, le serveur MCP exécutera le code généré par le LLM et renverra le résultat au LLM. Bien sûr, tu dois exécuter le code dans un bac à sable sécurisé pour éviter les problèmes de sécurité. Ne fais pas comme moi : le constructeur ",[156,6747,6748],{},"Function"," n'est pas un bac à sable sécurisé et l'exécution de code généré par un LLM peut entraîner des problèmes de sécurité majeurs, notamment l'exécution de code arbitraire et l'exfiltration de données. Je l'utilise actuellement uniquement pour simplifier le développement, mais lorsque je commencerai à partager le serveur MCP, j'utiliserai ",[39,6751,6754],{"href":6752,"rel":6753},"https:\u002F\u002Fnpmjs.org\u002Fpackage\u002Fisolate-vm",[77],"isolate-vm"," pour exécuter le code dans un bac à sable sécurisé. Une injection de prompt pourrait demander au LLM d'exfiltrer des données du serveur MCP, tu dois donc être prudent. Je sais que les Workers dynamiques de Cloudflare permettent de bloquer les requêtes réseau sortantes.",[35,6757,6758],{},"Avec ces deux outils et toutes les informations fournies par la spécification OpenAPI, comme le chemin, la description, le nom du paramètre et toute autre information pertinente lisible par un humain, le LLM peut comprendre comment interagir avec ton API et parfois déduire une partie de ta logique métier, sans avoir à écrire un outil personnalisé pour chaque endpoint. Mais une spécification OpenAPI ne décrit pas toujours complètement la logique métier, et ses descriptions peuvent être incomplètes ou ambiguës. Combine-la avec un accès à la documentation métier et ton LLM devient l'agent idéal pour interagir avec ton API.",[35,6760,6761],{},"C'est toute la puissance de Code Mode.",[51,6763,6765],{"id":6764},"démonstration-du-mcp-de-cloudflare","Démonstration du MCP de Cloudflare",[35,6767,6768],{},"Jusqu'ici, tout était théorique. Regardons maintenant comment le serveur MCP de Cloudflare se comporte en pratique :",[130,6770,132,6771,132,6776],{},[134,6772,136,6773,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,6774],{"src":6775,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context\u002Fmcp-showcase.mp4",[143,6777,6778],{},"Le serveur MCP de Cloudflare en action.",[1158,6780],{},[35,6782,6783],{},"Tu te demandes peut-être maintenant : « C'était une bonne lecture, mais comment puis-je construire mon propre serveur MCP pour résoudre mon problème de contexte ? »",[35,6785,6786],{},"Patience. J'aurai quelque chose à te proposer dans les prochaines semaines !",[1170,6788,6789],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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 .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}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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}",{"title":13,"searchDepth":14,"depth":14,"links":6791},[6792],{"id":5771,"depth":14,"text":5772},"5c76b554-ddce-47a6-b5e5-4405beec3043","2026-07-08","Code Mode aide les agents IA à exécuter des workflows MCP sans saturer leur contexte. Découvrez comment deux outils exploitent les spécifications OpenAPI.",{},"\u002Ffr\u002Fposts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context","---\ncontentId: 5c76b554-ddce-47a6-b5e5-4405beec3043\ntitle: Code Mode, deux outils et un MCP pour sauver le contexte\ndescription: Code Mode aide les agents IA à exécuter des workflows MCP sans saturer leur contexte. Découvrez comment deux outils exploitent les spécifications OpenAPI.\ndate: 2026-07-08\n---\n\nL'un des outils que je construis est un serveur MCP personnalisé, basé sur l'idée de Code Mode de Cloudflare, qui me permet d'interagir avec les services locaux que je développe via leurs API comme un client local, sans avoir à le faire moi-même.\n\nIl fonctionne bien mieux que ce à quoi je m'attendais. J'ai été à la fois impressionné et enthousiasmé par les possibilités offertes par cette approche. Mais je me suis heurté à un mur : absolument personne parmi les personnes à qui j'en ai parlé ne partageait mon enthousiasme ou n'avait une idée de la puissance que pouvait avoir cette nouvelle approche.\n\nJ'ai donc fait ce que je sais faire de mieux : j'ai écrit à ce sujet.\n\nCet article est le résultat de cette réflexion.\n\n## Le contexte, c'est tout\n\nL'une des choses auxquelles nous devons accorder le plus d'importance lorsque nous utilisons des LLM est le contexte. Le contexte est cette fenêtre de texte que le LLM voit et utilise pour générer sa réponse, alimentée par le fournisseur, l'utilisateur et le modèle lui-même.\n\nLa [spécification des modèles d'OpenAI](https:\u002F\u002Fmodel-spec.openai.com\u002F2025-02-12.html) définit cinq rôles qui peuvent être utilisés pour remplir le contexte :\n\n- system : messages ajoutés par OpenAI\n- developer : messages provenant du développeur de l'application (et éventuellement d'OpenAI)\n- user : entrées des utilisateurs finaux, ou catégorie générale pour les données que nous voulons fournir au modèle\n- assistant : messages générés par le modèle de langage\n- tool : messages générés par un programme, comme l'exécution de code ou un appel d'API\n\nTous ces rôles et leurs messages sont empilés pour former ce que nous appelons le contexte. Imaginez-le comme un long historique de conversation, dans lequel le LLM peut voir tout ce qui a été dit et fait.\n\n\u003CLlmContext \u002F>\n\nLe contexte est limité par la longueur maximale de contexte du modèle, qui est généralement mesurée en tokens. Par exemple, GPT-5.5 a une longueur maximale de contexte pouvant atteindre 1M de tokens. Mais il y a un piège : le prix augmente au-delà de 272k tokens, et les performances du modèle commencent à se dégrader au-delà de 50 % de la longueur maximale du contexte (ce qui n'est pas vraiment vrai pour les modèles d'OpenAI si tu restes dans la limite de 272k tokens). Et si le contexte dépasse la longueur maximale de contexte, les anciens messages peuvent être tronqués ou supprimés, ce qui entraîne la perte d'informations précieuses que le modèle aurait pu utiliser pour générer de meilleures réponses.\n\n\u003CLlmContextLength \u002F>\n\nBien sûr, supprimer les anciens messages n'est pas la seule façon de faire de la place. Une application peut condenser le contexte en demandant à un modèle de résumer ces messages, puis en les remplaçant par un seul message condensé.\n\n\u003CLlmContextCompaction \u002F>\n\nCela permet de conserver les décisions et les contraintes importantes tout en libérant de l'espace. Ce résumé omettra ou déformera certains détails, il est donc essentiel de guider le LLM lorsqu'il résume. Tu peux indiquer au LLM quelles seront les prochaines actions afin de l'aider à conserver les informations pertinentes.\n\nIl faut donc faire attention au contexte et en prendre soin comme de la prunelle de ses yeux.\n\nCela signifie que tu ne peux pas simplement remplir le contexte avec une grande quantité de texte et t'attendre à ce que le modèle trouve les informations pertinentes tout en générant une bonne réponse. Tu dois lui fournir uniquement le contexte pertinent.\n\nC'est l'une des raisons pour lesquelles le RAG, ou Retrieval-Augmented Generation, est si important.\n\nMais le RAG ne peut pas tout résoudre. Il est efficace pour ajouter à la demande des connaissances externes et spécifiques dans le contexte, mais il ne permet pas d'exécuter quoi que ce soit. C'est un problème.\n\n## Les LLM connaissent le code\n\nJe suis ingénieur logiciel. Je sais écrire du code et, si tu lis ceci, tu sais probablement aussi en écrire.\n\nDepuis la fin de l'année 2025, impossible de ne pas avoir remarqué que les LLM sont désormais capables d'écrire du code, et de bien l'écrire. On en est arrivé au point où plus de 95 % du code que je mets en production ou que j'utilise est généré par des LLM avec l'aide d'un agent.\n\nNous pouvons tirer parti de cela pour résoudre le problème du contexte. Cela peut sembler contre-intuitif. Mais regardons l'article sur [Cloudflare Code Mode](https:\u002F\u002Fblog.cloudflare.com\u002Fcode-mode\u002F).\n\n> Les concepteurs de serveurs MCP sont encouragés à proposer des API largement simplifiées par rapport aux API plus traditionnelles qu'ils pourraient exposer aux développeurs. [...] Les LLM à qui l'on demande d'écrire du code avec les API complètes et complexes normalement exposées aux développeurs ne semblent pas rencontrer trop de difficultés. Pourquoi les interfaces MCP devraient-elles alors être « simplifiées à l'extrême » ?\n\nQue veulent-ils dire par là ?\n\nLes LLM peuvent appeler plusieurs outils à la suite, et parfois en parallèle. Mais lorsqu'un appel dépend du résultat d'un autre, le LLM doit recevoir ce résultat au tour suivant. Imagine le scénario suivant : tu disposes d'un outil capable de lire un fichier sur un disque et d'un autre capable de créer un nouveau fichier sur ce disque. Tu veux ensuite créer un nouveau fichier contenant tous les titres d'un fichier présent sur le disque.\n\nQue va faire le LLM ?\n\nPour résoudre ce problème, le LLM effectuera deux appels sur deux tours :\n\n1. Appeler le premier outil pour lire le fichier. Cette action renverra le contenu du fichier, qui sera ajouté au contexte.\n2. Appeler le second outil pour créer le nouveau fichier contenant tous les titres. L'appel de l'outil, qui contient les titres extraits de la sortie du premier outil, sera ajouté au contexte.\n\nVisuellement, cela ressemble à ceci :\n\n\u003CLlmContextTwoTools \u002F>\n\nMais c'est loin d'être optimal.\n\nLe contexte est encombré par le contenu du fichier, que le LLM n'a pas vraiment besoin de voir. Il est possible d'extraire les titres sans que le LLM connaisse le contenu du fichier. Cela signifie également que, pour un fichier confidentiel, le LLM et, par extension, le fournisseur auront accès à son contenu, ce qui n'est pas idéal. La nature non déterministe des LLM signifie aussi que l'extraction des titres peut ne pas être parfaite. Cela pourrait causer de vrais problèmes à une entreprise. Enfin, cette approche ne passe pas à l'échelle. Si le document fait 1M de tokens, le LLM devra tout lire et le contexte sera gonflé par une grande quantité d'informations inutiles.\n\nMême si cet exemple peut sembler simple et irréaliste, il ne l'est pas. Si nous voulons que notre IA effectue des tâches complexes, nous devons trouver une meilleure façon de procéder. C'est là que Code Mode entre en jeu.\n\nAvec Code Mode, tu demandes au LLM d'écrire du code qui effectuera la tâche à ta place. Pour l'informer de l'API qu'il peut utiliser, tu fournis des interfaces de types dans la description de l'outil. Le LLM génère alors du code qui appellera les outils dans le bon ordre et avec les bons paramètres pour effectuer la tâche.\n\nDans notre exemple, la description de l'outil ressemblera à ceci :\n\n```md\nExécute du code JavaScript contre l'API Drive.\n\nDisponible dans ton code :\ninterface Drive {\n  listFiles: () => Promise\u003C{ title: string, filePath: string }[]>;\n  readFile: (filePath: string) => Promise\u003Cstring>;\n  createFile: (filePath: string, content: string) => Promise\u003Cvoid>;\n}\n\ndeclare const drive: Drive;\n\nTon code doit être une fonction fléchée asynchrone.\n\nExemple :\n\n\u002F\u002F Crée une table des matières à partir de tous les fichiers du disque\nasync () => {\n  const files = await drive.listFiles();\n  const titles = files.map(file => file.title);\n  await drive.createFile('table-of-contents.txt', titles.join('\\n'));\n};\n```\n\nLe LLM générera ensuite le code suivant pour effectuer la tâche :\n\n```ts\n\u002F\u002F Crée un nouveau fichier contenant tous les titres d'un fichier présent sur le disque\nasync () => {\n  const content = await drive.readFile('my-file.md')\n  const titles = content.split('\\n').filter(line => line.startsWith('#')).map(line => line.replace(\u002F^#+\\s*\u002F, ''))\n  await drive.createFile('titles.txt', titles.join('\\n'))\n}\n```\n\nL'outil renvoie `{ success: true }`. Le contexte du LLM ne contient donc plus que l'appel de l'outil et le résultat de cet appel, ce qui est bien mieux que d'avoir tout le contenu du fichier dans le contexte. À condition que l'environnement d'exécution ne puisse pas le divulguer, le LLM n'a pas besoin d'accéder au contenu du fichier.\n\n\u003CLlmContextCodeMode \u002F>\n\nLe LLM peut toujours générer du mauvais code. Mais le traitement des données est désormais explicite, reproductible et testable. Une fois le code validé, l'extraction des titres n'est plus laissée au LLM. C'est énorme.\n\n## Deux façons de découper un texte\n\nImaginez la situation suivante.\n\nTu récupères le contenu d'une page web pour en extraire le contenu d'un article et l'envoyer à un agrégateur d'actualités. Pour cela, tu utilises [Cloudflare Browser Run](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fbrowser-run\u002F), qui peut récupérer une page web et renvoyer son contenu au format Markdown.\n\nMais voici le problème : la page web complète est renvoyée. Elle comprend le contenu de l'article, mais aussi l'en-tête, le pied de page, la barre latérale et tout le reste de ce qui n'est pas pertinent pour le contexte. Tu veux uniquement extraire le contenu de l'article.\n\nÀ partir de là, deux façons de procéder s'offrent à toi. _Trois, en réalité, si l'on considère que nous pouvons ignorer le problème et transmettre l'intégralité du contenu au LLM._\n\n### Réécrire le contenu\n\nLa première façon, la plus simple, serait de demander au LLM de réécrire le contenu et de ne conserver que la partie pertinente. Le LLM produirait alors le contenu de l'article. Simple. Et très inefficace.\n\nTu as peut-être remarqué que les tokens de sortie sont beaucoup plus chers que les tokens d'entrée. Pour GPT-5.5, les tokens d'entrée coûtent 5 $ par million de tokens, tandis que les tokens de sortie coûtent 30 $ par million de tokens. Ainsi, si tu as une page web de 100k tokens et que le contenu de l'article ne fait que 10k tokens, tu paieras 0,50 $ pour les tokens d'entrée et 0,30 $ pour les tokens de sortie. Cela représente un total de 0,80 $ pour extraire 10k tokens de contenu, sans aucune certitude que le LLM réécrira parfaitement l'article.\n\n### Découper le contenu\n\nLa seconde façon est un peu plus complexe, mais bien plus efficace. L'idée est de donner l'intégralité de la page web au LLM et de lui demander de générer une fonction qui en extraira le contenu de l'article. Ainsi, le LLM ne produira que la fonction, qui ne fera que quelques tokens, puis tu pourras exécuter cette fonction pour extraire le contenu de l'article.\n\nPour te donner une idée, la fonction pourrait ressembler à ceci :\n\n```ts\n() => content.split('#')[1].split('next steps.')[0].trim() \u002F\u002F Le contenu de l'article se trouve entre le premier H1 et le texte « next steps. ». C'est toute la puissance d'un LLM : il s'adapte au contenu de la page web et génère une fonction qui extrait le contenu de l'article sans avoir à le coder en dur.\n```\n\nCette fonction ne fait que 18 tokens. En utilisant GPT-5.5 pour les deux approches, tu paieras 0,50 $ pour les tokens d'entrée et 0,00054 $ pour les tokens de sortie. Cela représente un total de 0,50054 $ pour extraire 10k tokens de contenu, avec une petite fonction que tu peux inspecter et tester.\n\nCode Mode permet également d'utiliser des modèles moins chers pour écrire la fonction. Par exemple, GPT-5.4-nano coûte respectivement 0,20 $ et 1,25 $ par million de tokens en entrée et en sortie. Avec ce modèle, tu paieras 0,02 $ pour les tokens d'entrée et 0,0000225 $ pour les tokens de sortie. Cela représente un total de 0,0200225 $ pour extraire 10k tokens de contenu, avec une petite fonction que tu peux inspecter et tester.\n\n0,80 $ pour une réécriture par un modèle contre 0,50054 $ pour une petite fonction avec le même modèle, ou 0,0200225 $ avec un modèle moins cher.\n\nLe choix t'appartient, mais le mien est clair. Code Mode est la voie à suivre.\n\n## Le problème de la spécification OpenAPI\n\nImaginez une spécification OpenAPI contenant 1,7 million de caractères, soit environ 425k tokens.\n\nC'est beaucoup trop pour être transmis au LLM, et cette approche ne peut pas passer à l'échelle. Si la spécification OpenAPI dépasse la longueur maximale du contexte, le LLM ne pourra pas voir l'intégralité de la spécification et, même s'il le peut, le contexte sera encombré par des informations inutiles.\n\nMais avec uniquement la spécification OpenAPI et sans aucune connaissance de la logique métier, le LLM a été capable de réaliser une tâche assez complexe en quelques minutes en utilisant moins de 100k tokens de contexte. C'est incroyable.\n\nC'est une histoire vraie, résolue grâce au serveur MCP personnalisé que j'ai mentionné plus tôt. Mais c'est aussi [l'histoire de Cloudflare avec son serveur MCP](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fmcp). La première ligne du README rend explicite le problème du contexte :\n\n> Un serveur MCP économe en tokens pour l'ensemble de l'API Cloudflare. 2 500 endpoints en 1k tokens, propulsé par Code Mode.\n\nLeur spécification OpenAPI fait environ 2M tokens. À ce stade, ce n'est pas énorme. C'est inutilisable.\n\n### Code Mode à la rescousse\n\nPour résoudre notre problème de spécification OpenAPI, notre solution Code Mode sera composée de deux outils :\n\n1. Search, qui permet au LLM de rechercher les endpoints et les paramètres pertinents dans la spécification OpenAPI.\n2. Execute, qui permet au LLM d'exécuter les endpoints pertinents avec les bons paramètres.\n\nChaque outil recevra `{ code: string }` en entrée.\n\nUn déroulement nominal ressemblera à ceci :\n\n1. Le LLM recherchera les endpoints et les paramètres pertinents dans la spécification OpenAPI à l'aide de l'outil Search. L'outil renverra une liste d'endpoints et de paramètres pertinents, qui sera ajoutée au contexte. Le code généré ressemblera à ceci :\n\n  ```ts\n  async () => {\n    const results = []\n    for (const [path, methods] of Object.entries(spec.paths)) {\n      for (const [method, op] of Object.entries(methods)) {\n        if (op.tags?.some(tag => tag.toLowerCase() === '\u003Ctag>')) { \u002F\u002F Dans un scénario réel, le LLM remplacera \u003Ctag> par le tag pertinent à rechercher dans la spécification OpenAPI.\n          results.push({ method: method.toUpperCase(), path, summary: op.summary })\n        }\n      }\n    }\n    return results\n  }\n  ```\n\n  Il s'agit simplement d'une fonction qui sera exécutée sur la spécification OpenAPI afin de trouver les endpoints et les paramètres pertinents.\n\n2. Le LLM générera du code pour exécuter les endpoints pertinents avec les bons paramètres à l'aide de l'outil Execute. L'outil renverra le résultat de l'exécution, qui sera ajouté au contexte. Le code généré ressemblera à ceci :\n\n  ```ts\n  async () => api.request({\n    method: 'GET',\n    path: '\u002Fapi\u002Fv1\u002Fresources', \u002F\u002F Dans un scénario réel, le LLM remplacera ceci par l'endpoint pertinent à exécuter.\n    query: { max: 10 },\n  })\n  ```\n\n  Une fois encore, il s'agit simplement d'une fonction qui sera exécutée au sein du serveur MCP afin d'appeler l'endpoint pertinent avec les bons paramètres. C'est tout.\n\nEn coulisses, le serveur MCP exécutera le code généré par le LLM et renverra le résultat au LLM. Bien sûr, tu dois exécuter le code dans un bac à sable sécurisé pour éviter les problèmes de sécurité. Ne fais pas comme moi : le constructeur `Function` n'est pas un bac à sable sécurisé et l'exécution de code généré par un LLM peut entraîner des problèmes de sécurité majeurs, notamment l'exécution de code arbitraire et l'exfiltration de données. Je l'utilise actuellement uniquement pour simplifier le développement, mais lorsque je commencerai à partager le serveur MCP, j'utiliserai [isolate-vm](https:\u002F\u002Fnpmjs.org\u002Fpackage\u002Fisolate-vm) pour exécuter le code dans un bac à sable sécurisé. Une injection de prompt pourrait demander au LLM d'exfiltrer des données du serveur MCP, tu dois donc être prudent. Je sais que les Workers dynamiques de Cloudflare permettent de bloquer les requêtes réseau sortantes.\n\nAvec ces deux outils et toutes les informations fournies par la spécification OpenAPI, comme le chemin, la description, le nom du paramètre et toute autre information pertinente lisible par un humain, le LLM peut comprendre comment interagir avec ton API et parfois déduire une partie de ta logique métier, sans avoir à écrire un outil personnalisé pour chaque endpoint. Mais une spécification OpenAPI ne décrit pas toujours complètement la logique métier, et ses descriptions peuvent être incomplètes ou ambiguës. Combine-la avec un accès à la documentation métier et ton LLM devient l'agent idéal pour interagir avec ton API.\n\nC'est toute la puissance de Code Mode.\n\n## Démonstration du MCP de Cloudflare\n\nJusqu'ici, tout était théorique. Regardons maintenant comment le serveur MCP de Cloudflare se comporte en pratique :\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context\u002Fmcp-showcase.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Le serveur MCP de Cloudflare en action.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n---\n\nTu te demandes peut-être maintenant : « C'était une bonne lecture, mais comment puis-je construire mon propre serveur MCP pour résoudre mon problème de contexte ? »\n\nPatience. J'aurai quelque chose à te proposer dans les prochaines semaines !\n",{"title":5754,"description":6795},{"loc":6797},"fr\u002Fposts\u002Fcode-mode-two-tools-and-an-mcp-can-save-your-llm-context","CtFR23h7zJ-0QO_niBYwzVRl1_kb_Y2KV_QpmqBPtWU",{"id":6804,"title":6805,"body":6806,"contentId":7464,"date":7465,"description":7466,"extension":18,"meta":7467,"navigation":20,"path":7468,"rawbody":7469,"robots":23,"schemaOrg":23,"seo":7470,"sitemap":7471,"stem":7483,"__hash__":7484},"posts\u002Ffr\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks.md","Devoxx France 2026 : les personnes plus que les talks",{"type":7,"value":6807,"toc":7457},[6808,6811,6814,6820,6823,6826,6831,6835,6838,6846,6849,6852,7136,7141,7147,7150],[35,6809,6810],{},"Je sais, j'arrive un peu après la bataille sur celui-ci. Mais comme je le fais désormais pour la plupart des conférences auxquelles j'assiste, j'avais envie de partager quelques notes et réflexions sur Devoxx France 2026.",[35,6812,6813],{},"Devoxx France est une conférence très différente en raison de sa taille. Avec 4 500 participants, 3 jours, 14 tracks et plus de 250 talks, elle dépasse largement n'importe quelle autre conférence technique en France. Certains intervenants ne parlent qu'à Devoxx, ce qui en fait la seule occasion de les voir.",[35,6815,6816],{},[1707,6817],{"alt":6818,"src":6819},"La scène principale de Devoxx France, pendant l'ouverture","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fmain-stage.webp",[35,6821,6822],{},"En même temps, et c'est mon avis personnel, la taille de la conférence rend sa pleine appréciation très difficile. Une fois un talk terminé, on court, littéralement, vers le suivant en espérant trouver une place. On est toujours dans l'urgence. On a toujours la FOMO à cause du nombre de tracks, et on ne peut pas simplement prendre le temps de ralentir, parler avec les gens et profiter de la conférence. On peut vite tomber dans le piège de vouloir voir un maximum de talks.",[35,6824,6825],{},"Cette année, j'ai vraiment pris le temps de discuter avec les gens. Il y a eu des conférences auxquelles je n'ai pas assisté pour libérer du temps. Par exemple, je voulais aller au meetup Dev with AI mardi soir, mais je discutais avec des amis, je n'ai pas vu le temps passer et je l'ai raté. C'est la loi des conférences, il faut faire des choix.",[35,6827,6828],{},[1216,6829,6830],{},"Pitié, ne me demandez pas ce qu'il en est de la nourriture à Devoxx. Sachez simplement que j'ai toujours quelques biscuits dans mon sac.",[51,6832,6834],{"id":6833},"mon-programme","Mon programme",[35,6836,6837],{},"Cette année, pour la première fois, j'ai regardé le programme avant la conférence. Je voulais avoir un aperçu de l'ambiance générale de l'événement. Ces jours-ci, tout tourne autour de l'IA. J'ouvre X, je vois de l'IA. Je parle avec des amis, on parle d'IA. Je vais au travail, on pousse encore plus loin l'IA. Même avec ma date, ma famille ou des gens que je ne connais pas pendant un covoiturage, je parle d'IA. Ce sujet est partout. Devoxx ne fait pas exception.",[35,6839,6840,6841,215],{},"En plus de ça, j'ai passé les quatre derniers mois à écrire un talk sur l'avenir des développeurs avec ",[39,6842,6845],{"href":6843,"rel":6844},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux\u002F",[77],"Julie",[35,6847,6848],{},"L'IA sature tellement nos vies que je voulais une petite pause. J'ai donc décidé de me concentrer sur des talks centrés sur l'humain.",[35,6850,6851],{},"Voici mon programme pour la conférence :",[68,6853,6854,6956,7056],{},[71,6855,6856,6857],{},"Mercredi 15 avril 2026\n",[1593,6858,6859,6871,6881,6891,6901,6911,6921,6936,6946],{},[71,6860,6861,6865,6866],{},[6862,6863,6864],"strong",{},"L'IA au service du bien commun : Agir avec raison dans l'incertain"," par ",[39,6867,6870],{"href":6868,"rel":6869},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurence-devillers-4041899\u002F",[77],"Laurence Devillers",[71,6872,6873,6865,6876],{},[6862,6874,6875],{},"Les échecs à l'ère de l'intelligence artificielle",[39,6877,6880],{"href":6878,"rel":6879},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurent-fressinet-752236188\u002F",[77],"Laurent Fressinet",[71,6882,6883,6865,6886],{},[6862,6884,6885],{},"Neurodiversité & Tech : vos “profils atypiques” sont votre plus grande force",[39,6887,6890],{"href":6888,"rel":6889},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Famelierigano\u002F",[77],"Amélie Rigano",[71,6892,6893,6865,6896],{},[6862,6894,6895],{},"The evolution of Software engineering",[39,6897,6900],{"href":6898,"rel":6899},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftheophane-gregoir\u002F",[77],"Theophane Gregoir",[71,6902,6903,6865,6906],{},[6862,6904,6905],{},"La transactionalité expliquée",[39,6907,6910],{"href":6908,"rel":6909},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbenjamin-cavy-aa67a2a6\u002F",[77],"Benjamin Cavy",[71,6912,6913,6865,6916],{},[6862,6914,6915],{},"Et si on débuguait notre rapport au temps ?",[39,6917,6920],{"href":6918,"rel":6919},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falbane-fagot-veyron\u002F",[77],"Albane Fagot-Veyron",[71,6922,6923,6865,6926,1655,6931],{},[6862,6924,6925],{},"De zéro à des milliards de traces, le tracing distribué chez Winamax",[39,6927,6930],{"href":6928,"rel":6929},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanthonymaffert\u002F",[77],"Anthony Maffert",[39,6932,6935],{"href":6933,"rel":6934},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-fidel\u002F",[77],"Nicolas Fidel",[71,6937,6938,6865,6941],{},[6862,6939,6940],{},"Kafka 4, fantastique ?",[39,6942,6945],{"href":6943,"rel":6944},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdamien-lucas\u002F",[77],"Damien Lucas",[71,6947,6948,6865,6951],{},[6862,6949,6950],{},"CLI-Driven Developer Experience : L'ingrédient secret d'une équipe alignée",[39,6952,6955],{"href":6953,"rel":6954},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F",[77],"Julien Briault",[71,6957,6958,6959],{},"Jeudi 16 avril 2026\n",[1593,6960,6961,6971,6981,6991,7001,7011,7021,7036,7046],{},[71,6962,6963,6865,6966],{},[6862,6964,6965],{},"Le développeur face à l'IA : du prototypage rapide à la fin des intermédiaires ?",[39,6967,6970],{"href":6968,"rel":6969},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolasgrenie\u002F",[77],"Nicolas Grenié",[71,6972,6973,6865,6976],{},[6862,6974,6975],{},"L'IA sur le terrain : l'humain au cœur de la valeur",[39,6977,6980],{"href":6978,"rel":6979},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarjory-canonne-7aab19153\u002F",[77],"Marjory Canonne",[71,6982,6983,6865,6986],{},[6862,6984,6985],{},"L'ampoule qui en savait trop : voyage au cœur de la sécurité des objets connecté",[39,6987,6990],{"href":6988,"rel":6989},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpaulpinault\u002F",[77],"Paul Pinault",[71,6992,6993,6865,6996],{},[6862,6994,6995],{},"Top 10 Event-Driven Architecture Pitfalls",[39,6997,7000],{"href":6998,"rel":6999},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictor-rentea-trainer\u002F",[77],"Victor Rentea",[71,7002,7003,6865,7006],{},[6862,7004,7005],{},"Transformer son API Doc en levier de conversion : best practices",[39,7007,7010],{"href":7008,"rel":7009},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanne-flore-bernard\u002F",[77],"Anne-Flore Bernard",[71,7012,7013,6865,7016],{},[6862,7014,7015],{},"Clean Code Matters",[39,7017,7020],{"href":7018,"rel":7019},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarc-lecanu\u002F",[77],"Marc LECANU",[71,7022,7023,6865,7026,1655,7031],{},[6862,7024,7025],{},"Les gardiens du Prompt: menaces et recettes de sécurité pour une production zen.",[39,7027,7030],{"href":7028,"rel":7029},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaumay\u002F",[77],"Philippe Laumay",[39,7032,7035],{"href":7033,"rel":7034},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floic-gudet\u002F",[77],"Loïc Gudet",[71,7037,7038,6865,7041],{},[6862,7039,7040],{},"Au cœur d'une pipeline: démystifions Vite et ses plugins",[39,7042,7045],{"href":7043,"rel":7044},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F",[77],"Estéban Soubiran",[71,7047,7048,6865,7051],{},[6862,7049,7050],{},"Debugging with IntelliJ IDEA",[39,7052,7055],{"href":7053,"rel":7054},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fantonarhipov\u002F",[77],"Anton Arhipov",[71,7057,7058,7059],{},"Vendredi 17 avril 2026\n",[1593,7060,7061,7071,7081,7091,7101,7116,7126],{},[71,7062,7063,6865,7066],{},[6862,7064,7065],{},"L'IA au défi de la condition humaine",[39,7067,7070],{"href":7068,"rel":7069},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjganascia\u002F",[77],"Jean-Gabriel Ganascia",[71,7072,7073,6865,7076],{},[6862,7074,7075],{},"La course à l'IA : La France risque-t-elle de devenir un \"territoire servant\" ?",[39,7077,7080],{"href":7078,"rel":7079},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floup-cellard-b2502297\u002F",[77],"Loup Cellard",[71,7082,7083,6865,7086],{},[6862,7084,7085],{},"Et si on ouvrait le capot de PostgreSQL ?",[39,7087,7090],{"href":7088,"rel":7089},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcoenenbenjamin\u002F",[77],"Benjamin Coenen",[71,7092,7093,6865,7096],{},[6862,7094,7095],{},"Et si écrire du SQL redevenait cool ?",[39,7097,7100],{"href":7098,"rel":7099},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fs%C3%A9bastien-ferrer-076ba052\u002F",[77],"Sébastien Ferrer",[71,7102,7103,6865,7106,1655,7111],{},[6862,7104,7105],{},"Kubernetes et la JVM",[39,7107,7110],{"href":7108,"rel":7109},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmdoudoux\u002F",[77],"Jean-Michel Doudoux",[39,7112,7115],{"href":7113,"rel":7114},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falainregnier\u002F",[77],"Alain Regnier",[71,7117,7118,6865,7121],{},[6862,7119,7120],{},"Éloge de la simplicité",[39,7122,7125],{"href":7123,"rel":7124},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffleguedois\u002F",[77],"Frédéric Leguédois",[71,7127,7128,6865,7131],{},[6862,7129,7130],{},"Performance de backend Spring : les techniques que tout dev devrait connaître",[39,7132,7135],{"href":7133,"rel":7134},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffbeaufume\u002F",[77],"Florian Beaufumé",[35,7137,7138],{},[1216,7139,7140],{},"Je dois avouer que j'aurais pu dormir un peu pendant l'un des talks. Désolé !",[35,7142,7143,7144],{},"Oui ! Ça fait beaucoup. Ça peut expliquer pourquoi j'ai terminé la conférence avec un mal de tête, fatigué et avec besoin de me reposer seul. ",[1216,7145,7146],{},"Mais ça en valait la peine.",[35,7148,7149],{},"Toutes les rediffusions seront disponibles sur la chaîne YouTube de Devoxx France, et je vous encourage vivement à les regarder !",[7151,7152,7157,7161,7164,7215,7219,7222,7228,7231,7237,7243,7256,7264,7268,7271,7277,7280,7286,7289,7336,7339,7342,7348,7354,7360,7364,7370,7392,7398,7414,7438,7441,7447,7449,7452,7454],"callout",{"w-full":13,"icon":7153,"text":7154,"to":7155,"target":7156},"i-simple-icons-youtube","Devoxx France","https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos","_blank",[51,7158,7160],{"id":7159},"les-talks-que-jai-aimés","Les talks que j'ai aimés",[35,7162,7163],{},"Comme d'habitude, certains talks se sont vraiment démarqués, et je voulais les mettre en avant ici.",[68,7165,7166,7183,7199],{},[71,7167,7168,6865,7170,7173,7176,7177,7179],{},[6862,7169,7120],{},[39,7171,7125],{"href":7123,"rel":7172},[77],[7174,7175],"br",{},"C'est peut-être le meilleur talk que j'aie vu à Devoxx, toutes éditions confondues. C'est un talk de 45 minutes, mais honnêtement, on ne les voit pas passer. Frédéric n'utilise pas de slides, et il construit son talk comme une pièce de théâtre, avec beaucoup d'humour. Malgré cette impression de légèreté, le talk va loin dans l'état actuel de l'agilité, l'usage excessif de la planification et l'importance de reconstruire notre rapport à la simplicité. Honnêtement, vous devriez simplement le regarder.",[7174,7178],{},[1707,7180],{"alt":7181,"src":7182},"Frédéric sur scène, pendant son talk sur la simplicité","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ffrederic-simplicity.webp",[71,7184,7185,6865,7187,7190,7192,7193,7195],{},[6862,7186,6915],{},[39,7188,6920],{"href":6918,"rel":7189},[77],[7174,7191],{},"C'est un talk profondément centré sur l'humain, et j'ai adoré. Albane parle de notre rapport au temps, de la manière dont nous le percevons et de la façon dont il influence notre quotidien et notre travail. \"Je n'ai pas le temps\" est une phrase que nous entendons souvent, ou que nous prononçons nous-mêmes, mais avons-nous déjà pris le temps de creuser le sujet ? C'est vraiment bien écrit et agréable à écouter. Il est déjà dans ma liste à voir.",[7174,7194],{},[1707,7196],{"alt":7197,"src":7198},"Albane sur scène, pendant son talk sur notre rapport au temps","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Falbane-time.webp",[71,7200,7201,6865,7203,7206,7208,7209,7211],{},[6862,7202,6985],{},[39,7204,6990],{"href":6988,"rel":7205},[77],[7174,7207],{},"Je suis venu à ce talk sans aucune attente, et j'ai passé un moment formidable. Au début, Paul montre qu'allumer une lumière peut se résumer à 50 lignes de code, avant d'expliquer tous les prérequis imposés par l'Union européenne en matière de sécurité, de vie privée et de maintenance, ce qui rend la tâche beaucoup plus complexe, et donc plus intéressante. La vraie complexité n'est pas dans l'ampoule elle-même, mais dans l'écosystème autour, nécessaire pour la rendre conforme. Ce talk très concret peut facilement servir d'analogie pour n'importe quel projet.",[7174,7210],{},[1707,7212],{"alt":7213,"src":7214},"Paul sur scène, pendant son talk sur la sécurité des objets connectés","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fpaul-connected-objects.webp",[51,7216,7218],{"id":7217},"mon-talk","Mon talk",[35,7220,7221],{},"Cette année, j'ai donné un talk sur Vite et son pipeline intitulé \"Au cœur d'une pipeline : démystifions Vite et ses plugins\".",[35,7223,7224],{},[1707,7225],{"alt":7226,"src":7227},"Estéban devant le panneau d'annonce, avant le début du talk","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-panel.webp",[35,7229,7230],{},"C'est un talk de 45 minutes dans lequel j'explique comment fonctionne Vite, à quoi ressemble son pipeline et comment écrire un plugin pour lui. À travers plusieurs exemples concrets, j'essaie de démystifier le fonctionnement interne de Vite et de montrer à quel point il peut être puissant, tout en restant accessible, lorsqu'on commence à écrire des plugins pour lui.",[35,7232,7233],{},[1707,7234],{"alt":7235,"src":7236},"Estéban derrière le pupitre, avant le début du talk","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-desk.webp",[35,7238,7239],{},[1707,7240],{"alt":7241,"src":7242},"Mon selfie avec l'audience, pendant mon talk sur Vite","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-selfie.webp",[35,7244,7245,7246,7250,7251,215],{},"J'avais déjà donné le même talk à ",[39,7247,7249],{"href":7248},".\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026","MiXiT 2026",", et je n'ai pas trouvé le temps de l'améliorer pour Devoxx. Moins d'une semaine, c'est vraiment très court pour améliorer un talk. En revanche, beaucoup d'améliorations arrivent en juin, et j'en parlerai davantage sur ",[39,7252,7255],{"href":7253,"rel":7254},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25",[77],"LinkedIn",[35,7257,7258,7259,215],{},"Vous pouvez retrouver les slides du talk sur ",[39,7260,7263],{"href":7261,"rel":7262},"https:\u002F\u002Ftalks.soubiran.dev",[77],"talks.soubiran.dev",[51,7265,7267],{"id":7266},"les-personnes-dabord","Les personnes d'abord",[35,7269,7270],{},"Pour la première fois dans une conférence, j'ai priorisé les personnes plutôt que les talks.",[35,7272,7273,7274],{},"Je suis convaincu que la partie la plus précieuse d'une conférence n'est pas la conférence elle-même. On peut toujours retrouver les talks plus tard sur YouTube, donc le contenu n'est pas exclusif à l'événement. ",[1216,7275,7276],{},"Mais je comprends la valeur d'assister à un talk en direct : c'est une expérience différente, et cela oblige à être plus présent et plus concentré.",[35,7278,7279],{},"Mais les gens, qu'il s'agisse des intervenants qui rendent la conférence possible, des participants curieux qui ont envie d'apprendre, ou même de ceux qui sont simplement là pour découvrir de nouvelles choses, sont la vraie valeur d'une conférence. Avec la montée récente de l'IA, où nous avons tous tendance à interagir surtout avec des machines, il devient encore plus important de prioriser les interactions humaines.",[35,7281,7282,7283],{},"Ainsi, du dîner des intervenants à la soirée Meet and Greet, et des sponsors aux participants, j'ai passé beaucoup de temps à discuter. J'ai découvert leurs histoires, comment ils sont arrivés à Devoxx, pourquoi ils donnent des talks en conférence ou apprécient l'entreprise pour laquelle ils travaillent, les dernières nouvelles des entreprises, ou tout simplement à parler de la vie. C'est aussi une excellente occasion de mettre un visage sur le nom de personnes que l'on ne connaît que via LinkedIn. ",[1216,7284,7285],{},"Et de ressentir l'étrange malaise des interactions parasociales.",[35,7287,7288],{},"À travers cette section, je voulais remercier toutes les personnes avec qui j'ai eu l'occasion de parler pendant la conférence. Ce fut un vrai plaisir, et j'espère vous revoir bientôt.",[1593,7290,7291,7294,7297,7300,7303,7306,7309,7312,7315,7318,7321,7324,7327,7330],{},[71,7292,7293],{},"Lorenzo,",[71,7295,7296],{},"Octavie et Alexandre,",[71,7298,7299],{},"Julien,",[71,7301,7302],{},"Fanny,",[71,7304,7305],{},"Denis,",[71,7307,7308],{},"Philippe et Loïc,",[71,7310,7311],{},"Benjamin,",[71,7313,7314],{},"Stephen,",[71,7316,7317],{},"Joan,",[71,7319,7320],{},"Damien,",[71,7322,7323],{},"Albane,",[71,7325,7326],{},"Christophe,",[71,7328,7329],{},"l'équipe Clever Cloud avec qui j'ai eu l'occasion d'échanger,",[71,7331,7332,7333],{},"et beaucoup d'autres dont je ne me souviens pas sur le moment. ",[1216,7334,7335],{},"Désolé, ma mémoire n'est pas la meilleure.",[35,7337,7338],{},"Si vous me croisez à une conférence, n'hésitez pas à venir me dire bonjour !",[35,7340,7341],{},"Takima, l'entreprise pour laquelle je travaille, n'était pas sponsor cette année, mais elle a envoyé trois reporters par jour pour couvrir la conférence. Elle a aussi envoyé un engineering manager et une personne dédiée pour encadrer l'équipe, car pour certains, c'était leur première conférence, et Devoxx France peut être très intimidant.",[35,7343,7344],{},[1707,7345],{"alt":7346,"src":7347},"L'équipe Takima du mercredi, de gauche à droite : Camille, Joseph, Estéban, Guillaume et Anaïs","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-1.webp",[35,7349,7350],{},[1707,7351],{"alt":7352,"src":7353},"L'équipe Takima du jeudi, de gauche à droite : Camille, Mathilde, Maxime, Delphine et Estéban","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-2.webp",[35,7355,7356],{},[1707,7357],{"alt":7358,"src":7359},"L'équipe Takima du vendredi, de gauche à droite : Estéban, Jonathan, Marion, Camille, Camille et Alexis","\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-3.webp",[51,7361,7363],{"id":7362},"et-ensuite","Et ensuite ?",[35,7365,7366,7367],{},"Mai est un mois relativement calme pour moi, mais juin va être vraiment chargé. ",[1216,7368,7369],{},"Mon Dieu, dans quoi me suis-je embarqué ?",[35,7371,7372,7373,3092,7376,1655,7381,106,7386,7391],{},"Les 11 et 12 juin, je pars à Lille pour la conférence DevLille. Ce sera ma première fois là-bas, et tout le monde me dit que c'est une super conférence, donc j'ai vraiment hâte d'y être. Je vais reparler de Vite, et j'ai vu que je serai dans le grand théâtre. C'est un peu intimidant. Comme pour MiXiT, j'irai à DevLille avec ",[39,7374,1792],{"href":1790,"rel":7375},[77],[39,7377,7380],{"href":7378,"rel":7379},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F",[77],"Elise",[39,7382,7385],{"href":7383,"rel":7384},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F",[77],"Victoire",[39,7387,7390],{"href":7388,"rel":7389},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurine-le-net\u002F",[77],"Laurine"," nous rejoindra également pour la conférence ! C'est bon de savoir que je ne serai pas seul et que je pourrai partager cette expérience avec elles.",[35,7393,7394,7395],{},"Ensuite, le 16 juin, j'ai mon talk aux Natural Change Leaders Days. Je parlerai de la capacité d'agir dans un monde en mouvement. Ce sera une conférence 100 % en ligne, donc je n'aurai pas besoin de voyager pour celle-ci. Ce ne sera pas un talk technique, et ce sera un nouveau talk que je dois écrire. ",[1216,7396,7397],{},"Oh, et vous devriez vraiment nous rejoindre pour cette conférence de 20 jours. Je n'ai jamais vu un programme aussi bon !",[35,7399,7400,7401,3092,7404,1655,7407,7410,7411],{},"Deux jours plus tard, les 18 et 19 juin, je pars au Luxembourg pour Voxxed Days Luxembourg. Là encore, je parlerai de Vite. C'est aussi une conférence à laquelle je n'ai jamais assisté, donc je suis vraiment curieux de la découvrir ! J'espère y voir de nouveaux visages ! Ne changeons pas une équipe qui gagne : ",[39,7402,1792],{"href":1790,"rel":7403},[77],[39,7405,7380],{"href":7378,"rel":7406},[77],[39,7408,7385],{"href":7383,"rel":7409},[77]," seront aussi de la partie. ",[1216,7412,7413],{},"Nom d'une pipe, vous êtes partout ?",[35,7415,7416,7417,7422,7423,7428,7429,7434,7435],{},"Enfin, le 27 juin, je vais à La Défense pour la conférence Asynconf, organisée par ",[39,7418,7421],{"href":7419,"rel":7420},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Florenzo-async\u002F",[77],"Lorenzo",". J'y aurai deux talks, l'un sur Vite et l'autre sur les signals et la réactivité frontend. Quand je regarde le programme, je me demande vraiment ce que je fais là. ",[39,7424,7427],{"href":7425,"rel":7426},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathieunebra\u002F",[77],"Mathieu Nebra",", cofondateur d'OpenClassrooms, ",[39,7430,7433],{"href":7431,"rel":7432},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanis-ayari\u002F",[77],"Anis Ayari",", créateur de contenu, ingénieur IA et auteur, participent aussi à cette conférence. ",[1216,7436,7437],{},"Aïe, le syndrome de l'imposteur est bien réel pour celle-ci.",[35,7439,7440],{},"Et voilà pour juin ! Une fois que ce sera terminé, vous n'aurez plus de mes nouvelles pendant au moins quelques jours. J'aurai besoin de récupérer de tout ça.",[35,7442,7443,7444],{},"Ensuite, je commencerai à travailler sur la seconde partie de l'année, avec davantage de conférences et un nouveau gros projet dont je ne peux pas encore parler. ",[1216,7445,7446],{},"Désolé, on achète où du sommeil ?",[1158,7448],{},[35,7450,7451],{},"Est-ce que je vous ai parlé de Mirakl ?",[1158,7453],{},[35,7455,7456],{},"Pour l'instant, tous les voyants sont au vert, donc ralentir ne fait pas partie du plan.",{"title":13,"searchDepth":14,"depth":14,"links":7458},[7459,7460,7461,7462,7463],{"id":6833,"depth":14,"text":6834},{"id":7159,"depth":14,"text":7160},{"id":7217,"depth":14,"text":7218},{"id":7266,"depth":14,"text":7267},{"id":7362,"depth":14,"text":7363},"84b7df2d-645f-4e3f-bd23-f3f07c1dcfc4","2026-05-11","IA, IA, et IA. Tech, tech, et tech. Mais qu'en est-il des personnes ? Les conférences sont bien plus que juste des talks.",{},"\u002Ffr\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks","---\ncontentId: 84b7df2d-645f-4e3f-bd23-f3f07c1dcfc4\ntitle: \"Devoxx France 2026 : les personnes plus que les talks\"\ndescription: \"IA, IA, et IA. Tech, tech, et tech. Mais qu'en est-il des personnes ? Les conférences sont bien plus que juste des talks.\"\ndate: 2026-05-11\n---\n\nJe sais, j'arrive un peu après la bataille sur celui-ci. Mais comme je le fais désormais pour la plupart des conférences auxquelles j'assiste, j'avais envie de partager quelques notes et réflexions sur Devoxx France 2026.\n\nDevoxx France est une conférence très différente en raison de sa taille. Avec 4 500 participants, 3 jours, 14 tracks et plus de 250 talks, elle dépasse largement n'importe quelle autre conférence technique en France. Certains intervenants ne parlent qu'à Devoxx, ce qui en fait la seule occasion de les voir.\n\n![La scène principale de Devoxx France, pendant l'ouverture](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fmain-stage.webp)\n\nEn même temps, et c'est mon avis personnel, la taille de la conférence rend sa pleine appréciation très difficile. Une fois un talk terminé, on court, littéralement, vers le suivant en espérant trouver une place. On est toujours dans l'urgence. On a toujours la FOMO à cause du nombre de tracks, et on ne peut pas simplement prendre le temps de ralentir, parler avec les gens et profiter de la conférence. On peut vite tomber dans le piège de vouloir voir un maximum de talks.\n\nCette année, j'ai vraiment pris le temps de discuter avec les gens. Il y a eu des conférences auxquelles je n'ai pas assisté pour libérer du temps. Par exemple, je voulais aller au meetup Dev with AI mardi soir, mais je discutais avec des amis, je n'ai pas vu le temps passer et je l'ai raté. C'est la loi des conférences, il faut faire des choix.\n\n_Pitié, ne me demandez pas ce qu'il en est de la nourriture à Devoxx. Sachez simplement que j'ai toujours quelques biscuits dans mon sac._\n\n## Mon programme\n\nCette année, pour la première fois, j'ai regardé le programme avant la conférence. Je voulais avoir un aperçu de l'ambiance générale de l'événement. Ces jours-ci, tout tourne autour de l'IA. J'ouvre X, je vois de l'IA. Je parle avec des amis, on parle d'IA. Je vais au travail, on pousse encore plus loin l'IA. Même avec ma date, ma famille ou des gens que je ne connais pas pendant un covoiturage, je parle d'IA. Ce sujet est partout. Devoxx ne fait pas exception.\n\nEn plus de ça, j'ai passé les quatre derniers mois à écrire un talk sur l'avenir des développeurs avec [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux\u002F).\n\nL'IA sature tellement nos vies que je voulais une petite pause. J'ai donc décidé de me concentrer sur des talks centrés sur l'humain.\n\nVoici mon programme pour la conférence :\n\n1. Mercredi 15 avril 2026\n    - **L'IA au service du bien commun : Agir avec raison dans l'incertain** par [Laurence Devillers](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurence-devillers-4041899\u002F)\n    - **Les échecs à l'ère de l'intelligence artificielle** par [Laurent Fressinet](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurent-fressinet-752236188\u002F)\n    - **Neurodiversité & Tech : vos “profils atypiques” sont votre plus grande force** par [Amélie Rigano](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Famelierigano\u002F)\n    - **The evolution of Software engineering** par [Theophane Gregoir](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftheophane-gregoir\u002F)\n    - **La transactionalité expliquée** par [Benjamin Cavy](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbenjamin-cavy-aa67a2a6\u002F)\n    - **Et si on débuguait notre rapport au temps ?** par [Albane Fagot-Veyron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falbane-fagot-veyron\u002F)\n    - **De zéro à des milliards de traces, le tracing distribué chez Winamax** par [Anthony Maffert](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanthonymaffert\u002F) et [Nicolas Fidel](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-fidel\u002F)\n    - **Kafka 4, fantastique ?** par [Damien Lucas](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdamien-lucas\u002F)\n    - **CLI-Driven Developer Experience : L'ingrédient secret d'une équipe alignée** par [Julien Briault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F)\n2. Jeudi 16 avril 2026\n    - **Le développeur face à l'IA : du prototypage rapide à la fin des intermédiaires ?** par [Nicolas Grenié](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolasgrenie\u002F)\n    - **L'IA sur le terrain : l'humain au cœur de la valeur** par [Marjory Canonne](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarjory-canonne-7aab19153\u002F)\n    - **L'ampoule qui en savait trop : voyage au cœur de la sécurité des objets connecté** par [Paul Pinault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpaulpinault\u002F)\n    - **Top 10 Event-Driven Architecture Pitfalls** par [Victor Rentea](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictor-rentea-trainer\u002F)\n    - **Transformer son API Doc en levier de conversion : best practices** par [Anne-Flore Bernard](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanne-flore-bernard\u002F)\n    - **Clean Code Matters** par [Marc LECANU](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarc-lecanu\u002F)\n    - **Les gardiens du Prompt: menaces et recettes de sécurité pour une production zen.** par [Philippe Laumay](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaumay\u002F) et [Loïc Gudet](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floic-gudet\u002F)\n    - **Au cœur d'une pipeline: démystifions Vite et ses plugins** par [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n    - **Debugging with IntelliJ IDEA** par [Anton Arhipov](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fantonarhipov\u002F)\n3. Vendredi 17 avril 2026\n    - **L'IA au défi de la condition humaine** par [Jean-Gabriel Ganascia](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjganascia\u002F)\n    - **La course à l'IA : La France risque-t-elle de devenir un \"territoire servant\" ?** par [Loup Cellard](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floup-cellard-b2502297\u002F)\n    - **Et si on ouvrait le capot de PostgreSQL ?** par [Benjamin Coenen](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcoenenbenjamin\u002F)\n    - **Et si écrire du SQL redevenait cool ?** par [Sébastien Ferrer](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fs%C3%A9bastien-ferrer-076ba052\u002F)\n    - **Kubernetes et la JVM** par [Jean-Michel Doudoux](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmdoudoux\u002F) et [Alain Regnier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falainregnier\u002F)\n    - **Éloge de la simplicité** par [Frédéric Leguédois](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffleguedois\u002F)\n    - **Performance de backend Spring : les techniques que tout dev devrait connaître** par [Florian Beaufumé](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffbeaufume\u002F)\n\n_Je dois avouer que j'aurais pu dormir un peu pendant l'un des talks. Désolé !_\n\nOui ! Ça fait beaucoup. Ça peut expliquer pourquoi j'ai terminé la conférence avec un mal de tête, fatigué et avec besoin de me reposer seul. _Mais ça en valait la peine._\n\nToutes les rediffusions seront disponibles sur la chaîne YouTube de Devoxx France, et je vous encourage vivement à les regarder !\n\n\u003CCallout w-full icon=\"i-simple-icons-youtube\" text=\"Devoxx France\" to=\"https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos\" target=\"_blank\" \u002F>\n\n## Les talks que j'ai aimés\n\nComme d'habitude, certains talks se sont vraiment démarqués, et je voulais les mettre en avant ici.\n\n1. **Éloge de la simplicité** par [Frédéric Leguédois](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffleguedois\u002F)\n\n    C'est peut-être le meilleur talk que j'aie vu à Devoxx, toutes éditions confondues. C'est un talk de 45 minutes, mais honnêtement, on ne les voit pas passer. Frédéric n'utilise pas de slides, et il construit son talk comme une pièce de théâtre, avec beaucoup d'humour. Malgré cette impression de légèreté, le talk va loin dans l'état actuel de l'agilité, l'usage excessif de la planification et l'importance de reconstruire notre rapport à la simplicité. Honnêtement, vous devriez simplement le regarder.\n\n    ![Frédéric sur scène, pendant son talk sur la simplicité](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ffrederic-simplicity.webp)\n\n2. **Et si on débuguait notre rapport au temps ?** par [Albane Fagot-Veyron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falbane-fagot-veyron\u002F)\n\n    C'est un talk profondément centré sur l'humain, et j'ai adoré. Albane parle de notre rapport au temps, de la manière dont nous le percevons et de la façon dont il influence notre quotidien et notre travail. \"Je n'ai pas le temps\" est une phrase que nous entendons souvent, ou que nous prononçons nous-mêmes, mais avons-nous déjà pris le temps de creuser le sujet ? C'est vraiment bien écrit et agréable à écouter. Il est déjà dans ma liste à voir.\n\n    ![Albane sur scène, pendant son talk sur notre rapport au temps](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Falbane-time.webp)\n\n3. **L'ampoule qui en savait trop : voyage au cœur de la sécurité des objets connecté** par [Paul Pinault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpaulpinault\u002F)\n\n    Je suis venu à ce talk sans aucune attente, et j'ai passé un moment formidable. Au début, Paul montre qu'allumer une lumière peut se résumer à 50 lignes de code, avant d'expliquer tous les prérequis imposés par l'Union européenne en matière de sécurité, de vie privée et de maintenance, ce qui rend la tâche beaucoup plus complexe, et donc plus intéressante. La vraie complexité n'est pas dans l'ampoule elle-même, mais dans l'écosystème autour, nécessaire pour la rendre conforme. Ce talk très concret peut facilement servir d'analogie pour n'importe quel projet.\n\n    ![Paul sur scène, pendant son talk sur la sécurité des objets connectés](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fpaul-connected-objects.webp)\n\n## Mon talk\n\nCette année, j'ai donné un talk sur Vite et son pipeline intitulé \"Au cœur d'une pipeline : démystifions Vite et ses plugins\".\n\n![Estéban devant le panneau d'annonce, avant le début du talk](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-panel.webp)\n\nC'est un talk de 45 minutes dans lequel j'explique comment fonctionne Vite, à quoi ressemble son pipeline et comment écrire un plugin pour lui. À travers plusieurs exemples concrets, j'essaie de démystifier le fonctionnement interne de Vite et de montrer à quel point il peut être puissant, tout en restant accessible, lorsqu'on commence à écrire des plugins pour lui.\n\n![Estéban derrière le pupitre, avant le début du talk](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-desk.webp)\n\n![Mon selfie avec l'audience, pendant mon talk sur Vite](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Fvite-talk-selfie.webp)\n\nJ'avais déjà donné le même talk à [MiXiT 2026](.\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026.md), et je n'ai pas trouvé le temps de l'améliorer pour Devoxx. Moins d'une semaine, c'est vraiment très court pour améliorer un talk. En revanche, beaucoup d'améliorations arrivent en juin, et j'en parlerai davantage sur [LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25).\n\nVous pouvez retrouver les slides du talk sur [talks.soubiran.dev](https:\u002F\u002Ftalks.soubiran.dev).\n\n## Les personnes d'abord\n\nPour la première fois dans une conférence, j'ai priorisé les personnes plutôt que les talks.\n\nJe suis convaincu que la partie la plus précieuse d'une conférence n'est pas la conférence elle-même. On peut toujours retrouver les talks plus tard sur YouTube, donc le contenu n'est pas exclusif à l'événement. _Mais je comprends la valeur d'assister à un talk en direct : c'est une expérience différente, et cela oblige à être plus présent et plus concentré._\n\nMais les gens, qu'il s'agisse des intervenants qui rendent la conférence possible, des participants curieux qui ont envie d'apprendre, ou même de ceux qui sont simplement là pour découvrir de nouvelles choses, sont la vraie valeur d'une conférence. Avec la montée récente de l'IA, où nous avons tous tendance à interagir surtout avec des machines, il devient encore plus important de prioriser les interactions humaines.\n\nAinsi, du dîner des intervenants à la soirée Meet and Greet, et des sponsors aux participants, j'ai passé beaucoup de temps à discuter. J'ai découvert leurs histoires, comment ils sont arrivés à Devoxx, pourquoi ils donnent des talks en conférence ou apprécient l'entreprise pour laquelle ils travaillent, les dernières nouvelles des entreprises, ou tout simplement à parler de la vie. C'est aussi une excellente occasion de mettre un visage sur le nom de personnes que l'on ne connaît que via LinkedIn. _Et de ressentir l'étrange malaise des interactions parasociales._\n\nÀ travers cette section, je voulais remercier toutes les personnes avec qui j'ai eu l'occasion de parler pendant la conférence. Ce fut un vrai plaisir, et j'espère vous revoir bientôt.\n\n- Lorenzo,\n- Octavie et Alexandre,\n- Julien,\n- Fanny,\n- Denis,\n- Philippe et Loïc,\n- Benjamin,\n- Stephen,\n- Joan,\n- Damien,\n- Albane,\n- Christophe,\n- l'équipe Clever Cloud avec qui j'ai eu l'occasion d'échanger,\n- et beaucoup d'autres dont je ne me souviens pas sur le moment. _Désolé, ma mémoire n'est pas la meilleure._\n\nSi vous me croisez à une conférence, n'hésitez pas à venir me dire bonjour !\n\nTakima, l'entreprise pour laquelle je travaille, n'était pas sponsor cette année, mais elle a envoyé trois reporters par jour pour couvrir la conférence. Elle a aussi envoyé un engineering manager et une personne dédiée pour encadrer l'équipe, car pour certains, c'était leur première conférence, et Devoxx France peut être très intimidant.\n\n![L'équipe Takima du mercredi, de gauche à droite : Camille, Joseph, Estéban, Guillaume et Anaïs](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-1.webp)\n\n![L'équipe Takima du jeudi, de gauche à droite : Camille, Mathilde, Maxime, Delphine et Estéban](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-2.webp)\n\n![L'équipe Takima du vendredi, de gauche à droite : Estéban, Jonathan, Marion, Camille, Camille et Alexis](\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks\u002Ftakima-team-day-3.webp)\n\n## Et ensuite ?\n\nMai est un mois relativement calme pour moi, mais juin va être vraiment chargé. _Mon Dieu, dans quoi me suis-je embarqué ?_\n\nLes 11 et 12 juin, je pars à Lille pour la conférence DevLille. Ce sera ma première fois là-bas, et tout le monde me dit que c'est une super conférence, donc j'ai vraiment hâte d'y être. Je vais reparler de Vite, et j'ai vu que je serai dans le grand théâtre. C'est un peu intimidant. Comme pour MiXiT, j'irai à DevLille avec [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F) et [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F). [Laurine](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurine-le-net\u002F) nous rejoindra également pour la conférence ! C'est bon de savoir que je ne serai pas seul et que je pourrai partager cette expérience avec elles.\n\nEnsuite, le 16 juin, j'ai mon talk aux Natural Change Leaders Days. Je parlerai de la capacité d'agir dans un monde en mouvement. Ce sera une conférence 100 % en ligne, donc je n'aurai pas besoin de voyager pour celle-ci. Ce ne sera pas un talk technique, et ce sera un nouveau talk que je dois écrire. _Oh, et vous devriez vraiment nous rejoindre pour cette conférence de 20 jours. Je n'ai jamais vu un programme aussi bon !_\n\nDeux jours plus tard, les 18 et 19 juin, je pars au Luxembourg pour Voxxed Days Luxembourg. Là encore, je parlerai de Vite. C'est aussi une conférence à laquelle je n'ai jamais assisté, donc je suis vraiment curieux de la découvrir ! J'espère y voir de nouveaux visages ! Ne changeons pas une équipe qui gagne : [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F) et [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F) seront aussi de la partie. _Nom d'une pipe, vous êtes partout ?_\n\nEnfin, le 27 juin, je vais à La Défense pour la conférence Asynconf, organisée par [Lorenzo](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Florenzo-async\u002F). J'y aurai deux talks, l'un sur Vite et l'autre sur les signals et la réactivité frontend. Quand je regarde le programme, je me demande vraiment ce que je fais là. [Mathieu Nebra](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathieunebra\u002F), cofondateur d'OpenClassrooms, [Anis Ayari](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fanis-ayari\u002F), créateur de contenu, ingénieur IA et auteur, participent aussi à cette conférence. _Aïe, le syndrome de l'imposteur est bien réel pour celle-ci._\n\nEt voilà pour juin ! Une fois que ce sera terminé, vous n'aurez plus de mes nouvelles pendant au moins quelques jours. J'aurai besoin de récupérer de tout ça.\n\nEnsuite, je commencerai à travailler sur la seconde partie de l'année, avec davantage de conférences et un nouveau gros projet dont je ne peux pas encore parler. _Désolé, on achète où du sommeil ?_\n\n---\n\nEst-ce que je vous ai parlé de Mirakl ?\n\n---\n\nPour l'instant, tous les voyants sont au vert, donc ralentir ne fait pas partie du plan.\n",{"title":6805,"description":7466},{"loc":7468,"images":7472},[7473,7474,7475,7476,7477,7478,7479,7480,7481,7482],{"loc":6819},{"loc":7182},{"loc":7198},{"loc":7214},{"loc":7227},{"loc":7236},{"loc":7242},{"loc":7347},{"loc":7353},{"loc":7359},"fr\u002Fposts\u002Fdevoxx-france-2026-prioritizing-people-over-talks","jg_CEKc8Fnhklp497pcYXlG3pvgnHYQyFg2ggl41m2E",{"id":7486,"title":7487,"body":7488,"contentId":8164,"date":8165,"description":8166,"extension":18,"meta":8167,"navigation":20,"path":8168,"rawbody":8169,"robots":23,"schemaOrg":23,"seo":8170,"sitemap":8171,"stem":8187,"__hash__":8188},"posts\u002Ffr\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026.md","Montagnes russes inattendues d'émotions à MiXiT 2026",{"type":7,"value":7489,"toc":8154},[7490,7503,7506,7512,7515,7527,7533,7550,7553,7556,7564,7573,7583,7586,7589,7595,7598,7601,7667,7673,7676,7682,7685,7742,7748,7755,7768,7780,7786,7792,7798,7802,7805,7854,7864,7868,7874,7878,7884,7887,7896,7899,7905,7911,7914,7917],[35,7491,7492,7493,106,7498,7502],{},"C'était ma deuxième fois à ",[39,7494,7497],{"href":7495,"rel":7496},"https:\u002F\u002Fmixitconf.org\u002F",[77],"MiXiT",[39,7499,7501],{"href":7500},".\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025","La première remonte à un an",", mais j'ai l'impression que c'était hier.",[35,7504,7505],{},"MiXiT est une conférence tech qui s'est tenue à Lyon sur deux jours, autour de la technologie, de l'éthique et de la diversité. Par rapport à l'année dernière, la conférence était tout aussi chaleureuse et accueillante, avec plus de 600 participants.",[35,7507,7508],{},[1707,7509],{"alt":7510,"src":7511},"Vue de Lyon depuis Fourvière","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Flyon-point-of-view-from-fourviere.webp",[35,7513,7514],{},"Pourtant, cette fois-ci, c'était différent. Le contexte était différent.",[35,7516,7517,7518,3092,7521,1655,7524,215],{},"Pour la première fois, j'allais assister à une conférence avec d'autres personnes, et pas n'importe qui, mais des personnes formidables de Takima, l'entreprise où je travaille. J'allais vivre la conférence avec ",[39,7519,1792],{"href":1790,"rel":7520},[77],[39,7522,7380],{"href":7378,"rel":7523},[77],[39,7525,7385],{"href":7383,"rel":7526},[77],[35,7528,7529],{},[1707,7530],{"alt":7531,"src":7532},"De gauche à droite : Estéban, Victoire, Marion et Elise, photo par Elise","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fvictoire-elise-marion-and-me.webp",[35,7534,7535,7536,1655,7539,7542,7543,7546,7547],{},"Elles avaient toutes les deux été acceptées pour prendre la parole à MiXiT pour la première fois sur scène. ",[39,7537,1792],{"href":1790,"rel":7538},[77],[39,7540,7380],{"href":7378,"rel":7541},[77]," allaient présenter ensemble un talk sur les feature flags dans des environnements critiques en production. ",[39,7544,7385],{"href":7383,"rel":7545},[77]," allait parler de mutation testing. Elles le méritent vraiment. Elles ont mis tellement de travail, de passion et d'amour dans leurs talks. ",[1216,7548,7549],{},"Bravo, les filles !",[35,7551,7552],{},"Pour une première conférence, elles en ont vraiment choisi une bonne pour monter sur scène. Les participants sont vraiment bienveillants et compréhensifs, et les organisateurs sont présents et bienveillants. MiXiT est clairement un bon endroit pour démarrer un parcours de speaker.",[35,7554,7555],{},"Mais en même temps, nous aurions dû être cinq speakers, pas quatre.",[35,7557,7558,7559,7563],{},"Depuis novembre, je préparais un talk sur le futur des développeurs avec une amie qui s'appelle ",[39,7560,6845],{"href":7561,"rel":7562},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux",[77],". Jusqu'ici, j'avais surtout donné des talks sur des sujets techniques. Celui-ci allait être différent. D'abord, ce serait un talk non technique, écrit à partir d'articles scientifiques, de recherches, de billets de blog, de livres et d'entretiens avec d'autres développeurs. Ensuite, ce serait un duo, de l'écriture jusqu'à la présentation.",[35,7565,7566,7569,7570],{},[39,7567,6845],{"href":7561,"rel":7568},[77]," était à la fois très enthousiaste et inquiète, parce que c'était aussi sa première fois sur scène. Malheureusement, pour des raisons personnelles, elle a dû arrêter de travailler sur le talk quelques jours avant la conférence, et elle n'a pas pu me rejoindre à MiXiT. ",[1216,7571,7572],{},"Je sais qu'elle essaie vraiment de faire en sorte que ça marche. Ce sera pour une autre fois, le talk en duo.",[35,7574,7575,7576],{},"Je me sentais terriblement mal pour elle. J'étais à ça d'annuler le talk, mais nous étions engagés auprès des organisateurs. Je ne voulais pas les laisser tomber. Ils nous avaient accordé leur confiance, et nous devions l'honorer. Alors j'ai continué à me pousser pour finir le talk jusqu'à la dernière minute. ",[1216,7577,7578,7579,7582],{},"Pour être totalement transparent, la semaine avant la conférence a peut-être été l'une des pires semaines de ma vie. La conférence sur Vite était presque terminée, mais pas tout à fait, celle sur le futur des développeurs était à moins de la moitié, et ",[39,7580,6845],{"href":7561,"rel":7581},[77]," ne pouvait pas me rejoindre. Sensation horrible.",[51,7584,7249],{"id":7585},"mixit-2026",[35,7587,7588],{},"Tout a commencé le mercredi 15 avril 2026, avec le dîner des speakers, organisé dans un restaurant chic près de la Saône. On nous a servi du fromage, de la charcuterie et du vin. Honnêtement, la combinaison parfaite pour passer une excellente soirée. C'était aussi une belle occasion de rencontrer d'autres speakers et d'échanger sur nos talks. J'adore ces moments où les gens essaient de te vendre leur talk. Au fond, c'est toujours un plaisir d'entendre quelqu'un parler de sa passion. Et ça m'a aidé à planifier les deux jours suivants !",[35,7590,7591,7592],{},"Comme pour tous les événements, ne demandez pas de photos ! Ce qui s'est passé au dîner des speakers reste au dîner des speakers. ",[1216,7593,7594],{},"J'ai peut-être passé un peu trop de temps à discuter près des boissons.",[35,7596,7597],{},"Vers 22 h, nous avons quitté le restaurant pour aller à l'hôtel nous reposer en vue du lendemain. J'étais vraiment fatigué, mais j'avais une conférence à terminer. J'ai travaillé jusqu'à 1 h 30 du matin pour essayer de finir le talk sur le futur des développeurs, et j'ai rédigé le post LinkedIn pour le lendemain matin.",[35,7599,7600],{},"7 heures, l'heure de se réveiller, de continuer à travailler sur le talk et de se préparer pour la conférence. Pas beaucoup de sommeil, mais je n'avais pas vraiment le choix. Le premier jour, jeudi 16 avril 2026, j'ai vu les talks suivants :",[1593,7602,7603,7613,7621,7631,7641,7651,7661],{},[71,7604,7605,6865,7608],{},[6862,7606,7607],{},"L'intelligence artificielle générale n'aura pas lieu",[39,7609,7612],{"href":7610,"rel":7611},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthibault-prevost\u002F",[77],"Thibault Prévost",[71,7614,7615,6865,7618],{},[6862,7616,7617],{},"Au cœur d'une pipeline : démystifions Vite et ses plugins",[39,7619,7045],{"href":7043,"rel":7620},[77],[71,7622,7623,6865,7626],{},[6862,7624,7625],{},"Le mythe de la portabilité cloud avec Kubernetes",[39,7627,7630],{"href":7628,"rel":7629},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffederico-fregosi\u002F",[77],"Federico Fregosi",[71,7632,7633,6865,7636],{},[6862,7634,7635],{},"Déconstruisons les frameworks JS pour mieux réinventer le web !",[39,7637,7640],{"href":7638,"rel":7639},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnirinarabeson\u002F",[77],"Nirina Rabeson",[71,7642,7643,6865,7646],{},[6862,7644,7645],{},"Mes trains Jouef passent au numérique, avec TinyGo",[39,7647,7650],{"href":7648,"rel":7649},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fflorian-forestier\u002F",[77],"Florian Forestier",[71,7652,7653,6865,7656],{},[6862,7654,7655],{},"Assistant IA pour le code : comment ça marche ? J'en ai codé un !",[39,7657,7660],{"href":7658,"rel":7659},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fxnopre\u002F",[77],"Xavier Nopre",[71,7662,7663,7666],{},[6862,7664,7665],{},"L'indépendance des médias... indés"," par David Dufresne",[35,7668,7669],{},[1707,7670],{"alt":7671,"src":7672},"Photo de l'amphithéâtre pendant le mot d'introduction des organisateurs","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Famphitheater-during-introduction.webp",[35,7674,7675],{},"Oui, j'étais le premier talk, juste après la keynote d'ouverture. Pas le temps de souffler, juste brancher l'ordinateur, ouvrir les slides, monter sur scène et commencer à parler. Grâce à l'adrénaline, la fatigue n'a pas vraiment été un problème pendant le talk. En revanche, après, c'était vraiment dur et intense.",[35,7677,7678,7679],{},"Après cette première journée de conférence, MiXiT a organisé une soirée à l'Hôtel de Ville de Lyon. C'est un endroit vraiment magnifique et une occasion unique de le visiter tout en profitant de quelques verres et en prenant le temps d'échanger avec d'autres participants. Trouver du temps pour parler avec les gens pendant une conférence est vraiment difficile, donc ces moments sont précieux. Après une longue journée et une longue soirée, nous sommes rentrés à l'hôtel. J'avais envie de me reposer, mais je devais finir un talk, oui, encore. ",[1216,7680,7681],{},"Dormir pendant une conférence ? Je devrais vraiment essayer ! Ça a l'air cool !",[35,7683,7684],{},"Le lendemain, même heure de réveil : 7 heures. À ce stade, on ne parle même plus de fatigue. J'étais complètement épuisé, et même si j'avais beaucoup travaillé sur le talk, il n'était pas terminé quand nous avons quitté l'hôtel. J'ai dû le finir pendant la keynote d'ouverture et le premier talk. Le deuxième jour, vendredi 17 avril 2026, j'ai assisté aux talks suivants :",[1593,7686,7687,7697,7707,7719,7732],{},[71,7688,7689,6865,7692],{},[6862,7690,7691],{},"Wikipédia à 25 ans : De l'utopie numérique au pilier du savoir commun",[39,7693,7696],{"href":7694,"rel":7695},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fr%C3%A9my-gerbet-511249a1\u002F",[77],"Rémy Gerbet",[71,7698,7699,6865,7702],{},[6862,7700,7701],{},"Conception de logiciels : le match humains vs AI",[39,7703,7706],{"href":7704,"rel":7705},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fazeau\u002F",[77],"Olivier Azeau",[71,7708,7709,6865,7712,1655,7716],{},[6862,7710,7711],{},"Le futur du développeur : IA, compétences et obsolescence",[39,7713,7715],{"href":7561,"rel":7714},[77],"Julie Mouveroux",[39,7717,7045],{"href":7043,"rel":7718},[77],[71,7720,7721,6865,7724,1655,7728],{},[6862,7722,7723],{},"Déployer souvent, stresser moins : feature flags en prod critique",[39,7725,7727],{"href":1790,"rel":7726},[77],"Marion Chineaud",[39,7729,7731],{"href":7378,"rel":7730},[77],"Elise Souvannavong",[71,7733,7734,6865,7737],{},[6862,7735,7736],{},"Pas de managers, pas de réunions, pas de problème",[39,7738,7741],{"href":7739,"rel":7740},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsofialescanocarroll\u002F",[77],"Sofia Lescano Carroll",[35,7743,7744,7745],{},"Pour mon talk, certaines slides n'étaient pas finies, et les speaker notes n'étaient pas écrites. Je n'utilise jamais les speaker notes, mais je devais le faire pour ce talk parce qu'il allait être complètement improvisé. Ce n'est pas ce que je maîtrise le mieux, mais je n'avais pas le choix. Faisons en sorte que mes trente derniers talks servent à quelque chose pour celui-là ! ",[1216,7746,7747],{},"Vous vous demandez peut-être si j'étais stressé. Je ne l'étais pas. Fatigué, bien sûr, mais pas stressé. Le stress n'allait pas m'aider ni rendre mon talk meilleur, alors j'ai simplement accepté la situation, le retard, et j'ai fait de mon mieux pour honorer le travail de Julie sur le talk et la confiance des organisateurs.",[35,7749,7750,7751,7754],{},"Au même moment, ",[39,7752,7385],{"href":7383,"rel":7753},[77]," présentait son talk sur le mutation testing. Je n'ai pas pu y assister, mais je sais que ça s'est vraiment bien passé.",[35,7756,7757,7758,1655,7761,7764,7765],{},"Ensuite, nous avons tous assisté au talk de ",[39,7759,1792],{"href":1790,"rel":7760},[77],[39,7762,7380],{"href":7378,"rel":7763},[77]," sur les feature flags. C'était vraiment bien, et le public était à fond dedans. J'étais tellement fier d'elles, et je sais qu'elles étaient tellement fières d'elles-mêmes. Elles le méritaient vraiment. ",[1216,7766,7767],{},"Pour un premier talk, je suis sans voix.",[35,7769,7770,7771,3092,7774,1655,7777,215],{},"À la fin de cette deuxième journée, les talks étaient terminés et les participants étaient heureux d'avoir vu mes deux talks. Bien sûr, cela a représenté énormément de travail, mais ça n'aurait pas été possible sans le soutien de ",[39,7772,1792],{"href":1790,"rel":7773},[77],[39,7775,7380],{"href":7378,"rel":7776},[77],[39,7778,7385],{"href":7383,"rel":7779},[77],[35,7781,7782],{},[1707,7783],{"alt":7784,"src":7785},"De gauche à droite : Victoire, Elise, Marion et Estéban au milieu","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fvictoire-elise-marion-and-me-in-the-middle.webp",[35,7787,7788],{},[1707,7789],{"alt":7790,"src":7791},"Photo de Marion, Elise et Victoire à l'Hôtel de Ville de Lyon pendant la soirée","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmarion-elise-and-victoire-in-lyon-city-hall.webp",[35,7793,7794,7795],{},"Le fait de ne pas être seul a tout changé. C'était tellement agréable de partager cette expérience, d'échanger sur les talks, de parler de nos ressentis, et simplement de nous soutenir et de nous donner de la force les uns aux autres. ",[1216,7796,7797],{},"Merci infiniment !",[51,7799,7801],{"id":7800},"les-talks-qui-mont-marqué","Les talks qui m'ont marqué",[35,7803,7804],{},"Plus j'assiste à des conférences, plus la barre monte pour mes « talks highlights ». J'ai envie de partager les talks que j'ai vraiment aimés et que je pense mériter d'être partagés. Comme toujours, cette liste est subjective, et je vous encourage à aller voir le programme de la conférence pour découvrir d'autres talks qui pourraient vous intéresser.",[68,7806,7807,7833],{},[71,7808,7809,6865,7811,1655,7814,7817,7819,7820,1655,7823,7826,7827,7829],{},[6862,7810,7723],{},[39,7812,7727],{"href":1790,"rel":7813},[77],[39,7815,7731],{"href":7378,"rel":7816},[77],[7174,7818],{},"Vous vous dites peut-être que je suis un peu biaisé en mettant ce talk à la première place, mais c'était la première fois que je le voyais, et j'ai vraiment été impressionné. Le talk est très complet, couvrant différents types de feature flags, de l'implémentation à l'usage et aux cas d'usage. C'était bien structuré, avec un bon rythme et des blagues drôles au bon moment. On sent la complicité entre ",[39,7821,1792],{"href":1790,"rel":7822},[77],[39,7824,7380],{"href":7378,"rel":7825},[77],", ainsi que la passion qu'elles ont pour le sujet.",[7174,7828],{},[1707,7830],{"alt":7831,"src":7832},"Photo de Marion et Elise pendant leur présentation","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmarion-and-elise-presenting.webp",[71,7834,7835,6865,7837,7840,7842,7843,7847,7848,7850],{},[6862,7836,7645],{},[39,7838,7650],{"href":7648,"rel":7839},[77],[7174,7841],{},"Pas un talk technique, mais un talk profondément humain, sincère et passionné. ",[39,7844,7846],{"href":7648,"rel":7845},[77],"Florian"," a partagé sa passion pour les trains miniatures et la manière dont il a réussi à les rendre numériques et pilotables via le web, en comparaison avec les anciens modèles analogiques. Au-delà de l'histoire, le cas d'usage réel de Raspberry Pi avec un logiciel à la bonne developer experience, TinyGo, était vraiment intéressant. Je ne sais pas encore comment en faire quelque chose aujourd'hui, mais je sais que le jour où j'en aurai besoin, je me souviendrai de ce talk. Écouter des personnes passionnées parler de leur passion, c'est toujours si agréable et inspirant.",[7174,7849],{},[1707,7851],{"alt":7852,"src":7853},"Photo de Florian pendant sa présentation","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fflorian-presenting.webp",[35,7855,7856,7857,7860,7861],{},"J'aurais adoré assister au talk de ",[39,7858,7385],{"href":7383,"rel":7859},[77],", « Testez vos tests avant qu'ils ne vous trahissent : le mutation testing ! ». Malheureusement, il avait lieu en même temps que le mien. J'espère vraiment pouvoir y assister dans une autre conférence. Elle a travaillé tellement dur dessus, et je sais que ça va être incroyable ! ",[1216,7862,7863],{},"J'ai trop hâte de le voir à DevLille en juin !",[51,7865,7867],{"id":7866},"vite-futur-du-développeur-web-et-podcast","Vite, futur du développeur web et podcast",[35,7869,7870,7871],{},"Cette année, j'ai eu l'opportunité de donner deux talks à MiXiT. L'année dernière aussi, j'en avais donné deux, mais l'un d'eux était un talk de lancement sur un sujet très léger. Cette année, j'avais deux conférences de 45 minutes sur des sujets très techniques. C'était un vrai défi pour moi, surtout avec le contexte du second talk. ",[1216,7872,7873],{},"Je ne recommande pas de construire deux talks en même temps. C'est beaucoup trop de travail.",[1929,7875,7877],{"id":7876},"au-cœur-dune-pipeline-démystifions-vite-et-ses-plugins","\"Au cœur d'une pipeline : démystifions Vite et ses plugins\"",[35,7879,7880],{},[1707,7881],{"alt":7882,"src":7883},"Selfie d'Estéban avec le public au début de son talk sur Vite","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-selfie-with-audience-before-vite-talk.webp",[35,7885,7886],{},"C'est le plus gros talk technique que j'ai jamais donné. Il fait 70 slides avec énormément de contenu dynamique. Sa particularité, c'est que le public est le héros du talk. Les gens influencent le talk en choisissant des options pendant celui-ci, ce qui rend chaque replay différent. Comme vous pouvez l'imaginer, cela signifie écrire plusieurs chemins dans les slides et beaucoup plus de contenu qu'un talk classique.",[35,7888,7889,7890,7895],{},"J'ai aussi dû développer tout le système sous-jacent pour que cela fonctionne. Même si cela reposait sur ",[39,7891,7894],{"href":7892,"rel":7893},"https:\u002F\u002Finalia.app",[77],"Inalia",", le SaaS que je construis pour rendre les talks plus engageants avec des interactions en temps réel, j'ai dû développer beaucoup de nouvelle logique frontend pour que cela colle aux besoins du talk.",[35,7897,7898],{},"J'écris tous mes talks avec Slidev, un outil qui permet d'écrire ses slides en Markdown. Ce qui est intéressant avec Slidev, c'est qu'un talk est en réalité un site Vite et Vue. Donc, en l'utilisant, on a toute la puissance de Vite et Vue pour créer ses slides. On peut créer n'importe quelle slide interactive, avec n'importe quelle animation, et j'en ai bien profité pour construire un talk très dynamique. Bonne idée, mais très chronophage ! Heureusement, j'ai eu l'aide de l'IA pour développer la majeure partie de la logique.",[35,7900,7901,7902],{},"Heureusement, j'ai eu l'opportunité de donner ce talk lors d'un événement interne à L'Atelier à Paris, deux semaines avant la conférence. J'ai aussi eu la chance de le faire en crash test chez Takima devant beaucoup de mes collègues. Ils m'ont donné un feedback précieux pour l'améliorer et le rendre meilleur. ",[1216,7903,7904],{},"Et je sais qu'il est meilleur maintenant. Merci à toutes les personnes qui ont pris le temps de me faire des retours dessus !",[35,7906,7907],{},[1707,7908],{"alt":7909,"src":7910},"Photo d'Estéban pendant son talk sur Vite, photo par Marion","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-presenting-about-vite.webp",[35,7912,7913],{},"Malgré tout, j'en suis loin d'être pleinement satisfait. Je sais qu'il pourrait être tellement meilleur, même après avoir travaillé dessus pendant des mois, donc je vais continuer à l'améliorer. Rendons-le meilleur pour DevLille en juin !",[35,7915,7916],{},"Je vais consacrer un billet de blog entier à la création de ce talk, avec tous les détails techniques et les défis auxquels j'ai fait face. Il y a énormément de travail caché derrière, et beaucoup de personnes m'ont posé des questions à ce sujet !",[7918,7919,7922,7926,7936,7939,7950,7953,7956,7959,7965,7971,7977,7984,7987,7990,7996,8003,8017,8023,8026,8034,8037,8041,8054,8057,8069,8075,8077,8083,8089,8092,8108,8122,8140,8143,8149,8151],"callout-read-more",{"href":7920,"text":7921},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Ftalks","Découvrez le dépôt GitHub du talk",[1929,7923,7925],{"id":7924},"le-futur-du-développeur-ia-compétences-et-obsolescence","\"Le futur du développeur : IA, compétences et obsolescence\"",[35,7927,7928,7929,7932,7933],{},"L'histoire de ce talk commence en novembre 2025. Après être allé au théâtre avec ",[39,7930,6845],{"href":7561,"rel":7931},[77],", nous nous sommes assis dans un bar pour discuter de plein de choses. À un moment, je lui ai demandé si elle serait intéressée par l'idée de créer un talk ensemble. Je lui ai demandé de ne pas répondre tout de suite, mais d'y réfléchir et de me dire si elle était intéressée. ",[1216,7934,7935],{},"Je déteste la pression sociale, et je ne veux pas d'un « oui » juste à cause du moment. Créer un talk demande tellement de travail que je veux être sûr que la personne est vraiment intéressée et motivée à le faire.",[35,7937,7938],{},"Elle a répondu quelques heures plus tard qu'elle était partante. J'étais tellement heureux de lire ce message !",[35,7940,7941,7942],{},"À ce moment-là de l'histoire, il faut savoir que nous ne connaissions pas le sujet du talk, Julie n'avait jamais donné de talk auparavant, et je n'avais jamais donné de talk à deux. Pour compliquer encore un peu les choses, nous ne travaillions pas dans le même domaine et nous avions un mois pour soumettre cela à un CFP. Franchement, quelle idée folle ! ",[1216,7943,7944,7945,7949],{},"Deux règles : les choses commencent souvent dans un bar, et ",[39,7946,7948],{"href":7947},".\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things","on peut juste faire des choses",". On n'a pas besoin d'être prêt.",[35,7951,7952],{},"Après quelques sessions de brainstorming, nous avons, enfin, trouvé le sujet du talk, surtout grâce à elle. Nous voulions parler du futur du travail sous l'angle de l'IA. Comme les conférences auxquelles nous soumettons sont surtout techniques, nous avons ciblé le futur des développeurs. Cela a aussi permis de réduire un peu le périmètre du talk pour le rendre « plus simple » à écrire.",[35,7954,7955],{},"Ensuite, nous avons commencé à écrire le CFP. Pour cela, il nous fallait un plan pour le talk. Cela signifiait faire des recherches initiales pour mieux comprendre les tendances, la réalité et la direction que prenaient les choses. Ce n'était pas une étape facile, mais nous l'avons traversée. Quelques jours avant Noël, nous avons soumis le CFP à MiXiT et Devoxx France.",[35,7957,7958],{},"Honnêtement, même si nous avions été refusés aux deux, nous aurions quand même tout donné pour en faire quelque chose dont nous pourrions être fiers.",[35,7960,7961,7962],{},"Même avant l'acceptation, j'ai commencé à travailler dessus parce que je savais que ce serait énormément de travail. J'ai commencé à lire des papiers, des articles et des livres, et à regarder des vidéos sur le sujet. C'était compliqué parce qu'on ne peut pas vraiment chercher « futur du développeur » sur Google. Personne n'a vraiment pris le temps d'écrire là-dessus, et les quelques articles qui existent sont biaisés ou pas pertinents. Nous avons dû lire des dizaines d'articles, en extraire les informations utiles, et leur donner du sens autour du plan que nous avions défini des semaines plus tôt. J'ai passé beaucoup de dimanches matin à lire des articles que j'aimerais ne plus jamais relire tant ils étaient traumatisants. ",[1216,7963,7964],{},"Une bonne musique est recommandée pour éviter de sombrer dans la peur et le désespoir.",[35,7966,7967,7968],{},"Nous avons aussi interviewé des développeurs du secteur pour recueillir leur point de vue sur le sujet. C'était très utile pour éviter de rester dans notre bulle et pour mieux comprendre la réalité. Nous avons eu de très bonnes conversations avec eux et nous avons beaucoup appris grâce à elles. ",[1216,7969,7970],{},"Merci infiniment à tous les développeurs qui ont pris le temps d'échanger avec nous !",[35,7972,7973,7974],{},"Ce processus a été un vrai défi, et j'ai mis en place beaucoup d'automatisation pour le faire fonctionner. Je ne vais pas entrer dans les détails ici parce que je consacrerai un billet de blog entier à la création de ce talk. Ce processus a pris absolument tout notre temps. Nous nous voyions régulièrement pour discuter du contenu, nous assurer que nous restions bien alignés en continu, et simplement travailler dessus ensemble. ",[1216,7975,7976],{},"Honnêtement, nous passions plus de temps à parler de nos vies que du talk. Et c'est très bien comme ça. Après tout, ce n'est pas notre travail.",[35,7978,7979,7980,7983],{},"Malheureusement, quelques jours avant la conférence, ",[39,7981,6845],{"href":7561,"rel":7982},[77]," a dû arrêter de travailler sur le talk pour des raisons personnelles. Je me sentais vraiment mal pour elle, et j'étais vraiment triste. J'ai continué à travailler sur le talk jusqu'à la dernière minute. Pendant les derniers jours, je me réveillais une heure plus tôt pour travailler dessus. J'allais travailler, puis après le travail, je continuais jusqu'à minuit ou 1 heure du matin. Tous les jours.",[35,7985,7986],{},"Ensuite, j'ai continué à travailler dessus pendant la conférence. La veille du début de MiXiT, moins de 50 % des slides étaient terminées. J'ai continué à travailler dessus pendant la conférence, avant et après le dîner des speakers, après la soirée au milieu de la conférence, et jusqu'à 10 minutes avant le talk, j'écrivais encore les speaker notes.",[35,7988,7989],{},"Puis le moment est venu de parler du futur des développeurs. J'étais fatigué, pas prêt, et même pas sûr que le talk allait être bon. Mais j'étais là, sur scène, avec quelques slides, 45 minutes devant moi, et la confiance des organisateurs. La salle était pleine, et malgré un grand panneau « COMPLET » sur la porte, les gens continuaient d'entrer, tassés au fond de la salle.",[35,7991,7992],{},[1707,7993],{"alt":7994,"src":7995},"La salle pendant le talk sur le futur du développeur, avec beaucoup de personnes debout au fond","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Froom-during-future-of-developer-talk.webp",[35,7997,7998,7999,8002],{},"J'ai donné le talk. Ma plus grande peur était de traverser le contenu trop vite en me contentant de lire les slides. C'est l'inverse qui s'est produit. J'étais calme, et j'ai dû accélérer les deuxième et troisième parties pour éviter de manquer de temps. Puis le talk s'est terminé. Ouf, quel soulagement ! Pendant le talk, voir les gens hocher la tête ou prendre des photos des slides était tellement gratifiant, parce que cela me montrait que nous n'étions pas complètement à côté de la plaque avec ",[39,8000,6845],{"href":7561,"rel":8001},[77],". À la fin, j'ai eu quelques questions, puis beaucoup de personnes sont venues me remercier pour le talk. « Enfin un talk pragmatique sans des tonnes de bullshit », et des discussions incroyables sur le sujet qui auraient pu durer des heures.",[35,8004,8005,8006,8011,8012,215],{},"Je vous recommande vivement de lire l'article de ",[39,8007,8010],{"href":8008,"rel":8009},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffanny-klauk\u002F",[77],"Fanny Klauk",", qui a assisté au talk et en a écrit un magnifique compte-rendu, avec beaucoup d'insights et de takeaways : ",[39,8013,8016],{"href":8014,"rel":8015},"https:\u002F\u002Fklaukf.github.io\u002Fblog\u002FRetour-sur-MiXiT-2026\u002F#chapitre-10--le-futur-du-d%C3%A9veloppeur--ia-comp%C3%A9tences-et-obsolescence---est%C3%A9ban-soubiran--julie-mouveroux",[77],"Retour sur MiXiT 2026",[35,8018,8019],{},[1707,8020],{"alt":8021,"src":8022},"Estéban sur scène pendant son talk sur le futur du développeur, photo par Fanny Klauk","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-presenting-about-future-of-developer.webp",[35,8024,8025],{},"Juste pour vous donner une idée, le CFP nous a pris entre 30 et 50 heures, et le talk lui-même a demandé plus de 200 heures. Le tout en moins de six mois. C'est énorme. Alors la prochaine fois que vous voyez un talk que vous appréciez, même si vous n'avez pas de question, dites simplement « merci, super talk, je l'ai adoré ! » au speaker. Cela compte énormément pour lui.",[35,8027,8028,8029,215],{},"C'était, de très loin, la pire idée que j'aie eue pour un talk. Ne refaites jamais ça. S'il vous plaît, ",[39,8030,8033],{"href":8031,"rel":8032},"https:\u002F\u002Faaronfrancis.com\u002F2024\u002Fwhat-if-you-tried-hard-dac139a5#:~:text=outcomes%20for%20me.-,You%27re%20scared%20to%20fail,-Trying%20hard%20won%27t",[77],"préparez-vous avant de donner un talk",[35,8035,8036],{},"Mais parfois, des choses imprévisibles arrivent. C'était ça, ou il aurait fallu annuler mon talk. Pourtant, annuler n'était pas une option pour moi. Les organisateurs nous avaient accordé leur confiance, et nous devions l'honorer. Alors, jusqu'à la dernière minute, j'ai continué à me pousser pour finir le talk.",[1929,8038,8040],{"id":8039},"mixit-on-air","MiXiT On Air",[35,8042,8043,8044,8048,8049,215],{},"L'année dernière, j'ai eu l'opportunité de participer au podcast ",[39,8045,8040],{"href":8046,"rel":8047},"https:\u002F\u002Fpodcast.ausha.co\u002Fmixit",[77]," ",[39,8050,8053],{"href":8051,"rel":8052},"https:\u002F\u002Fpodcast.ausha.co\u002Fmixit\u002Fepisode-12-les-signaux-en-javascript",[77],"pour parler des signaux en JavaScript",[35,8055,8056],{},"Cette année, ils m'ont invité à nouveau pour parler du futur des développeurs. Enregistrer un podcast est une expérience très différente, parce que cela prend vraiment la forme d'une conversation. Il faut être calme et s'exprimer clairement, avec le contenu le plus intéressant et le plus pertinent possible.",[35,8058,8059,8060,8065,8066],{},"Grâce au magnifique travail de l'équipe de MiXiT On Air, tout s'est déroulé sans accroc. Ils ont même pensé à prévoir de quoi manger, puisque l'enregistrement avait lieu pendant la pause déjeuner. J'ai vraiment apprécié l'expérience, en particulier le duo avec ",[39,8061,8064],{"href":8062,"rel":8063},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcamilleroux\u002F",[77],"Camille",". J'espère que l'épisode sortira bientôt pour que vous puissiez l'écouter. Je le partagerai sur mon LinkedIn quand il sera disponible. ",[1216,8067,8068],{},"Je réfléchis sérieusement à lancer mon propre podcast. Est-ce que je devrais ?",[35,8070,8071],{},[1707,8072],{"alt":8073,"src":8074},"Photo de l'enregistrement du podcast MiXiT On Air. De gauche à droite : Clément Staron, Hubert Sablonnière, Estéban Soubiran, Charles Bouttaz et Camille Roux","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmixit-on-air-podcast-recording.webp",[51,8076,5580],{"id":5579},[35,8078,8079,8080],{},"MiXiT 2026, c'est terminé ! C'était une expérience incroyable, avec beaucoup d'émotions et énormément de travail. J'espère vraiment que les participants l'ont autant appréciée que moi. ",[1216,8081,8082],{},"Et qu'ils ont plus dormi que moi !",[35,8084,8085],{},[1707,8086],{"alt":8087,"src":8088},"Photo de la TakiTeam à MiXiT, de gauche à droite : Estéban, Marion, Elise et Victoire","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Ftakiteam-at-mixit.webp",[35,8090,8091],{},"Au moment où j'écris ces lignes, Devoxx France arrive dans une semaine. J'y présenterai mon talk, « Au cœur d'une pipeline : démystifions Vite et ses plugins ». J'ai vraiment hâte d'y être et de découvrir les autres talks, et de rencontrer du monde.",[35,8093,8094,8095,3092,8098,1655,8101,106,8104,8107],{},"En juin, je vais à Lille pour DevFest Lille avec la même équipe, ",[39,8096,7385],{"href":7383,"rel":8097},[77],[39,8099,7380],{"href":7378,"rel":8100},[77],[39,8102,1792],{"href":1790,"rel":8103},[77],[39,8105,7390],{"href":7388,"rel":8106},[77]," nous rejoindra aussi pour parler de micro-frontend. Ce sera mon premier DevFest Lille, et j'en ai entendu beaucoup de bien, donc j'ai vraiment hâte d'y être. Ça va être chouette !",[35,8109,8110,8111,8116,8117,8121],{},"Puis, à la fin du mois de juin, je vais à ",[39,8112,8115],{"href":8113,"rel":8114},"https:\u002F\u002Fapp.itsasync.fr\u002Fconference",[77],"Asynconf"," à Paris. ",[39,8118,8120],{"href":7419,"rel":8119},[77],"Graven"," m'a invité à parler de réactivité frontend. C'est un véritable honneur pour moi, et des speakers connus comme Mathieu Nebra, cofondateur d'OpenClassrooms, seront là.",[35,8123,8124,8125,8130,8131,1655,8136,8139],{},"Je participe aussi à ",[39,8126,8129],{"href":8127,"rel":8128},"https:\u002F\u002Fwww.ncldays.fr\u002F",[77],"Natural Change Leaders Days"," en juin. C'est un événement organisé par ",[39,8132,8135],{"href":8133,"rel":8134},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Foliviermy\u002F",[77],"Olivier My",[39,8137,6920],{"href":6918,"rel":8138},[77],". Ce n'est pas une conférence technique et, pour sa première édition, elle aura lieu en ligne. C'est quelque chose de nouveau pour moi, mais aussi de très attirant et un peu effrayant. J'y parlerai d'agentivité et de l'importance de rester en mouvement dans un monde en changement constant.",[35,8141,8142],{},"Donc oui, le programme à venir est bien chargé !",[35,8144,8145],{},[1707,8146],{"alt":8147,"src":8148},"Lyon de nuit","\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Flyon-by-night.webp",[1158,8150],{},[35,8152,8153],{},"Julie, j'espère que tu vas mieux maintenant !",{"title":13,"searchDepth":14,"depth":14,"links":8155},[8156,8157,8158,8163],{"id":7585,"depth":14,"text":7249},{"id":7800,"depth":14,"text":7801},{"id":7866,"depth":14,"text":7867,"children":8159},[8160,8161,8162],{"id":7876,"depth":255,"text":7877},{"id":7924,"depth":255,"text":7925},{"id":8039,"depth":255,"text":8040},{"id":5579,"depth":14,"text":5580},"6106cedc-3b20-44be-8994-da3d7da5a98f","2026-04-29","Deux conférences, une intervenante absente, beaucoup trop de travail, mais trois amies avec qui le partager. MiXiT 2026 a été traversé par beaucoup d'émotions.",{},"\u002Ffr\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026","---\ncontentId: 6106cedc-3b20-44be-8994-da3d7da5a98f\ntitle: Montagnes russes inattendues d'émotions à MiXiT 2026\ndescription: Deux conférences, une intervenante absente, beaucoup trop de travail, mais trois amies avec qui le partager. MiXiT 2026 a été traversé par beaucoup d'émotions.\ndate: 2026-04-29\n---\n\nC'était ma deuxième fois à [MiXiT](https:\u002F\u002Fmixitconf.org\u002F). [La première remonte à un an](.\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md), mais j'ai l'impression que c'était hier.\n\nMiXiT est une conférence tech qui s'est tenue à Lyon sur deux jours, autour de la technologie, de l'éthique et de la diversité. Par rapport à l'année dernière, la conférence était tout aussi chaleureuse et accueillante, avec plus de 600 participants.\n\n![Vue de Lyon depuis Fourvière](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Flyon-point-of-view-from-fourviere.webp)\n\nPourtant, cette fois-ci, c'était différent. Le contexte était différent.\n\nPour la première fois, j'allais assister à une conférence avec d'autres personnes, et pas n'importe qui, mais des personnes formidables de Takima, l'entreprise où je travaille. J'allais vivre la conférence avec [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F) et [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F).\n\n![De gauche à droite : Estéban, Victoire, Marion et Elise, photo par Elise](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fvictoire-elise-marion-and-me.webp)\n\nElles avaient toutes les deux été acceptées pour prendre la parole à MiXiT pour la première fois sur scène. [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) et [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F) allaient présenter ensemble un talk sur les feature flags dans des environnements critiques en production. [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F) allait parler de mutation testing. Elles le méritent vraiment. Elles ont mis tellement de travail, de passion et d'amour dans leurs talks. _Bravo, les filles !_\n\nPour une première conférence, elles en ont vraiment choisi une bonne pour monter sur scène. Les participants sont vraiment bienveillants et compréhensifs, et les organisateurs sont présents et bienveillants. MiXiT est clairement un bon endroit pour démarrer un parcours de speaker.\n\nMais en même temps, nous aurions dû être cinq speakers, pas quatre.\n\nDepuis novembre, je préparais un talk sur le futur des développeurs avec une amie qui s'appelle [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux). Jusqu'ici, j'avais surtout donné des talks sur des sujets techniques. Celui-ci allait être différent. D'abord, ce serait un talk non technique, écrit à partir d'articles scientifiques, de recherches, de billets de blog, de livres et d'entretiens avec d'autres développeurs. Ensuite, ce serait un duo, de l'écriture jusqu'à la présentation.\n\n[Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux) était à la fois très enthousiaste et inquiète, parce que c'était aussi sa première fois sur scène. Malheureusement, pour des raisons personnelles, elle a dû arrêter de travailler sur le talk quelques jours avant la conférence, et elle n'a pas pu me rejoindre à MiXiT. _Je sais qu'elle essaie vraiment de faire en sorte que ça marche. Ce sera pour une autre fois, le talk en duo._\n\nJe me sentais terriblement mal pour elle. J'étais à ça d'annuler le talk, mais nous étions engagés auprès des organisateurs. Je ne voulais pas les laisser tomber. Ils nous avaient accordé leur confiance, et nous devions l'honorer. Alors j'ai continué à me pousser pour finir le talk jusqu'à la dernière minute. _Pour être totalement transparent, la semaine avant la conférence a peut-être été l'une des pires semaines de ma vie. La conférence sur Vite était presque terminée, mais pas tout à fait, celle sur le futur des développeurs était à moins de la moitié, et [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux) ne pouvait pas me rejoindre. Sensation horrible._\n\n## MiXiT 2026\n\nTout a commencé le mercredi 15 avril 2026, avec le dîner des speakers, organisé dans un restaurant chic près de la Saône. On nous a servi du fromage, de la charcuterie et du vin. Honnêtement, la combinaison parfaite pour passer une excellente soirée. C'était aussi une belle occasion de rencontrer d'autres speakers et d'échanger sur nos talks. J'adore ces moments où les gens essaient de te vendre leur talk. Au fond, c'est toujours un plaisir d'entendre quelqu'un parler de sa passion. Et ça m'a aidé à planifier les deux jours suivants !\n\nComme pour tous les événements, ne demandez pas de photos ! Ce qui s'est passé au dîner des speakers reste au dîner des speakers. _J'ai peut-être passé un peu trop de temps à discuter près des boissons._\n\nVers 22 h, nous avons quitté le restaurant pour aller à l'hôtel nous reposer en vue du lendemain. J'étais vraiment fatigué, mais j'avais une conférence à terminer. J'ai travaillé jusqu'à 1 h 30 du matin pour essayer de finir le talk sur le futur des développeurs, et j'ai rédigé le post LinkedIn pour le lendemain matin.\n\n7 heures, l'heure de se réveiller, de continuer à travailler sur le talk et de se préparer pour la conférence. Pas beaucoup de sommeil, mais je n'avais pas vraiment le choix. Le premier jour, jeudi 16 avril 2026, j'ai vu les talks suivants :\n\n  - **L'intelligence artificielle générale n'aura pas lieu** par [Thibault Prévost](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthibault-prevost\u002F)\n  - **Au cœur d'une pipeline : démystifions Vite et ses plugins** par [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n  - **Le mythe de la portabilité cloud avec Kubernetes** par [Federico Fregosi](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffederico-fregosi\u002F)\n  - **Déconstruisons les frameworks JS pour mieux réinventer le web !** par [Nirina Rabeson](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnirinarabeson\u002F)\n  - **Mes trains Jouef passent au numérique, avec TinyGo** par [Florian Forestier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fflorian-forestier\u002F)\n  - **Assistant IA pour le code : comment ça marche ? J'en ai codé un !** par [Xavier Nopre](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fxnopre\u002F)\n  - **L'indépendance des médias... indés** par David Dufresne\n\n![Photo de l'amphithéâtre pendant le mot d'introduction des organisateurs](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Famphitheater-during-introduction.webp)\n\nOui, j'étais le premier talk, juste après la keynote d'ouverture. Pas le temps de souffler, juste brancher l'ordinateur, ouvrir les slides, monter sur scène et commencer à parler. Grâce à l'adrénaline, la fatigue n'a pas vraiment été un problème pendant le talk. En revanche, après, c'était vraiment dur et intense.\n\nAprès cette première journée de conférence, MiXiT a organisé une soirée à l'Hôtel de Ville de Lyon. C'est un endroit vraiment magnifique et une occasion unique de le visiter tout en profitant de quelques verres et en prenant le temps d'échanger avec d'autres participants. Trouver du temps pour parler avec les gens pendant une conférence est vraiment difficile, donc ces moments sont précieux. Après une longue journée et une longue soirée, nous sommes rentrés à l'hôtel. J'avais envie de me reposer, mais je devais finir un talk, oui, encore. _Dormir pendant une conférence ? Je devrais vraiment essayer ! Ça a l'air cool !_\n\nLe lendemain, même heure de réveil : 7 heures. À ce stade, on ne parle même plus de fatigue. J'étais complètement épuisé, et même si j'avais beaucoup travaillé sur le talk, il n'était pas terminé quand nous avons quitté l'hôtel. J'ai dû le finir pendant la keynote d'ouverture et le premier talk. Le deuxième jour, vendredi 17 avril 2026, j'ai assisté aux talks suivants :\n\n  - **Wikipédia à 25 ans : De l'utopie numérique au pilier du savoir commun** par [Rémy Gerbet](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fr%C3%A9my-gerbet-511249a1\u002F)\n  - **Conception de logiciels : le match humains vs AI** par [Olivier Azeau](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fazeau\u002F)\n  - **Le futur du développeur : IA, compétences et obsolescence** par [Julie Mouveroux](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux) et [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n  - **Déployer souvent, stresser moins : feature flags en prod critique** par [Marion Chineaud](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) et [Elise Souvannavong](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F)\n  - **Pas de managers, pas de réunions, pas de problème** par [Sofia Lescano Carroll](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsofialescanocarroll\u002F)\n\nPour mon talk, certaines slides n'étaient pas finies, et les speaker notes n'étaient pas écrites. Je n'utilise jamais les speaker notes, mais je devais le faire pour ce talk parce qu'il allait être complètement improvisé. Ce n'est pas ce que je maîtrise le mieux, mais je n'avais pas le choix. Faisons en sorte que mes trente derniers talks servent à quelque chose pour celui-là ! _Vous vous demandez peut-être si j'étais stressé. Je ne l'étais pas. Fatigué, bien sûr, mais pas stressé. Le stress n'allait pas m'aider ni rendre mon talk meilleur, alors j'ai simplement accepté la situation, le retard, et j'ai fait de mon mieux pour honorer le travail de Julie sur le talk et la confiance des organisateurs._\n\nAu même moment, [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F) présentait son talk sur le mutation testing. Je n'ai pas pu y assister, mais je sais que ça s'est vraiment bien passé.\n\nEnsuite, nous avons tous assisté au talk de [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) et [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F) sur les feature flags. C'était vraiment bien, et le public était à fond dedans. J'étais tellement fier d'elles, et je sais qu'elles étaient tellement fières d'elles-mêmes. Elles le méritaient vraiment. _Pour un premier talk, je suis sans voix._\n\nÀ la fin de cette deuxième journée, les talks étaient terminés et les participants étaient heureux d'avoir vu mes deux talks. Bien sûr, cela a représenté énormément de travail, mais ça n'aurait pas été possible sans le soutien de [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F) et [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F).\n\n![De gauche à droite : Victoire, Elise, Marion et Estéban au milieu](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fvictoire-elise-marion-and-me-in-the-middle.webp)\n\n![Photo de Marion, Elise et Victoire à l'Hôtel de Ville de Lyon pendant la soirée](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmarion-elise-and-victoire-in-lyon-city-hall.webp)\n\nLe fait de ne pas être seul a tout changé. C'était tellement agréable de partager cette expérience, d'échanger sur les talks, de parler de nos ressentis, et simplement de nous soutenir et de nous donner de la force les uns aux autres. _Merci infiniment !_\n\n## Les talks qui m'ont marqué\n\nPlus j'assiste à des conférences, plus la barre monte pour mes « talks highlights ». J'ai envie de partager les talks que j'ai vraiment aimés et que je pense mériter d'être partagés. Comme toujours, cette liste est subjective, et je vous encourage à aller voir le programme de la conférence pour découvrir d'autres talks qui pourraient vous intéresser.\n\n1. **Déployer souvent, stresser moins : feature flags en prod critique** par [Marion Chineaud](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) et [Elise Souvannavong](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F)\n\n    Vous vous dites peut-être que je suis un peu biaisé en mettant ce talk à la première place, mais c'était la première fois que je le voyais, et j'ai vraiment été impressionné. Le talk est très complet, couvrant différents types de feature flags, de l'implémentation à l'usage et aux cas d'usage. C'était bien structuré, avec un bon rythme et des blagues drôles au bon moment. On sent la complicité entre [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F) et [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F), ainsi que la passion qu'elles ont pour le sujet.\n\n    ![Photo de Marion et Elise pendant leur présentation](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmarion-and-elise-presenting.webp)\n\n2. **Mes trains Jouef passent au numérique, avec TinyGo** par [Florian Forestier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fflorian-forestier\u002F)\n\n    Pas un talk technique, mais un talk profondément humain, sincère et passionné. [Florian](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fflorian-forestier\u002F) a partagé sa passion pour les trains miniatures et la manière dont il a réussi à les rendre numériques et pilotables via le web, en comparaison avec les anciens modèles analogiques. Au-delà de l'histoire, le cas d'usage réel de Raspberry Pi avec un logiciel à la bonne developer experience, TinyGo, était vraiment intéressant. Je ne sais pas encore comment en faire quelque chose aujourd'hui, mais je sais que le jour où j'en aurai besoin, je me souviendrai de ce talk. Écouter des personnes passionnées parler de leur passion, c'est toujours si agréable et inspirant.\n\n    ![Photo de Florian pendant sa présentation](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fflorian-presenting.webp)\n\nJ'aurais adoré assister au talk de [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F), « Testez vos tests avant qu'ils ne vous trahissent : le mutation testing ! ». Malheureusement, il avait lieu en même temps que le mien. J'espère vraiment pouvoir y assister dans une autre conférence. Elle a travaillé tellement dur dessus, et je sais que ça va être incroyable ! _J'ai trop hâte de le voir à DevLille en juin !_\n\n## Vite, futur du développeur web et podcast\n\nCette année, j'ai eu l'opportunité de donner deux talks à MiXiT. L'année dernière aussi, j'en avais donné deux, mais l'un d'eux était un talk de lancement sur un sujet très léger. Cette année, j'avais deux conférences de 45 minutes sur des sujets très techniques. C'était un vrai défi pour moi, surtout avec le contexte du second talk. _Je ne recommande pas de construire deux talks en même temps. C'est beaucoup trop de travail._\n\n### \"Au cœur d'une pipeline : démystifions Vite et ses plugins\"\n\n![Selfie d'Estéban avec le public au début de son talk sur Vite](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-selfie-with-audience-before-vite-talk.webp)\n\nC'est le plus gros talk technique que j'ai jamais donné. Il fait 70 slides avec énormément de contenu dynamique. Sa particularité, c'est que le public est le héros du talk. Les gens influencent le talk en choisissant des options pendant celui-ci, ce qui rend chaque replay différent. Comme vous pouvez l'imaginer, cela signifie écrire plusieurs chemins dans les slides et beaucoup plus de contenu qu'un talk classique.\n\nJ'ai aussi dû développer tout le système sous-jacent pour que cela fonctionne. Même si cela reposait sur [Inalia](https:\u002F\u002Finalia.app), le SaaS que je construis pour rendre les talks plus engageants avec des interactions en temps réel, j'ai dû développer beaucoup de nouvelle logique frontend pour que cela colle aux besoins du talk.\n\nJ'écris tous mes talks avec Slidev, un outil qui permet d'écrire ses slides en Markdown. Ce qui est intéressant avec Slidev, c'est qu'un talk est en réalité un site Vite et Vue. Donc, en l'utilisant, on a toute la puissance de Vite et Vue pour créer ses slides. On peut créer n'importe quelle slide interactive, avec n'importe quelle animation, et j'en ai bien profité pour construire un talk très dynamique. Bonne idée, mais très chronophage ! Heureusement, j'ai eu l'aide de l'IA pour développer la majeure partie de la logique.\n\nHeureusement, j'ai eu l'opportunité de donner ce talk lors d'un événement interne à L'Atelier à Paris, deux semaines avant la conférence. J'ai aussi eu la chance de le faire en crash test chez Takima devant beaucoup de mes collègues. Ils m'ont donné un feedback précieux pour l'améliorer et le rendre meilleur. _Et je sais qu'il est meilleur maintenant. Merci à toutes les personnes qui ont pris le temps de me faire des retours dessus !_\n\n![Photo d'Estéban pendant son talk sur Vite, photo par Marion](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-presenting-about-vite.webp)\n\nMalgré tout, j'en suis loin d'être pleinement satisfait. Je sais qu'il pourrait être tellement meilleur, même après avoir travaillé dessus pendant des mois, donc je vais continuer à l'améliorer. Rendons-le meilleur pour DevLille en juin !\n\nJe vais consacrer un billet de blog entier à la création de ce talk, avec tous les détails techniques et les défis auxquels j'ai fait face. Il y a énormément de travail caché derrière, et beaucoup de personnes m'ont posé des questions à ce sujet !\n\n\u003CCalloutReadMore\n  href=\"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Ftalks\"\n  text=\"Découvrez le dépôt GitHub du talk\"\n\u002F>\n\n### \"Le futur du développeur : IA, compétences et obsolescence\"\n\nL'histoire de ce talk commence en novembre 2025. Après être allé au théâtre avec [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux), nous nous sommes assis dans un bar pour discuter de plein de choses. À un moment, je lui ai demandé si elle serait intéressée par l'idée de créer un talk ensemble. Je lui ai demandé de ne pas répondre tout de suite, mais d'y réfléchir et de me dire si elle était intéressée. _Je déteste la pression sociale, et je ne veux pas d'un « oui » juste à cause du moment. Créer un talk demande tellement de travail que je veux être sûr que la personne est vraiment intéressée et motivée à le faire._\n\nElle a répondu quelques heures plus tard qu'elle était partante. J'étais tellement heureux de lire ce message !\n\nÀ ce moment-là de l'histoire, il faut savoir que nous ne connaissions pas le sujet du talk, Julie n'avait jamais donné de talk auparavant, et je n'avais jamais donné de talk à deux. Pour compliquer encore un peu les choses, nous ne travaillions pas dans le même domaine et nous avions un mois pour soumettre cela à un CFP. Franchement, quelle idée folle ! _Deux règles : les choses commencent souvent dans un bar, et [on peut juste faire des choses](.\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things.md). On n'a pas besoin d'être prêt._\n\nAprès quelques sessions de brainstorming, nous avons, enfin, trouvé le sujet du talk, surtout grâce à elle. Nous voulions parler du futur du travail sous l'angle de l'IA. Comme les conférences auxquelles nous soumettons sont surtout techniques, nous avons ciblé le futur des développeurs. Cela a aussi permis de réduire un peu le périmètre du talk pour le rendre « plus simple » à écrire.\n\nEnsuite, nous avons commencé à écrire le CFP. Pour cela, il nous fallait un plan pour le talk. Cela signifiait faire des recherches initiales pour mieux comprendre les tendances, la réalité et la direction que prenaient les choses. Ce n'était pas une étape facile, mais nous l'avons traversée. Quelques jours avant Noël, nous avons soumis le CFP à MiXiT et Devoxx France.\n\nHonnêtement, même si nous avions été refusés aux deux, nous aurions quand même tout donné pour en faire quelque chose dont nous pourrions être fiers.\n\nMême avant l'acceptation, j'ai commencé à travailler dessus parce que je savais que ce serait énormément de travail. J'ai commencé à lire des papiers, des articles et des livres, et à regarder des vidéos sur le sujet. C'était compliqué parce qu'on ne peut pas vraiment chercher « futur du développeur » sur Google. Personne n'a vraiment pris le temps d'écrire là-dessus, et les quelques articles qui existent sont biaisés ou pas pertinents. Nous avons dû lire des dizaines d'articles, en extraire les informations utiles, et leur donner du sens autour du plan que nous avions défini des semaines plus tôt. J'ai passé beaucoup de dimanches matin à lire des articles que j'aimerais ne plus jamais relire tant ils étaient traumatisants. _Une bonne musique est recommandée pour éviter de sombrer dans la peur et le désespoir._\n\nNous avons aussi interviewé des développeurs du secteur pour recueillir leur point de vue sur le sujet. C'était très utile pour éviter de rester dans notre bulle et pour mieux comprendre la réalité. Nous avons eu de très bonnes conversations avec eux et nous avons beaucoup appris grâce à elles. _Merci infiniment à tous les développeurs qui ont pris le temps d'échanger avec nous !_\n\nCe processus a été un vrai défi, et j'ai mis en place beaucoup d'automatisation pour le faire fonctionner. Je ne vais pas entrer dans les détails ici parce que je consacrerai un billet de blog entier à la création de ce talk. Ce processus a pris absolument tout notre temps. Nous nous voyions régulièrement pour discuter du contenu, nous assurer que nous restions bien alignés en continu, et simplement travailler dessus ensemble. _Honnêtement, nous passions plus de temps à parler de nos vies que du talk. Et c'est très bien comme ça. Après tout, ce n'est pas notre travail._\n\nMalheureusement, quelques jours avant la conférence, [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux) a dû arrêter de travailler sur le talk pour des raisons personnelles. Je me sentais vraiment mal pour elle, et j'étais vraiment triste. J'ai continué à travailler sur le talk jusqu'à la dernière minute. Pendant les derniers jours, je me réveillais une heure plus tôt pour travailler dessus. J'allais travailler, puis après le travail, je continuais jusqu'à minuit ou 1 heure du matin. Tous les jours.\n\nEnsuite, j'ai continué à travailler dessus pendant la conférence. La veille du début de MiXiT, moins de 50 % des slides étaient terminées. J'ai continué à travailler dessus pendant la conférence, avant et après le dîner des speakers, après la soirée au milieu de la conférence, et jusqu'à 10 minutes avant le talk, j'écrivais encore les speaker notes.\n\nPuis le moment est venu de parler du futur des développeurs. J'étais fatigué, pas prêt, et même pas sûr que le talk allait être bon. Mais j'étais là, sur scène, avec quelques slides, 45 minutes devant moi, et la confiance des organisateurs. La salle était pleine, et malgré un grand panneau « COMPLET » sur la porte, les gens continuaient d'entrer, tassés au fond de la salle.\n\n![La salle pendant le talk sur le futur du développeur, avec beaucoup de personnes debout au fond](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Froom-during-future-of-developer-talk.webp)\n\nJ'ai donné le talk. Ma plus grande peur était de traverser le contenu trop vite en me contentant de lire les slides. C'est l'inverse qui s'est produit. J'étais calme, et j'ai dû accélérer les deuxième et troisième parties pour éviter de manquer de temps. Puis le talk s'est terminé. Ouf, quel soulagement ! Pendant le talk, voir les gens hocher la tête ou prendre des photos des slides était tellement gratifiant, parce que cela me montrait que nous n'étions pas complètement à côté de la plaque avec [Julie](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjmouveroux). À la fin, j'ai eu quelques questions, puis beaucoup de personnes sont venues me remercier pour le talk. « Enfin un talk pragmatique sans des tonnes de bullshit », et des discussions incroyables sur le sujet qui auraient pu durer des heures.\n\nJe vous recommande vivement de lire l'article de [Fanny Klauk](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ffanny-klauk\u002F), qui a assisté au talk et en a écrit un magnifique compte-rendu, avec beaucoup d'insights et de takeaways : [Retour sur MiXiT 2026](https:\u002F\u002Fklaukf.github.io\u002Fblog\u002FRetour-sur-MiXiT-2026\u002F#chapitre-10--le-futur-du-d%C3%A9veloppeur--ia-comp%C3%A9tences-et-obsolescence---est%C3%A9ban-soubiran--julie-mouveroux).\n\n![Estéban sur scène pendant son talk sur le futur du développeur, photo par Fanny Klauk](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Festeban-presenting-about-future-of-developer.webp)\n\nJuste pour vous donner une idée, le CFP nous a pris entre 30 et 50 heures, et le talk lui-même a demandé plus de 200 heures. Le tout en moins de six mois. C'est énorme. Alors la prochaine fois que vous voyez un talk que vous appréciez, même si vous n'avez pas de question, dites simplement « merci, super talk, je l'ai adoré ! » au speaker. Cela compte énormément pour lui.\n\nC'était, de très loin, la pire idée que j'aie eue pour un talk. Ne refaites jamais ça. S'il vous plaît, [préparez-vous avant de donner un talk](https:\u002F\u002Faaronfrancis.com\u002F2024\u002Fwhat-if-you-tried-hard-dac139a5#:~:text=outcomes%20for%20me.-,You%27re%20scared%20to%20fail,-Trying%20hard%20won%27t).\n\nMais parfois, des choses imprévisibles arrivent. C'était ça, ou il aurait fallu annuler mon talk. Pourtant, annuler n'était pas une option pour moi. Les organisateurs nous avaient accordé leur confiance, et nous devions l'honorer. Alors, jusqu'à la dernière minute, j'ai continué à me pousser pour finir le talk.\n\n### MiXiT On Air\n\nL'année dernière, j'ai eu l'opportunité de participer au podcast [MiXiT On Air](https:\u002F\u002Fpodcast.ausha.co\u002Fmixit) [pour parler des signaux en JavaScript](https:\u002F\u002Fpodcast.ausha.co\u002Fmixit\u002Fepisode-12-les-signaux-en-javascript).\n\nCette année, ils m'ont invité à nouveau pour parler du futur des développeurs. Enregistrer un podcast est une expérience très différente, parce que cela prend vraiment la forme d'une conversation. Il faut être calme et s'exprimer clairement, avec le contenu le plus intéressant et le plus pertinent possible.\n\nGrâce au magnifique travail de l'équipe de MiXiT On Air, tout s'est déroulé sans accroc. Ils ont même pensé à prévoir de quoi manger, puisque l'enregistrement avait lieu pendant la pause déjeuner. J'ai vraiment apprécié l'expérience, en particulier le duo avec [Camille](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcamilleroux\u002F). J'espère que l'épisode sortira bientôt pour que vous puissiez l'écouter. Je le partagerai sur mon LinkedIn quand il sera disponible. _Je réfléchis sérieusement à lancer mon propre podcast. Est-ce que je devrais ?_\n\n![Photo de l'enregistrement du podcast MiXiT On Air. De gauche à droite : Clément Staron, Hubert Sablonnière, Estéban Soubiran, Charles Bouttaz et Camille Roux](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Fmixit-on-air-podcast-recording.webp)\n\n## Et maintenant ?\n\nMiXiT 2026, c'est terminé ! C'était une expérience incroyable, avec beaucoup d'émotions et énormément de travail. J'espère vraiment que les participants l'ont autant appréciée que moi. _Et qu'ils ont plus dormi que moi !_\n\n![Photo de la TakiTeam à MiXiT, de gauche à droite : Estéban, Marion, Elise et Victoire](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Ftakiteam-at-mixit.webp)\n\nAu moment où j'écris ces lignes, Devoxx France arrive dans une semaine. J'y présenterai mon talk, « Au cœur d'une pipeline : démystifions Vite et ses plugins ». J'ai vraiment hâte d'y être et de découvrir les autres talks, et de rencontrer du monde.\n\nEn juin, je vais à Lille pour DevFest Lille avec la même équipe, [Victoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fvictoire-ladreit-de-lacharri%C3%A8re\u002F), [Elise](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Felise-souvannavong-insa\u002F) et [Marion](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmarion-chineaud-98000b214\u002F). [Laurine](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurine-le-net\u002F) nous rejoindra aussi pour parler de micro-frontend. Ce sera mon premier DevFest Lille, et j'en ai entendu beaucoup de bien, donc j'ai vraiment hâte d'y être. Ça va être chouette !\n\nPuis, à la fin du mois de juin, je vais à [Asynconf](https:\u002F\u002Fapp.itsasync.fr\u002Fconference) à Paris. [Graven](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Florenzo-async\u002F) m'a invité à parler de réactivité frontend. C'est un véritable honneur pour moi, et des speakers connus comme Mathieu Nebra, cofondateur d'OpenClassrooms, seront là.\n\nJe participe aussi à [Natural Change Leaders Days](https:\u002F\u002Fwww.ncldays.fr\u002F) en juin. C'est un événement organisé par [Olivier My](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Foliviermy\u002F) et [Albane Fagot-Veyron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falbane-fagot-veyron\u002F). Ce n'est pas une conférence technique et, pour sa première édition, elle aura lieu en ligne. C'est quelque chose de nouveau pour moi, mais aussi de très attirant et un peu effrayant. J'y parlerai d'agentivité et de l'importance de rester en mouvement dans un monde en changement constant.\n\nDonc oui, le programme à venir est bien chargé !\n\n![Lyon de nuit](\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026\u002Flyon-by-night.webp)\n\n---\n\nJulie, j'espère que tu vas mieux maintenant !\n",{"title":7487,"description":8166},{"loc":8168,"images":8172},[8173,8174,8175,8176,8177,8178,8179,8180,8181,8182,8183,8184,8185,8186],{"loc":7511},{"loc":7532},{"loc":7672},{"loc":7785},{"loc":7791},{"loc":7832},{"loc":7853},{"loc":7883},{"loc":7910},{"loc":7995},{"loc":8022},{"loc":8074},{"loc":8088},{"loc":8148},"fr\u002Fposts\u002Fthe-unexpected-rollercoaster-of-feelings-at-mixit-2026","dS5FT1Hy4CoBvKOXDiJ0AQF_sOtrqZaLXcBTukkK2mI",{"id":8190,"title":1757,"body":8191,"contentId":8759,"date":8760,"description":8761,"extension":18,"meta":8762,"navigation":20,"path":8763,"rawbody":8764,"robots":23,"schemaOrg":23,"seo":8765,"sitemap":8766,"stem":8777,"__hash__":8778},"posts\u002Ffr\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon.md",{"type":7,"value":8192,"toc":8751},[8193,8199,8208,8212,8221,8224,8227,8230,8236,8242,8245,8249,8252,8255,8258,8261,8264,8267,8271,8276,8283,8303,8306,8312,8318,8324,8330,8333,8336,8342,8345,8348,8354,8360,8366,8372,8381,8387,8398,8401,8407,8410,8419,8425,8428,8431,8437,8440,8444,8447,8451,8454,8457,8460,8463,8466,8469,8473,8476,8479,8487,8490],[35,8194,8195,8196],{},"Tout a commencé par un tweet. ",[1216,8197,8198],{},"Les choses commencent souvent par un tweet.",[35,8200,8201,8202,8207],{},"Le 10 février 2026, ",[39,8203,8206],{"href":8204,"rel":8205},"https:\u002F\u002Fx.com\u002Fmistralai\u002Fstatus\u002F2021239601503912409",[77],"Mistral AI a annoncé un hackathon mondial",". Je savais que les places étaient limitées, alors j'ai postulé immédiatement, en espérant être sélectionné, mais sans la moindre idée de ce que j'allais faire, et sans vraiment savoir dans quoi je m'embarquais. Juste l'envie de me laisser surprendre par l'aventure.",[51,8209,8211],{"id":8210},"pour-le-défi","Pour le défi",[35,8213,8214,8215,8220],{},"J'ai déjà fait quelques hackathons, comme la ",[39,8216,8219],{"href":8217,"rel":8218},"https:\u002F\u002Fwww.nuitdelinfo.com\u002F",[77],"Nuit de l'Info"," ou des hackathons plus entrepreneuriaux, mais le dernier remonte à des années. J'ai appris énormément grâce à ces expériences, et je suis sûr qu'elles m'ont fait progresser techniquement. Le premier hackathon que j'ai fait m'a ouvert les yeux sur le développement web, et je n'en suis jamais reparti.",[35,8222,8223],{},"Aujourd'hui, je connais évidemment beaucoup plus de choses en programmation, mais aussi plus de technos, plus de choses sur l'architecture d'un projet, et plus de méthodes pour découper le travail afin d'avoir quelque chose qui fonctionne en peu de temps.",[35,8225,8226],{},"Dans un hackathon, ces compétences sont décisives : un mauvais choix de tech ou un mauvais découpage peut ruiner toutes tes chances d'avoir un projet qui marche à la fin. Avoir un prototype fonctionnel (ou proof-of-concept) est obligatoire. L'idée et la façon dont elle est présentée comptent, bien sûr, mais sans prototype, tu ne peux même pas passer devant un jury.",[35,8228,8229],{},"Je vois donc vraiment ce hackathon comme une super opportunité de me challenger sur ces compétences. Est-ce que tout ce que j'ai appris ces dernières années aide vraiment ? Est-ce que je suis maintenant capable de livrer plus qu'une app CRUD dans un hackathon ?",[35,8231,8232,8233,215],{},"En parallèle, l'IA a complètement changé la donne. Avant, les gens qui savaient à peine coder n'avaient pas beaucoup de chances de gagner un hackathon. Maintenant, tout le monde peut construire quelque chose avec l'IA. Jouer avec du code devient une commodité. Mais ça veut aussi dire que ceux qui savent coder peuvent produire des projets bien plus complexes et intéressants. Donc ",[6862,8234,8235],{},"l'IA a clairement relevé la barre",[35,8237,8238,8239,215],{},"Qu'on soit clairs : le software engineering n'est pas mort. À un moment, sans une architecture correcte, une compréhension claire du code et de la façon de résoudre les problèmes, et savoir quel outil utiliser (et quand), tu ne termines pas le hackathon. ",[6862,8240,8241],{},"Sans garde-fous, l'IA n'est qu'un générateur de dette",[35,8243,8244],{},"Le dimanche avant le hackathon, j'ai reçu un email de Mistral AI avec pour sujet « Registration approved ». J'étais dedans ! Toujours aucune idée et aucune équipe, mais super heureux de faire partie de l'aventure, surtout avec une boîte comme Mistral AI.",[51,8246,8248],{"id":8247},"laventure-commence","L'aventure commence",[35,8250,8251],{},"La première étape, c'était de trouver une équipe. Le hackathon a démarré le 28 février 2026. J'ai reçu l'email d'acceptation le 22 février, donc j'avais une semaine pour y arriver.",[35,8253,8254],{},"Mistral a ouvert un canal dédié sur leur serveur Discord pour que les participants puissent se présenter et trouver des coéquipiers. Ça m'a pris quelques jours, mais j'ai fini par me présenter sur Discord. J'étais le premier dans le canal. Surprise : seules trois personnes (moi compris) se sont présentées.",[35,8256,8257],{},"Un autre canal était dédié à la recherche de coéquipiers. J'ai vu un message de quelqu'un nommé « Godefroy » qui disait qu'ils étaient deux doctorants en physique et qu'ils cherchaient quelqu'un avec des compétences frontend et une expérience produit. Il a aussi dit qu'ils pratiquaient le vibe coding. J'ai répondu immédiatement : j'étais partant pour les rejoindre.",[35,8259,8260],{},"Ils se connaissaient déjà, donc travailler avec eux serait plus simple. Et apporter un bon bagage en software engineering à l'équipe me semblait être un complément parfait à leurs compétences, leur façon de bosser en vibe-coding, et leur détermination à produire le meilleur projet possible. Le vibe coding dans la bonne direction, avec les bons outils au bon moment, peut rendre des vibe coders incroyablement productifs.",[35,8262,8263],{},"Quelques heures plus tard, il m'a envoyé un message sur Discord pour m'expliquer leur idée de projet.",[35,8265,8266],{},"Ensuite, on a planifié un appel le vendredi pour discuter de toutes les idées qu'on avait en tête et choisir celle sur laquelle on voulait travailler. Nous (eux) avions quelques idées. On a terminé l'appel avec une short list, et on a décidé de dormir dessus et de faire le choix final avant d'arriver au hackathon. À ce moment-là, on était quatre dans l'équipe.",[51,8268,8270],{"id":8269},"pour-la-victoire","Pour la victoire",[35,8272,8273],{},[1216,8274,8275],{},"Jusqu'à ce qu'on entende le nom de notre projet annoncé comme gagnant du hackathon, notre seul objectif était de s'amuser et d'apprendre.",[35,8277,8278,8279,8282],{},"Avant d'arriver au hackathon, on a eu une dernière discussion sur WhatsApp à propos de l'idée qu'on voulait faire. On a choisi ",[6862,8280,8281],{},"un système en temps réel pour débunker des émissions politiques à la TV",". Bien plus compliqué que ça n'en a l'air, mais on parlera du projet plus tard.",[35,8284,8285,8286,8291,8292,1655,8297,8302],{},"Le hackathon, organisé par ",[39,8287,8290],{"href":8288,"rel":8289},"https:\u002F\u002Fiterate.inc\u002F",[77],"Iterate",", a commencé à 9 h le 28 février 2026, dans les bureaux de Raise à Paris, près des Invalides. Le petit-déjeuner était servi, et on a eu l'occasion de rencontrer les autres participants (et notre équipe) pour la première fois. C'est là que j'ai rencontré ",[39,8293,8296],{"href":8294,"rel":8295},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femma-grospellier-3491791a1\u002F",[77],"Emma",[39,8298,8301],{"href":8299,"rel":8300},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgodefroymeynard\u002F",[77],"Godefroy",", les deux doctorants en physique.",[35,8304,8305],{},"La réunion d'introduction était à 10 h, donc on avait le temps de discuter et de faire connaissance. La vérité, c'est qu'on avait surtout envie d'ouvrir nos laptops et de commencer à hacker. C'était aussi le moment où le quatrième membre de l'équipe a décidé de rejoindre une autre team. On n'était plus que trois, mais ça n'a pas changé notre détermination à produire le meilleur projet possible.",[35,8307,8308],{},[1707,8309],{"alt":8310,"src":8311},"La réunion d'introduction.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fintro-meeting.webp",[35,8313,8314],{},[1707,8315],{"alt":8316,"src":8317},"Critères de jugement du hackathon. 20% pour la technicité, 20% pour la créativité, 20% pour l'utilité, 20% pour la démo, 20% pour l'alignement avec la track.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fjudging-criteria.webp",[35,8319,8320,8321],{},"Après une heure de présentations sur le hackathon, les challenges des sponsors, et le planning du week-end, on a enfin pu aller chercher un endroit pour travailler dans le bâtiment. C'était un peu chaotique : 100 participants et pas beaucoup de place. Mais on a trouvé une petite table dans un coin d'une salle. On a passé les 30 heures suivantes là-bas, à bosser sur notre projet. On était idéalement placés près de l'espace repas, donc on pouvait savoir quand la nourriture était servie et être les premiers dans la file. ",[1216,8322,8323],{},"Pas besoin d'attendre : tu prends et tu continues de travailler.",[35,8325,8326],{},[1707,8327],{"alt":8328,"src":8329},"Notre poste de travail pour le week-end.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fworking-place.webp",[35,8331,8332],{},"Dans les premières heures, on a structuré le projet en définissant les frontières : ce qu'on voulait faire, comment on voulait le faire, et quand on considérerait le projet comme « fini ». On a aussi commencé à se répartir le travail en fonction de notre compréhension initiale du problème et de nos compétences respectives.",[35,8334,8335],{},"Ensuite, on a codé jusqu'à la fin de la journée. Les pauses étaient courtes et rares, on était vraiment concentrés pour que ça marche, et c'était plus dur que prévu. Vers 22 h, nos cerveaux commençaient à être fatigués et irrités. Godefroy et Emma ont décidé d'aller dormir, et c'était clairement une bonne idée. J'ai pris une longue pause et je suis allé à la cuisine manger de la pizza. Une pause bien méritée avant de reprendre.",[35,8337,8338],{},[1707,8339],{"alt":8340,"src":8341},"Manger de la pizza pour recharger les batteries.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpizza.webp",[35,8343,8344],{},"J'ai continué à bosser sur le projet de 23 h à 1 h 30. J'ai beaucoup refactoré du code, en essayant de le simplifier et de le rendre plus robuste, en espérant que ça marcherait pour le lendemain. Je suis finalement rentré vers 2 h, juste à temps pour attraper le dernier métro. Je n'étais pas vraiment fatigué, porté par l'adrénaline du hackathon, donc j'ai continué à hacker jusqu'à 6 h. J'ai fait une sieste jusqu'à 8 h, puis je suis retourné au hackathon pour le deuxième (et dernier) jour.",[35,8346,8347],{},"On avait jusqu'à 15 h 30 pour finir le projet, enregistrer une vidéo pour le présenter, et préparer la présentation pour le jury. Honnêtement, c'était énorme comme charge de travail, parce qu'on n'arrivait pas à faire fonctionner tous les composants ensemble. Chaque partie marchait bien seule, mais une fois assemblées, ça ne marchait plus du tout.",[35,8349,8350,8351],{},"Pas le choix : il fallait que ça marche. Les choses ont commencé à fonctionner vers 14 h 30. On a filmé la vidéo à 15 h 22, on l'a uploadée à 15 h 28, et on a soumis le projet à 15 h 29. Oui, une minute avant la deadline. ",[1216,8352,8353],{},"Comme d'habitude dans un hackathon.",[35,8355,8356,8357],{},"On était très stressés, mais on y est arrivés, et on était vraiment fiers d'avoir quelque chose qui fonctionne à la fin du hackathon. ",[1216,8358,8359],{},"On l'a fait marcher ! Merci Godefroy d'avoir trouvé la solution finale, et merci Emma d'avoir préparé à la fois la soumission et la présentation.",[35,8361,8362,8363],{},"Tu crois que c'était la fin de l'aventure ? Non ! On avait une présentation, inachevée, et le texte n'était pas prêt. On avait seulement 3 minutes pour pitcher notre projet devant un jury d'experts, et on devait le faire en anglais. Donc, jusqu'à notre passage à 16 h 30, on a peaufiné les slides et préparé la démo live. Tout est allé si vite qu'on n'a même pas eu le temps d'être nerveux ou stressés. ",[1216,8364,8365],{},"Ah, et la démo live a marché comme un charme. Aucun problème !",[35,8367,8368],{},[1707,8369],{"alt":8370,"src":8371},"Quelques minutes avant le pitch.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpitch-preparation.webp",[35,8373,8374,8375,8380],{},"Trois minutes, c'est court, et on avait beaucoup à dire, mais ce sont les règles. Choisir les bons mots pour convaincre le jury que notre projet méritait de gagner. Un énorme merci à ",[39,8376,8379],{"href":8377,"rel":8378},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Froland-sosin\u002F",[77],"Roland"," d'avoir été notre timekeeper, et d'avoir été gentil et rassurant juste avant le pitch.",[35,8382,8383],{},[1707,8384],{"alt":8385,"src":8386},"Pitcher devant le jury.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpitch.webp",[35,8388,8389],{},[1216,8390,8391,8392,8397],{},"Merci ",[39,8393,8396],{"href":8394,"rel":8395},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsam-rouan-5a3677110\u002F",[77],"Sam"," pour les photos, et pour la longue discussion du samedi après-midi !",[35,8399,8400],{},"Une fois que toutes les équipes ont présenté, tous les participants sont allés dans une salle pour attendre les résultats. Ils ont annoncé 12 finalistes qui devaient refaire une présentation finale devant le jury et tous les participants. Ils ont annoncé le premier nom, puis le deuxième. C'était nous ! Wow, vraiment ? On était finalistes ? Inattendu, et peut-être, juste peut-être, qu'on avait une chance de gagner ce hackathon ?",[35,8402,8403,8404],{},"On a repitché devant tout le monde du mieux qu'on pouvait, et encore une fois, on a attendu les résultats. ",[1216,8405,8406],{},"Cette fois, on n'a pas fait la démo live : on a juste présenté la vidéo.",[35,8408,8409],{},"Cinq minutes plus tard, le jury a annoncé le gagnant. Ils ont commencé par annoncer la troisième place, puis la deuxième, et enfin le vainqueur. On a entendu le nom de notre projet, et on n'y croyait pas. On s'est regardés, pour être sûrs qu'on avait entendu la même chose, et ouais : c'était bien notre équipe.",[35,8411,8412,8415,8416],{},[6862,8413,8414],{},"On a gagné le Hackathon Mistral AI Paris 2026 !"," Irréel et inattendu ! ",[1216,8417,8418],{},"On l'a fait ! 🫶",[35,8420,8421],{},[1707,8422],{"alt":8423,"src":8424},"L'équipe devant l'écran principal affichant le logo de notre projet. De gauche à droite : Estéban, Godefroy et Emma.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fteam.webp",[35,8426,8427],{},"La semaine prochaine, on part pour la finale internationale ! On aura l'opportunité de présenter notre projet en live, devant des participants de tous les hackathons, pour tenter de gagner le prix mondial.",[35,8429,8430],{},"Une aventure incroyable qui a commencé par un tweet, et qui s'est terminée par une victoire. Très fier de l'équipe, du projet qu'on a construit, et de la façon dont on a travaillé ensemble. J'adore les hackathons !",[35,8432,8433],{},[1707,8434],{"alt":8435,"src":8436},"Tous les participants, le jury et les organisateurs du hackathon formant un M.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fgroup.webp",[35,8438,8439],{},"Maintenant, il est temps de se reposer un peu et de préparer la suite.",[51,8441,8443],{"id":8442},"veristral","Veristral",[35,8445,8446],{},"J'ai beaucoup parlé du hackathon, de l'ambiance, du planning, et de comment on a réussi à le faire marcher, mais je n'ai pas dit un mot du projet qu'on a construit (et qui a gagné le hackathon). Alors voilà.",[51,8448,8450],{"id":8449},"lidée","L'idée",[35,8452,8453],{},"Notre idée était de construire un système en temps réel pour débunker des émissions politiques à la TV. On voulait pouvoir afficher, pendant l'émission, pendant que le présentateur parle, si ce qu'il dit est vrai ou non, s'il esquive la question, et s'il se contredit. Automatiquement, un pop-up apparaîtrait à l'écran pour apporter des informations supplémentaires aux spectateurs, les aider à mieux comprendre l'émission, et leur permettre de se faire leur propre avis sur ce qu'ils regardent.",[35,8455,8456],{},"Les fake news peuvent être vraiment dangereuses, et elles se propagent très vite, bien plus vite que la vérité. Parfois, des journalistes les débunkent plus tard, même des jours après l'émission. Malheureusement, c'est trop tard. Les dégâts sont faits, et le débunking ne touchera pas la même audience que la fake news. Les fake news sont une vraie menace pour la démocratie, et elles font partie des moyens qu'ont des puissances extérieures pour déstabiliser un pays.",[35,8458,8459],{},"Le vrai défi, c'était la contrainte temps réel. Comment, en quelques millisecondes, analyser la parole du présentateur, la comprendre, décider si c'est vrai ou non, l'enrichir avec des informations supplémentaires, et l'afficher à l'écran ? Honnêtement, c'est impossible. Pour l'instant, on n'a pas la technologie pour le faire de bout en bout.",[35,8461,8462],{},"Voxtral, le modèle speech-to-text (STT) temps réel de Mistral AI, a été annoncé comme étant sous les 200 ms, ce qui est impressionnant, mais ensuite il faut quand même analyser le texte, chercher des informations, et les renvoyer à l'écran. On a vite tapé dans le mur de la latence.",[35,8464,8465],{},"Il y a un autre problème. Le présentateur peut dire quelque chose puis se corriger quelques secondes plus tard. Si on affiche des informations dès qu'on les a, on peut se retrouver avec une expérience utilisateur catastrophique, et décrédibiliser complètement le système.",[35,8467,8468],{},"Donc il fallait trouver une autre solution pour avoir un système en temps réel.",[51,8470,8472],{"id":8471},"le-buffer-à-la-rescousse","Le buffer à la rescousse",[35,8474,8475],{},"La plupart des émissions de TV en direct ne sont pas vraiment en direct. Elles sont décalées de quelques secondes, et elles ont un buffer de retard pour pouvoir couper le flux si quelque chose d'inattendu se produit. Ce délai peut être bien plus important. Une émission française problématique a été forcée d'avoir un buffer de 45 minutes pour éditer le live en temps réel et retirer du contenu problématique avant la diffusion.",[35,8477,8478],{},"Ça nous apprend deux choses :",[68,8480,8481,8484],{},[71,8482,8483],{},"Le concept de buffer entre le live et la diffusion existe déjà dans les émissions de TV.",[71,8485,8486],{},"Le buffer peut être très long.",[35,8488,8489],{},"On peut utiliser ce buffer pour notre système temps réel. Imagine la situation suivante. À t0, le présentateur parle de quelque chose. Le flux est décalé de 30 secondes, donc les spectateurs regardent en réalité ce que le présentateur a dit à t-30 s. Pendant ces 30 secondes, on peut analyser la parole, la comprendre, décider si c'est vrai ou non, l'enrichir avec des informations supplémentaires, et renvoyer le tout à l'écran. À t+30 s, les spectateurs verront l'information à l'écran au même moment où ils entendent le présentateur en parler. De cette façon, on peut avoir un système en temps réel sans être limités par la technologie.",[8491,8492,8493,8496],"veristral-timeline",{},[35,8494,8495],{},"Du point de vue workflow, on a l'architecture simplifiée suivante :",[8497,8498,8503,8506,8515,8519,8522,8525,8528,8535,8541,8544,8549,8558,8564,8567,8574,8577,8603,8606,8613],"workflow",{"title":8499,":steps":8500,":relations":8501,":ui":8502},"Workflow de Veristral avec le buffer","[\n { key: 'microphone', verb: 'capturer l\\'audio' },\n { key: 'OBS 1', verb: 'envoyer le flux avec buffer' },\n { key: 'system', verb: 'analyser et enrichir' },\n { key: 'OBS 2', verb: 'envoyer le flux enrichi' },\n { key: 'live show', verb: 'afficher aux spectateurs' }\n]","[\n { source: 'microphone', target: 'OBS 1' },\n { source: 'OBS 1', target: 'system' },\n { source: 'system', target: 'OBS 2' },\n { source: 'OBS 2', target: 'live show' },\n { source: 'OBS 1', target: 'OBS 2' }\n]","{\n 'root': 'h-140'\n}",[35,8504,8505],{},"Cette solution de buffer nous donne une large fenêtre pour analyser la parole du présentateur et planifier l'affichage de l'information au bon moment. On peut même utiliser les phrases suivantes pour améliorer le contexte et annuler un fact-check si le présentateur se corrige. C'est vraiment puissant et élégant.",[35,8507,8508,8509,106,8512],{},"Tu as peut-être déjà entendu parler de systèmes IA pour analyser et débunker des émissions TV, mais c'était toujours de l'analyse après coup. Notre système change la donne en pouvant le faire en temps réel et en touchant la même audience que la fake news. ",[6862,8510,8511],{},"C'est un vrai game-changer dans la lutte contre les fake news",[1216,8513,8514],{},"Si tu es un provider IA ou une entreprise média, et que ce projet t'intéresse, contacte-nous.",[1929,8516,8518],{"id":8517},"construire-le-système","Construire le système",[35,8520,8521],{},"Construire un tel système en 30 heures, c'est un défi. Sans démonstration, on ne peut pas passer devant le jury, donc il fallait que ça marche à tout prix.",[35,8523,8524],{},"L'entrée du système, c'est le flux live de l'émission TV, plus précisément son audio. Cet audio est envoyé à un modèle de speech-to-text pour être transcrit en texte. Ensuite, un pipeline d'analyse (un peu) complexe se met en route pour analyser le texte, le comprendre, le fact-checker, et l'enrichir avec des informations supplémentaires. Enfin, l'information est envoyée à l'overlay pour être affichée à l'écran au bon moment.",[35,8526,8527],{},"Entre l'entrée et la sortie, on a utilisé deux instances d'OBS pour simuler un environnement de diffusion live. La première capturait le flux live (une seule caméra et un micro dans notre cas) avec un délai de 30 secondes pour simuler le buffer. La seconde capturait la sortie de la première OBS et ajoutait l'overlay pour obtenir un design propre et sympa. C'est cette seconde OBS qui est contrôlée par le système pour afficher l'information à l'écran au bon moment.",[35,8529,8530,8531,8534],{},"On a vraiment galéré à trouver une façon de simuler un live avec un buffer. Sur macOS, lancer OBS deux fois ne marche pas. Utiliser ",[156,8532,8533],{},"open -n -a OBS.app"," pour démarrer une seconde instance marche, mais la seconde instance était vraiment buggée. Pour les faire communiquer, j'ai essayé MediaMTX, même FFMpeg, avec tous les formats de broadcast possibles, mais OBS rappelait le flux de la première OBS sans aucun délai. OBS était trop smart pour nous. Au final, on a trouvé un contournement : streamer sur Internet depuis un premier ordinateur avec 30 secondes de délai et capturer ce stream sur un autre ordinateur.\nOn avait un peu plus de 30 secondes de délai à cause du réseau et du temps de traitement, donc on a dû ajuster notre système de scheduling.",[35,8536,8537],{},[1707,8538],{"alt":8539,"src":8540},"Travailler avec OBS.","\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fobs.webp",[35,8542,8543],{},"Ça ressemble à un setup instable, mais ça a marché !",[35,8545,8546],{},[1216,8547,8548],{},"La suite va devenir technique, n'hésite pas à sauter cette partie si les détails techniques ne t'intéressent pas.",[35,8550,8551,8552,8557],{},"Pour le speech-to-text, on a utilisé des modèles Mistral AI, en particulier ",[39,8553,8556],{"href":8554,"rel":8555},"https:\u002F\u002Fmistral.ai\u002Fnews\u002Fvoxtral-transcribe-2",[77],"Voxtral Realtime",", sorti quelques semaines avant le hackathon. On attendait d'avoir une phrase complète avant de démarrer l'analyse. Cette partie pourrait être beaucoup améliorée : on pourrait avoir un système plus intelligent, et garder plus d'une phrase pour plus de contexte.",[35,8559,8560,8561],{},"Pour le hackathon, on a juste ajouté une étape pour nettoyer le texte, parce que le speech-to-text peut faire des erreurs. ",[1216,8562,8563],{},"Plus tu réduis le délai, moins la transcription est précise.",[35,8565,8566],{},"Pour le pipeline d'analyse, on a utilisé une combinaison de différents modèles Mistral AI assemblés de façon à former un workflow.",[35,8568,8569,8570,8573],{},"Tout commence avec un ",[6862,8571,8572],{},"router agent"," qui reçoit des phrases. Ce router les analyse et détermine quels agents appeler ensuite via une sortie structurée. Ça nous permet d'avoir un workflow dynamique qui s'adapte au contenu de chaque phrase.",[35,8575,8576],{},"Les agents suivants sont ceux qui peuvent être exécutés après le router :",[68,8578,8579,8585,8591,8597],{},[71,8580,8581,8584],{},[6862,8582,8583],{},"Stats Agent"," : cet agent est appelé si la phrase contient des nombres, comme un budget, un pourcentage, ou une quantification. Il va essayer de vérifier si les chiffres sont corrects et de trouver leur source.",[71,8586,8587,8590],{},[6862,8588,8589],{},"Rhetoric Agent"," : cet agent est appelé si la phrase contient une question. Il analyse si le présentateur essaie d'esquiver la question, et si oui, il essaie de retrouver la question esquivée et d'en trouver la réponse.",[71,8592,8593,8596],{},[6862,8594,8595],{},"Personal Coherence Agent"," : cet agent est appelé si la phrase contient une déclaration qu'on peut vérifier en regardant les déclarations précédentes du présentateur. Il analyse si le présentateur est cohérent avec lui-même, ou s'il se contredit.",[71,8598,8599,8602],{},[6862,8600,8601],{},"Context Agent"," : cet agent est appelé si la phrase contient une déclaration à propos d'un événement, d'une loi, ou de toute information contextuelle qu'on peut vérifier. Il va chercher des informations pertinentes sur cette déclaration, et vérifier si c'est correct ou non.",[35,8604,8605],{},"Les Stats Agent et Context Agent avaient tous les deux accès à Internet via un outil de recherche web, afin de trouver des informations pertinentes pour vérifier les affirmations du présentateur.",[35,8607,8608,8609,8612],{},"Ensuite, les sorties de tous ces agents (selon lesquels sont appelés) sont agrégées dans un ",[6862,8610,8611],{},"Editor Agent",". Cet agent écrit un texte lisible et compréhensible pour les spectateurs. Sa sortie est aussi structurée, et c'est ce qu'on rendait dans OBS via l'overlay web.",[8497,8614,8618,8621,8624,8627,8634,8637,8640,8643,8646,8649],{"title":8615,":steps":8616,":relations":8617},"Workflow d'analyse de Veristral","[\n { id: 'router', key: 'router', verb: 'déterminer les prochains agents' },\n { id: 'stats', key: 'stats agent' },\n { id: 'rhetoric', key: 'rhetoric agent' },\n { id: 'coherence', key: 'personal coherence agent' },\n { id: 'context', key: 'context agent' },\n { id: 'editor', key: 'editor agent', verb: 'écrire le texte final' }\n]","[\n { source: 'router', target: 'stats' },\n { source: 'router', target: 'rhetoric' },\n { source: 'router', target: 'coherence' },\n { source: 'router', target: 'context' },\n { source: 'stats', target: 'editor' },\n { source: 'rhetoric', target: 'editor' },\n { source: 'coherence', target: 'editor' },\n { source: 'context', target: 'editor' }\n]",[35,8619,8620],{},"Honnêtement, c'est un pipeline très simple. Ça a bien marché pour le hackathon, mais dans un produit réel, il devrait être beaucoup plus complexe, avec plus d'agents et plus de fonctionnalités. Par exemple, on a exploré un système où le workflow attend un signal avant de se mettre en sommeil. Ce signal serait envoyé par le STT un peu après la phrase initiale pour apporter plus de contexte, et déterminer si le présentateur se corrige ou non.",[35,8622,8623],{},"Ça fait partie d'un workflow Temporal. Une fois terminé, on demande à Temporal de dormir jusqu'au bon moment pour afficher l'information à l'écran. On connaît ce moment parce qu'on connaît t0 (l'instant juste avant que le STT renvoie une phrase) et la taille du buffer. Par exemple, si t0 vaut 14:15:13, on sait que le workflow doit dormir jusqu'à 14:15:43. Peu importe combien de temps prend l'analyse, l'information sera affichée au bon moment.",[35,8625,8626],{},"Avant de dormir, on vérifie si l'heure actuelle est toujours avant l'heure planifiée. Sinon, on jette l'information car il est trop tard pour l'afficher. Quand le workflow se réveille, il envoie l'information à une autre application dédiée.",[35,8628,8629,8630,8633],{},"Temporal nous permet de paralléliser l'analyse sans complexité, et de planifier l'affichage des informations au bon moment sans passer par une base de données et un scheduler custom. ",[1216,8631,8632],{},"On aurait pu faire ça avec le task scheduling de Laravel, mais Emma et Godefroy étaient plus à l'aise avec Python."," Un autre avantage de Temporal (et de l'approche « trigger un workflow ») est qu'on découple complètement l'analyse du scheduling.",[35,8635,8636],{},"L'interface entre Godefroy, qui bossait sur le STT, et Emma, qui bossait sur l'analyse, c'était Temporal : un système robuste et bien documenté. Pas besoin de réinventer la roue, et c'est super important dans un hackathon pour gagner du temps et éviter les bugs bloquants.",[35,8638,8639],{},"Pour contrôler la seconde OBS, j'ai construit une application Laravel. Cette application a un endpoint qui reçoit l'information à afficher depuis le workflow Temporal. Ensuite, elle envoie les nouvelles données au frontend avec Reverb (le système WebSocket de Laravel). Enfin, l'endpoint déclenche un changement de scène dans la seconde OBS pour afficher l'information à l'écran.",[35,8641,8642],{},"Tu peux avoir du mal à comprendre cette partie. OBS est un logiciel de live streaming. Tu as des scènes dans lesquelles tu peux empiler différentes sources, comme une caméra, une capture d'écran, ou une source navigateur. Cette dernière est très intéressante parce qu'elle permet d'utiliser une page web comme source dans OBS.",[35,8644,8645],{},"Donc, l'application que j'ai construite contient à la fois le backend (pour recevoir l'information et l'envoyer au frontend) et un frontend qui écoute le WebSocket et met à jour le contenu de la page web. Ce frontend est intégré dans la seconde OBS comme une source navigateur avec un fond transparent, ce qui permet toujours de voir le présentateur derrière. On avait deux scènes dans la seconde OBS : une avec une grande source caméra pour montrer le présentateur quand il n'y avait rien à afficher, et une avec une plus petite caméra pour faire de la place à l'information à l'écran.",[35,8647,8648],{},"Avec un peu plus de recul, le système complet ressemble à ça :",[8497,8650,8655,8661,8665,8668,8671,8685,8691,8694,8697,8701,8704,8707,8710,8715,8719,8722,8725,8728,8731,8734,8737,8740,8742,8745,8748],{"title":8651,":steps":8652,":relations":8653,":ui":8654},"Workflow global de Veristral","[\n { key: 'microphone', verb: 'capturer l\\'audio' },\n { key: 'OBS 1', verb: 'envoyer le flux avec buffer' },\n { key: 'router', verb: 'déterminer les prochains agents' },\n { key: 'stats', verb: 'stats agent' },\n { key: 'rhetoric', verb: 'rhetoric agent' },\n { key: 'coherence', verb: 'personal coherence agent' },\n { key: 'context', verb: 'context agent' },\n { key: 'editor', verb: 'editor agent' },\n { key: 'trigger', verb: 'déclencher une mise à jour de l\\'overlay' },\n { key: 'app', verb: 'mettre à jour l\\'overlay' },\n { key: 'OBS 2', verb: 'envoyer le flux enrichi' },\n { key: 'live show', verb: 'afficher aux spectateurs' }\n]","[\n { source: 'microphone', target: 'OBS 1' },\n { source: 'OBS 1', target: 'router' },\n { source: 'router', target: 'stats' },\n { source: 'router', target: 'rhetoric' },\n { source: 'router', target: 'coherence' },\n { source: 'router', target: 'context' },\n { source: 'stats', target: 'editor' },\n { source: 'rhetoric', target: 'editor' },\n { source: 'coherence', target: 'editor' },\n { source: 'context', target: 'editor' },\n { source: 'editor', target: 'trigger' },\n { source: 'trigger', target: 'app' },\n { source: 'app', target: 'OBS 2' },\n { source: 'OBS 2', target: 'live show' },\n { source: 'OBS 1', target: 'OBS 2' }\n]","{\n 'root': 'h-240'\n}",[35,8656,8657,8658],{},"Tout notre code source est disponible sur GitHub : ",[1599,8659],{"repo":8660},"Barbapapazes\u002Fhackathon-paris",[1929,8662,8664],{"id":8663},"petite-note-sur-temporal","Petite note sur Temporal",[35,8666,8667],{},"Temporal peut sembler overkill, mais ça a sauvé notre projet. Prends un moment pour trouver une solution à notre problème de parallélisation et de scheduling sans Temporal, et tu réaliseras vite à quel point c'est compliqué de construire un système de scheduling robuste qui scale. Avec Temporal, ça marche, tout simplement.",[35,8669,8670],{},"On l'a utilisé pour deux raisons :",[68,8672,8673,8682],{},[71,8674,8675,8676,8681],{},"Temporal a organisé un meetup à Paris le 5 novembre 2025, et j'y ai assisté. Un des talks était « Combining Temporal's durability with streaming of LLM responses using NATS Jetstream » par ",[39,8677,8680],{"href":8678,"rel":8679},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-moreau-26060614b\u002F",[77],"Nicolas Moreau",", software engineer chez Mistral AI.",[71,8683,8684],{},"Le scheduling, ou le fait d'attendre un moment précis pour faire quelque chose, c'est vraiment un sujet de workflow. Temporal est un moteur de workflow.",[35,8686,8687,8688],{},"Ces deux points ont suffi à me convaincre d'utiliser Temporal. ",[1216,8689,8690],{},"Si on utilise la même technologie que Mistral AI, peut-être que ça rendra le projet plus facile à comprendre et plus intéressant et réaliste pour le jury. Je ne sais pas comment ça a influencé la perception du jury, mais on en a parlé avec des ingénieurs Mistral AI qui étaient présents.",[35,8692,8693],{},"Au travail, on utilise Temporal dans notre infrastructure backend. C'est tellement puissant que j'essaie de tout mettre dans Temporal.",[35,8695,8696],{},"Pour mes projets perso, j'utilise surtout Cloudflare Workflows. Sous le capot, c'est très différent, mais d'un point de vue usage, ça donne la même sensation. Une fois que tu as la puissance d'un workflow, tout dans ta vie devient un workflow. Le truc drôle, c'est que j'automatise ma vie en ce moment, principalement avec Cloudflare Workflows, donc les utiliser pendant le hackathon était naturel pour moi.",[1929,8698,8700],{"id":8699},"passer-au-monde-réel","Passer au monde réel",[35,8702,8703],{},"Faire confiance au système dès le début peut être très difficile, donc on peut facilement imaginer un setup où le système fait la majeure partie du travail, avec un humain dans la boucle pour valider, vérifier, et corriger l'information avant qu'elle soit affichée à l'écran. C'est une solution faisable comme première étape.",[35,8705,8706],{},"Mais un système complètement autonome est le vrai objectif, parce que retirer l'humain de la boucle, surtout dans des émissions politiques, est vraiment important pour réduire les biais et s'assurer que tous les partis politiques sont traités de la même manière.",[35,8708,8709],{},"Maintenant que le hackathon est terminé, il y a tellement de choses qu'on peut faire pour continuer d'améliorer le projet, et j'ai entendu de sources fiables que Mistral AI était intéressé, et peut-être même en train de construire un système similaire.",[35,8711,8712],{},[1216,8713,8714],{},"On est allés un peu plus loin pendant le hackathon, mais on n'a pas réussi à le faire marcher. On a commencé à construire un système qui collecte tout ce qu'on fact-check pendant l'émission live et le rend disponible sur un site centralisé. Comme ça, les spectateurs pourraient juste scanner un QR code à l'écran pour accéder au fact-check complet, puisque l'espace est limité sur l'overlay.",[51,8716,8718],{"id":8717},"à-propos-de-lusage-intensif-de-lia","À propos de l'usage intensif de l'IA",[35,8720,8721],{},"Pendant les deux jours du hackathon, on a énormément utilisé l'IA pour écrire du code.",[35,8723,8724],{},"Emma s'est principalement appuyée sur Gemini Pro via son chat UI pour écrire du code, tandis que Godefroy utilisait à la fois Gemini Pro via Antigravity et le chat, ainsi que Codex d'OpenAI. De mon côté, j'ai utilisé GitHub Copilot Pro via VS Code, et principalement Sonnet 4.6 et GPT-5.3-Codex.",[35,8726,8727],{},"Même avec son plan payant, Emma a atteint la limite de Gemini Pro, et elle a dû attendre quelques heures avant de pouvoir l'utiliser à nouveau. Godefroy a dû alterner entre Antigravity et Codex pour éviter d'atteindre la limite de l'un des deux. Avec GitHub Copilot Pro, il n'y a pas de hard limit car on paye par requête. Pour te donner une idée, j'ai fait 94 requêtes en un week-end. Le plan Pro inclut 300 requêtes par mois, donc j'ai brûlé à peu près un tiers du quota mensuel sur un week-end.",[35,8729,8730],{},"C'est intéressant de voir comment on a chacun utilisé l'IA différemment. Emma utilisait un notebook Jupyter pour écrire l'algorithme de débunking. Elle alternait entre poser des questions à Gemini Pro via le chat et copier-coller le code dans le notebook. Godefroy utilisait Antigravity Agent ou Codex pour éditer et commit du code directement. C'était une façon de travailler très vibe-coding.",[35,8732,8733],{},"Pour tous les deux, j'ai été vraiment impressionné par la vitesse à laquelle ils généraient du code qui marche. Même pour configurer OBS, Godefroy a demandé à Gemini Pro comment faire, et en quelques minutes, il avait une configuration fonctionnelle.",[35,8735,8736],{},"Cependant, comme je l'ai dit au début de l'article, sans comprendre le code, une organisation claire, et un projet bien structuré, ça devient un bazar très vite. Et ensuite, restructurer le code pour le rendre plus propre, extensible, et robuste devient un cauchemar.",[35,8738,8739],{},"J'ai passé beaucoup de temps entre samedi et dimanche à refactorer à la fois l'algorithme de débunking et le code du workflow pour le rendre plus robuste pour les fonctionnalités à venir.",[1158,8741],{},[35,8743,8744],{},"Avec l'IA, perdre le contrôle du code est très facile. Est-ce que c'est grave ? Je ne sais toujours pas.",[35,8746,8747],{},"Peut-être qu'on est en train de créer une abstraction de plus haut niveau au-dessus du code, où on ne se soucie pas des détails d'implémentation. Peut-être que c'est ok la plupart du temps. Peut-être que c'est la pire abstraction, et que le code comme commodité est une illusion pour les non-software engineers.",[35,8749,8750],{},"Une chose dont je suis sûr, en revanche : créer des applications complexes requiert toujours une bonne architecture, la capacité à découper des problèmes complexes en problèmes plus petits (adaptés aux capacités de l'IA, pas aux capacités humaines), et une bonne compréhension du code.",{"title":13,"searchDepth":14,"depth":14,"links":8752},[8753,8754,8755,8756,8757,8758],{"id":8210,"depth":14,"text":8211},{"id":8247,"depth":14,"text":8248},{"id":8269,"depth":14,"text":8270},{"id":8442,"depth":14,"text":8443},{"id":8449,"depth":14,"text":8450},{"id":8471,"depth":14,"text":8472},"775ff098-6d18-49de-80b5-311153044aaa","2026-03-02","Comment on a construit Veristral et gagné le Hackathon Mistral AI Paris 2026 : fact-checking TV en temps réel grâce au buffer de diffusion.",{},"\u002Ffr\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon","---\ncontentId: 775ff098-6d18-49de-80b5-311153044aaa\ntitle: \"De l'idée à la victoire : Hackathon Mistral AI Paris 2026\"\ndescription: \"Comment on a construit Veristral et gagné le Hackathon Mistral AI Paris 2026 : fact-checking TV en temps réel grâce au buffer de diffusion.\"\ndate: 2026-03-02\n---\n\nTout a commencé par un tweet. _Les choses commencent souvent par un tweet._\n\nLe 10 février 2026, [Mistral AI a annoncé un hackathon mondial](https:\u002F\u002Fx.com\u002Fmistralai\u002Fstatus\u002F2021239601503912409). Je savais que les places étaient limitées, alors j'ai postulé immédiatement, en espérant être sélectionné, mais sans la moindre idée de ce que j'allais faire, et sans vraiment savoir dans quoi je m'embarquais. Juste l'envie de me laisser surprendre par l'aventure.\n\n## Pour le défi\n\nJ'ai déjà fait quelques hackathons, comme la [Nuit de l'Info](https:\u002F\u002Fwww.nuitdelinfo.com\u002F) ou des hackathons plus entrepreneuriaux, mais le dernier remonte à des années. J'ai appris énormément grâce à ces expériences, et je suis sûr qu'elles m'ont fait progresser techniquement. Le premier hackathon que j'ai fait m'a ouvert les yeux sur le développement web, et je n'en suis jamais reparti.\n\nAujourd'hui, je connais évidemment beaucoup plus de choses en programmation, mais aussi plus de technos, plus de choses sur l'architecture d'un projet, et plus de méthodes pour découper le travail afin d'avoir quelque chose qui fonctionne en peu de temps.\n\nDans un hackathon, ces compétences sont décisives : un mauvais choix de tech ou un mauvais découpage peut ruiner toutes tes chances d'avoir un projet qui marche à la fin. Avoir un prototype fonctionnel (ou proof-of-concept) est obligatoire. L'idée et la façon dont elle est présentée comptent, bien sûr, mais sans prototype, tu ne peux même pas passer devant un jury.\n\nJe vois donc vraiment ce hackathon comme une super opportunité de me challenger sur ces compétences. Est-ce que tout ce que j'ai appris ces dernières années aide vraiment ? Est-ce que je suis maintenant capable de livrer plus qu'une app CRUD dans un hackathon ?\n\nEn parallèle, l'IA a complètement changé la donne. Avant, les gens qui savaient à peine coder n'avaient pas beaucoup de chances de gagner un hackathon. Maintenant, tout le monde peut construire quelque chose avec l'IA. Jouer avec du code devient une commodité. Mais ça veut aussi dire que ceux qui savent coder peuvent produire des projets bien plus complexes et intéressants. Donc **l'IA a clairement relevé la barre**.\n\nQu'on soit clairs : le software engineering n'est pas mort. À un moment, sans une architecture correcte, une compréhension claire du code et de la façon de résoudre les problèmes, et savoir quel outil utiliser (et quand), tu ne termines pas le hackathon. **Sans garde-fous, l'IA n'est qu'un générateur de dette**.\n\nLe dimanche avant le hackathon, j'ai reçu un email de Mistral AI avec pour sujet « Registration approved ». J'étais dedans ! Toujours aucune idée et aucune équipe, mais super heureux de faire partie de l'aventure, surtout avec une boîte comme Mistral AI.\n\n## L'aventure commence\n\nLa première étape, c'était de trouver une équipe. Le hackathon a démarré le 28 février 2026. J'ai reçu l'email d'acceptation le 22 février, donc j'avais une semaine pour y arriver.\n\nMistral a ouvert un canal dédié sur leur serveur Discord pour que les participants puissent se présenter et trouver des coéquipiers. Ça m'a pris quelques jours, mais j'ai fini par me présenter sur Discord. J'étais le premier dans le canal. Surprise : seules trois personnes (moi compris) se sont présentées.\n\nUn autre canal était dédié à la recherche de coéquipiers. J'ai vu un message de quelqu'un nommé « Godefroy » qui disait qu'ils étaient deux doctorants en physique et qu'ils cherchaient quelqu'un avec des compétences frontend et une expérience produit. Il a aussi dit qu'ils pratiquaient le vibe coding. J'ai répondu immédiatement : j'étais partant pour les rejoindre.\n\nIls se connaissaient déjà, donc travailler avec eux serait plus simple. Et apporter un bon bagage en software engineering à l'équipe me semblait être un complément parfait à leurs compétences, leur façon de bosser en vibe-coding, et leur détermination à produire le meilleur projet possible. Le vibe coding dans la bonne direction, avec les bons outils au bon moment, peut rendre des vibe coders incroyablement productifs.\n\nQuelques heures plus tard, il m'a envoyé un message sur Discord pour m'expliquer leur idée de projet.\n\nEnsuite, on a planifié un appel le vendredi pour discuter de toutes les idées qu'on avait en tête et choisir celle sur laquelle on voulait travailler. Nous (eux) avions quelques idées. On a terminé l'appel avec une short list, et on a décidé de dormir dessus et de faire le choix final avant d'arriver au hackathon. À ce moment-là, on était quatre dans l'équipe.\n\n## Pour la victoire\n\n_Jusqu'à ce qu'on entende le nom de notre projet annoncé comme gagnant du hackathon, notre seul objectif était de s'amuser et d'apprendre._\n\nAvant d'arriver au hackathon, on a eu une dernière discussion sur WhatsApp à propos de l'idée qu'on voulait faire. On a choisi **un système en temps réel pour débunker des émissions politiques à la TV**. Bien plus compliqué que ça n'en a l'air, mais on parlera du projet plus tard.\n\nLe hackathon, organisé par [Iterate](https:\u002F\u002Fiterate.inc\u002F), a commencé à 9 h le 28 février 2026, dans les bureaux de Raise à Paris, près des Invalides. Le petit-déjeuner était servi, et on a eu l'occasion de rencontrer les autres participants (et notre équipe) pour la première fois. C'est là que j'ai rencontré [Emma](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femma-grospellier-3491791a1\u002F) et [Godefroy](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgodefroymeynard\u002F), les deux doctorants en physique.\n\nLa réunion d'introduction était à 10 h, donc on avait le temps de discuter et de faire connaissance. La vérité, c'est qu'on avait surtout envie d'ouvrir nos laptops et de commencer à hacker. C'était aussi le moment où le quatrième membre de l'équipe a décidé de rejoindre une autre team. On n'était plus que trois, mais ça n'a pas changé notre détermination à produire le meilleur projet possible.\n\n![La réunion d'introduction.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fintro-meeting.webp)\n\n![Critères de jugement du hackathon. 20% pour la technicité, 20% pour la créativité, 20% pour l'utilité, 20% pour la démo, 20% pour l'alignement avec la track.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fjudging-criteria.webp)\n\nAprès une heure de présentations sur le hackathon, les challenges des sponsors, et le planning du week-end, on a enfin pu aller chercher un endroit pour travailler dans le bâtiment. C'était un peu chaotique : 100 participants et pas beaucoup de place. Mais on a trouvé une petite table dans un coin d'une salle. On a passé les 30 heures suivantes là-bas, à bosser sur notre projet. On était idéalement placés près de l'espace repas, donc on pouvait savoir quand la nourriture était servie et être les premiers dans la file. _Pas besoin d'attendre : tu prends et tu continues de travailler._\n\n![Notre poste de travail pour le week-end.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fworking-place.webp)\n\nDans les premières heures, on a structuré le projet en définissant les frontières : ce qu'on voulait faire, comment on voulait le faire, et quand on considérerait le projet comme « fini ». On a aussi commencé à se répartir le travail en fonction de notre compréhension initiale du problème et de nos compétences respectives.\n\nEnsuite, on a codé jusqu'à la fin de la journée. Les pauses étaient courtes et rares, on était vraiment concentrés pour que ça marche, et c'était plus dur que prévu. Vers 22 h, nos cerveaux commençaient à être fatigués et irrités. Godefroy et Emma ont décidé d'aller dormir, et c'était clairement une bonne idée. J'ai pris une longue pause et je suis allé à la cuisine manger de la pizza. Une pause bien méritée avant de reprendre.\n\n![Manger de la pizza pour recharger les batteries.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpizza.webp)\n\nJ'ai continué à bosser sur le projet de 23 h à 1 h 30. J'ai beaucoup refactoré du code, en essayant de le simplifier et de le rendre plus robuste, en espérant que ça marcherait pour le lendemain. Je suis finalement rentré vers 2 h, juste à temps pour attraper le dernier métro. Je n'étais pas vraiment fatigué, porté par l'adrénaline du hackathon, donc j'ai continué à hacker jusqu'à 6 h. J'ai fait une sieste jusqu'à 8 h, puis je suis retourné au hackathon pour le deuxième (et dernier) jour.\n\nOn avait jusqu'à 15 h 30 pour finir le projet, enregistrer une vidéo pour le présenter, et préparer la présentation pour le jury. Honnêtement, c'était énorme comme charge de travail, parce qu'on n'arrivait pas à faire fonctionner tous les composants ensemble. Chaque partie marchait bien seule, mais une fois assemblées, ça ne marchait plus du tout.\n\nPas le choix : il fallait que ça marche. Les choses ont commencé à fonctionner vers 14 h 30. On a filmé la vidéo à 15 h 22, on l'a uploadée à 15 h 28, et on a soumis le projet à 15 h 29. Oui, une minute avant la deadline. _Comme d'habitude dans un hackathon._\n\nOn était très stressés, mais on y est arrivés, et on était vraiment fiers d'avoir quelque chose qui fonctionne à la fin du hackathon. _On l'a fait marcher ! Merci Godefroy d'avoir trouvé la solution finale, et merci Emma d'avoir préparé à la fois la soumission et la présentation._\n\nTu crois que c'était la fin de l'aventure ? Non ! On avait une présentation, inachevée, et le texte n'était pas prêt. On avait seulement 3 minutes pour pitcher notre projet devant un jury d'experts, et on devait le faire en anglais. Donc, jusqu'à notre passage à 16 h 30, on a peaufiné les slides et préparé la démo live. Tout est allé si vite qu'on n'a même pas eu le temps d'être nerveux ou stressés. _Ah, et la démo live a marché comme un charme. Aucun problème !_\n\n![Quelques minutes avant le pitch.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpitch-preparation.webp)\n\nTrois minutes, c'est court, et on avait beaucoup à dire, mais ce sont les règles. Choisir les bons mots pour convaincre le jury que notre projet méritait de gagner. Un énorme merci à [Roland](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Froland-sosin\u002F) d'avoir été notre timekeeper, et d'avoir été gentil et rassurant juste avant le pitch.\n\n![Pitcher devant le jury.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fpitch.webp)\n\n_Merci [Sam](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsam-rouan-5a3677110\u002F) pour les photos, et pour la longue discussion du samedi après-midi !_\n\nUne fois que toutes les équipes ont présenté, tous les participants sont allés dans une salle pour attendre les résultats. Ils ont annoncé 12 finalistes qui devaient refaire une présentation finale devant le jury et tous les participants. Ils ont annoncé le premier nom, puis le deuxième. C'était nous ! Wow, vraiment ? On était finalistes ? Inattendu, et peut-être, juste peut-être, qu'on avait une chance de gagner ce hackathon ?\n\nOn a repitché devant tout le monde du mieux qu'on pouvait, et encore une fois, on a attendu les résultats. _Cette fois, on n'a pas fait la démo live : on a juste présenté la vidéo._\n\nCinq minutes plus tard, le jury a annoncé le gagnant. Ils ont commencé par annoncer la troisième place, puis la deuxième, et enfin le vainqueur. On a entendu le nom de notre projet, et on n'y croyait pas. On s'est regardés, pour être sûrs qu'on avait entendu la même chose, et ouais : c'était bien notre équipe.\n\n**On a gagné le Hackathon Mistral AI Paris 2026 !** Irréel et inattendu ! _On l'a fait ! 🫶_\n\n![L'équipe devant l'écran principal affichant le logo de notre projet. De gauche à droite : Estéban, Godefroy et Emma.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fteam.webp)\n\nLa semaine prochaine, on part pour la finale internationale ! On aura l'opportunité de présenter notre projet en live, devant des participants de tous les hackathons, pour tenter de gagner le prix mondial.\n\nUne aventure incroyable qui a commencé par un tweet, et qui s'est terminée par une victoire. Très fier de l'équipe, du projet qu'on a construit, et de la façon dont on a travaillé ensemble. J'adore les hackathons !\n\n![Tous les participants, le jury et les organisateurs du hackathon formant un M.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fgroup.webp)\n\nMaintenant, il est temps de se reposer un peu et de préparer la suite.\n\n## Veristral\n\nJ'ai beaucoup parlé du hackathon, de l'ambiance, du planning, et de comment on a réussi à le faire marcher, mais je n'ai pas dit un mot du projet qu'on a construit (et qui a gagné le hackathon). Alors voilà.\n\n## L'idée\n\nNotre idée était de construire un système en temps réel pour débunker des émissions politiques à la TV. On voulait pouvoir afficher, pendant l'émission, pendant que le présentateur parle, si ce qu'il dit est vrai ou non, s'il esquive la question, et s'il se contredit. Automatiquement, un pop-up apparaîtrait à l'écran pour apporter des informations supplémentaires aux spectateurs, les aider à mieux comprendre l'émission, et leur permettre de se faire leur propre avis sur ce qu'ils regardent.\n\nLes fake news peuvent être vraiment dangereuses, et elles se propagent très vite, bien plus vite que la vérité. Parfois, des journalistes les débunkent plus tard, même des jours après l'émission. Malheureusement, c'est trop tard. Les dégâts sont faits, et le débunking ne touchera pas la même audience que la fake news. Les fake news sont une vraie menace pour la démocratie, et elles font partie des moyens qu'ont des puissances extérieures pour déstabiliser un pays.\n\nLe vrai défi, c'était la contrainte temps réel. Comment, en quelques millisecondes, analyser la parole du présentateur, la comprendre, décider si c'est vrai ou non, l'enrichir avec des informations supplémentaires, et l'afficher à l'écran ? Honnêtement, c'est impossible. Pour l'instant, on n'a pas la technologie pour le faire de bout en bout.\n\nVoxtral, le modèle speech-to-text (STT) temps réel de Mistral AI, a été annoncé comme étant sous les 200 ms, ce qui est impressionnant, mais ensuite il faut quand même analyser le texte, chercher des informations, et les renvoyer à l'écran. On a vite tapé dans le mur de la latence.\n\nIl y a un autre problème. Le présentateur peut dire quelque chose puis se corriger quelques secondes plus tard. Si on affiche des informations dès qu'on les a, on peut se retrouver avec une expérience utilisateur catastrophique, et décrédibiliser complètement le système.\n\nDonc il fallait trouver une autre solution pour avoir un système en temps réel.\n\n## Le buffer à la rescousse\n\nLa plupart des émissions de TV en direct ne sont pas vraiment en direct. Elles sont décalées de quelques secondes, et elles ont un buffer de retard pour pouvoir couper le flux si quelque chose d'inattendu se produit. Ce délai peut être bien plus important. Une émission française problématique a été forcée d'avoir un buffer de 45 minutes pour éditer le live en temps réel et retirer du contenu problématique avant la diffusion.\n\nÇa nous apprend deux choses :\n\n1. Le concept de buffer entre le live et la diffusion existe déjà dans les émissions de TV.\n2. Le buffer peut être très long.\n\nOn peut utiliser ce buffer pour notre système temps réel. Imagine la situation suivante. À t0, le présentateur parle de quelque chose. Le flux est décalé de 30 secondes, donc les spectateurs regardent en réalité ce que le présentateur a dit à t-30 s. Pendant ces 30 secondes, on peut analyser la parole, la comprendre, décider si c'est vrai ou non, l'enrichir avec des informations supplémentaires, et renvoyer le tout à l'écran. À t+30 s, les spectateurs verront l'information à l'écran au même moment où ils entendent le présentateur en parler. De cette façon, on peut avoir un système en temps réel sans être limités par la technologie.\n\n\u003CVeristralTimeline \u002F>\n\nDu point de vue workflow, on a l'architecture simplifiée suivante :\n\n\u003CWorkflow\n  title=\"Workflow de Veristral avec le buffer\"\n  :steps=\"[\n    { key: 'microphone', verb: 'capturer l\\'audio' },\n    { key: 'OBS 1', verb: 'envoyer le flux avec buffer' },\n    { key: 'system', verb: 'analyser et enrichir' },\n    { key: 'OBS 2', verb: 'envoyer le flux enrichi' },\n    { key: 'live show', verb: 'afficher aux spectateurs' }\n  ]\"\n  :relations=\"[\n    { source: 'microphone', target: 'OBS 1' },\n    { source: 'OBS 1', target: 'system' },\n    { source: 'system', target: 'OBS 2' },\n    { source: 'OBS 2', target: 'live show' },\n    { source: 'OBS 1', target: 'OBS 2' }\n  ]\"\n  :ui=\"{\n    'root': 'h-140'\n  }\"\n\u002F>\n\nCette solution de buffer nous donne une large fenêtre pour analyser la parole du présentateur et planifier l'affichage de l'information au bon moment. On peut même utiliser les phrases suivantes pour améliorer le contexte et annuler un fact-check si le présentateur se corrige. C'est vraiment puissant et élégant.\n\nTu as peut-être déjà entendu parler de systèmes IA pour analyser et débunker des émissions TV, mais c'était toujours de l'analyse après coup. Notre système change la donne en pouvant le faire en temps réel et en touchant la même audience que la fake news. **C'est un vrai game-changer dans la lutte contre les fake news**. _Si tu es un provider IA ou une entreprise média, et que ce projet t'intéresse, contacte-nous._\n\n### Construire le système\n\nConstruire un tel système en 30 heures, c'est un défi. Sans démonstration, on ne peut pas passer devant le jury, donc il fallait que ça marche à tout prix.\n\nL'entrée du système, c'est le flux live de l'émission TV, plus précisément son audio. Cet audio est envoyé à un modèle de speech-to-text pour être transcrit en texte. Ensuite, un pipeline d'analyse (un peu) complexe se met en route pour analyser le texte, le comprendre, le fact-checker, et l'enrichir avec des informations supplémentaires. Enfin, l'information est envoyée à l'overlay pour être affichée à l'écran au bon moment.\n\nEntre l'entrée et la sortie, on a utilisé deux instances d'OBS pour simuler un environnement de diffusion live. La première capturait le flux live (une seule caméra et un micro dans notre cas) avec un délai de 30 secondes pour simuler le buffer. La seconde capturait la sortie de la première OBS et ajoutait l'overlay pour obtenir un design propre et sympa. C'est cette seconde OBS qui est contrôlée par le système pour afficher l'information à l'écran au bon moment.\n\nOn a vraiment galéré à trouver une façon de simuler un live avec un buffer. Sur macOS, lancer OBS deux fois ne marche pas. Utiliser `open -n -a OBS.app` pour démarrer une seconde instance marche, mais la seconde instance était vraiment buggée. Pour les faire communiquer, j'ai essayé MediaMTX, même FFMpeg, avec tous les formats de broadcast possibles, mais OBS rappelait le flux de la première OBS sans aucun délai. OBS était trop smart pour nous. Au final, on a trouvé un contournement : streamer sur Internet depuis un premier ordinateur avec 30 secondes de délai et capturer ce stream sur un autre ordinateur.\nOn avait un peu plus de 30 secondes de délai à cause du réseau et du temps de traitement, donc on a dû ajuster notre système de scheduling.\n\n![Travailler avec OBS.](\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon\u002Fobs.webp)\n\nÇa ressemble à un setup instable, mais ça a marché !\n\n_La suite va devenir technique, n'hésite pas à sauter cette partie si les détails techniques ne t'intéressent pas._\n\nPour le speech-to-text, on a utilisé des modèles Mistral AI, en particulier [Voxtral Realtime](https:\u002F\u002Fmistral.ai\u002Fnews\u002Fvoxtral-transcribe-2), sorti quelques semaines avant le hackathon. On attendait d'avoir une phrase complète avant de démarrer l'analyse. Cette partie pourrait être beaucoup améliorée : on pourrait avoir un système plus intelligent, et garder plus d'une phrase pour plus de contexte.\n\nPour le hackathon, on a juste ajouté une étape pour nettoyer le texte, parce que le speech-to-text peut faire des erreurs. _Plus tu réduis le délai, moins la transcription est précise._\n\nPour le pipeline d'analyse, on a utilisé une combinaison de différents modèles Mistral AI assemblés de façon à former un workflow.\n\nTout commence avec un **router agent** qui reçoit des phrases. Ce router les analyse et détermine quels agents appeler ensuite via une sortie structurée. Ça nous permet d'avoir un workflow dynamique qui s'adapte au contenu de chaque phrase.\n\nLes agents suivants sont ceux qui peuvent être exécutés après le router :\n\n1. **Stats Agent** : cet agent est appelé si la phrase contient des nombres, comme un budget, un pourcentage, ou une quantification. Il va essayer de vérifier si les chiffres sont corrects et de trouver leur source.\n2. **Rhetoric Agent** : cet agent est appelé si la phrase contient une question. Il analyse si le présentateur essaie d'esquiver la question, et si oui, il essaie de retrouver la question esquivée et d'en trouver la réponse.\n3. **Personal Coherence Agent** : cet agent est appelé si la phrase contient une déclaration qu'on peut vérifier en regardant les déclarations précédentes du présentateur. Il analyse si le présentateur est cohérent avec lui-même, ou s'il se contredit.\n4. **Context Agent** : cet agent est appelé si la phrase contient une déclaration à propos d'un événement, d'une loi, ou de toute information contextuelle qu'on peut vérifier. Il va chercher des informations pertinentes sur cette déclaration, et vérifier si c'est correct ou non.\n\nLes Stats Agent et Context Agent avaient tous les deux accès à Internet via un outil de recherche web, afin de trouver des informations pertinentes pour vérifier les affirmations du présentateur.\n\nEnsuite, les sorties de tous ces agents (selon lesquels sont appelés) sont agrégées dans un **Editor Agent**. Cet agent écrit un texte lisible et compréhensible pour les spectateurs. Sa sortie est aussi structurée, et c'est ce qu'on rendait dans OBS via l'overlay web.\n\n\u003CWorkflow\n  title=\"Workflow d'analyse de Veristral\"\n  :steps=\"[\n    { id: 'router', key: 'router', verb: 'déterminer les prochains agents' },\n    { id: 'stats', key: 'stats agent' },\n    { id: 'rhetoric', key: 'rhetoric agent' },\n    { id: 'coherence', key: 'personal coherence agent' },\n    { id: 'context', key: 'context agent' },\n    { id: 'editor', key: 'editor agent', verb: 'écrire le texte final' }\n  ]\"\n  :relations=\"[\n    { source: 'router', target: 'stats' },\n    { source: 'router', target: 'rhetoric' },\n    { source: 'router', target: 'coherence' },\n    { source: 'router', target: 'context' },\n    { source: 'stats', target: 'editor' },\n    { source: 'rhetoric', target: 'editor' },\n    { source: 'coherence', target: 'editor' },\n    { source: 'context', target: 'editor' }\n  ]\"\n\u002F>\n\nHonnêtement, c'est un pipeline très simple. Ça a bien marché pour le hackathon, mais dans un produit réel, il devrait être beaucoup plus complexe, avec plus d'agents et plus de fonctionnalités. Par exemple, on a exploré un système où le workflow attend un signal avant de se mettre en sommeil. Ce signal serait envoyé par le STT un peu après la phrase initiale pour apporter plus de contexte, et déterminer si le présentateur se corrige ou non.\n\nÇa fait partie d'un workflow Temporal. Une fois terminé, on demande à Temporal de dormir jusqu'au bon moment pour afficher l'information à l'écran. On connaît ce moment parce qu'on connaît t0 (l'instant juste avant que le STT renvoie une phrase) et la taille du buffer. Par exemple, si t0 vaut 14:15:13, on sait que le workflow doit dormir jusqu'à 14:15:43. Peu importe combien de temps prend l'analyse, l'information sera affichée au bon moment.\n\nAvant de dormir, on vérifie si l'heure actuelle est toujours avant l'heure planifiée. Sinon, on jette l'information car il est trop tard pour l'afficher. Quand le workflow se réveille, il envoie l'information à une autre application dédiée.\n\nTemporal nous permet de paralléliser l'analyse sans complexité, et de planifier l'affichage des informations au bon moment sans passer par une base de données et un scheduler custom. _On aurait pu faire ça avec le task scheduling de Laravel, mais Emma et Godefroy étaient plus à l'aise avec Python._ Un autre avantage de Temporal (et de l'approche « trigger un workflow ») est qu'on découple complètement l'analyse du scheduling.\n\nL'interface entre Godefroy, qui bossait sur le STT, et Emma, qui bossait sur l'analyse, c'était Temporal : un système robuste et bien documenté. Pas besoin de réinventer la roue, et c'est super important dans un hackathon pour gagner du temps et éviter les bugs bloquants.\n\nPour contrôler la seconde OBS, j'ai construit une application Laravel. Cette application a un endpoint qui reçoit l'information à afficher depuis le workflow Temporal. Ensuite, elle envoie les nouvelles données au frontend avec Reverb (le système WebSocket de Laravel). Enfin, l'endpoint déclenche un changement de scène dans la seconde OBS pour afficher l'information à l'écran.\n\nTu peux avoir du mal à comprendre cette partie. OBS est un logiciel de live streaming. Tu as des scènes dans lesquelles tu peux empiler différentes sources, comme une caméra, une capture d'écran, ou une source navigateur. Cette dernière est très intéressante parce qu'elle permet d'utiliser une page web comme source dans OBS.\n\nDonc, l'application que j'ai construite contient à la fois le backend (pour recevoir l'information et l'envoyer au frontend) et un frontend qui écoute le WebSocket et met à jour le contenu de la page web. Ce frontend est intégré dans la seconde OBS comme une source navigateur avec un fond transparent, ce qui permet toujours de voir le présentateur derrière. On avait deux scènes dans la seconde OBS : une avec une grande source caméra pour montrer le présentateur quand il n'y avait rien à afficher, et une avec une plus petite caméra pour faire de la place à l'information à l'écran.\n\nAvec un peu plus de recul, le système complet ressemble à ça :\n\n\u003CWorkflow\n  title=\"Workflow global de Veristral\"\n  :steps=\"[\n    { key: 'microphone', verb: 'capturer l\\'audio' },\n    { key: 'OBS 1', verb: 'envoyer le flux avec buffer' },\n    { key: 'router', verb: 'déterminer les prochains agents' },\n    { key: 'stats', verb: 'stats agent' },\n    { key: 'rhetoric', verb: 'rhetoric agent' },\n    { key: 'coherence', verb: 'personal coherence agent' },\n    { key: 'context', verb: 'context agent' },\n    { key: 'editor', verb: 'editor agent' },\n    { key: 'trigger', verb: 'déclencher une mise à jour de l\\'overlay' },\n    { key: 'app', verb: 'mettre à jour l\\'overlay' },\n    { key: 'OBS 2', verb: 'envoyer le flux enrichi' },\n    { key: 'live show', verb: 'afficher aux spectateurs' }\n  ]\"\n  :relations=\"[\n    { source: 'microphone', target: 'OBS 1' },\n    { source: 'OBS 1', target: 'router' },\n    { source: 'router', target: 'stats' },\n    { source: 'router', target: 'rhetoric' },\n    { source: 'router', target: 'coherence' },\n    { source: 'router', target: 'context' },\n    { source: 'stats', target: 'editor' },\n    { source: 'rhetoric', target: 'editor' },\n    { source: 'coherence', target: 'editor' },\n    { source: 'context', target: 'editor' },\n    { source: 'editor', target: 'trigger' },\n    { source: 'trigger', target: 'app' },\n    { source: 'app', target: 'OBS 2' },\n    { source: 'OBS 2', target: 'live show' },\n    { source: 'OBS 1', target: 'OBS 2' }\n  ]\"\n  :ui=\"{\n    'root': 'h-240'\n  }\"\n\u002F>\n\nTout notre code source est disponible sur GitHub : \u003CGitHubLink repo=\"Barbapapazes\u002Fhackathon-paris\" \u002F>\n\n### Petite note sur Temporal\n\nTemporal peut sembler overkill, mais ça a sauvé notre projet. Prends un moment pour trouver une solution à notre problème de parallélisation et de scheduling sans Temporal, et tu réaliseras vite à quel point c'est compliqué de construire un système de scheduling robuste qui scale. Avec Temporal, ça marche, tout simplement.\n\nOn l'a utilisé pour deux raisons :\n\n1. Temporal a organisé un meetup à Paris le 5 novembre 2025, et j'y ai assisté. Un des talks était « Combining Temporal's durability with streaming of LLM responses using NATS Jetstream » par [Nicolas Moreau](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-moreau-26060614b\u002F), software engineer chez Mistral AI.\n2. Le scheduling, ou le fait d'attendre un moment précis pour faire quelque chose, c'est vraiment un sujet de workflow. Temporal est un moteur de workflow.\n\nCes deux points ont suffi à me convaincre d'utiliser Temporal. _Si on utilise la même technologie que Mistral AI, peut-être que ça rendra le projet plus facile à comprendre et plus intéressant et réaliste pour le jury. Je ne sais pas comment ça a influencé la perception du jury, mais on en a parlé avec des ingénieurs Mistral AI qui étaient présents._\n\nAu travail, on utilise Temporal dans notre infrastructure backend. C'est tellement puissant que j'essaie de tout mettre dans Temporal.\n\nPour mes projets perso, j'utilise surtout Cloudflare Workflows. Sous le capot, c'est très différent, mais d'un point de vue usage, ça donne la même sensation. Une fois que tu as la puissance d'un workflow, tout dans ta vie devient un workflow. Le truc drôle, c'est que j'automatise ma vie en ce moment, principalement avec Cloudflare Workflows, donc les utiliser pendant le hackathon était naturel pour moi.\n\n### Passer au monde réel\n\nFaire confiance au système dès le début peut être très difficile, donc on peut facilement imaginer un setup où le système fait la majeure partie du travail, avec un humain dans la boucle pour valider, vérifier, et corriger l'information avant qu'elle soit affichée à l'écran. C'est une solution faisable comme première étape.\n\nMais un système complètement autonome est le vrai objectif, parce que retirer l'humain de la boucle, surtout dans des émissions politiques, est vraiment important pour réduire les biais et s'assurer que tous les partis politiques sont traités de la même manière.\n\nMaintenant que le hackathon est terminé, il y a tellement de choses qu'on peut faire pour continuer d'améliorer le projet, et j'ai entendu de sources fiables que Mistral AI était intéressé, et peut-être même en train de construire un système similaire.\n\n_On est allés un peu plus loin pendant le hackathon, mais on n'a pas réussi à le faire marcher. On a commencé à construire un système qui collecte tout ce qu'on fact-check pendant l'émission live et le rend disponible sur un site centralisé. Comme ça, les spectateurs pourraient juste scanner un QR code à l'écran pour accéder au fact-check complet, puisque l'espace est limité sur l'overlay._\n\n## À propos de l'usage intensif de l'IA\n\nPendant les deux jours du hackathon, on a énormément utilisé l'IA pour écrire du code.\n\nEmma s'est principalement appuyée sur Gemini Pro via son chat UI pour écrire du code, tandis que Godefroy utilisait à la fois Gemini Pro via Antigravity et le chat, ainsi que Codex d'OpenAI. De mon côté, j'ai utilisé GitHub Copilot Pro via VS Code, et principalement Sonnet 4.6 et GPT-5.3-Codex.\n\nMême avec son plan payant, Emma a atteint la limite de Gemini Pro, et elle a dû attendre quelques heures avant de pouvoir l'utiliser à nouveau. Godefroy a dû alterner entre Antigravity et Codex pour éviter d'atteindre la limite de l'un des deux. Avec GitHub Copilot Pro, il n'y a pas de hard limit car on paye par requête. Pour te donner une idée, j'ai fait 94 requêtes en un week-end. Le plan Pro inclut 300 requêtes par mois, donc j'ai brûlé à peu près un tiers du quota mensuel sur un week-end.\n\nC'est intéressant de voir comment on a chacun utilisé l'IA différemment. Emma utilisait un notebook Jupyter pour écrire l'algorithme de débunking. Elle alternait entre poser des questions à Gemini Pro via le chat et copier-coller le code dans le notebook. Godefroy utilisait Antigravity Agent ou Codex pour éditer et commit du code directement. C'était une façon de travailler très vibe-coding.\n\nPour tous les deux, j'ai été vraiment impressionné par la vitesse à laquelle ils généraient du code qui marche. Même pour configurer OBS, Godefroy a demandé à Gemini Pro comment faire, et en quelques minutes, il avait une configuration fonctionnelle.\n\nCependant, comme je l'ai dit au début de l'article, sans comprendre le code, une organisation claire, et un projet bien structuré, ça devient un bazar très vite. Et ensuite, restructurer le code pour le rendre plus propre, extensible, et robuste devient un cauchemar.\n\nJ'ai passé beaucoup de temps entre samedi et dimanche à refactorer à la fois l'algorithme de débunking et le code du workflow pour le rendre plus robuste pour les fonctionnalités à venir.\n\n---\n\nAvec l'IA, perdre le contrôle du code est très facile. Est-ce que c'est grave ? Je ne sais toujours pas.\n\nPeut-être qu'on est en train de créer une abstraction de plus haut niveau au-dessus du code, où on ne se soucie pas des détails d'implémentation. Peut-être que c'est ok la plupart du temps. Peut-être que c'est la pire abstraction, et que le code comme commodité est une illusion pour les non-software engineers.\n\nUne chose dont je suis sûr, en revanche : créer des applications complexes requiert toujours une bonne architecture, la capacité à découper des problèmes complexes en problèmes plus petits (adaptés aux capacités de l'IA, pas aux capacités humaines), et une bonne compréhension du code.\n",{"title":1757,"description":8761},{"loc":8763,"images":8767},[8768,8769,8770,8771,8772,8773,8774,8775,8776],{"loc":8311},{"loc":8317},{"loc":8329},{"loc":8341},{"loc":8371},{"loc":8386},{"loc":8424},{"loc":8436},{"loc":8540},"fr\u002Fposts\u002Ffrom-idea-to-victory-the-2026-mistral-ai-paris-hackathon","jmIezkfHEZSF5K39kefXNxHBfYJy693zwFSRun7-bzM",{"id":8780,"title":8781,"body":8782,"contentId":9412,"date":9413,"description":9414,"extension":18,"meta":9415,"navigation":20,"path":9416,"rawbody":9417,"robots":23,"schemaOrg":23,"seo":9418,"sitemap":9419,"stem":9434,"__hash__":9435},"posts\u002Ffr\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action.md","2025 : Un Tournant, des Petits Pas à l'Action Claire",{"type":7,"value":8783,"toc":9404},[8784,8787,8790,8802,8810,8816,8819,8857,8860,8864,8870,8890,8893,8901,8907,8912,8915,8921,8925,8932,8938,8944,8947,8950,8955,8959,8967,8972,8975,8981,8989,8995,8998,9008,9014,9025,9031,9034,9056,9062,9068,9074,9080,9083,9089,9153,9156,9162,9168,9171,9182,9192,9196,9210,9220,9227,9241,9244,9248,9251,9254,9291,9294,9321,9324,9341,9344,9347,9350,9353,9359,9368,9374,9377,9380,9383,9386,9389,9392,9395,9398],[35,8785,8786],{},"Salut,",[35,8788,8789],{},"J'espère que tu vas bien en ce dernier jour de 2025.",[35,8791,8792,8793],{},"C'est le dernier post de l'année, et j'avais envie de prendre un moment pour regarder en arrière. C'est la troisième année que j'écris ce type de post, après ",[3045,8794,1655,8796,8801],{"id":8795},"2c1dd2f2-e4eb-483c-aa2e-b8bfb9ffd49c",[39,8797,8800],{"href":8798,"rel":8799},"https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023",[77],"Récap 2023"," (disponible uniquement en français). Je les trouve importants pour au moins deux raisons :",[68,8803,8804,8807],{},[71,8805,8806],{},"Quand tu travailles sur des objectifs long terme, c'est facile de perdre de vue les progrès que tu as faits et tout ce que tu as accompli. Tu veux toujours faire plus. Prendre le temps de noter le chemin parcouru t'aide à voir tout le progrès fait en une seule année.",[71,8808,8809],{},"De temps en temps, j'adore revenir sur ces posts pour voir comment mes objectifs, mes espoirs et mes envies ont évolué avec le temps.",[35,8811,8812],{},[1707,8813],{"alt":8814,"src":8815},"Estéban pendant un coucher de soleil entre des arbres, face à la mer.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fsunset-trees-sea.webp",[35,8817,8818],{},"Avant d'entrer dans les détails, je veux parler de chiffres. J'adore les chiffres, parce qu'ils donnent une vue d'ensemble de l'année en un coup d'œil.",[1593,8820,8821,8824,8827,8830,8833,8836,8839,8842,8845,8848,8851,8854],{},[71,8822,8823],{},"20 posts écrits, dont celui-ci. Cela fait un nouveau post toutes les 2,7 semaines.",[71,8825,8826],{},"1 série de 5 posts sur des agents IA et un serveur MCP. Moins de séries que l'année dernière, mais c'est énormément de travail, et j'ai été très occupé avec les talks.",[71,8828,8829],{},"19 talks donnés dans 11 événements différents (6 conférences et 5 meetups). Le talk « La réactivité et les signaux : démystifions la magie du frontend » (Reactivity and Signals: Demystifying the Frontend's Magic) a été donné 8 fois cette année.",[71,8831,8832],{},"9 villes visitées pour des conférences et des meetups : Paris, Bagneux, Rennes, Nantes, Bordeaux, Toulouse, Lyon, Dijon et Strasbourg.",[71,8834,8835],{},"Plus de 1 000 personnes ont assisté à mes talks cette année, avec un pic à plus de 200 personnes pour mon talk à Devoxx France sur la réactivité et les signaux.",[71,8837,8838],{},"711 euros dépensés en trains et hôtels pour des meetups et des conférences. Je m'attendais honnêtement à beaucoup plus. Cela représente plus de 3 000 km, plus de 35 heures de train (sans les retards) et 9 nuits d'hôtel.",[71,8840,8841],{},"1 518 abonnés sur X à la fin de 2025 et 8 153 posts partagés.",[71,8843,8844],{},"1 800 abonnés sur LinkedIn à la fin de 2025. Par rapport à l'année dernière, avec une croissance de 63,7 %, cela fait plus de 700 nouveaux abonnés cette année. J'ai touché plus de 34 000 membres pour un total de plus de 182 000 impressions sur 124 posts. J'ai clairement beaucoup écrit sur LinkedIn cette année.",[71,8846,8847],{},"Plus de 240 abonnés sur Twitch pour plus de 140 heures de live. C'est juste pour le fun, mais j'adore ça. Je continuerai en 2026, c'est sûr.",[71,8849,8850],{},"Plus de 1 600 heures passées sur VS Code, avec un pic à 14 heures et 1 minute le 24 janvier 2025 (ce qui est beaucoup trop pour avoir une vie saine). Cela représente une moyenne de 4,4 heures par jour, 7 jours sur 7.",[71,8852,8853],{},"143 réactions et 45 commentaires sur mes posts sur soubiran.dev.",[71,8855,8856],{},"25 pâtisseries différentes réalisées. Plus de détails en 2026.",[35,8858,8859],{},"Ça fait beaucoup de chiffres pour une seule année. Avec le recul, c'est fou de voir tout ce qu'il est possible d'accomplir en seulement 12 mois.",[51,8861,8863],{"id":8862},"retour-sur-les-objectifs-2025","Retour sur les Objectifs 2025",[35,8865,8866,8867],{},"Il s'est passé tellement de choses en 2025. Entre le changement de job et les talks, je ne me suis pas arrêté une seconde. ",[1216,8868,8869],{},"Parfois, je suis allé trop vite et trop loin.",[35,8871,8872,8873,8878,8879,8882,8883,8886,8887],{},"L'année dernière, j'ai acheté le cours ",[39,8874,8877],{"href":8875,"rel":8876},"https:\u002F\u002Fthreejs-journey.com\u002F",[77],"Three.js Journey",". Je voulais vraiment apprendre la 3D, mais je n'ai jamais trouvé le temps de m'y mettre. ",[1216,8880,8881],{},"Peut-être en 2026."," Mes talks pour Vue.js Amsterdam n'ont pas été acceptés, mais j'en ai soumis de nouveaux cette année. ",[1216,8884,8885],{},"C'est la quatrième fois. Croisons les doigts pour 2026."," Enfin, je n'ai pas pris le temps d'apprendre davantage sur l'infrastructure. Cependant, j'ai démarré un projet qui va dans cette direction. ",[1216,8888,8889],{},"Je t'en reparle dans les prochains mois.",[35,8891,8892],{},"Comme chaque année, beaucoup de choses inattendues se sont produites. Je pense que c'est important de les accueillir et d'adapter la feuille de route au fil de l'eau.",[1593,8894,8895],{},[71,8896,8897,8898],{},"J'ai découvert une passion pour la pâtisserie. Chaque week-end, j'essaie une nouvelle recette, j'achète du nouveau matériel et de nouveaux livres. Ça me garde loin des écrans et du code. Ça m'a aussi donné l'occasion de faire des gâteaux pour l'anniversaire de Jordan, un ami proche, et pour Thanksgiving avec des gens incroyables. ",[1216,8899,8900],{},"Mes collègues apprécient beaucoup, car j'apporte souvent des pâtisseries le lundi.",[35,8902,8903],{},[1707,8904],{"alt":8905,"src":8906},"Tartelettes aux figues avec chocolat blanc et sirop au porto.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Ffig-tartlet.webp",[1593,8908,8909],{},[71,8910,8911],{},"J'ai rencontré des gens incroyables, de nouveaux amis, avec qui je construis des projets, des conférences, et découvre de nouvelles choses. Mettre l'humain au centre de tout, créer des moments ensemble, est devenu vraiment important pour moi.",[35,8913,8914],{},"Avec un groupe d'amis, on a commencé à jouer aux fléchettes régulièrement. À partir d'un simple jeu de fléchettes, c'est vraiment incroyable tout ce qui peut arriver, entre jouer avec des inconnus, rencontrer de nouveaux amis et créer des moments mémorables ensemble.",[35,8916,8917],{},[1707,8918],{"alt":8919,"src":8920},"Une fléchette dans une fléchette.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdart.webp",[51,8922,8924],{"id":8923},"changements-de-vie","Changements de Vie",[35,8926,8927,8928,215],{},"En décembre 2024, j'ai commencé un nouveau job dans une entreprise appelée MaiaSpace. MaiaSpace construit un lanceur spatial appelé Maia. Je travaillais comme ingénieur logiciel dans l'équipe avionique, en construisant des outils pour automatiser une partie du travail des ingénieurs. J'ai rencontré des gens vraiment incroyables et j'ai énormément appris. C'était ",[39,8929,8931],{"href":8930},".\u002Fstarting-a-new-chapter-new-ambitions-and-new-job","une opportunité incroyable qui s'est terminée un an plus tard",[35,8933,8934,8935],{},"Après des semaines à réfléchir, j'ai décidé de quitter MaiaSpace pour rejoindre une nouvelle entreprise appelée Takiam. C'est une société de conseil spécialisée en Java et en infrastructure. C'est un grand changement pour moi, car je ne connais rien à Java, donc j'ai tout à apprendre. C'est un investissement vraiment long terme, mais je crois que l'apprentissage de Java va m'ouvrir beaucoup de portes. Pour l'instant, c'est juste énormément de travail. ",[1216,8936,8937],{},"Mais sortir de sa zone de confort est le seul moyen de grandir.",[35,8939,8940,8941],{},"Ce changement de job implique aussi de déménager dans un nouveau logement, plus proche du bureau et de mes amis. En janvier 2026, je vais emménager dans un nouvel appartement, plus proche de Paris. Il est plus petit que l'actuel, mais beaucoup plus pratique pour ma vie quotidienne. ",[1216,8942,8943],{},"Moins de temps dans les transports, c'est plus de temps pour moi et mes projets.",[35,8945,8946],{},"En avril 2025, j'ai lancé le premier stream de l'année. Pendant que j'étais en Suisse, j'ai commencé à streamer sur Twitch, mais un problème de performances lié à mon matériel m'a arrêté après quelques streams. Après avoir acheté un nouvel ordinateur, les streams ont repris. C'était encore très expérimental, car j'essaie vraiment de trouver mon style dans ce format. Depuis septembre, je n'ai pas été très actif. Les conférences m'ont pris beaucoup de temps, et des soucis au travail ont rendu difficile le fait de trouver la motivation pour streamer. En 2026, je devrais être plus régulier, parce que j'ai automatisé beaucoup de choses.",[35,8948,8949],{},"En parlant d'automatisation, je refonds tout mon workflow de création de contenu pour automatiser un maximum de choses. L'IA sera utilisée pour les tâches ennuyeuses, jamais pour les créatives. Ces changements devraient réduire le temps passé à créer du contenu, et me permettre de me concentrer sur ce qui compte le plus.",[35,8951,8952],{},[1216,8953,8954],{},"Organiser les choses, c'est tellement dur.",[51,8956,8958],{"id":8957},"meetups-et-conférences","Meetups et Conférences",[35,8960,8961,8962,8966],{},"Dans mon ",[39,8963,8965],{"href":8964},".\u002F2024-reflections-on-a-year-of-change-and-what-comes-next","récap de l'année dernière",", j'ai dit :",[938,8968,8969],{},[35,8970,8971],{},"I really hope this year will be as rich as 2024",[35,8973,8974],{},"Mon souhait a été plus que réalisé. Avec 19 talks donnés en 2025, contre 6 en 2024, et 1 en 2023, cette année a été intense. Vraiment intense.",[35,8976,8977,8978],{},"19 talks dans 11 événements différents, ça veut dire que j'ai parlé presque toutes les cinq semaines. C'est beaucoup, et je n'aurais jamais imaginé atteindre ce nombre aussi vite. La croissance est franchement folle, et j'espère secrètement la ralentir en 2026 pour trouver un meilleur équilibre. ",[1216,8979,8980],{},"Garder le même rythme en 2026 voudrait dire donner plus de 30 talks, ce qui est honnêtement impossible.",[35,8982,8983,8984,8988],{},"L'année a commencé avec ",[39,8985,8987],{"href":8986},".\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special","Devoxx France en avril",", où j'ai eu l'opportunité de donner deux talks : « La réactivité et les signaux : démystifions la magie du frontend » (Reactivity and Signals: Demystifying the Frontend's Magic) et « En JavaScript, il y a un paquet pour tout... et souvent pour rien » (In JavaScript, There's a Package for Everything… and Often for Nothing). Pour une conférence de cette taille, avec plus de 4 500 participants et seulement 281 talks acceptés sur plus de 1 100 soumissions, c'est vraiment un honneur d'être sélectionné deux fois.",[35,8990,8991],{},[1707,8992],{"alt":8993,"src":8994},"Esteban Soubiran présentant à Devoxx France 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevoxx-france-2025.webp",[35,8996,8997],{},"Ces deux talks avaient été soumis avant Noël 2024, mais je n'ai reçu la confirmation qu'en février 2025. Ensuite, j'ai beaucoup travaillé pour les préparer. Il m'a fallu deux mois pour avoir quelque chose de prêt. Comprendre Alien Signals m'a pris beaucoup de temps, et pour rendre ça compréhensible pour un grand public, j'ai dû être vraiment créatif. Aujourd'hui, je suis très fier du résultat, et les retours globaux sur les deux talks ont été très positifs.",[35,8999,9000,9001,9003,9004,9007],{},"Deux semaines plus tard, j'étais à ",[39,9002,7497],{"href":7500}," à Lyon pour donner aussi deux talks. Pendant cette conférence, j'ai rencontré beaucoup de nouvelles personnes incroyables. J'ai vraiment apprécié la taille plus réduite de la conférence, comparée à Devoxx France. C'est plus facile de connecter avec les gens, et il y a moins de stress quand on va voir des talks. ",[1216,9005,9006],{},"Est-ce que je vais avoir une place ?"," Pour l'anecdote, pour avoir une place à Devoxx France pour les keynotes, je devais arriver une heure avant le début. Je pensais que c'était pareil pour MiXiT, donc je suis arrivé 45 minutes avant le début. Complètement inutile, parce que tout le monde pouvait entrer dans la salle principale sans problème.",[35,9009,9010],{},[1707,9011],{"alt":9012,"src":9013},"Esteban Soubiran présentant à MiXiT 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fmixit-2025.webp",[35,9015,9016,9017,9021,9022],{},"Ensuite, en juin, je suis allé à Rennes pour le ",[39,9018,9020],{"href":9019},".\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker","BreizhCamp",". Je pense que tu peux deviner ce qu'il s'est passé : j'ai donné deux talks à nouveau, les mêmes qu'à Devoxx France et MiXiT. ",[1216,9023,9024],{},"Tu te demandes peut-être pourquoi je donne les mêmes talks encore et encore. Après avoir passé deux mois à les préparer, tu as envie de les donner autant que possible, pour toucher le plus grand public possible. Et puis, réutiliser les mêmes talks me permet d'améliorer à la fois le contenu et la présentation. Aujourd'hui, je suis à l'aise avec eux, et je peux vraiment prendre du plaisir à les donner. La première fois, c'est plus du stress que du plaisir.",[35,9026,9027],{},[1707,9028],{"alt":9029,"src":9030},"Esteban Soubiran présentant au BreizhCamp 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fbreizhcamp-2025.webp",[35,9032,9033],{},"Après la pause estivale, et jusqu'en décembre, je suis passé d'une conférence à une autre.",[1593,9035,9036,9043,9050,9053],{},[71,9037,9038,9042],{},[39,9039,9041],{"href":9040},".\u002Fmy-adventure-to-nantes-for-europes-largest-devfest","DevFest Nantes, 17 octobre 2025",", où j'ai donné mon talk sur les paquets JavaScript. Cette conférence est le plus grand DevFest d'Europe. La soirée était vraiment agréable, et la keynote de clôture, une performance de théâtre d'improvisation, était vraiment exceptionnelle (et tellement originale). Ça pourrait être ma conférence préférée de l'année.",[71,9044,9045,9049],{},[39,9046,9048],{"href":9047},".\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux","BDX I\u002FO, 7 novembre 2025",", où j'ai donné mes deux talks. La conférence était vraiment bien organisée, et le lieu était superbe. J'en ai profité pour visiter les bureaux de NuxtLabs à Bordeaux et rencontrer Sébastien et Benjamin.",[71,9051,9052],{},"DevFest Toulouse, 13 novembre 2025, où j'ai donné mon talk sur la réactivité et les signaux pour la septième fois cette année. Le dîner des speakers était vraiment exceptionnel. On était dans un restaurant superbe, en plein centre-ville.",[71,9054,9055],{},"DevFest Dijon, 5 décembre 2025, où j'ai donné mon talk sur la réactivité et les signaux pour la huitième fois cette année. Le cadeau speaker était tellement cool : un sac avec des produits locaux de Dijon et de la région.",[35,9057,9058],{},[1707,9059],{"alt":9060,"src":9061},"Estéban présentant au DevFest Nantes 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevfest-nantes-2025.webp",[35,9063,9064],{},[1707,9065],{"alt":9066,"src":9067},"Estéban présentant à BDX I\u002FO 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fbdx-io-2025.webp",[35,9069,9070],{},[1707,9071],{"alt":9072,"src":9073},"Estéban, Sébastien et Benjamin chez NuxtLabs.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fnuxtlabs-bordeaux-2025.webp",[35,9075,9076],{},[1707,9077],{"alt":9078,"src":9079},"Selfie d'Estéban au DevFest Dijon 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevfest-dijon-2025.webp",[35,9081,9082],{},"Le monde des speakers est assez petit, et tu rencontres souvent les mêmes personnes encore et encore. Ça a atténué la solitude que je ressentais en 2023 quand j'ai donné mon premier talk à Devoxx France.",[35,9084,9085,9086],{},"Avant et entre ces conférences, j'ai donné plusieurs talks dans des meetups à Paris et Strasbourg. ",[1216,9087,9088],{},"Oui, je suis allé et revenu de Strasbourg juste pour un talk en soirée. Démesuré, mais fun.",[1593,9090,9091,9099,9107,9115,9123,9130,9138,9145],{},[71,9092,9093,9094],{},"Paris TypeScript, 21 janvier 2025, ",[39,9095,9098],{"href":9096,"rel":9097},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-01-21\u002Fparis-typescript\u002F1",[77],"Devins full-stack avec TypeScript",[71,9100,9101,9102],{},"Node.js Paris, 5 mars 2025, ",[39,9103,9106],{"href":9104,"rel":9105},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-03-05\u002Fnodejs-paris\u002F1",[77],"Vous n'avez pas besoin de ces nouvelles API Node.js",[71,9108,9109,9110],{},"HumanTalks Paris, 8 avril 2025, ",[39,9111,9114],{"href":9112,"rel":9113},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-04-08\u002Fhuman-talks\u002F1",[77],"Ship Nuxt apps that scale.",[71,9116,9117,9118],{},"HumanTalks Paris, 9 septembre 2025, ",[39,9119,9122],{"href":9120,"rel":9121},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-09\u002Fhumantalks-paris\u002F1",[77],"Inalia: Un Anneau pour les gouverner tous",[71,9124,9125,9126],{},"Node.js Paris, 17 septembre 2025, ",[39,9127,9122],{"href":9128,"rel":9129},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-17\u002Fnodejs-paris\u002F1",[77],[71,9131,9132,9133],{},"Paris TypeScript, 14 octobre 2025, ",[39,9134,9137],{"href":9135,"rel":9136},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-10-14\u002Fparis-typescript\u002F1",[77],"La réactivité et les signaux : démystifions la magie du frontend",[71,9139,9140,9141],{},"StrasbourgJS, 21 octobre 2025, ",[39,9142,9137],{"href":9143,"rel":9144},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-10-21\u002Fstrasbourgjs\u002F1",[77],[71,9146,9147,9148],{},"Paris TypeScript, 9 décembre 2025, ",[39,9149,9152],{"href":9150,"rel":9151},"https:\u002F\u002Ftalks.soubiran.dev\u002F2025-12-09\u002Fparis-typescript\u002F1",[77],"En JavaScript, il y a un paquet pour tout... et souvent pour rien",[35,9154,9155],{},"J'ai vraiment aimé donner des talks en meetups. L'ambiance est beaucoup plus détendue qu'en conférence, et c'est plus facile d'interagir avec le public. Mais ne te trompe pas : ça peut être plus stressant, parce que le public est vraiment proche de toi, et tu peux facilement voir les réactions.",[35,9157,9158],{},[1707,9159],{"alt":9160,"src":9161},"Estéban présentant au meetup StrasbourgJS en octobre 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fstrasbourgjs-october-2025.webp",[35,9163,9164],{},[1707,9165],{"alt":9166,"src":9167},"Selfie d'Estéban au meetup Paris TypeScript en décembre 2025.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fparis-typescript-december-2025.webp",[35,9169,9170],{},"Ça a été une année exceptionnelle pour les talks et les conférences, au-delà de toutes mes attentes. Merci à toutes les personnes que j'ai rencontrées et à tous les organisateurs qui m'ont fait confiance pour donner des talks dans leurs événements.",[35,9172,9173,9174,9178,9179],{},"Retrouve tous mes talks sur ma ",[39,9175,9177],{"href":9176},"..\u002Ftalks","page talks",". J'ai ajouté une carte qui montre tous les endroits où j'ai donné des talks depuis 2023, ainsi que quelques statistiques. Tu peux trouver les slides sur ",[1599,9180,215],{"repo":9181},"barbapapazes\u002Ftalks",[35,9183,9184],{},[1216,9185,9186,9187,215],{},"Je ne reçois aucune compensation pour mes talks. Tous les voyages, les hôtels et le temps passé à les préparer sont à mes frais. Je le fais parce que j'aime partager mes connaissances et mes expériences avec la communauté. Cependant, ce n'est pas viable sur le long terme. Si tu veux me soutenir, tu peux devenir sponsor sur ",[39,9188,9191],{"href":9189,"rel":9190},"https:\u002F\u002Fgithub.com\u002Fsponsors\u002Fbarbapapazes",[77],"GitHub Sponsors",[51,9193,9195],{"id":9194},"parcours-open-source","Parcours Open Source",[35,9197,9198,9199,9204,9205,9209],{},"Cette année, j'ai principalement contribué à ",[39,9200,9203],{"href":9201,"rel":9202},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui",[77],"Nuxt UI",". Nuxt UI est ma librairie de composants par défaut, que je construise une application Nuxt ou Vue. Je l'utilise pour tous mes projets. Comme j'ai dû m'y plonger en profondeur pour ",[39,9206,9208],{"href":9207},".\u002Fsoftware-engineering-a-human-journey-before-anything-else","maintenir un fork",", je la connais très bien maintenant. Donc à chaque fois que je trouvais un problème ou une amélioration possible, je soumettais une pull request.",[35,9211,9212,9213],{},"J'ai aussi créé un plugin Markdown Exit qui exploite ses capacités asynchrones pour optimiser automatiquement les images et générer des placeholders blurhash. Tu peux le trouver sur ",[1599,9214,9216,9217],{"repo":9215},"barbapapazes\u002Fmarkdown-exit-image",". J'ai aussi créé un plugin VitePress pour générer automatiquement des images OG. Tu peux le trouver sur ",[1599,9218,215],{"repo":9219},"barbapapazes\u002Fvitepress-plugin-og",[35,9221,9222,9223,9226],{},"J'ai continué à construire ",[39,9224,7894],{"href":7892,"rel":9225},[77],". J'espère vraiment ouvrir une bêta publique au premier semestre 2026, mais il reste encore énormément de travail, et d'autres projets me prennent du temps.",[35,9228,9229,9230,9235,9236,215],{},"Tout au long de l'année, j'ai aussi continué à maintenir d'autres projets open source comme ",[39,9231,9234],{"href":9232,"rel":9233},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fnuxt-authorization",[77],"Nuxt Authorization",", et la plupart de mes projets sont open source. Tu peux retrouver tous mes projets open source sur mon ",[39,9237,9240],{"href":9238,"rel":9239},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes",[77],"profil GitHub",[35,9242,9243],{},"J'ai commencé à utiliser beaucoup l'IA pour écrire du code. GitHub Copilot Agent, depuis l'interface web, est vraiment puissant, et je l'utilise au quotidien. À chaque fois que j'ai une idée, où que je sois, j'ouvre l'app mobile, j'écris un petit prompt et je lance un agent. À la fin de la journée, je relis les PR générées, je fais des ajustements et je les merge. Ça a vraiment amélioré ma productivité, et ça me permet de me concentrer sur des tâches plus complexes.",[51,9245,9247],{"id":9246},"création-de-contenu-et-écriture","Création de Contenu et Écriture",[35,9249,9250],{},"Cette année, j'ai écrit 20 posts et une série. Pour te donner une idée, un seul post me prend entre 5 et 10 heures d'écriture. Mais compter les heures n'a pas vraiment de sens pour une activité aussi importante que l'écriture. Écrire m'aide à clarifier mes pensées, à libérer mon esprit et à faire grandir mes connaissances. Je le recommande à tout le monde, et je n'arrêterai pas. Cependant, j'ai une règle importante : j'écris uniquement quand j'ai quelque chose à partager. Ça veut dire que je n'ai pas de planning d'écriture.",[35,9252,9253],{},"Cette année, j'ai écrit des posts techniques sur JavaScript, Vite, Markdown It et Laravel :",[1593,9255,9256,9261,9266,9271,9276,9281,9286],{},[71,9257,9258],{},[3045,9259],{"id":9260},"98b9e72c-7ae6-4db7-a23a-da2412fbfc38",[71,9262,9263],{},[3045,9264],{"id":9265},"c3e7499a-3b98-4008-b25e-756b529d8dcc",[71,9267,9268],{},[3045,9269],{"id":9270},"fe3fd9b9-481e-4d38-9cea-969595fc913a",[71,9272,9273],{},[3045,9274],{"id":9275},"5326cb9f-fd51-4d43-bff8-2cee2a995d89",[71,9277,9278],{},[3045,9279],{"id":9280},"11e74f5a-eed4-4419-a931-1c659ebd7ff3",[71,9282,9283],{},[3045,9284],{"id":9285},"a505274a-14a6-471e-95a2-a3dbc64cd170",[71,9287,9288],{},[3045,9289],{"id":9290},"ca2372b3-15b3-4d66-9877-a4dd696580de",[35,9292,9293],{},"J'ai aussi écrit des posts sur mon parcours de speaker :",[1593,9295,9296,9301,9306,9311,9316],{},[71,9297,9298],{},[3045,9299],{"id":9300},"29c1e474-c428-4506-9d4b-23b420bf9f64",[71,9302,9303],{},[3045,9304],{"id":9305},"1f871ffb-d9c9-4b71-bc6b-bf260ceb227a",[71,9307,9308],{},[3045,9309],{"id":9310},"6f54e585-a2ec-4508-a863-5a0726f23744",[71,9312,9313],{},[3045,9314],{"id":9315},"da3b2630-1622-49fd-9311-35c9b25c5d54",[71,9317,9318],{},[3045,9319],{"id":9320},"49bf687d-212a-4a3c-af76-ec3c1dac3089",[35,9322,9323],{},"Et j'ai aussi écrit quelques posts plus personnels sur moi et ma vie :",[1593,9325,9326,9331,9336],{},[71,9327,9328],{},[3045,9329],{"id":9330},"6da35c27-6a73-4b8f-aafa-2aa01e7a1ed3",[71,9332,9333],{},[3045,9334],{"id":9335},"83de2151-b1db-48ee-8d5a-eb88e359c337",[71,9337,9338],{},[3045,9339],{"id":9340},"919c8df4-e6f7-4dc5-9cda-165d754168ed",[35,9342,9343],{},"Ce sont tous des posts que j'aurais adoré lire quand j'ai commencé mon parcours de développeur. Être ingénieur logiciel, ce n'est pas juste du code ; ce n'est pas toujours facile, et quand c'est aussi une passion, ça peut devenir écrasant. Partager mes expériences et mes pensées m'aide d'abord, et j'espère que ça aide aussi d'autres personnes.",[35,9345,9346],{},"J'ai aussi refactoré le backend de ce site pour gérer les prochains sites web que je construis. Ça a aussi débloqué de nouvelles fonctionnalités qui devraient arriver en 2026. Cependant, ça prendra du temps, parce que je vais devoir migrer ce site de VitePress vers du Vite pur avec Vue. C'est énormément de travail, et je l'ai déjà migré l'année dernière, quand je suis passé d'une UI custom à une version réécrite de Nuxt UI. Pouvoir migrer plutôt que réécrire from scratch, c'est quelque chose dont je suis vraiment fier. Ça montre que les choses évoluent, tout en gardant la continuité.",[51,9348,9349],{"id":5579},"Et Maintenant",[35,9351,9352],{},"Comme tu le vois, 2025 a été une année marquante. Je suis vraiment fier de tout ce que j'ai accompli, mais j'ai terminé l'année très fatigué.",[35,9354,9355,9356],{},"Cependant, j'ai plein de plans excitants pour 2026, donc ralentir n'est pas dans mes plans. ",[1216,9357,9358],{},"Ne t'inquiète pas, j'ai appris à comprendre mes limites, et je suis capable de réduire le rythme quand c'est nécessaire. Le burn-out n'est pas une option non plus.",[35,9360,9361,9362,9367],{},"D'abord, ",[39,9363,9366],{"href":9364,"rel":9365},"https:\u002F\u002Fx.com\u002Fhugorcd",[77],"Hugo",". Hugo, c'est quelqu'un à qui je n'ai parlé qu'en ligne. On partage la même passion pour le développement web, l'open source et Nuxt, et quand il a commencé à partager du contenu sur X, j'étais super curieux de son travail. Il a une approche unique de la beauté, avec des animations, des transitions et des assets élégants que j'ai trouvés très inspirants. Il a amené quelque chose de nouveau dans mon feed. Aujourd'hui, il travaille chez Vercel depuis Londres. Du coup, je prévois d'aller le voir. En 2026, je veux aller à Londres, le rencontrer et explorer un peu la ville.",[35,9369,9370,9371],{},"En parlant de partir à l'étranger, je veux assister à plus de conférences européennes, et peut-être même internationales. Je veux continuer à explorer ces événements incroyables, rencontrer des gens passionnés et partager mon amour pour le développement web. J'ai déjà quelques conférences en tête auxquelles je veux soumettre des talks. J'ai déjà soumis à Vue.js Amsterdam et MadVue. ",[1216,9372,9373],{},"Je devrais prendre des cours pour améliorer mon anglais à l'oral.",[35,9375,9376],{},"J'ai écrit deux nouveaux talks pendant le dernier mois de 2025. Pour le moment, je compte surtout les présenter en France, comme cette année. L'un d'eux parle de plugins Vite, écrit d'une manière différente de tous les talks que tu as vus auparavant. Je ne spoil pas la surprise, mais si j'arrive à le faire fonctionner, ce sera très fun à voir et à présenter. Le second est co-écrit avec une amie, Julie, que j'apprécie énormément. Je suis vraiment excité par ce nouveau talk. C'est la première fois que j'écris un talk avec quelqu'un d'autre. On n'a pas le même background, et c'est un sujet que je n'ai pas complètement maîtrisé. Cette collaboration est un vrai challenge. C'est bien plus qu'un simple talk. Ça consomme beaucoup de temps et ça nous sort de notre zone de confort, mais je crois vraiment que ça en vaudra la peine. L'état actuel est très prometteur.",[35,9378,9379],{},"À côté de ça, je veux continuer à écrire comme je le fais depuis deux ans maintenant. Écrire m'aide à clarifier mes pensées, partager mes expériences et creuser des sujets que je pensais maîtriser, mais qui ont toujours plus à révéler. Je veux continuer à repousser mes limites et explorer de nouvelles idées à travers l'écriture. Je veux aussi continuer à streamer sur Twitch. Depuis septembre, je n'ai pas trouvé beaucoup de temps, mais j'espère que ça va changer en 2026.",[35,9381,9382],{},"Pour mon contenu, je vais continuer à écrire sur JavaScript et Laravel, mais je vais devoir commencer à écrire davantage sur Java, puisque mon nouveau job est principalement centré dessus. Je ne suis pas complètement sûr de comment je vais m'y prendre, car je suis aussi en train de construire un nouveau site qui sert de second cerveau. Tu peux t'attendre à plus d'informations dans les prochains mois. Il y a quelques semaines, j'ai partagé sur X un autre site que je construis pour suivre toute mon infrastructure. C'est encore au tout début, mais j'espère en parler davantage dès que possible. Ça me demande d'écrire énormément, et écrire prend du temps, donc je ne donne pas de date, mais attends-toi à ce que ce soit prêt avant l'été 2026.",[35,9384,9385],{},"À mesure que l'IA prend de plus en plus de place dans mon quotidien, j'ai accepté le fait que je peux lui déléguer certaines tâches. Mais il y a une ligne rouge que je ne franchirai pas : je continuerai à écrire tous mes posts moi-même, et je n'utiliserai jamais l'IA pour générer du contenu à ma place. Si ça devait arriver, j'arrêterais simplement de travailler dans la tech et je commencerais une toute nouvelle carrière. C'est non négociable pour moi, à la fois personnellement et professionnellement. Je suis aussi fortement opposé au fait que quelqu'un fasse l'inverse.",[35,9387,9388],{},"La pâtisserie fera partie de 2026. C'est tellement bon de faire des choses avec mes mains, loin des écrans. Partager ça avec quelqu'un, c'est aussi une activité incroyable. Hautement recommandé.",[35,9390,9391],{},"Tout ça, c'est le plan principal pour 2026. Mais comme le changement est la seule constante, des choses inattendues arriveront, et je m'adapterai.",[35,9393,9394],{},"Ça a été une année incroyable. 2026 sera encore meilleure.",[35,9396,9397],{},"Prends soin de toi, passe du temps avec ceux que tu aimes, vis ta vie à fond, et bonne année ! 🫶",[35,9399,9400],{},[1707,9401],{"alt":9402,"src":9403},"Estéban qui vole vers de nouveaux horizons en wingfoil.","\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fhappy-new-year-2026.webp",{"title":13,"searchDepth":14,"depth":14,"links":9405},[9406,9407,9408,9409,9410,9411],{"id":8862,"depth":14,"text":8863},{"id":8923,"depth":14,"text":8924},{"id":8957,"depth":14,"text":8958},{"id":9194,"depth":14,"text":9195},{"id":9246,"depth":14,"text":9247},{"id":5579,"depth":14,"text":9349},"7c0b2a3e-6933-4cb6-8f68-7b7a5b108ccb","2025-12-31","2025 touche à sa fin. Il est temps de revenir sur une année intense, remplie de talks, de nouvelles expériences et de croissance personnelle.",{},"\u002Ffr\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action","---\ncontentId: 7c0b2a3e-6933-4cb6-8f68-7b7a5b108ccb\ntitle: \"2025 : Un Tournant, des Petits Pas à l'Action Claire\"\ndescription: 2025 touche à sa fin. Il est temps de revenir sur une année intense, remplie de talks, de nouvelles expériences et de croissance personnelle.\ndate: 2025-12-31\n---\n\nSalut,\n\nJ'espère que tu vas bien en ce dernier jour de 2025.\n\nC'est le dernier post de l'année, et j'avais envie de prendre un moment pour regarder en arrière. C'est la troisième année que j'écris ce type de post, après \u003CLinkToArticle id=\"2c1dd2f2-e4eb-483c-aa2e-b8bfb9ffd49c\" \u002F> et [Récap 2023](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023) (disponible uniquement en français). Je les trouve importants pour au moins deux raisons :\n\n1. Quand tu travailles sur des objectifs long terme, c'est facile de perdre de vue les progrès que tu as faits et tout ce que tu as accompli. Tu veux toujours faire plus. Prendre le temps de noter le chemin parcouru t'aide à voir tout le progrès fait en une seule année.\n2. De temps en temps, j'adore revenir sur ces posts pour voir comment mes objectifs, mes espoirs et mes envies ont évolué avec le temps.\n\n![Estéban pendant un coucher de soleil entre des arbres, face à la mer.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fsunset-trees-sea.webp)\n\nAvant d'entrer dans les détails, je veux parler de chiffres. J'adore les chiffres, parce qu'ils donnent une vue d'ensemble de l'année en un coup d'œil.\n\n- 20 posts écrits, dont celui-ci. Cela fait un nouveau post toutes les 2,7 semaines.\n- 1 série de 5 posts sur des agents IA et un serveur MCP. Moins de séries que l'année dernière, mais c'est énormément de travail, et j'ai été très occupé avec les talks.\n- 19 talks donnés dans 11 événements différents (6 conférences et 5 meetups). Le talk « La réactivité et les signaux : démystifions la magie du frontend » (Reactivity and Signals: Demystifying the Frontend's Magic) a été donné 8 fois cette année.\n- 9 villes visitées pour des conférences et des meetups : Paris, Bagneux, Rennes, Nantes, Bordeaux, Toulouse, Lyon, Dijon et Strasbourg.\n- Plus de 1 000 personnes ont assisté à mes talks cette année, avec un pic à plus de 200 personnes pour mon talk à Devoxx France sur la réactivité et les signaux.\n- 711 euros dépensés en trains et hôtels pour des meetups et des conférences. Je m'attendais honnêtement à beaucoup plus. Cela représente plus de 3 000 km, plus de 35 heures de train (sans les retards) et 9 nuits d'hôtel.\n- 1 518 abonnés sur X à la fin de 2025 et 8 153 posts partagés.\n- 1 800 abonnés sur LinkedIn à la fin de 2025. Par rapport à l'année dernière, avec une croissance de 63,7 %, cela fait plus de 700 nouveaux abonnés cette année. J'ai touché plus de 34 000 membres pour un total de plus de 182 000 impressions sur 124 posts. J'ai clairement beaucoup écrit sur LinkedIn cette année.\n- Plus de 240 abonnés sur Twitch pour plus de 140 heures de live. C'est juste pour le fun, mais j'adore ça. Je continuerai en 2026, c'est sûr.\n- Plus de 1 600 heures passées sur VS Code, avec un pic à 14 heures et 1 minute le 24 janvier 2025 (ce qui est beaucoup trop pour avoir une vie saine). Cela représente une moyenne de 4,4 heures par jour, 7 jours sur 7.\n- 143 réactions et 45 commentaires sur mes posts sur soubiran.dev.\n- 25 pâtisseries différentes réalisées. Plus de détails en 2026.\n\nÇa fait beaucoup de chiffres pour une seule année. Avec le recul, c'est fou de voir tout ce qu'il est possible d'accomplir en seulement 12 mois.\n\n## Retour sur les Objectifs 2025\n\nIl s'est passé tellement de choses en 2025. Entre le changement de job et les talks, je ne me suis pas arrêté une seconde. _Parfois, je suis allé trop vite et trop loin._\n\nL'année dernière, j'ai acheté le cours [Three.js Journey](https:\u002F\u002Fthreejs-journey.com\u002F). Je voulais vraiment apprendre la 3D, mais je n'ai jamais trouvé le temps de m'y mettre. _Peut-être en 2026._ Mes talks pour Vue.js Amsterdam n'ont pas été acceptés, mais j'en ai soumis de nouveaux cette année. _C'est la quatrième fois. Croisons les doigts pour 2026._ Enfin, je n'ai pas pris le temps d'apprendre davantage sur l'infrastructure. Cependant, j'ai démarré un projet qui va dans cette direction. _Je t'en reparle dans les prochains mois._\n\nComme chaque année, beaucoup de choses inattendues se sont produites. Je pense que c'est important de les accueillir et d'adapter la feuille de route au fil de l'eau.\n\n- J'ai découvert une passion pour la pâtisserie. Chaque week-end, j'essaie une nouvelle recette, j'achète du nouveau matériel et de nouveaux livres. Ça me garde loin des écrans et du code. Ça m'a aussi donné l'occasion de faire des gâteaux pour l'anniversaire de Jordan, un ami proche, et pour Thanksgiving avec des gens incroyables. _Mes collègues apprécient beaucoup, car j'apporte souvent des pâtisseries le lundi._\n\n![Tartelettes aux figues avec chocolat blanc et sirop au porto.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Ffig-tartlet.webp)\n\n- J'ai rencontré des gens incroyables, de nouveaux amis, avec qui je construis des projets, des conférences, et découvre de nouvelles choses. Mettre l'humain au centre de tout, créer des moments ensemble, est devenu vraiment important pour moi.\n\nAvec un groupe d'amis, on a commencé à jouer aux fléchettes régulièrement. À partir d'un simple jeu de fléchettes, c'est vraiment incroyable tout ce qui peut arriver, entre jouer avec des inconnus, rencontrer de nouveaux amis et créer des moments mémorables ensemble.\n\n![Une fléchette dans une fléchette.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdart.webp)\n\n## Changements de Vie\n\nEn décembre 2024, j'ai commencé un nouveau job dans une entreprise appelée MaiaSpace. MaiaSpace construit un lanceur spatial appelé Maia. Je travaillais comme ingénieur logiciel dans l'équipe avionique, en construisant des outils pour automatiser une partie du travail des ingénieurs. J'ai rencontré des gens vraiment incroyables et j'ai énormément appris. C'était [une opportunité incroyable qui s'est terminée un an plus tard](.\u002Fstarting-a-new-chapter-new-ambitions-and-new-job.md).\n\nAprès des semaines à réfléchir, j'ai décidé de quitter MaiaSpace pour rejoindre une nouvelle entreprise appelée Takiam. C'est une société de conseil spécialisée en Java et en infrastructure. C'est un grand changement pour moi, car je ne connais rien à Java, donc j'ai tout à apprendre. C'est un investissement vraiment long terme, mais je crois que l'apprentissage de Java va m'ouvrir beaucoup de portes. Pour l'instant, c'est juste énormément de travail. _Mais sortir de sa zone de confort est le seul moyen de grandir._\n\nCe changement de job implique aussi de déménager dans un nouveau logement, plus proche du bureau et de mes amis. En janvier 2026, je vais emménager dans un nouvel appartement, plus proche de Paris. Il est plus petit que l'actuel, mais beaucoup plus pratique pour ma vie quotidienne. _Moins de temps dans les transports, c'est plus de temps pour moi et mes projets._\n\nEn avril 2025, j'ai lancé le premier stream de l'année. Pendant que j'étais en Suisse, j'ai commencé à streamer sur Twitch, mais un problème de performances lié à mon matériel m'a arrêté après quelques streams. Après avoir acheté un nouvel ordinateur, les streams ont repris. C'était encore très expérimental, car j'essaie vraiment de trouver mon style dans ce format. Depuis septembre, je n'ai pas été très actif. Les conférences m'ont pris beaucoup de temps, et des soucis au travail ont rendu difficile le fait de trouver la motivation pour streamer. En 2026, je devrais être plus régulier, parce que j'ai automatisé beaucoup de choses.\n\nEn parlant d'automatisation, je refonds tout mon workflow de création de contenu pour automatiser un maximum de choses. L'IA sera utilisée pour les tâches ennuyeuses, jamais pour les créatives. Ces changements devraient réduire le temps passé à créer du contenu, et me permettre de me concentrer sur ce qui compte le plus.\n\n_Organiser les choses, c'est tellement dur._\n\n## Meetups et Conférences\n\nDans mon [récap de l'année dernière](.\u002F2024-reflections-on-a-year-of-change-and-what-comes-next.md), j'ai dit :\n\n> I really hope this year will be as rich as 2024\n\nMon souhait a été plus que réalisé. Avec 19 talks donnés en 2025, contre 6 en 2024, et 1 en 2023, cette année a été intense. Vraiment intense.\n\n19 talks dans 11 événements différents, ça veut dire que j'ai parlé presque toutes les cinq semaines. C'est beaucoup, et je n'aurais jamais imaginé atteindre ce nombre aussi vite. La croissance est franchement folle, et j'espère secrètement la ralentir en 2026 pour trouver un meilleur équilibre. _Garder le même rythme en 2026 voudrait dire donner plus de 30 talks, ce qui est honnêtement impossible._\n\nL'année a commencé avec [Devoxx France en avril](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md), où j'ai eu l'opportunité de donner deux talks : « La réactivité et les signaux : démystifions la magie du frontend » (Reactivity and Signals: Demystifying the Frontend's Magic) et « En JavaScript, il y a un paquet pour tout... et souvent pour rien » (In JavaScript, There's a Package for Everything… and Often for Nothing). Pour une conférence de cette taille, avec plus de 4 500 participants et seulement 281 talks acceptés sur plus de 1 100 soumissions, c'est vraiment un honneur d'être sélectionné deux fois.\n\n![Esteban Soubiran présentant à Devoxx France 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevoxx-france-2025.webp)\n\nCes deux talks avaient été soumis avant Noël 2024, mais je n'ai reçu la confirmation qu'en février 2025. Ensuite, j'ai beaucoup travaillé pour les préparer. Il m'a fallu deux mois pour avoir quelque chose de prêt. Comprendre Alien Signals m'a pris beaucoup de temps, et pour rendre ça compréhensible pour un grand public, j'ai dû être vraiment créatif. Aujourd'hui, je suis très fier du résultat, et les retours globaux sur les deux talks ont été très positifs.\n\nDeux semaines plus tard, j'étais à [MiXiT](.\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md) à Lyon pour donner aussi deux talks. Pendant cette conférence, j'ai rencontré beaucoup de nouvelles personnes incroyables. J'ai vraiment apprécié la taille plus réduite de la conférence, comparée à Devoxx France. C'est plus facile de connecter avec les gens, et il y a moins de stress quand on va voir des talks. _Est-ce que je vais avoir une place ?_ Pour l'anecdote, pour avoir une place à Devoxx France pour les keynotes, je devais arriver une heure avant le début. Je pensais que c'était pareil pour MiXiT, donc je suis arrivé 45 minutes avant le début. Complètement inutile, parce que tout le monde pouvait entrer dans la salle principale sans problème.\n\n![Esteban Soubiran présentant à MiXiT 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fmixit-2025.webp)\n\nEnsuite, en juin, je suis allé à Rennes pour le [BreizhCamp](.\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker.md). Je pense que tu peux deviner ce qu'il s'est passé : j'ai donné deux talks à nouveau, les mêmes qu'à Devoxx France et MiXiT. _Tu te demandes peut-être pourquoi je donne les mêmes talks encore et encore. Après avoir passé deux mois à les préparer, tu as envie de les donner autant que possible, pour toucher le plus grand public possible. Et puis, réutiliser les mêmes talks me permet d'améliorer à la fois le contenu et la présentation. Aujourd'hui, je suis à l'aise avec eux, et je peux vraiment prendre du plaisir à les donner. La première fois, c'est plus du stress que du plaisir._\n\n![Esteban Soubiran présentant au BreizhCamp 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fbreizhcamp-2025.webp)\n\nAprès la pause estivale, et jusqu'en décembre, je suis passé d'une conférence à une autre.\n\n- [DevFest Nantes, 17 octobre 2025](.\u002Fmy-adventure-to-nantes-for-europes-largest-devfest.md), où j'ai donné mon talk sur les paquets JavaScript. Cette conférence est le plus grand DevFest d'Europe. La soirée était vraiment agréable, et la keynote de clôture, une performance de théâtre d'improvisation, était vraiment exceptionnelle (et tellement originale). Ça pourrait être ma conférence préférée de l'année.\n- [BDX I\u002FO, 7 novembre 2025](.\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux.md), où j'ai donné mes deux talks. La conférence était vraiment bien organisée, et le lieu était superbe. J'en ai profité pour visiter les bureaux de NuxtLabs à Bordeaux et rencontrer Sébastien et Benjamin.\n- DevFest Toulouse, 13 novembre 2025, où j'ai donné mon talk sur la réactivité et les signaux pour la septième fois cette année. Le dîner des speakers était vraiment exceptionnel. On était dans un restaurant superbe, en plein centre-ville.\n- DevFest Dijon, 5 décembre 2025, où j'ai donné mon talk sur la réactivité et les signaux pour la huitième fois cette année. Le cadeau speaker était tellement cool : un sac avec des produits locaux de Dijon et de la région.\n\n![Estéban présentant au DevFest Nantes 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevfest-nantes-2025.webp)\n\n![Estéban présentant à BDX I\u002FO 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fbdx-io-2025.webp)\n\n![Estéban, Sébastien et Benjamin chez NuxtLabs.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fnuxtlabs-bordeaux-2025.webp)\n\n![Selfie d'Estéban au DevFest Dijon 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fdevfest-dijon-2025.webp)\n\nLe monde des speakers est assez petit, et tu rencontres souvent les mêmes personnes encore et encore. Ça a atténué la solitude que je ressentais en 2023 quand j'ai donné mon premier talk à Devoxx France.\n\nAvant et entre ces conférences, j'ai donné plusieurs talks dans des meetups à Paris et Strasbourg. _Oui, je suis allé et revenu de Strasbourg juste pour un talk en soirée. Démesuré, mais fun._\n\n- Paris TypeScript, 21 janvier 2025, [Devins full-stack avec TypeScript](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-01-21\u002Fparis-typescript\u002F1)\n- Node.js Paris, 5 mars 2025, [Vous n'avez pas besoin de ces nouvelles API Node.js](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-03-05\u002Fnodejs-paris\u002F1)\n- HumanTalks Paris, 8 avril 2025, [Ship Nuxt apps that scale.](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-04-08\u002Fhuman-talks\u002F1)\n- HumanTalks Paris, 9 septembre 2025, [Inalia: Un Anneau pour les gouverner tous](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-09\u002Fhumantalks-paris\u002F1)\n- Node.js Paris, 17 septembre 2025, [Inalia: Un Anneau pour les gouverner tous](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-17\u002Fnodejs-paris\u002F1)\n- Paris TypeScript, 14 octobre 2025, [La réactivité et les signaux : démystifions la magie du frontend](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-10-14\u002Fparis-typescript\u002F1)\n- StrasbourgJS, 21 octobre 2025, [La réactivité et les signaux : démystifions la magie du frontend](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-10-21\u002Fstrasbourgjs\u002F1)\n- Paris TypeScript, 9 décembre 2025, [En JavaScript, il y a un paquet pour tout... et souvent pour rien](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-12-09\u002Fparis-typescript\u002F1)\n\nJ'ai vraiment aimé donner des talks en meetups. L'ambiance est beaucoup plus détendue qu'en conférence, et c'est plus facile d'interagir avec le public. Mais ne te trompe pas : ça peut être plus stressant, parce que le public est vraiment proche de toi, et tu peux facilement voir les réactions.\n\n![Estéban présentant au meetup StrasbourgJS en octobre 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fstrasbourgjs-october-2025.webp)\n\n![Selfie d'Estéban au meetup Paris TypeScript en décembre 2025.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fparis-typescript-december-2025.webp)\n\nÇa a été une année exceptionnelle pour les talks et les conférences, au-delà de toutes mes attentes. Merci à toutes les personnes que j'ai rencontrées et à tous les organisateurs qui m'ont fait confiance pour donner des talks dans leurs événements.\n\nRetrouve tous mes talks sur ma [page talks](..\u002Ftalks.md). J'ai ajouté une carte qui montre tous les endroits où j'ai donné des talks depuis 2023, ainsi que quelques statistiques. Tu peux trouver les slides sur \u003CGitHubLink repo=\"barbapapazes\u002Ftalks\" \u002F>.\n\n_Je ne reçois aucune compensation pour mes talks. Tous les voyages, les hôtels et le temps passé à les préparer sont à mes frais. Je le fais parce que j'aime partager mes connaissances et mes expériences avec la communauté. Cependant, ce n'est pas viable sur le long terme. Si tu veux me soutenir, tu peux devenir sponsor sur [GitHub Sponsors](https:\u002F\u002Fgithub.com\u002Fsponsors\u002Fbarbapapazes)._\n\n## Parcours Open Source\n\nCette année, j'ai principalement contribué à [Nuxt UI](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui). Nuxt UI est ma librairie de composants par défaut, que je construise une application Nuxt ou Vue. Je l'utilise pour tous mes projets. Comme j'ai dû m'y plonger en profondeur pour [maintenir un fork](.\u002Fsoftware-engineering-a-human-journey-before-anything-else.md), je la connais très bien maintenant. Donc à chaque fois que je trouvais un problème ou une amélioration possible, je soumettais une pull request.\n\nJ'ai aussi créé un plugin Markdown Exit qui exploite ses capacités asynchrones pour optimiser automatiquement les images et générer des placeholders blurhash. Tu peux le trouver sur \u003CGitHubLink repo=\"barbapapazes\u002Fmarkdown-exit-image\" \u002F>. J'ai aussi créé un plugin VitePress pour générer automatiquement des images OG. Tu peux le trouver sur \u003CGitHubLink repo=\"barbapapazes\u002Fvitepress-plugin-og\" \u002F>.\n\nJ'ai continué à construire [Inalia](https:\u002F\u002Finalia.app). J'espère vraiment ouvrir une bêta publique au premier semestre 2026, mais il reste encore énormément de travail, et d'autres projets me prennent du temps.\n\nTout au long de l'année, j'ai aussi continué à maintenir d'autres projets open source comme [Nuxt Authorization](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fnuxt-authorization), et la plupart de mes projets sont open source. Tu peux retrouver tous mes projets open source sur mon [profil GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes).\n\nJ'ai commencé à utiliser beaucoup l'IA pour écrire du code. GitHub Copilot Agent, depuis l'interface web, est vraiment puissant, et je l'utilise au quotidien. À chaque fois que j'ai une idée, où que je sois, j'ouvre l'app mobile, j'écris un petit prompt et je lance un agent. À la fin de la journée, je relis les PR générées, je fais des ajustements et je les merge. Ça a vraiment amélioré ma productivité, et ça me permet de me concentrer sur des tâches plus complexes.\n\n## Création de Contenu et Écriture\n\nCette année, j'ai écrit 20 posts et une série. Pour te donner une idée, un seul post me prend entre 5 et 10 heures d'écriture. Mais compter les heures n'a pas vraiment de sens pour une activité aussi importante que l'écriture. Écrire m'aide à clarifier mes pensées, à libérer mon esprit et à faire grandir mes connaissances. Je le recommande à tout le monde, et je n'arrêterai pas. Cependant, j'ai une règle importante : j'écris uniquement quand j'ai quelque chose à partager. Ça veut dire que je n'ai pas de planning d'écriture.\n\nCette année, j'ai écrit des posts techniques sur JavaScript, Vite, Markdown It et Laravel :\n\n- \u003CLinkToArticle id=\"98b9e72c-7ae6-4db7-a23a-da2412fbfc38\" \u002F>\n- \u003CLinkToArticle id=\"c3e7499a-3b98-4008-b25e-756b529d8dcc\" \u002F>\n- \u003CLinkToArticle id=\"fe3fd9b9-481e-4d38-9cea-969595fc913a\" \u002F>\n- \u003CLinkToArticle id=\"5326cb9f-fd51-4d43-bff8-2cee2a995d89\" \u002F>\n- \u003CLinkToArticle id=\"11e74f5a-eed4-4419-a931-1c659ebd7ff3\" \u002F>\n- \u003CLinkToArticle id=\"a505274a-14a6-471e-95a2-a3dbc64cd170\" \u002F>\n- \u003CLinkToArticle id=\"ca2372b3-15b3-4d66-9877-a4dd696580de\" \u002F>\n\nJ'ai aussi écrit des posts sur mon parcours de speaker :\n\n- \u003CLinkToArticle id=\"29c1e474-c428-4506-9d4b-23b420bf9f64\" \u002F>\n- \u003CLinkToArticle id=\"1f871ffb-d9c9-4b71-bc6b-bf260ceb227a\" \u002F>\n- \u003CLinkToArticle id=\"6f54e585-a2ec-4508-a863-5a0726f23744\" \u002F>\n- \u003CLinkToArticle id=\"da3b2630-1622-49fd-9311-35c9b25c5d54\" \u002F>\n- \u003CLinkToArticle id=\"49bf687d-212a-4a3c-af76-ec3c1dac3089\" \u002F>\n\nEt j'ai aussi écrit quelques posts plus personnels sur moi et ma vie :\n\n- \u003CLinkToArticle id=\"6da35c27-6a73-4b8f-aafa-2aa01e7a1ed3\" \u002F>\n- \u003CLinkToArticle id=\"83de2151-b1db-48ee-8d5a-eb88e359c337\" \u002F>\n- \u003CLinkToArticle id=\"919c8df4-e6f7-4dc5-9cda-165d754168ed\" \u002F>\n\nCe sont tous des posts que j'aurais adoré lire quand j'ai commencé mon parcours de développeur. Être ingénieur logiciel, ce n'est pas juste du code ; ce n'est pas toujours facile, et quand c'est aussi une passion, ça peut devenir écrasant. Partager mes expériences et mes pensées m'aide d'abord, et j'espère que ça aide aussi d'autres personnes.\n\nJ'ai aussi refactoré le backend de ce site pour gérer les prochains sites web que je construis. Ça a aussi débloqué de nouvelles fonctionnalités qui devraient arriver en 2026. Cependant, ça prendra du temps, parce que je vais devoir migrer ce site de VitePress vers du Vite pur avec Vue. C'est énormément de travail, et je l'ai déjà migré l'année dernière, quand je suis passé d'une UI custom à une version réécrite de Nuxt UI. Pouvoir migrer plutôt que réécrire from scratch, c'est quelque chose dont je suis vraiment fier. Ça montre que les choses évoluent, tout en gardant la continuité.\n\n## Et Maintenant\n\nComme tu le vois, 2025 a été une année marquante. Je suis vraiment fier de tout ce que j'ai accompli, mais j'ai terminé l'année très fatigué.\n\nCependant, j'ai plein de plans excitants pour 2026, donc ralentir n'est pas dans mes plans. _Ne t'inquiète pas, j'ai appris à comprendre mes limites, et je suis capable de réduire le rythme quand c'est nécessaire. Le burn-out n'est pas une option non plus._\n\nD'abord, [Hugo](https:\u002F\u002Fx.com\u002Fhugorcd). Hugo, c'est quelqu'un à qui je n'ai parlé qu'en ligne. On partage la même passion pour le développement web, l'open source et Nuxt, et quand il a commencé à partager du contenu sur X, j'étais super curieux de son travail. Il a une approche unique de la beauté, avec des animations, des transitions et des assets élégants que j'ai trouvés très inspirants. Il a amené quelque chose de nouveau dans mon feed. Aujourd'hui, il travaille chez Vercel depuis Londres. Du coup, je prévois d'aller le voir. En 2026, je veux aller à Londres, le rencontrer et explorer un peu la ville.\n\nEn parlant de partir à l'étranger, je veux assister à plus de conférences européennes, et peut-être même internationales. Je veux continuer à explorer ces événements incroyables, rencontrer des gens passionnés et partager mon amour pour le développement web. J'ai déjà quelques conférences en tête auxquelles je veux soumettre des talks. J'ai déjà soumis à Vue.js Amsterdam et MadVue. _Je devrais prendre des cours pour améliorer mon anglais à l'oral._\n\nJ'ai écrit deux nouveaux talks pendant le dernier mois de 2025. Pour le moment, je compte surtout les présenter en France, comme cette année. L'un d'eux parle de plugins Vite, écrit d'une manière différente de tous les talks que tu as vus auparavant. Je ne spoil pas la surprise, mais si j'arrive à le faire fonctionner, ce sera très fun à voir et à présenter. Le second est co-écrit avec une amie, Julie, que j'apprécie énormément. Je suis vraiment excité par ce nouveau talk. C'est la première fois que j'écris un talk avec quelqu'un d'autre. On n'a pas le même background, et c'est un sujet que je n'ai pas complètement maîtrisé. Cette collaboration est un vrai challenge. C'est bien plus qu'un simple talk. Ça consomme beaucoup de temps et ça nous sort de notre zone de confort, mais je crois vraiment que ça en vaudra la peine. L'état actuel est très prometteur.\n\nÀ côté de ça, je veux continuer à écrire comme je le fais depuis deux ans maintenant. Écrire m'aide à clarifier mes pensées, partager mes expériences et creuser des sujets que je pensais maîtriser, mais qui ont toujours plus à révéler. Je veux continuer à repousser mes limites et explorer de nouvelles idées à travers l'écriture. Je veux aussi continuer à streamer sur Twitch. Depuis septembre, je n'ai pas trouvé beaucoup de temps, mais j'espère que ça va changer en 2026.\n\nPour mon contenu, je vais continuer à écrire sur JavaScript et Laravel, mais je vais devoir commencer à écrire davantage sur Java, puisque mon nouveau job est principalement centré dessus. Je ne suis pas complètement sûr de comment je vais m'y prendre, car je suis aussi en train de construire un nouveau site qui sert de second cerveau. Tu peux t'attendre à plus d'informations dans les prochains mois. Il y a quelques semaines, j'ai partagé sur X un autre site que je construis pour suivre toute mon infrastructure. C'est encore au tout début, mais j'espère en parler davantage dès que possible. Ça me demande d'écrire énormément, et écrire prend du temps, donc je ne donne pas de date, mais attends-toi à ce que ce soit prêt avant l'été 2026.\n\nÀ mesure que l'IA prend de plus en plus de place dans mon quotidien, j'ai accepté le fait que je peux lui déléguer certaines tâches. Mais il y a une ligne rouge que je ne franchirai pas : je continuerai à écrire tous mes posts moi-même, et je n'utiliserai jamais l'IA pour générer du contenu à ma place. Si ça devait arriver, j'arrêterais simplement de travailler dans la tech et je commencerais une toute nouvelle carrière. C'est non négociable pour moi, à la fois personnellement et professionnellement. Je suis aussi fortement opposé au fait que quelqu'un fasse l'inverse.\n\nLa pâtisserie fera partie de 2026. C'est tellement bon de faire des choses avec mes mains, loin des écrans. Partager ça avec quelqu'un, c'est aussi une activité incroyable. Hautement recommandé.\n\nTout ça, c'est le plan principal pour 2026. Mais comme le changement est la seule constante, des choses inattendues arriveront, et je m'adapterai.\n\nÇa a été une année incroyable. 2026 sera encore meilleure.\n\nPrends soin de toi, passe du temps avec ceux que tu aimes, vis ta vie à fond, et bonne année ! 🫶\n\n![Estéban qui vole vers de nouveaux horizons en wingfoil.](\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action\u002Fhappy-new-year-2026.webp)\n",{"title":8781,"description":9414},{"loc":9416,"images":9420},[9421,9422,9423,9424,9425,9426,9427,9428,9429,9430,9431,9432,9433],{"loc":8815},{"loc":8906},{"loc":8920},{"loc":8994},{"loc":9013},{"loc":9030},{"loc":9061},{"loc":9067},{"loc":9073},{"loc":9079},{"loc":9161},{"loc":9167},{"loc":9403},"fr\u002Fposts\u002F2025-a-turning-point-from-slow-steps-to-clear-action","lhTA8b9kYqaAAy0nTuAUkvGC5WxWWwMTkK-kkw7QPbU",{"id":9437,"title":9438,"body":9439,"contentId":9656,"date":9657,"description":9658,"extension":18,"meta":9659,"navigation":20,"path":9660,"rawbody":9661,"robots":23,"schemaOrg":23,"seo":9662,"sitemap":9663,"stem":9664,"__hash__":9665},"posts\u002Ffr\u002Fposts\u002Fstarting-a-new-chapter-new-ambitions-and-new-job.md","Un nouveau chapitre : nouvelles ambitions et nouveau travail",{"type":7,"value":9440,"toc":9650},[9441,9447,9451,9454,9460,9466,9469,9475,9478,9481,9484,9488,9495,9506,9509,9512,9515,9518,9526,9531,9537,9540,9543,9547,9554,9557,9560,9563,9566,9569,9572,9575,9579,9582,9591,9600,9608,9611,9614,9617,9623,9625,9631,9633,9636,9639,9642,9645,9647],[35,9442,9443,9444],{},"C'est une décision déchirante, mais j'ai décidé de partir, ou plus précisément, de ne pas renouveler mon contrat. ",[1216,9445,9446],{},"J'étais ingénieur logiciel dans une entreprise aérospatiale appelée MaiaSpace.",[51,9448,9450],{"id":9449},"tout-a-une-fin","Tout a une fin",[35,9452,9453],{},"C'était mon premier emploi, mais pas nécessairement mon job de rêve, ou le travail dont j'avais rêvé.",[35,9455,9456,9457],{},"Je n'avais jamais pensé pouvoir travailler dans l'aérospatiale, et je ne suis pas particulièrement passionné par les fusées. J'apprécie vraiment regarder les lancements et toutes les nouvelles technologies développées, comme l'atterrissage du booster et du premier étage de la Falcon 9 ou suivre le développement du Starship. Mais sans plus. ",[1216,9458,9459],{},"La capture du premier étage du Starship entre les chopsticks était vraiment impressionnante !",[35,9461,9462,9463],{},"Fin août 2024, j'ai posté sur LinkedIn pour partager que je cherchais une nouvelle opportunité, sans grandes attentes. Mais un ami a vu mon post et m'a contacté pour un poste dans son entreprise. Ils cherchaient quelqu'un avec mes compétences en développement web. Génial ! ",[1216,9464,9465],{},"Je peux le dire sans risque maintenant, mais je n'ai postulé nulle part ailleurs. Tout mon espoir reposait sur cette opportunité.",[35,9467,9468],{},"Je lui ai demandé où il travaillait. Il m'a dit qu'il construisait une fusée réutilisable au sein d'une startup appelée MaiaSpace. Je n'ai pas vraiment demandé plus. J'étais juste emballé par le projet et l'idée de travailler sur un projet industriel, concret et impactant pour la France. Pour rejoindre l'entreprise, j'ai passé deux entretiens d'une heure, l'un avec l'équipe, et l'autre avec le PO et le CTO. Tout s'est bien passé, et j'ai été embauché pour un contrat d'un an.",[35,9470,9471,9472],{},"J'ai été le premier, et le seul, développeur web de l'entreprise pendant presque toute cette période. J'ai donc eu l'opportunité de construire en partant de 0, avec les technologies que je voulais. La seule contrainte était d'avoir un produit fonctionnel le plus rapidement possible. J'ai choisi une stack assez simple pour pouvoir la gérer seul mais suffisamment puissante pour gérer les exigences du projet. ",[1216,9473,9474],{},"Laravel, Vue.js et Inertia.",[35,9476,9477],{},"La tech n'a jamais été la partie compliquée de ma mission.",[35,9479,9480],{},"Quand vous arrivez dans un domaine qui vous est complètement inconnu, écrire du code est, de loin, la tâche la plus simple que vous puissiez faire. Comprendre le métier et le domaine, quand la plupart des mots sont des acronymes et que personne ne comprend votre travail, est vraiment difficile. Honnêtement, il m'a fallu des mois pour vraiment saisir suffisamment le domaine pour commencer à travailler efficacement, et pour livrer un produit utile. Une fois que vous l'avez, tout devient plus facile, et vous commencez à livrer des fonctionnalités qui aident vraiment les utilisateurs.",[35,9482,9483],{},"J'ai vraiment aimé tout ce dans quoi j'étais impliqué, et j'ai vraiment apprécié tout le monde dans l'équipe dont je faisais partie.",[51,9485,9487],{"id":9486},"un-départ-déchirant","Un départ déchirant",[35,9489,9490,9491,9494],{},"J'ai développé le logiciel seul pendant près d'un an, passant plus de 845 heures dans VS Code. J'ai écrit et réfléchi à chaque ligne. Je l'ai vu naître, mourir et renaître. Ensuite, je l'ai vu grandir et évoluer, d'une simple idée à un système complexe, d'une page blanche à une application ",[1216,9492,9493],{},"déployée",", interconnectée avec divers composants et services. Au fil du temps, c'est devenu une partie cruciale du workflow de l'équipe.",[35,9496,9497,9498],{},"Cela a même inspiré certains articles que j'ai écrits : ",[3045,9499,3092,9500],{"id":9260},[3045,9501,9502,9503],{"id":9280},", et ",[3045,9504,215],{"id":9505},"ba80c73f-cf16-41cc-b1f8-bc548c4c82f4",[35,9507,9508],{},"L'opportunité de travailler sur un tel projet était incroyable, et ça a été une opportunité unique. Il n'y a pas deux MaiaSpace sur Terre, et il n'y a pas beaucoup d'entreprises travaillant sur des fusées réutilisables.",[35,9510,9511],{},"La vérité est que, même avec une opportunité aussi unique, je n'ai pas réussi à trouver ma place au sein de l'entreprise. Je n'étais pas aligné avec la vision, les valeurs et l'état d'esprit de l'entreprise. L'importance du projet industriel n'a pas été suffisante pour me garder motivé.",[35,9513,9514],{},"L'industrie aérospatiale européenne est petite, très fermée d'esprit, malgré les longues déclarations sur l'innovation et la disruption. Adopter de nouvelles idées est un défi pour eux. Certains travaillent dans l'industrie depuis plus de 20 ans. Ça a marché pour Ariane 5 et Ariane 6, alors pourquoi changer ? Ce n'est pas nécessaire.",[35,9516,9517],{},"Ils ont oublié une chose, une chose importante. Le but de MaiaSpace n'est pas seulement de construire un lanceur. Il s'agit de réduire les coûts et d'augmenter l'accessibilité à l'espace, ce qui est un changement fondamental. Nous savons comment construire une fusée. Ariane 6 se porte bien, et nous n'avons pas besoin d'une Ariane 6 bis. Le vrai défi est de se débarrasser de la couche bureaucratique, d'augmenter la vélocité, d'innover, et de le faire intelligemment. C'est honnêtement un objectif énorme, presque un changement culturel.",[35,9519,9520,9521,9525],{},"L'équipe dont je faisais partie était incroyablement talentueuse et passionnée. Ils comprenaient l'importance de mon travail, créer des logiciels pour faciliter, accélérer et automatiser le workflow. Malheureusement, au-delà de ce périmètre, la direction de l'entreprise voyait mon travail comme un simple centre de coûts, plutôt que comme une création de valeur. Au quotidien, cela signifiait que ",[39,9522,9524],{"href":9523},".\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty","chaque petite action me demandait de déplacer des montagnes",", et de gérer des batailles politiques internes auxquelles je n'aurais jamais dû avoir à faire face.",[35,9527,9528],{},[1216,9529,9530],{},"N'oubliez jamais que la jeunesse et une perspective nouvelle peuvent apporter bien plus de valeur que l'expérience. Accueillez le feedback.",[35,9532,9533,9534],{},"De plus, aucun plan de carrière clair n'était proposé, et quand on est juste au début de sa carrière, c'est dur quand on ne voit pas de trajectoire pour progresser. Dans le même temps, j'adore écrire des talks et les donner lors de meetups et de conférences. Je consacre beaucoup de mon temps libre à cette activité, mais l'entreprise n'en a jamais vu la valeur. Plus de la moitié de mes jours de congés ont été passés à aller à des conférences. ",[1216,9535,9536],{},"On ne peut pas vraiment appeler ça des jours de \"vacances\".",[35,9538,9539],{},"Enfin, être seul signifie que vous atteignez inévitablement un plateau où vous arrêtez d'apprendre et de grandir. Cela crée de la frustration, et un an après mon arrivée, j'ai senti que j'avais atteint un point de stagnation.",[35,9541,9542],{},"Il y avait des avantages à rester, comme il y en avait à partir.",[51,9544,9546],{"id":9545},"le-processus-de-deuil","Le processus de deuil",[35,9548,9549,9550,9553],{},"Je ne suis pas parti sur un coup de tête. ",[39,9551,9552],{"href":9523},"Il m'a fallu des semaines"," pour arriver à cette conclusion.",[35,9555,9556],{},"En septembre, j'ai traversé une période importante de réflexion et de réévaluation. Avant d'envisager de partir, j'ai essayé de trouver des moyens d'améliorer ma situation au sein de l'entreprise. J'ai discuté de mes préoccupations avec plusieurs collègues pour trouver des solutions. Après des semaines à essayer de nombreuses approches différentes, j'ai dû accepter les faits. Je ne pouvais rien changer, et envisager de partir était la seule option restante.",[35,9558,9559],{},"Au début, je voulais vraiment rester, et je me suis remis en question. Suis-je le problème ? Que devrais-je faire pour améliorer la situation ? Aussi, je me persuadais que les choses pouvaient s'améliorer. Les gens à qui j'ai parlé m'ont dit qu'ils discutaient à trouver une façon de résoudre la situation. Je les ai crus.",[35,9561,9562],{},"Pendant l'attente, j'étais au plus bas. J'ai complètement perdu la joie de travailler, de créer, tant personnellement que professionnellement. La seule chose que je voulais, c'était rester sur le canapé, scroller sur mon téléphone, en attendant demain. La réalité était que la situation ne s'améliorerait pas par magie.",[35,9564,9565],{},"Après quelques semaines d'attente, rien n'avait changé au travail. Certains jours étaient même pires que d'autres. De toutes les options que j'ai envisagées, il n'en restait qu'une : partir. Je ne voulais pas l'accepter au début, mais après avoir parlé avec de nombreux amis et personnes extérieures à la situation, j'ai réalisé que c'était la seule option acceptable.",[35,9567,9568],{},"Il m'a fallu quelques jours pour l'admettre, mais ils avaient raison.",[35,9570,9571],{},"Savoir que je voulais partir a été une étape cruciale car je pouvais commencer à trouver des moyens de me relever. Qu'est-ce que je veux ensuite ? Où vais-je à partir de là ? Je peux explorer absolument tout ce que je souhaite. Une fois que j'ai trouvé ma direction et un nouveau travail, la vie est devenue plus douce, et le travail a cessé d'être difficile.",[35,9573,9574],{},"Avec du recul, c'était vraiment un processus de deuil qui a eu lieu en septembre. Je l'ai compris après coup, mais je suis sûr que le comprendre pendant que je le traversais aurait aidé à savoir comment le résoudre plus rapidement.",[51,9576,9578],{"id":9577},"aller-de-lavant","Aller de l'avant",[35,9580,9581],{},"C'était le passé, et maintenant il est temps de regarder vers l'avenir.",[35,9583,9584,9585,9590],{},"Je commence un nouveau chapitre dans une entreprise appelée ",[39,9586,9589],{"href":9587,"rel":9588},"https:\u002F\u002Fwww.takima.fr\u002F",[77],"Takima",". C'est une société de conseil spécialisée en Angular, Java et tout ce qui touche à l'infrastructure. Je ne sais pas encore exactement sur quoi je vais travailler, mais je suis impatient de commencer à travailler avec eux.",[35,9592,9593,9594,9599],{},"J'ai rencontré Takima il y a deux ans, à Devoxx France 2023. Le premier talk que j'ai vu était celui de ",[39,9595,9598],{"href":9596,"rel":9597},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F",[77],"Loïc",", actuel CTO, et Aurélien, ancien consultant, sur la construction d'une infrastructure GitOps from scratch. J'ai trouvé ça incroyable, et même si je ne comprenais pas tout, je savais que je voulais être capable de faire comme eux un jour.",[35,9601,9602,9603,9607],{},"En 2024, quand je suis retourné à Devoxx France, j'ai cherché les talks de Takima. Ils sont bien écrits, on apprend beaucoup, et on passe un bon moment. Plus tard dans l'année, ",[39,9604,8064],{"href":9605,"rel":9606},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcamille-pillot-455517177\u002F",[77],", consultante chez Takima et organisatrice d'événements tech, m'a invité à parler à l'un de leurs meetups appelé TakiMeet.",[35,9609,9610],{},"En 2025, toujours à Devoxx France, j'ai passé la plupart de mon temps sur leur stand, à discuter, échanger et apprendre avec l'équipe. Depuis, j'ai eu de multiples occasions de les rencontrer lors de conférences.",[35,9612,9613],{},"À travers toutes ces interactions, j'ai découvert plus qu'une simple entreprise. J'ai trouvé des gens passionnés qui se soucient de leur travail, qui aiment partager leurs connaissances, avec une vision et une culture tech que j'apprécie vraiment. J'étais aligné avec eux. De plus, je serai entouré de personnes qui font le même travail. Je ne serai pas le seul. L'un de mes espoirs est de trouver l'épanouissement dans cet environnement.",[35,9615,9616],{},"Alors, quand l'opportunité de les rejoindre s'est présentée, je n'ai pas hésité.",[35,9618,9619,9620],{},"Maintenant, il est temps d'apprendre Java ! ",[1216,9621,9622],{},"Je n'ai jamais vraiment écrit de Java auparavant, donc ce sera un nouveau défi pour moi. Je serai payé pour écrire du Java, donc il va me falloir être bon.",[1158,9624],{},[35,9626,9627,9628],{},"C'est quoi ton métier ? Je construis une fusée spatiale réutilisable. ",[1216,9629,9630],{},"Était-ce vraiment la vérité ? Qui sait ? Ça n'a plus vraiment d'importance maintenant, je ne peux plus dire ça.",[1158,9632],{},[35,9634,9635],{},"Qu'est-ce que je veux vraiment ?",[35,9637,9638],{},"Cette question m'a vraiment hanté. Au début, je n'étais pas capable de trouver une réponse. Je voulais trouver un point fixe, une direction claire qui ne changerait pas avec le temps.",[35,9640,9641],{},"Je suis maintenant convaincu que c'est une quête impossible.",[35,9643,9644],{},"La réponse à cette question fluctue inévitablement, et j'avais besoin d'accepter qu'il est normal que mes objectifs et mes désirs évoluent, en même temps que mes expériences et les personnes dont je m'entoure. Cette acceptation a vraiment réduit ma charge mentale et m'a fait me sentir plus apaisé.",[1158,9646],{},[35,9648,9649],{},"Le changement est la seule constante.",{"title":13,"searchDepth":14,"depth":14,"links":9651},[9652,9653,9654,9655],{"id":9449,"depth":14,"text":9450},{"id":9486,"depth":14,"text":9487},{"id":9545,"depth":14,"text":9546},{"id":9577,"depth":14,"text":9578},"939bab10-62cf-4f7d-9130-dd42ba87cabf","2025-12-02","Je quitte mon poste chez MaiaSpace après des semaines de réflexion pour rejoindre Takima, une entreprise plus humaine.",{},"\u002Ffr\u002Fposts\u002Fstarting-a-new-chapter-new-ambitions-and-new-job","---\ncontentId: 939bab10-62cf-4f7d-9130-dd42ba87cabf\ntitle: \"Un nouveau chapitre : nouvelles ambitions et nouveau travail\"\ndescription: \"Je quitte mon poste chez MaiaSpace après des semaines de réflexion pour rejoindre Takima, une entreprise plus humaine.\"\ndate: 2025-12-02\n---\n\nC'est une décision déchirante, mais j'ai décidé de partir, ou plus précisément, de ne pas renouveler mon contrat. _J'étais ingénieur logiciel dans une entreprise aérospatiale appelée MaiaSpace._\n\n## Tout a une fin\n\nC'était mon premier emploi, mais pas nécessairement mon job de rêve, ou le travail dont j'avais rêvé.\n\nJe n'avais jamais pensé pouvoir travailler dans l'aérospatiale, et je ne suis pas particulièrement passionné par les fusées. J'apprécie vraiment regarder les lancements et toutes les nouvelles technologies développées, comme l'atterrissage du booster et du premier étage de la Falcon 9 ou suivre le développement du Starship. Mais sans plus. _La capture du premier étage du Starship entre les chopsticks était vraiment impressionnante !_\n\nFin août 2024, j'ai posté sur LinkedIn pour partager que je cherchais une nouvelle opportunité, sans grandes attentes. Mais un ami a vu mon post et m'a contacté pour un poste dans son entreprise. Ils cherchaient quelqu'un avec mes compétences en développement web. Génial ! _Je peux le dire sans risque maintenant, mais je n'ai postulé nulle part ailleurs. Tout mon espoir reposait sur cette opportunité._\n\nJe lui ai demandé où il travaillait. Il m'a dit qu'il construisait une fusée réutilisable au sein d'une startup appelée MaiaSpace. Je n'ai pas vraiment demandé plus. J'étais juste emballé par le projet et l'idée de travailler sur un projet industriel, concret et impactant pour la France. Pour rejoindre l'entreprise, j'ai passé deux entretiens d'une heure, l'un avec l'équipe, et l'autre avec le PO et le CTO. Tout s'est bien passé, et j'ai été embauché pour un contrat d'un an.\n\nJ'ai été le premier, et le seul, développeur web de l'entreprise pendant presque toute cette période. J'ai donc eu l'opportunité de construire en partant de 0, avec les technologies que je voulais. La seule contrainte était d'avoir un produit fonctionnel le plus rapidement possible. J'ai choisi une stack assez simple pour pouvoir la gérer seul mais suffisamment puissante pour gérer les exigences du projet. _Laravel, Vue.js et Inertia._\n\nLa tech n'a jamais été la partie compliquée de ma mission.\n\nQuand vous arrivez dans un domaine qui vous est complètement inconnu, écrire du code est, de loin, la tâche la plus simple que vous puissiez faire. Comprendre le métier et le domaine, quand la plupart des mots sont des acronymes et que personne ne comprend votre travail, est vraiment difficile. Honnêtement, il m'a fallu des mois pour vraiment saisir suffisamment le domaine pour commencer à travailler efficacement, et pour livrer un produit utile. Une fois que vous l'avez, tout devient plus facile, et vous commencez à livrer des fonctionnalités qui aident vraiment les utilisateurs.\n\nJ'ai vraiment aimé tout ce dans quoi j'étais impliqué, et j'ai vraiment apprécié tout le monde dans l'équipe dont je faisais partie.\n\n## Un départ déchirant\n\nJ'ai développé le logiciel seul pendant près d'un an, passant plus de 845 heures dans VS Code. J'ai écrit et réfléchi à chaque ligne. Je l'ai vu naître, mourir et renaître. Ensuite, je l'ai vu grandir et évoluer, d'une simple idée à un système complexe, d'une page blanche à une application _déployée_, interconnectée avec divers composants et services. Au fil du temps, c'est devenu une partie cruciale du workflow de l'équipe.\n\nCela a même inspiré certains articles que j'ai écrits : \u003CLinkToArticle id=\"98b9e72c-7ae6-4db7-a23a-da2412fbfc38\" \u002F>, \u003CLinkToArticle id=\"11e74f5a-eed4-4419-a931-1c659ebd7ff3\" \u002F>, et \u003CLinkToArticle id=\"ba80c73f-cf16-41cc-b1f8-bc548c4c82f4\" \u002F>.\n\nL'opportunité de travailler sur un tel projet était incroyable, et ça a été une opportunité unique. Il n'y a pas deux MaiaSpace sur Terre, et il n'y a pas beaucoup d'entreprises travaillant sur des fusées réutilisables.\n\nLa vérité est que, même avec une opportunité aussi unique, je n'ai pas réussi à trouver ma place au sein de l'entreprise. Je n'étais pas aligné avec la vision, les valeurs et l'état d'esprit de l'entreprise. L'importance du projet industriel n'a pas été suffisante pour me garder motivé.\n\nL'industrie aérospatiale européenne est petite, très fermée d'esprit, malgré les longues déclarations sur l'innovation et la disruption. Adopter de nouvelles idées est un défi pour eux. Certains travaillent dans l'industrie depuis plus de 20 ans. Ça a marché pour Ariane 5 et Ariane 6, alors pourquoi changer ? Ce n'est pas nécessaire.\n\nIls ont oublié une chose, une chose importante. Le but de MaiaSpace n'est pas seulement de construire un lanceur. Il s'agit de réduire les coûts et d'augmenter l'accessibilité à l'espace, ce qui est un changement fondamental. Nous savons comment construire une fusée. Ariane 6 se porte bien, et nous n'avons pas besoin d'une Ariane 6 bis. Le vrai défi est de se débarrasser de la couche bureaucratique, d'augmenter la vélocité, d'innover, et de le faire intelligemment. C'est honnêtement un objectif énorme, presque un changement culturel.\n\nL'équipe dont je faisais partie était incroyablement talentueuse et passionnée. Ils comprenaient l'importance de mon travail, créer des logiciels pour faciliter, accélérer et automatiser le workflow. Malheureusement, au-delà de ce périmètre, la direction de l'entreprise voyait mon travail comme un simple centre de coûts, plutôt que comme une création de valeur. Au quotidien, cela signifiait que [chaque petite action me demandait de déplacer des montagnes](.\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty.md), et de gérer des batailles politiques internes auxquelles je n'aurais jamais dû avoir à faire face.\n\n_N'oubliez jamais que la jeunesse et une perspective nouvelle peuvent apporter bien plus de valeur que l'expérience. Accueillez le feedback._\n\nDe plus, aucun plan de carrière clair n'était proposé, et quand on est juste au début de sa carrière, c'est dur quand on ne voit pas de trajectoire pour progresser. Dans le même temps, j'adore écrire des talks et les donner lors de meetups et de conférences. Je consacre beaucoup de mon temps libre à cette activité, mais l'entreprise n'en a jamais vu la valeur. Plus de la moitié de mes jours de congés ont été passés à aller à des conférences. _On ne peut pas vraiment appeler ça des jours de \"vacances\"._\n\nEnfin, être seul signifie que vous atteignez inévitablement un plateau où vous arrêtez d'apprendre et de grandir. Cela crée de la frustration, et un an après mon arrivée, j'ai senti que j'avais atteint un point de stagnation.\n\nIl y avait des avantages à rester, comme il y en avait à partir.\n\n## Le processus de deuil\n\nJe ne suis pas parti sur un coup de tête. [Il m'a fallu des semaines](.\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty.md) pour arriver à cette conclusion.\n\nEn septembre, j'ai traversé une période importante de réflexion et de réévaluation. Avant d'envisager de partir, j'ai essayé de trouver des moyens d'améliorer ma situation au sein de l'entreprise. J'ai discuté de mes préoccupations avec plusieurs collègues pour trouver des solutions. Après des semaines à essayer de nombreuses approches différentes, j'ai dû accepter les faits. Je ne pouvais rien changer, et envisager de partir était la seule option restante.\n\nAu début, je voulais vraiment rester, et je me suis remis en question. Suis-je le problème ? Que devrais-je faire pour améliorer la situation ? Aussi, je me persuadais que les choses pouvaient s'améliorer. Les gens à qui j'ai parlé m'ont dit qu'ils discutaient à trouver une façon de résoudre la situation. Je les ai crus.\n\nPendant l'attente, j'étais au plus bas. J'ai complètement perdu la joie de travailler, de créer, tant personnellement que professionnellement. La seule chose que je voulais, c'était rester sur le canapé, scroller sur mon téléphone, en attendant demain. La réalité était que la situation ne s'améliorerait pas par magie.\n\nAprès quelques semaines d'attente, rien n'avait changé au travail. Certains jours étaient même pires que d'autres. De toutes les options que j'ai envisagées, il n'en restait qu'une : partir. Je ne voulais pas l'accepter au début, mais après avoir parlé avec de nombreux amis et personnes extérieures à la situation, j'ai réalisé que c'était la seule option acceptable.\n\nIl m'a fallu quelques jours pour l'admettre, mais ils avaient raison.\n\nSavoir que je voulais partir a été une étape cruciale car je pouvais commencer à trouver des moyens de me relever. Qu'est-ce que je veux ensuite ? Où vais-je à partir de là ? Je peux explorer absolument tout ce que je souhaite. Une fois que j'ai trouvé ma direction et un nouveau travail, la vie est devenue plus douce, et le travail a cessé d'être difficile.\n\nAvec du recul, c'était vraiment un processus de deuil qui a eu lieu en septembre. Je l'ai compris après coup, mais je suis sûr que le comprendre pendant que je le traversais aurait aidé à savoir comment le résoudre plus rapidement.\n\n## Aller de l'avant\n\nC'était le passé, et maintenant il est temps de regarder vers l'avenir.\n\nJe commence un nouveau chapitre dans une entreprise appelée [Takima](https:\u002F\u002Fwww.takima.fr\u002F). C'est une société de conseil spécialisée en Angular, Java et tout ce qui touche à l'infrastructure. Je ne sais pas encore exactement sur quoi je vais travailler, mais je suis impatient de commencer à travailler avec eux.\n\nJ'ai rencontré Takima il y a deux ans, à Devoxx France 2023. Le premier talk que j'ai vu était celui de [Loïc](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F), actuel CTO, et Aurélien, ancien consultant, sur la construction d'une infrastructure GitOps from scratch. J'ai trouvé ça incroyable, et même si je ne comprenais pas tout, je savais que je voulais être capable de faire comme eux un jour.\n\nEn 2024, quand je suis retourné à Devoxx France, j'ai cherché les talks de Takima. Ils sont bien écrits, on apprend beaucoup, et on passe un bon moment. Plus tard dans l'année, [Camille](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcamille-pillot-455517177\u002F), consultante chez Takima et organisatrice d'événements tech, m'a invité à parler à l'un de leurs meetups appelé TakiMeet.\n\nEn 2025, toujours à Devoxx France, j'ai passé la plupart de mon temps sur leur stand, à discuter, échanger et apprendre avec l'équipe. Depuis, j'ai eu de multiples occasions de les rencontrer lors de conférences.\n\nÀ travers toutes ces interactions, j'ai découvert plus qu'une simple entreprise. J'ai trouvé des gens passionnés qui se soucient de leur travail, qui aiment partager leurs connaissances, avec une vision et une culture tech que j'apprécie vraiment. J'étais aligné avec eux. De plus, je serai entouré de personnes qui font le même travail. Je ne serai pas le seul. L'un de mes espoirs est de trouver l'épanouissement dans cet environnement.\n\nAlors, quand l'opportunité de les rejoindre s'est présentée, je n'ai pas hésité.\n\nMaintenant, il est temps d'apprendre Java ! _Je n'ai jamais vraiment écrit de Java auparavant, donc ce sera un nouveau défi pour moi. Je serai payé pour écrire du Java, donc il va me falloir être bon._\n\n---\n\nC'est quoi ton métier ? Je construis une fusée spatiale réutilisable. _Était-ce vraiment la vérité ? Qui sait ? Ça n'a plus vraiment d'importance maintenant, je ne peux plus dire ça._\n\n---\n\nQu'est-ce que je veux vraiment ?\n\nCette question m'a vraiment hanté. Au début, je n'étais pas capable de trouver une réponse. Je voulais trouver un point fixe, une direction claire qui ne changerait pas avec le temps.\n\nJe suis maintenant convaincu que c'est une quête impossible.\n\nLa réponse à cette question fluctue inévitablement, et j'avais besoin d'accepter qu'il est normal que mes objectifs et mes désirs évoluent, en même temps que mes expériences et les personnes dont je m'entoure. Cette acceptation a vraiment réduit ma charge mentale et m'a fait me sentir plus apaisé.\n\n---\n\nLe changement est la seule constante.\n",{"title":9438,"description":9658},{"loc":9660},"fr\u002Fposts\u002Fstarting-a-new-chapter-new-ambitions-and-new-job","3EaWHBVyzHjjxhc4rudb90L-5ry79MDDrnnmfVaq4sI",{"id":9667,"title":9668,"body":9669,"contentId":9290,"date":11300,"description":11301,"extension":18,"meta":11302,"navigation":20,"path":11303,"rawbody":11304,"robots":23,"schemaOrg":23,"seo":11305,"sitemap":11306,"stem":11307,"__hash__":11308},"posts\u002Ffr\u002Fposts\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering.md","Markdown Exit brise les règles avec le rendu asynchrone",{"type":7,"value":9670,"toc":11293},[9671,9677,9680,9682,9691,9700,9703,9707,9710,9736,11290],[35,9672,9673,9674],{},"Cet article est la suite de ",[3045,9675,9676],{"id":9285}," et se concentre sur un cas d'utilisation plus concret : l'optimisation automatique des images dans les fichiers Markdown.",[35,9678,9679],{},"C'est très technique, et à moins que vous ne traitiez des fichiers Markdown, cela pourrait être un peu abstrait. Néanmoins, je vous encourage vivement à lire l'article précédent et celui-ci, car cela pourrait vous donner de bonnes idées.",[1158,9681],{},[35,9683,9684,9685,9690],{},"Pourquoi écrire un article dédié à ce sujet ? Parce que, jusqu'à présent, ce n'était pas possible. Le principal parseur Markdown dans le monde JavaScript est ",[39,9686,9689],{"href":9687,"rel":9688},"https:\u002F\u002Fgithub.com\u002Fmarkdown-it\u002Fmarkdown-it",[77],"markdown-it",", et il est entièrement synchrone.",[35,9692,9693,9694,9699],{},"Cela pourrait ne pas être un problème, mais cela signifie que ",[39,9695,9698],{"href":9696,"rel":9697},"https:\u002F\u002Fgithub.com\u002Fmarkdown-it\u002Fmarkdown-it\u002Fblob\u002Fmaster\u002Fdocs\u002Fdevelopment.md#i-need-async-rule-how-to-do-it",[77],"toutes les règles doivent être synchrones"," également. Vous ne pouvez pas avoir de règles asynchrones. Vous ne pouvez pas effectuer de fetch dans une règle, et vous ne pouvez rien paralléliser.",[35,9701,9702],{},"Un petit récapitulatif est nécessaire pour s'assurer que nous sommes tous sur la même longueur d'onde.",[51,9704,9706],{"id":9705},"écrire-un-parseur-markdown","Écrire un Parseur Markdown",[35,9708,9709],{},"Je n'entrerai pas dans les détails, mais essentiellement, le parseur va diviser le texte Markdown en tokens puis appliquer des règles, qui sont essentiellement des fonctions, à ces tokens pour générer le HTML.",[35,9711,9712,9713,9716,9717,9720,9721,9724,9725,9728,9729,9731,9732,9735],{},"Par exemple, le texte suivant ",[156,9714,9715],{},"**bold text**"," sera tokenisé en : ",[156,9718,9719],{},"['strong_open', 'text', 'strong_close']"," et les règles correspondantes généreront le HTML : ",[156,9722,9723],{},"\u003Cstrong>bold text\u003C\u002Fstrong>",". Le token ",[156,9726,9727],{},"strong_open"," sera traité par la règle ",[156,9730,9727],{},", qui retourne ",[156,9733,9734],{},"\u003Cstrong>",", et ainsi de suite.",[7918,9737,9740,9743,9753,9956,9967,9971,9974,9987,9990,9999,10003,10011,10014,10021,10028,10031,10044,10047,10081,10084,10211,10215,10223,10226,10355,10358,10415,10418,10424,10432,10663,10669,10672,10681,10698,10706,10840,10843,10910,10913,10919,11040,11043,11175,11182,11219,11226,11233,11235,11238,11241,11279,11284,11287],{"text":9738,"href":9739},"Explorez l'implémentation de la règle d'emphase dans markdown-exit","https:\u002F\u002Fgithub.com\u002Fserkodev\u002Fmarkdown-exit\u002Fblob\u002F3e0cfeb7926355f6896fcd4dd7afd797e7f6689f\u002Fpackages\u002Fmarkdown-exit\u002Fsrc\u002Fparser\u002Finline\u002Frules\u002Femphasis.ts",[35,9741,9742],{},"La règle dont nous parlons depuis le début transforme un token en sa représentation HTML correspondante. La partie intéressante est que vous pouvez attacher vos propres règles au parseur pour étendre ses fonctionnalités.",[35,9744,9745,9746,9749,9750,215],{},"Imaginez que vous vouliez appliquer automatiquement l'attribut ",[156,9747,9748],{},"loading=\"lazy\""," à toutes les images de vos fichiers Markdown. Vous pourriez écrire une règle qui cible la règle ",[156,9751,9752],{},"image",[192,9754,9757],{"className":9755,"code":9756,"language":1383,"meta":13,"style":13},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight has-highlighted","md.use((md) => {\n  const imageRule = md.renderer.rules.image!\n  md.renderer.rules.image = (tokens, idx, options, env, self) => {\n    const token = tokens[idx]\n\n    token.attrSet('loading', 'lazy') \u002F\u002F [!code highlight]\n\n    return imageRule(tokens, idx, options, env, self)\n  }\n})\n",[156,9758,9759,9780,9809,9858,9878,9882,9914,9918,9946,9950],{"__ignoreMap":13},[200,9760,9761,9763,9765,9768,9770,9772,9774,9776,9778],{"class":202,"line":203},[200,9762,18],{"class":210},[200,9764,215],{"class":214},[200,9766,9767],{"class":223},"use",[200,9769,227],{"class":210},[200,9771,227],{"class":214},[200,9773,18],{"class":386},[200,9775,2104],{"class":214},[200,9777,393],{"class":379},[200,9779,280],{"class":214},[200,9781,9782,9784,9787,9789,9792,9794,9797,9799,9802,9804,9806],{"class":202,"line":14},[200,9783,3738],{"class":379},[200,9785,9786],{"class":210}," imageRule",[200,9788,509],{"class":214},[200,9790,9791],{"class":210}," md",[200,9793,215],{"class":214},[200,9795,9796],{"class":210},"renderer",[200,9798,215],{"class":214},[200,9800,9801],{"class":210},"rules",[200,9803,215],{"class":214},[200,9805,9752],{"class":210},[200,9807,9808],{"class":214},"!\n",[200,9810,9811,9814,9816,9818,9820,9822,9824,9826,9828,9830,9833,9835,9838,9840,9843,9845,9847,9849,9852,9854,9856],{"class":202,"line":255},[200,9812,9813],{"class":210},"  md",[200,9815,215],{"class":214},[200,9817,9796],{"class":210},[200,9819,215],{"class":214},[200,9821,9801],{"class":210},[200,9823,215],{"class":214},[200,9825,9752],{"class":223},[200,9827,509],{"class":214},[200,9829,1094],{"class":214},[200,9831,9832],{"class":386},"tokens",[200,9834,549],{"class":214},[200,9836,9837],{"class":386}," idx",[200,9839,549],{"class":214},[200,9841,9842],{"class":386}," options",[200,9844,549],{"class":214},[200,9846,4892],{"class":386},[200,9848,549],{"class":214},[200,9850,9851],{"class":386}," self",[200,9853,2104],{"class":214},[200,9855,393],{"class":379},[200,9857,280],{"class":214},[200,9859,9860,9862,9865,9867,9870,9873,9876],{"class":202,"line":272},[200,9861,2014],{"class":379},[200,9863,9864],{"class":210}," token",[200,9866,509],{"class":214},[200,9868,9869],{"class":210}," tokens",[200,9871,9872],{"class":235},"[",[200,9874,9875],{"class":210},"idx",[200,9877,4511],{"class":235},[200,9879,9880],{"class":202,"line":283},[200,9881,528],{"emptyLinePlaceholder":20},[200,9883,9886,9889,9891,9894,9896,9898,9901,9903,9905,9907,9910,9912],{"class":9884,"line":300},[202,9885],"highlighted",[200,9887,9888],{"class":210},"    token",[200,9890,215],{"class":214},[200,9892,9893],{"class":223},"attrSet",[200,9895,227],{"class":235},[200,9897,249],{"class":214},[200,9899,9900],{"class":245},"loading",[200,9902,249],{"class":214},[200,9904,549],{"class":214},[200,9906,242],{"class":214},[200,9908,9909],{"class":245},"lazy",[200,9911,249],{"class":214},[200,9913,5282],{"class":235},[200,9915,9916],{"class":202,"line":310},[200,9917,528],{"emptyLinePlaceholder":20},[200,9919,9920,9922,9924,9926,9928,9930,9932,9934,9936,9938,9940,9942,9944],{"class":202,"line":336},[200,9921,3311],{"class":206},[200,9923,9786],{"class":223},[200,9925,227],{"class":235},[200,9927,9832],{"class":210},[200,9929,549],{"class":214},[200,9931,9837],{"class":210},[200,9933,549],{"class":214},[200,9935,9842],{"class":210},[200,9937,549],{"class":214},[200,9939,4892],{"class":210},[200,9941,549],{"class":214},[200,9943,9851],{"class":210},[200,9945,416],{"class":235},[200,9947,9948],{"class":202,"line":342},[200,9949,4505],{"class":214},[200,9951,9952,9954],{"class":202,"line":365},[200,9953,413],{"class":214},[200,9955,416],{"class":210},[35,9957,9958,9959,9962,9963,9966],{},"Maintenant, le Markdown suivant ",[156,9960,9961],{},"![alt text](image.jpg)"," sera rendu comme ",[156,9964,9965],{},"\u003Cimg src=\"image.jpg\" alt=\"alt text\" loading=\"lazy\" \u002F>",". C'est un rêve : appliquer automatiquement des changements aux fichiers sans les toucher.",[51,9968,9970],{"id":9969},"limitations-synchrones","Limitations Synchrones",[35,9972,9973],{},"C'est un rêve jusqu'à ce que vous réalisiez que tout cela se passe de manière synchrone. Les règles sont appliquées une par une, sans aucun moyen de paralléliser le processus. Pour la règle de surlignage du code, cela peut réduire considérablement les performances car vous ne pouvez pas rendre tous les blocs de code en même temps, c'est un par un.",[938,9975,9976],{},[35,9977,9978,9980,9981,9986],{},[200,9979,944],{},"\nAnthony Fu en était bien conscient et il a créé ",[39,9982,9985],{"href":9983,"rel":9984},"https:\u002F\u002Fgithub.com\u002Fantfu\u002Fmarkdown-it-async",[77],"markdown-it-async"," pour rendre cela possible.",[35,9988,9989],{},"En plus de paralléliser le processus, les règles synchrones signifient que vous ne pouvez effectuer aucune opération asynchrone au sein d'une règle. Par exemple, vous ne pouvez pas récupérer de données depuis une API. Cela peut rapidement devenir limitant.",[35,9991,9992,9993,9998],{},"Mais, avec ",[39,9994,9997],{"href":9995,"rel":9996},"https:\u002F\u002Fgithub.com\u002Fserkodev\u002Fmarkdown-exit",[77],"markdown-exit",", un remplacement direct et une réécriture complète de markdown-it en utilisant TypeScript, c'est désormais possible. Les règles asynchrones sont maintenant une réalité.",[51,10000,10002],{"id":10001},"optimiser-les-images-à-la-volée","Optimiser les Images à la Volée",[35,10004,10005,10006,10010],{},"Dans ",[39,10007,10009],{"href":10008},".\u002Ferase-cls-by-automatically-optimizing-images-with-vite","l'article précédent",", nous avons exploité un plugin Vite pour appliquer automatiquement des optimisations aux images dans notre HTML. C'était un bon début utile.",[35,10012,10013],{},"Pour moi, ce n'était pas suffisant car je traite principalement des fichiers Markdown, et construire une règle personnalisée est un meilleur choix. C'est ce que nous allons voir maintenant.",[35,10015,10016,10017],{},"Tout le code source est disponible sur GitHub : ",[1599,10018,10020],{"repo":10019},"Barbapapazes\u002Fmarkdown-exit-unpic",". Pour garder cet article simple, je ne montrerai pas le code non lié comme la configuration du projet.",[35,10022,10023,10024,10027],{},"Imaginons que nous ayons un fichier ",[156,10025,10026],{},"src\u002Findex.ts",". Ce fichier est notre script principal, et le but est de rendre du Markdown brut en HTML tout en optimisant les images.",[35,10029,10030],{},"Nous voulons exécuter la commande suivante :",[192,10032,10034],{"className":1354,"code":10033,"language":1357,"meta":13,"style":13},"node src\u002Findex.ts\n",[156,10035,10036],{"__ignoreMap":13},[200,10037,10038,10041],{"class":202,"line":203},[200,10039,10040],{"class":1364},"node",[200,10042,10043],{"class":245}," src\u002Findex.ts\n",[35,10045,10046],{},"avec ce contenu Markdown :",[192,10048,10050],{"className":5903,"code":10049,"language":18,"meta":13,"style":13},"# Hello World\n\n![This is a placeholder](https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666)\n",[156,10051,10052,10060,10064],{"__ignoreMap":13},[200,10053,10054,10057],{"class":202,"line":203},[200,10055,10056],{"class":214},"# ",[200,10058,10059],{"class":1364},"Hello World\n",[200,10061,10062],{"class":202,"line":14},[200,10063,528],{"emptyLinePlaceholder":20},[200,10065,10066,10069,10072,10075,10079],{"class":202,"line":255},[200,10067,10068],{"class":214},"![",[200,10070,10071],{"class":245},"This is a placeholder",[200,10073,10074],{"class":214},"](",[200,10076,10078],{"class":10077},"sQLHv","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666",[200,10080,416],{"class":214},[35,10082,10083],{},"et avoir la sortie suivante :",[192,10085,10087],{"className":590,"code":10086,"language":592,"meta":13,"style":13},"\u003Ch1>Hello World\u003C\u002Fh1>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666?auto=format&amp;fit=crop&amp;q=80&amp;w=800\" alt=\"This is a placeholder\" loading=\"lazy\" width=\"2567\" height=\"2000\" style=\"background-size: cover; background-image: url(data:image\u002Fbmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAACQYICgcIDAoKEQ8PFBISFRISEQ4OCgYGCAYGDAoKExESGhgYHRsbHBoaFhMTDAgICAcGDgwMGBcXIB8fIyIiISAgGhgYDgoLCQcGDw0NGRgYIiEhJiUlJCIiHBoaDwsMCgcHDgwMFxYWHx4eIyIiISAgGhgYDgsLCQYGDAkJEhAQGBcXGxoaGxkZFRMTDAgICAUECQYGDAkJEA0OEhAQEhAQDwwMCQYFBwUEBwUECQYGCwcJDQoKDQsKCwgHCQUE);\">\u003C\u002Fp>\n",[156,10088,10089,10107],{"__ignoreMap":13},[200,10090,10091,10093,10096,10098,10101,10103,10105],{"class":202,"line":203},[200,10092,599],{"class":214},[200,10094,10095],{"class":235},"h1",[200,10097,674],{"class":214},[200,10099,10100],{"class":210},"Hello World",[200,10102,680],{"class":214},[200,10104,10095],{"class":235},[200,10106,647],{"class":214},[200,10108,10109,10111,10113,10116,10118,10121,10123,10125,10128,10131,10134,10136,10139,10141,10144,10146,10149,10151,10153,10155,10157,10160,10162,10164,10166,10168,10171,10173,10175,10178,10180,10183,10185,10187,10190,10192,10195,10197,10199,10202,10204,10207,10209],{"class":202,"line":14},[200,10110,599],{"class":214},[200,10112,35],{"class":235},[200,10114,10115],{"class":214},">\u003C",[200,10117,1707],{"class":235},[200,10119,10120],{"class":379}," src",[200,10122,449],{"class":214},[200,10124,610],{"class":214},[200,10126,10127],{"class":245},"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666?auto=format",[200,10129,10130],{"class":210},"&amp;",[200,10132,10133],{"class":245},"fit=crop",[200,10135,10130],{"class":210},[200,10137,10138],{"class":245},"q=80",[200,10140,10130],{"class":210},[200,10142,10143],{"class":245},"w=800",[200,10145,610],{"class":214},[200,10147,10148],{"class":379}," alt",[200,10150,449],{"class":214},[200,10152,610],{"class":214},[200,10154,10071],{"class":245},[200,10156,610],{"class":214},[200,10158,10159],{"class":379}," loading",[200,10161,449],{"class":214},[200,10163,610],{"class":214},[200,10165,9909],{"class":245},[200,10167,610],{"class":214},[200,10169,10170],{"class":379}," width",[200,10172,449],{"class":214},[200,10174,610],{"class":214},[200,10176,10177],{"class":245},"2567",[200,10179,610],{"class":214},[200,10181,10182],{"class":379}," height",[200,10184,449],{"class":214},[200,10186,610],{"class":214},[200,10188,10189],{"class":245},"2000",[200,10191,610],{"class":214},[200,10193,10194],{"class":379}," style",[200,10196,449],{"class":214},[200,10198,610],{"class":214},[200,10200,10201],{"class":245},"background-size: cover; background-image: url(data:image\u002Fbmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAACQYICgcIDAoKEQ8PFBISFRISEQ4OCgYGCAYGDAoKExESGhgYHRsbHBoaFhMTDAgICAcGDgwMGBcXIB8fIyIiISAgGhgYDgoLCQcGDw0NGRgYIiEhJiUlJCIiHBoaDwsMCgcHDgwMFxYWHx4eIyIiISAgGhgYDgsLCQYGDAkJEhAQGBcXGxoaGxkZFRMTDAgICAUECQYGDAkJEA0OEhAQEhAQDwwMCQYFBwUEBwUECQYGCwcJDQoKDQsKCwgHCQUE);",[200,10203,610],{"class":214},[200,10205,10206],{"class":214},">\u003C\u002F",[200,10208,35],{"class":235},[200,10210,647],{"class":214},[51,10212,10214],{"id":10213},"construire-une-règle-asynchrone-personnalisée","Construire une Règle Asynchrone Personnalisée",[35,10216,10217,10218,215],{},"La logique centrale de la règle ne différera pas de ce que nous avons construit dans le plugin Vite. Au lieu de lire le fichier depuis le système de fichiers local, nous le récupérerons depuis Unsplash pour simuler un bucket distant. Ensuite, nous récupérerons la largeur et la hauteur, et générerons le blurhash. À la fin, nous appliquerons également quelques paramètres de requête personnalisés pour éviter que le client ne charge l'image complète, comme vous pouvez le faire avec ",[39,10219,10222],{"href":10220,"rel":10221},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fimages\u002Ftransform-images\u002Ftransform-via-url\u002F",[77],"Cloudflare Images",[35,10224,10225],{},"D'abord, nous devons créer une nouvelle instance de markdown-exit et rendre le Markdown. C'est vraiment simple.",[192,10227,10229],{"className":1380,"code":10228,"language":1383,"meta":13,"style":13},"import { createMarkdownExit } from 'markdown-exit'\n\nconst md = createMarkdownExit()\n\nasync function render() {\n  const html = await md.renderAsync(`# Hello World\n\n![This is a placeholder](https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666)`)\n\n  console.log(html)\n}\n\nrender()\n",[156,10230,10231,10250,10254,10267,10271,10284,10309,10313,10322,10326,10340,10344,10348],{"__ignoreMap":13},[200,10232,10233,10235,10237,10240,10242,10244,10246,10248],{"class":202,"line":203},[200,10234,972],{"class":206},[200,10236,318],{"class":214},[200,10238,10239],{"class":210}," createMarkdownExit",[200,10241,980],{"class":214},[200,10243,983],{"class":206},[200,10245,242],{"class":214},[200,10247,9997],{"class":245},[200,10249,991],{"class":214},[200,10251,10252],{"class":202,"line":14},[200,10253,528],{"emptyLinePlaceholder":20},[200,10255,10256,10258,10261,10263,10265],{"class":202,"line":255},[200,10257,443],{"class":379},[200,10259,10260],{"class":210}," md ",[200,10262,449],{"class":214},[200,10264,10239],{"class":223},[200,10266,466],{"class":210},[200,10268,10269],{"class":202,"line":272},[200,10270,528],{"emptyLinePlaceholder":20},[200,10272,10273,10275,10277,10280,10282],{"class":202,"line":283},[200,10274,3726],{"class":379},[200,10276,5002],{"class":379},[200,10278,10279],{"class":223}," render",[200,10281,1549],{"class":214},[200,10283,280],{"class":214},[200,10285,10286,10288,10291,10293,10295,10297,10299,10302,10304,10306],{"class":202,"line":300},[200,10287,3738],{"class":379},[200,10289,10290],{"class":210}," html",[200,10292,509],{"class":214},[200,10294,452],{"class":206},[200,10296,9791],{"class":210},[200,10298,215],{"class":214},[200,10300,10301],{"class":223},"renderAsync",[200,10303,227],{"class":235},[200,10305,3333],{"class":214},[200,10307,10308],{"class":245},"# Hello World\n",[200,10310,10311],{"class":202,"line":310},[200,10312,528],{"emptyLinePlaceholder":20},[200,10314,10315,10318,10320],{"class":202,"line":336},[200,10316,10317],{"class":245},"![This is a placeholder](https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666)",[200,10319,3333],{"class":214},[200,10321,416],{"class":235},[200,10323,10324],{"class":202,"line":342},[200,10325,528],{"emptyLinePlaceholder":20},[200,10327,10328,10330,10332,10334,10336,10338],{"class":202,"line":365},[200,10329,1112],{"class":210},[200,10331,215],{"class":214},[200,10333,1117],{"class":223},[200,10335,227],{"class":235},[200,10337,592],{"class":210},[200,10339,416],{"class":235},[200,10341,10342],{"class":202,"line":371},[200,10343,1129],{"class":214},[200,10345,10346],{"class":202,"line":398},[200,10347,528],{"emptyLinePlaceholder":20},[200,10349,10350,10353],{"class":202,"line":405},[200,10351,10352],{"class":223},"render",[200,10354,466],{"class":210},[35,10356,10357],{},"Rien d'extraordinaire dans ce script. Cependant, la sortie est la suivante :",[192,10359,10361],{"className":590,"code":10360,"language":592,"meta":13,"style":13},"\u003Ch1>Hello World\u003C\u002Fh1>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666\" alt=\"This is a placeholder\">\u003C\u002Fp>\n",[156,10362,10363,10379],{"__ignoreMap":13},[200,10364,10365,10367,10369,10371,10373,10375,10377],{"class":202,"line":203},[200,10366,599],{"class":214},[200,10368,10095],{"class":235},[200,10370,674],{"class":214},[200,10372,10100],{"class":210},[200,10374,680],{"class":214},[200,10376,10095],{"class":235},[200,10378,647],{"class":214},[200,10380,10381,10383,10385,10387,10389,10391,10393,10395,10397,10399,10401,10403,10405,10407,10409,10411,10413],{"class":202,"line":14},[200,10382,599],{"class":214},[200,10384,35],{"class":235},[200,10386,10115],{"class":214},[200,10388,1707],{"class":235},[200,10390,10120],{"class":379},[200,10392,449],{"class":214},[200,10394,610],{"class":214},[200,10396,10078],{"class":245},[200,10398,610],{"class":214},[200,10400,10148],{"class":379},[200,10402,449],{"class":214},[200,10404,610],{"class":214},[200,10406,10071],{"class":245},[200,10408,610],{"class":214},[200,10410,10206],{"class":214},[200,10412,35],{"class":235},[200,10414,647],{"class":214},[35,10416,10417],{},"Il manque quelques parties, mais c'est un bon début.",[35,10419,10420,10421,10423],{},"Pour changer le comportement de rendu de l'image, nous allons nous accrocher à la règle ",[156,10422,9752],{},", ajuster le token à nos besoins, et utiliser l'implémentation par défaut pour générer la sortie.",[35,10425,10426,10427,10429,10430,215],{},"Pour créer une nouvelle règle, nous pouvons utiliser la fonction ",[156,10428,9767],{},". C'est comme créer un plugin Vue. Ensuite, liez la fonction que nous voulons à la règle ",[156,10431,9752],{},[192,10433,10435],{"className":1380,"code":10434,"language":1383,"meta":13,"style":13},"import type { MarkdownExit } from 'markdown-exit'\nimport { createMarkdownExit } from 'markdown-exit'\n\nconst md = createMarkdownExit()\n\nmd.use((md: MarkdownExit) => {\n  const imageRule = md.renderer.rules.image!\n  md.renderer.rules.image = async (tokens, idx, options, env, self) => {\n    return imageRule(tokens, idx, options, env, self)\n  }\n})\n\nasync function render() {\n  \u002F\u002F ...\n}\n\nrender()\n",[156,10436,10437,10458,10476,10480,10492,10496,10520,10544,10590,10618,10622,10628,10632,10644,10649,10653,10657],{"__ignoreMap":13},[200,10438,10439,10441,10443,10445,10448,10450,10452,10454,10456],{"class":202,"line":203},[200,10440,972],{"class":206},[200,10442,321],{"class":206},[200,10444,318],{"class":214},[200,10446,10447],{"class":210}," MarkdownExit",[200,10449,980],{"class":214},[200,10451,983],{"class":206},[200,10453,242],{"class":214},[200,10455,9997],{"class":245},[200,10457,991],{"class":214},[200,10459,10460,10462,10464,10466,10468,10470,10472,10474],{"class":202,"line":14},[200,10461,972],{"class":206},[200,10463,318],{"class":214},[200,10465,10239],{"class":210},[200,10467,980],{"class":214},[200,10469,983],{"class":206},[200,10471,242],{"class":214},[200,10473,9997],{"class":245},[200,10475,991],{"class":214},[200,10477,10478],{"class":202,"line":255},[200,10479,528],{"emptyLinePlaceholder":20},[200,10481,10482,10484,10486,10488,10490],{"class":202,"line":272},[200,10483,443],{"class":379},[200,10485,10260],{"class":210},[200,10487,449],{"class":214},[200,10489,10239],{"class":223},[200,10491,466],{"class":210},[200,10493,10494],{"class":202,"line":283},[200,10495,528],{"emptyLinePlaceholder":20},[200,10497,10498,10500,10502,10504,10506,10508,10510,10512,10514,10516,10518],{"class":202,"line":300},[200,10499,18],{"class":210},[200,10501,215],{"class":214},[200,10503,9767],{"class":223},[200,10505,227],{"class":210},[200,10507,227],{"class":214},[200,10509,18],{"class":386},[200,10511,239],{"class":214},[200,10513,10447],{"class":1364},[200,10515,2104],{"class":214},[200,10517,393],{"class":379},[200,10519,280],{"class":214},[200,10521,10522,10524,10526,10528,10530,10532,10534,10536,10538,10540,10542],{"class":202,"line":310},[200,10523,3738],{"class":379},[200,10525,9786],{"class":210},[200,10527,509],{"class":214},[200,10529,9791],{"class":210},[200,10531,215],{"class":214},[200,10533,9796],{"class":210},[200,10535,215],{"class":214},[200,10537,9801],{"class":210},[200,10539,215],{"class":214},[200,10541,9752],{"class":210},[200,10543,9808],{"class":214},[200,10545,10546,10548,10550,10552,10554,10556,10558,10560,10562,10564,10566,10568,10570,10572,10574,10576,10578,10580,10582,10584,10586,10588],{"class":202,"line":336},[200,10547,9813],{"class":210},[200,10549,215],{"class":214},[200,10551,9796],{"class":210},[200,10553,215],{"class":214},[200,10555,9801],{"class":210},[200,10557,215],{"class":214},[200,10559,9752],{"class":223},[200,10561,509],{"class":214},[200,10563,380],{"class":379},[200,10565,1094],{"class":214},[200,10567,9832],{"class":386},[200,10569,549],{"class":214},[200,10571,9837],{"class":386},[200,10573,549],{"class":214},[200,10575,9842],{"class":386},[200,10577,549],{"class":214},[200,10579,4892],{"class":386},[200,10581,549],{"class":214},[200,10583,9851],{"class":386},[200,10585,2104],{"class":214},[200,10587,393],{"class":379},[200,10589,280],{"class":214},[200,10591,10592,10594,10596,10598,10600,10602,10604,10606,10608,10610,10612,10614,10616],{"class":202,"line":342},[200,10593,3311],{"class":206},[200,10595,9786],{"class":223},[200,10597,227],{"class":235},[200,10599,9832],{"class":210},[200,10601,549],{"class":214},[200,10603,9837],{"class":210},[200,10605,549],{"class":214},[200,10607,9842],{"class":210},[200,10609,549],{"class":214},[200,10611,4892],{"class":210},[200,10613,549],{"class":214},[200,10615,9851],{"class":210},[200,10617,416],{"class":235},[200,10619,10620],{"class":202,"line":365},[200,10621,4505],{"class":214},[200,10623,10624,10626],{"class":202,"line":371},[200,10625,413],{"class":214},[200,10627,416],{"class":210},[200,10629,10630],{"class":202,"line":398},[200,10631,528],{"emptyLinePlaceholder":20},[200,10633,10634,10636,10638,10640,10642],{"class":202,"line":405},[200,10635,3726],{"class":379},[200,10637,5002],{"class":379},[200,10639,10279],{"class":223},[200,10641,1549],{"class":214},[200,10643,280],{"class":214},[200,10645,10646],{"class":202,"line":410},[200,10647,10648],{"class":401},"  \u002F\u002F ...\n",[200,10650,10651],{"class":202,"line":860},[200,10652,1129],{"class":214},[200,10654,10655],{"class":202,"line":889},[200,10656,528],{"emptyLinePlaceholder":20},[200,10658,10659,10661],{"class":202,"line":899},[200,10660,10352],{"class":223},[200,10662,466],{"class":210},[35,10664,10665,10666,10668],{},"Notre fonction ne fait rien à part appeler la règle ",[156,10667,9752],{}," originale, ce qui est déjà une bonne étape car nous ne voulons pas la réimplémenter nous-mêmes.",[35,10670,10671],{},"Ensuite, nous pouvons saisir le token actuel et essayer de lui ajouter un attribut.",[35,10673,10674,10675,10677,10678,10680],{},"La variable ",[156,10676,9832],{}," contient tous les tokens disponibles. C'est un tableau de tokens. Pour accéder à l'actuel, nous devons utiliser la variable ",[156,10679,9875],{},", qui est l'index actuel du token.",[192,10682,10684],{"className":1380,"code":10683,"language":1383,"meta":13,"style":13},"const token = tokens[idx]\n",[156,10685,10686],{"__ignoreMap":13},[200,10687,10688,10690,10693,10695],{"class":202,"line":203},[200,10689,443],{"class":379},[200,10691,10692],{"class":210}," token ",[200,10694,449],{"class":214},[200,10696,10697],{"class":210}," tokens[idx]\n",[35,10699,10700,10701,10703,10704,215],{},"Maintenant, nous pouvons modifier le token. Par exemple, nous pouvons ajouter un attribut ",[156,10702,9900],{}," avec la valeur ",[156,10705,9909],{},[192,10707,10709],{"className":1380,"code":10708,"language":1383,"meta":13,"style":13},"md.renderer.rules.image = async (tokens, idx, options, env, self) => {\n  const token = tokens[idx]\n\n  token.attrSet('loading', 'lazy')\n\n  return imageRule(tokens, idx, options, env, self)\n}\n",[156,10710,10711,10757,10773,10777,10804,10808,10836],{"__ignoreMap":13},[200,10712,10713,10715,10717,10719,10721,10723,10725,10727,10729,10731,10733,10735,10737,10739,10741,10743,10745,10747,10749,10751,10753,10755],{"class":202,"line":203},[200,10714,18],{"class":210},[200,10716,215],{"class":214},[200,10718,9796],{"class":210},[200,10720,215],{"class":214},[200,10722,9801],{"class":210},[200,10724,215],{"class":214},[200,10726,9752],{"class":223},[200,10728,509],{"class":214},[200,10730,380],{"class":379},[200,10732,1094],{"class":214},[200,10734,9832],{"class":386},[200,10736,549],{"class":214},[200,10738,9837],{"class":386},[200,10740,549],{"class":214},[200,10742,9842],{"class":386},[200,10744,549],{"class":214},[200,10746,4892],{"class":386},[200,10748,549],{"class":214},[200,10750,9851],{"class":386},[200,10752,2104],{"class":214},[200,10754,393],{"class":379},[200,10756,280],{"class":214},[200,10758,10759,10761,10763,10765,10767,10769,10771],{"class":202,"line":14},[200,10760,3738],{"class":379},[200,10762,9864],{"class":210},[200,10764,509],{"class":214},[200,10766,9869],{"class":210},[200,10768,9872],{"class":235},[200,10770,9875],{"class":210},[200,10772,4511],{"class":235},[200,10774,10775],{"class":202,"line":255},[200,10776,528],{"emptyLinePlaceholder":20},[200,10778,10779,10782,10784,10786,10788,10790,10792,10794,10796,10798,10800,10802],{"class":202,"line":272},[200,10780,10781],{"class":210},"  token",[200,10783,215],{"class":214},[200,10785,9893],{"class":223},[200,10787,227],{"class":235},[200,10789,249],{"class":214},[200,10791,9900],{"class":245},[200,10793,249],{"class":214},[200,10795,549],{"class":214},[200,10797,242],{"class":214},[200,10799,9909],{"class":245},[200,10801,249],{"class":214},[200,10803,416],{"class":235},[200,10805,10806],{"class":202,"line":283},[200,10807,528],{"emptyLinePlaceholder":20},[200,10809,10810,10812,10814,10816,10818,10820,10822,10824,10826,10828,10830,10832,10834],{"class":202,"line":300},[200,10811,3758],{"class":206},[200,10813,9786],{"class":223},[200,10815,227],{"class":235},[200,10817,9832],{"class":210},[200,10819,549],{"class":214},[200,10821,9837],{"class":210},[200,10823,549],{"class":214},[200,10825,9842],{"class":210},[200,10827,549],{"class":214},[200,10829,4892],{"class":210},[200,10831,549],{"class":214},[200,10833,9851],{"class":210},[200,10835,416],{"class":235},[200,10837,10838],{"class":202,"line":310},[200,10839,1129],{"class":214},[35,10841,10842],{},"La sortie de notre script est maintenant la suivante :",[192,10844,10846],{"className":590,"code":10845,"language":592,"meta":13,"style":13},"\u003Ch1>Hello World\u003C\u002Fh1>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666\" alt=\"This is a placeholder\" loading=\"lazy\">\u003C\u002Fp>\n",[156,10847,10848,10864],{"__ignoreMap":13},[200,10849,10850,10852,10854,10856,10858,10860,10862],{"class":202,"line":203},[200,10851,599],{"class":214},[200,10853,10095],{"class":235},[200,10855,674],{"class":214},[200,10857,10100],{"class":210},[200,10859,680],{"class":214},[200,10861,10095],{"class":235},[200,10863,647],{"class":214},[200,10865,10866,10868,10870,10872,10874,10876,10878,10880,10882,10884,10886,10888,10890,10892,10894,10896,10898,10900,10902,10904,10906,10908],{"class":202,"line":14},[200,10867,599],{"class":214},[200,10869,35],{"class":235},[200,10871,10115],{"class":214},[200,10873,1707],{"class":235},[200,10875,10120],{"class":379},[200,10877,449],{"class":214},[200,10879,610],{"class":214},[200,10881,10078],{"class":245},[200,10883,610],{"class":214},[200,10885,10148],{"class":379},[200,10887,449],{"class":214},[200,10889,610],{"class":214},[200,10891,10071],{"class":245},[200,10893,610],{"class":214},[200,10895,10159],{"class":379},[200,10897,449],{"class":214},[200,10899,610],{"class":214},[200,10901,9909],{"class":245},[200,10903,610],{"class":214},[200,10905,10206],{"class":214},[200,10907,35],{"class":235},[200,10909,647],{"class":214},[35,10911,10912],{},"Facile, non ?",[35,10914,10915,10916],{},"Basé sur notre comportement attendu, nous devons ajouter la largeur, la hauteur, le blurhash, et quelques paramètres de requête à l'image. Pour ce faire, nous utiliserons la même logique que dans ",[3045,10917,10918],{"id":9285},", sauf que nous récupérerons l'image depuis Unsplash, comme vous pourriez le faire avec n'importe quel bucket distant.",[192,10920,10922],{"className":1380,"code":10921,"language":1383,"meta":13,"style":13},"const img = await fetch(src).then(res => res.bytes())\nconst data = await getPixels(img)\n\nconst blurhash = encode(Uint8ClampedArray.from(data.data), data.width, data.height, 4, 4)\n",[156,10923,10924,10963,10980,10984],{"__ignoreMap":13},[200,10925,10926,10928,10931,10933,10935,10937,10940,10942,10945,10947,10950,10952,10955,10957,10960],{"class":202,"line":203},[200,10927,443],{"class":379},[200,10929,10930],{"class":210}," img ",[200,10932,449],{"class":214},[200,10934,452],{"class":206},[200,10936,3314],{"class":223},[200,10938,10939],{"class":210},"(src)",[200,10941,215],{"class":214},[200,10943,10944],{"class":223},"then",[200,10946,227],{"class":210},[200,10948,10949],{"class":386},"res",[200,10951,393],{"class":379},[200,10953,10954],{"class":210}," res",[200,10956,215],{"class":214},[200,10958,10959],{"class":223},"bytes",[200,10961,10962],{"class":210},"())\n",[200,10964,10965,10967,10970,10972,10974,10977],{"class":202,"line":14},[200,10966,443],{"class":379},[200,10968,10969],{"class":210}," data ",[200,10971,449],{"class":214},[200,10973,452],{"class":206},[200,10975,10976],{"class":223}," getPixels",[200,10978,10979],{"class":210},"(img)\n",[200,10981,10982],{"class":202,"line":255},[200,10983,528],{"emptyLinePlaceholder":20},[200,10985,10986,10988,10991,10993,10996,10999,11001,11003,11006,11008,11011,11013,11015,11017,11020,11022,11024,11026,11029,11031,11034,11036,11038],{"class":202,"line":272},[200,10987,443],{"class":379},[200,10989,10990],{"class":210}," blurhash ",[200,10992,449],{"class":214},[200,10994,10995],{"class":223}," encode",[200,10997,10998],{"class":210},"(Uint8ClampedArray",[200,11000,215],{"class":214},[200,11002,1489],{"class":223},[200,11004,11005],{"class":210},"(data",[200,11007,215],{"class":214},[200,11009,11010],{"class":210},"data)",[200,11012,549],{"class":214},[200,11014,5056],{"class":210},[200,11016,215],{"class":214},[200,11018,11019],{"class":210},"width",[200,11021,549],{"class":214},[200,11023,5056],{"class":210},[200,11025,215],{"class":214},[200,11027,11028],{"class":210},"height",[200,11030,549],{"class":214},[200,11032,11033],{"class":2060}," 4",[200,11035,549],{"class":214},[200,11037,11033],{"class":2060},[200,11039,416],{"class":210},[35,11041,11042],{},"Maintenant, nous pouvons définir les attributs width, height et style sur le token.",[192,11044,11046],{"className":1380,"code":11045,"language":1383,"meta":13,"style":13},"import { blurhashToDataUri } from '@unpic\u002Fplaceholder'\n\ntoken.attrSet('width', data.width.toString())\ntoken.attrSet('height', data.height.toString())\ntoken.attrSet('style', `background-size: cover; background-image: url(${blurhashToDataUri(blurhash)});`)\n",[156,11047,11048,11068,11072,11104,11134],{"__ignoreMap":13},[200,11049,11050,11052,11054,11057,11059,11061,11063,11066],{"class":202,"line":203},[200,11051,972],{"class":206},[200,11053,318],{"class":214},[200,11055,11056],{"class":210}," blurhashToDataUri",[200,11058,980],{"class":214},[200,11060,983],{"class":206},[200,11062,242],{"class":214},[200,11064,11065],{"class":245},"@unpic\u002Fplaceholder",[200,11067,991],{"class":214},[200,11069,11070],{"class":202,"line":14},[200,11071,528],{"emptyLinePlaceholder":20},[200,11073,11074,11077,11079,11081,11083,11085,11087,11089,11091,11093,11095,11097,11099,11102],{"class":202,"line":255},[200,11075,11076],{"class":210},"token",[200,11078,215],{"class":214},[200,11080,9893],{"class":223},[200,11082,227],{"class":210},[200,11084,249],{"class":214},[200,11086,11019],{"class":245},[200,11088,249],{"class":214},[200,11090,549],{"class":214},[200,11092,5056],{"class":210},[200,11094,215],{"class":214},[200,11096,11019],{"class":210},[200,11098,215],{"class":214},[200,11100,11101],{"class":223},"toString",[200,11103,10962],{"class":210},[200,11105,11106,11108,11110,11112,11114,11116,11118,11120,11122,11124,11126,11128,11130,11132],{"class":202,"line":272},[200,11107,11076],{"class":210},[200,11109,215],{"class":214},[200,11111,9893],{"class":223},[200,11113,227],{"class":210},[200,11115,249],{"class":214},[200,11117,11028],{"class":245},[200,11119,249],{"class":214},[200,11121,549],{"class":214},[200,11123,5056],{"class":210},[200,11125,215],{"class":214},[200,11127,11028],{"class":210},[200,11129,215],{"class":214},[200,11131,11101],{"class":223},[200,11133,10962],{"class":210},[200,11135,11136,11138,11140,11142,11144,11146,11148,11150,11152,11155,11158,11160,11163,11166,11168,11171,11173],{"class":202,"line":283},[200,11137,11076],{"class":210},[200,11139,215],{"class":214},[200,11141,9893],{"class":223},[200,11143,227],{"class":210},[200,11145,249],{"class":214},[200,11147,1170],{"class":245},[200,11149,249],{"class":214},[200,11151,549],{"class":214},[200,11153,11154],{"class":214}," `",[200,11156,11157],{"class":245},"background-size: cover; background-image: url(",[200,11159,4868],{"class":214},[200,11161,11162],{"class":223},"blurhashToDataUri",[200,11164,11165],{"class":210},"(blurhash)",[200,11167,413],{"class":214},[200,11169,11170],{"class":245},");",[200,11172,3333],{"class":214},[200,11174,416],{"class":210},[35,11176,11177,11178,11181],{},"Enfin, nous pouvons ajuster l'attribut ",[156,11179,11180],{},"src"," pour ajouter quelques paramètres de requête.",[192,11183,11185],{"className":1380,"code":11184,"language":1383,"meta":13,"style":13},"token.attrSet('src', `${src}?auto=format&fit=crop&q=80&w=800`)\n",[156,11186,11187],{"__ignoreMap":13},[200,11188,11189,11191,11193,11195,11197,11199,11201,11203,11205,11208,11210,11212,11215,11217],{"class":202,"line":203},[200,11190,11076],{"class":210},[200,11192,215],{"class":214},[200,11194,9893],{"class":223},[200,11196,227],{"class":210},[200,11198,249],{"class":214},[200,11200,11180],{"class":245},[200,11202,249],{"class":214},[200,11204,549],{"class":214},[200,11206,11207],{"class":214}," `${",[200,11209,11180],{"class":210},[200,11211,413],{"class":214},[200,11213,11214],{"class":245},"?auto=format&fit=crop&q=80&w=800",[200,11216,3333],{"class":214},[200,11218,416],{"class":210},[35,11220,11221,11222,11225],{},"Ces requêtes sont spécifiques à Unsplash, mais vous pourriez faire la même chose avec ",[39,11223,10222],{"href":10220,"rel":11224},[77]," ou n'importe quel service d'optimisation d'image.",[35,11227,11228,11229,11232],{},"Une petite note sur cette approche : elle fonctionne bien. Le parallélisme améliore légèrement les performances car la plupart des calculs se produisent dans la fonction ",[156,11230,11231],{},"encode"," qui transforme l'image en blurhash de manière synchrone. Sur mon ordinateur, récupérer l'image prend environ 100ms, tandis que l'encoder prend environ 3 secondes.",[51,11234,2461],{"id":2460},[35,11236,11237],{},"L'implémentation actuelle est simple, et c'est intentionnel. Maintenant que vous savez comment optimiser vos images et créer des règles personnalisées, vous pouvez facilement adapter cette approche à vos besoins, et même en créer de nouvelles qui correspondent à vos cas d'utilisation spécifiques.",[35,11239,11240],{},"Pour gérer le goulot d'étranglement des performances, vous avez de nombreuses possibilités. Trois d'entre elles sont :",[68,11242,11243,11249,11258],{},[71,11244,11245,11248],{},[6862,11246,11247],{},"Pré-traitement local et stockage local"," : Si vous stockez des images dans votre code source, vous pouvez créer un script qui parcourra toutes les images et générera un fichier JSON contenant la largeur, la hauteur et le blurhash. Ensuite, dans la règle, vous n'aurez qu'à lire ce fichier, et plus aucun calcul n'est nécessaire. Cela réduira également le temps de build.",[71,11250,11251,11254,11255],{},[6862,11252,11253],{},"Pré-traitement local et stockage distant"," : Si vos images sont stockées à distance, vous pouvez créer un script similaire qui récupérera les images, les traitera et générera le fichier JSON. Ce fichier peut être poussé vers un bucket pour une utilisation ultérieure. Dans la règle, au lieu de lire un fichier, vous pouvez le récupérer depuis le bucket. ",[1216,11256,11257],{},"Sans règles asynchrones, ce n'était pas possible !",[71,11259,11260,11263,11264],{},[6862,11261,11262],{},"Pré-traitement distant et stockage distant"," : Si vos images sont stockées à distance, vous pouvez créer un workflow qui sera déclenché chaque fois qu'un nouveau fichier est ajouté. Le workflow génère le fichier JSON et le stocke dans le même bucket. Dans la règle, vous pouvez récupérer le fichier JSON depuis le bucket et l'utiliser directement. ",[1216,11265,11266,11267,11272,11273,11278],{},"L'optimisation maximale. Avec les ",[39,11268,11271],{"href":11269,"rel":11270},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002Fbuckets\u002Fevent-notifications\u002F",[77],"notifications R2"," et les ",[39,11274,11277],{"href":11275,"rel":11276},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fqueues\u002Fevent-subscriptions\u002Fevents-schemas\u002F#r2",[77],"Queues",", c'est simple à implémenter.",[35,11280,11281],{},[1216,11282,11283],{},"Vous pourriez aussi créer un pool de workers en utilisant les threads Node, mais c'est excessif pour ce cas d'utilisation.",[35,11285,11286],{},"Je suis sûr qu'il existe de nombreuses autres façons d'automatiser l'optimisation des images. Maintenant, c'est à vous de trouver la meilleure approche qui correspond à votre flux de travail.",[35,11288,11289],{},"J'espère que vous avez apprécié l'article ! Si vous implémentez quelque chose de similaire, tenez-moi au courant !",[1170,11291,11292],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sQLHv, html code.shiki .sQLHv{--shiki-light:#90A4AE;--shiki-light-text-decoration:underline;--shiki-default:#EEFFFF;--shiki-default-text-decoration:underline;--shiki-dark:#BABED8;--shiki-dark-text-decoration:underline}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}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":13,"searchDepth":14,"depth":14,"links":11294},[11295,11296,11297,11298,11299],{"id":9705,"depth":14,"text":9706},{"id":9969,"depth":14,"text":9970},{"id":10001,"depth":14,"text":10002},{"id":10213,"depth":14,"text":10214},{"id":2460,"depth":14,"text":2461},"2025-11-19","Auparavant, markdown-it nécessitait un rendu et des règles synchrones. markdown-exit prend en charge le rendu et les règles asynchrones.",{},"\u002Ffr\u002Fposts\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering","---\ncontentId: ca2372b3-15b3-4d66-9877-a4dd696580de\ntitle: Markdown Exit brise les règles avec le rendu asynchrone\ndescription: Auparavant, markdown-it nécessitait un rendu et des règles synchrones. markdown-exit prend en charge le rendu et les règles asynchrones.\ndate: 2025-11-19\n---\n\nCet article est la suite de \u003CLinkToArticle id=\"a505274a-14a6-471e-95a2-a3dbc64cd170\" \u002F> et se concentre sur un cas d'utilisation plus concret : l'optimisation automatique des images dans les fichiers Markdown.\n\nC'est très technique, et à moins que vous ne traitiez des fichiers Markdown, cela pourrait être un peu abstrait. Néanmoins, je vous encourage vivement à lire l'article précédent et celui-ci, car cela pourrait vous donner de bonnes idées.\n\n---\n\nPourquoi écrire un article dédié à ce sujet ? Parce que, jusqu'à présent, ce n'était pas possible. Le principal parseur Markdown dans le monde JavaScript est [markdown-it](https:\u002F\u002Fgithub.com\u002Fmarkdown-it\u002Fmarkdown-it), et il est entièrement synchrone.\n\nCela pourrait ne pas être un problème, mais cela signifie que [toutes les règles doivent être synchrones](https:\u002F\u002Fgithub.com\u002Fmarkdown-it\u002Fmarkdown-it\u002Fblob\u002Fmaster\u002Fdocs\u002Fdevelopment.md#i-need-async-rule-how-to-do-it) également. Vous ne pouvez pas avoir de règles asynchrones. Vous ne pouvez pas effectuer de fetch dans une règle, et vous ne pouvez rien paralléliser.\n\nUn petit récapitulatif est nécessaire pour s'assurer que nous sommes tous sur la même longueur d'onde.\n\n## Écrire un Parseur Markdown\n\nJe n'entrerai pas dans les détails, mais essentiellement, le parseur va diviser le texte Markdown en tokens puis appliquer des règles, qui sont essentiellement des fonctions, à ces tokens pour générer le HTML.\n\nPar exemple, le texte suivant `**bold text**` sera tokenisé en : `['strong_open', 'text', 'strong_close']` et les règles correspondantes généreront le HTML : `\u003Cstrong>bold text\u003C\u002Fstrong>`. Le token `strong_open` sera traité par la règle `strong_open`, qui retourne `\u003Cstrong>`, et ainsi de suite.\n\n\u003CCalloutReadMore\n  text=\"Explorez l'implémentation de la règle d'emphase dans markdown-exit\"\n  href=\"https:\u002F\u002Fgithub.com\u002Fserkodev\u002Fmarkdown-exit\u002Fblob\u002F3e0cfeb7926355f6896fcd4dd7afd797e7f6689f\u002Fpackages\u002Fmarkdown-exit\u002Fsrc\u002Fparser\u002Finline\u002Frules\u002Femphasis.ts\"\n\u002F>\n\nLa règle dont nous parlons depuis le début transforme un token en sa représentation HTML correspondante. La partie intéressante est que vous pouvez attacher vos propres règles au parseur pour étendre ses fonctionnalités.\n\nImaginez que vous vouliez appliquer automatiquement l'attribut `loading=\"lazy\"` à toutes les images de vos fichiers Markdown. Vous pourriez écrire une règle qui cible la règle `image`.\n\n```ts\nmd.use((md) => {\n  const imageRule = md.renderer.rules.image!\n  md.renderer.rules.image = (tokens, idx, options, env, self) => {\n    const token = tokens[idx]\n\n    token.attrSet('loading', 'lazy') \u002F\u002F [!code highlight]\n\n    return imageRule(tokens, idx, options, env, self)\n  }\n})\n```\n\nMaintenant, le Markdown suivant `![alt text](image.jpg)` sera rendu comme `\u003Cimg src=\"image.jpg\" alt=\"alt text\" loading=\"lazy\" \u002F>`. C'est un rêve : appliquer automatiquement des changements aux fichiers sans les toucher.\n\n## Limitations Synchrones\n\nC'est un rêve jusqu'à ce que vous réalisiez que tout cela se passe de manière synchrone. Les règles sont appliquées une par une, sans aucun moyen de paralléliser le processus. Pour la règle de surlignage du code, cela peut réduire considérablement les performances car vous ne pouvez pas rendre tous les blocs de code en même temps, c'est un par un.\n\n> [!NOTE]\n> Anthony Fu en était bien conscient et il a créé [markdown-it-async](https:\u002F\u002Fgithub.com\u002Fantfu\u002Fmarkdown-it-async) pour rendre cela possible.\n\nEn plus de paralléliser le processus, les règles synchrones signifient que vous ne pouvez effectuer aucune opération asynchrone au sein d'une règle. Par exemple, vous ne pouvez pas récupérer de données depuis une API. Cela peut rapidement devenir limitant.\n\nMais, avec [markdown-exit](https:\u002F\u002Fgithub.com\u002Fserkodev\u002Fmarkdown-exit), un remplacement direct et une réécriture complète de markdown-it en utilisant TypeScript, c'est désormais possible. Les règles asynchrones sont maintenant une réalité.\n\n## Optimiser les Images à la Volée\n\nDans [l'article précédent](.\u002Ferase-cls-by-automatically-optimizing-images-with-vite.md), nous avons exploité un plugin Vite pour appliquer automatiquement des optimisations aux images dans notre HTML. C'était un bon début utile.\n\nPour moi, ce n'était pas suffisant car je traite principalement des fichiers Markdown, et construire une règle personnalisée est un meilleur choix. C'est ce que nous allons voir maintenant.\n\nTout le code source est disponible sur GitHub : \u003CGitHubLink repo=\"Barbapapazes\u002Fmarkdown-exit-unpic\" \u002F>. Pour garder cet article simple, je ne montrerai pas le code non lié comme la configuration du projet.\n\nImaginons que nous ayons un fichier `src\u002Findex.ts`. Ce fichier est notre script principal, et le but est de rendre du Markdown brut en HTML tout en optimisant les images.\n\nNous voulons exécuter la commande suivante :\n\n```sh\nnode src\u002Findex.ts\n```\n\navec ce contenu Markdown :\n\n```md\n# Hello World\n\n![This is a placeholder](https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666)\n```\n\net avoir la sortie suivante :\n\n```html\n\u003Ch1>Hello World\u003C\u002Fh1>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666?auto=format&amp;fit=crop&amp;q=80&amp;w=800\" alt=\"This is a placeholder\" loading=\"lazy\" width=\"2567\" height=\"2000\" style=\"background-size: cover; background-image: url(data:image\u002Fbmp;base64,Qk32BAAAAAAAADYAAAAoAAAACAAAAAgAAAABABgAAAAAAMAAAAATCwAAEwsAAAAAAAAAAAAACQYICgcIDAoKEQ8PFBISFRISEQ4OCgYGCAYGDAoKExESGhgYHRsbHBoaFhMTDAgICAcGDgwMGBcXIB8fIyIiISAgGhgYDgoLCQcGDw0NGRgYIiEhJiUlJCIiHBoaDwsMCgcHDgwMFxYWHx4eIyIiISAgGhgYDgsLCQYGDAkJEhAQGBcXGxoaGxkZFRMTDAgICAUECQYGDAkJEA0OEhAQEhAQDwwMCQYFBwUEBwUECQYGCwcJDQoKDQsKCwgHCQUE);\">\u003C\u002Fp>\n```\n\n## Construire une Règle Asynchrone Personnalisée\n\nLa logique centrale de la règle ne différera pas de ce que nous avons construit dans le plugin Vite. Au lieu de lire le fichier depuis le système de fichiers local, nous le récupérerons depuis Unsplash pour simuler un bucket distant. Ensuite, nous récupérerons la largeur et la hauteur, et générerons le blurhash. À la fin, nous appliquerons également quelques paramètres de requête personnalisés pour éviter que le client ne charge l'image complète, comme vous pouvez le faire avec [Cloudflare Images](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fimages\u002Ftransform-images\u002Ftransform-via-url\u002F).\n\nD'abord, nous devons créer une nouvelle instance de markdown-exit et rendre le Markdown. C'est vraiment simple.\n\n```ts\nimport { createMarkdownExit } from 'markdown-exit'\n\nconst md = createMarkdownExit()\n\nasync function render() {\n  const html = await md.renderAsync(`# Hello World\n\n![This is a placeholder](https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666)`)\n\n  console.log(html)\n}\n\nrender()\n```\n\nRien d'extraordinaire dans ce script. Cependant, la sortie est la suivante :\n\n```html\n\u003Ch1>Hello World\u003C\u002Fh1>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666\" alt=\"This is a placeholder\">\u003C\u002Fp>\n```\n\nIl manque quelques parties, mais c'est un bon début.\n\nPour changer le comportement de rendu de l'image, nous allons nous accrocher à la règle `image`, ajuster le token à nos besoins, et utiliser l'implémentation par défaut pour générer la sortie.\n\nPour créer une nouvelle règle, nous pouvons utiliser la fonction `use`. C'est comme créer un plugin Vue. Ensuite, liez la fonction que nous voulons à la règle `image`.\n\n```ts\nimport type { MarkdownExit } from 'markdown-exit'\nimport { createMarkdownExit } from 'markdown-exit'\n\nconst md = createMarkdownExit()\n\nmd.use((md: MarkdownExit) => {\n  const imageRule = md.renderer.rules.image!\n  md.renderer.rules.image = async (tokens, idx, options, env, self) => {\n    return imageRule(tokens, idx, options, env, self)\n  }\n})\n\nasync function render() {\n  \u002F\u002F ...\n}\n\nrender()\n```\n\nNotre fonction ne fait rien à part appeler la règle `image` originale, ce qui est déjà une bonne étape car nous ne voulons pas la réimplémenter nous-mêmes.\n\nEnsuite, nous pouvons saisir le token actuel et essayer de lui ajouter un attribut.\n\nLa variable `tokens` contient tous les tokens disponibles. C'est un tableau de tokens. Pour accéder à l'actuel, nous devons utiliser la variable `idx`, qui est l'index actuel du token.\n\n```ts\nconst token = tokens[idx]\n```\n\nMaintenant, nous pouvons modifier le token. Par exemple, nous pouvons ajouter un attribut `loading` avec la valeur `lazy`.\n\n```ts\nmd.renderer.rules.image = async (tokens, idx, options, env, self) => {\n  const token = tokens[idx]\n\n  token.attrSet('loading', 'lazy')\n\n  return imageRule(tokens, idx, options, env, self)\n}\n```\n\nLa sortie de notre script est maintenant la suivante :\n\n```html\n\u003Ch1>Hello World\u003C\u002Fh1>\n\u003Cp>\u003Cimg src=\"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1762707826575-d48c35b9b666\" alt=\"This is a placeholder\" loading=\"lazy\">\u003C\u002Fp>\n```\n\nFacile, non ?\n\nBasé sur notre comportement attendu, nous devons ajouter la largeur, la hauteur, le blurhash, et quelques paramètres de requête à l'image. Pour ce faire, nous utiliserons la même logique que dans \u003CLinkToArticle id=\"a505274a-14a6-471e-95a2-a3dbc64cd170\" \u002F>, sauf que nous récupérerons l'image depuis Unsplash, comme vous pourriez le faire avec n'importe quel bucket distant.\n\n```ts\nconst img = await fetch(src).then(res => res.bytes())\nconst data = await getPixels(img)\n\nconst blurhash = encode(Uint8ClampedArray.from(data.data), data.width, data.height, 4, 4)\n```\n\nMaintenant, nous pouvons définir les attributs width, height et style sur le token.\n\n```ts\nimport { blurhashToDataUri } from '@unpic\u002Fplaceholder'\n\ntoken.attrSet('width', data.width.toString())\ntoken.attrSet('height', data.height.toString())\ntoken.attrSet('style', `background-size: cover; background-image: url(${blurhashToDataUri(blurhash)});`)\n```\n\nEnfin, nous pouvons ajuster l'attribut `src` pour ajouter quelques paramètres de requête.\n\n```ts\ntoken.attrSet('src', `${src}?auto=format&fit=crop&q=80&w=800`)\n```\n\nCes requêtes sont spécifiques à Unsplash, mais vous pourriez faire la même chose avec [Cloudflare Images](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fimages\u002Ftransform-images\u002Ftransform-via-url\u002F) ou n'importe quel service d'optimisation d'image.\n\nUne petite note sur cette approche : elle fonctionne bien. Le parallélisme améliore légèrement les performances car la plupart des calculs se produisent dans la fonction `encode` qui transforme l'image en blurhash de manière synchrone. Sur mon ordinateur, récupérer l'image prend environ 100ms, tandis que l'encoder prend environ 3 secondes.\n\n## Aller Plus Loin\n\nL'implémentation actuelle est simple, et c'est intentionnel. Maintenant que vous savez comment optimiser vos images et créer des règles personnalisées, vous pouvez facilement adapter cette approche à vos besoins, et même en créer de nouvelles qui correspondent à vos cas d'utilisation spécifiques.\n\nPour gérer le goulot d'étranglement des performances, vous avez de nombreuses possibilités. Trois d'entre elles sont :\n\n1. **Pré-traitement local et stockage local** : Si vous stockez des images dans votre code source, vous pouvez créer un script qui parcourra toutes les images et générera un fichier JSON contenant la largeur, la hauteur et le blurhash. Ensuite, dans la règle, vous n'aurez qu'à lire ce fichier, et plus aucun calcul n'est nécessaire. Cela réduira également le temps de build.\n2. **Pré-traitement local et stockage distant** : Si vos images sont stockées à distance, vous pouvez créer un script similaire qui récupérera les images, les traitera et générera le fichier JSON. Ce fichier peut être poussé vers un bucket pour une utilisation ultérieure. Dans la règle, au lieu de lire un fichier, vous pouvez le récupérer depuis le bucket. _Sans règles asynchrones, ce n'était pas possible !_\n3. **Pré-traitement distant et stockage distant** : Si vos images sont stockées à distance, vous pouvez créer un workflow qui sera déclenché chaque fois qu'un nouveau fichier est ajouté. Le workflow génère le fichier JSON et le stocke dans le même bucket. Dans la règle, vous pouvez récupérer le fichier JSON depuis le bucket et l'utiliser directement. _L'optimisation maximale. Avec les [notifications R2](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002Fbuckets\u002Fevent-notifications\u002F) et les [Queues](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fqueues\u002Fevent-subscriptions\u002Fevents-schemas\u002F#r2), c'est simple à implémenter._\n\n_Vous pourriez aussi créer un pool de workers en utilisant les threads Node, mais c'est excessif pour ce cas d'utilisation._\n\nJe suis sûr qu'il existe de nombreuses autres façons d'automatiser l'optimisation des images. Maintenant, c'est à vous de trouver la meilleure approche qui correspond à votre flux de travail.\n\nJ'espère que vous avez apprécié l'article ! Si vous implémentez quelque chose de similaire, tenez-moi au courant !\n",{"title":9668,"description":11301},{"loc":11303},"fr\u002Fposts\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering","R21CdeKrTjA9KIP10sk5Po8pk35TULF20HLkHl76u2I",{"id":11310,"title":11311,"body":11312,"contentId":9285,"date":13780,"description":13781,"extension":18,"meta":13782,"navigation":20,"path":13783,"rawbody":13784,"robots":23,"schemaOrg":23,"seo":13785,"sitemap":13786,"stem":13789,"__hash__":13790},"posts\u002Ffr\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite.md","Éliminer le CLS en optimisant les images avec Vite",{"type":7,"value":11313,"toc":13773},[11314,11317,11320,11326,11328,11331,11334,11337,11347,11353,11370,11374,11377,11383,11386,11389,11441,11444,11559,11562,11568,11575,11579,11582,11595,11602,11605,11619,11622,11625,11637,11643,11771,11774,11787,11930,11933,12130,12139,13770],[35,11315,11316],{},"J'adore Vite, non seulement parce qu'il est rapide, mais aussi pour son incroyable système de plugins.",[35,11318,11319],{},"À la base, un plugin Vite vous permet d'éditer, à la volée, n'importe quel fichier importé, qu'il existe ou non, dans votre projet.",[35,11321,11322,11323],{},"Une fois que vous avez compris cela, tout devient possible. ",[1216,11324,11325],{},"Tout en tant que plugin Vite !",[1158,11327],{},[35,11329,11330],{},"Depuis des années maintenant, je construis des sites web axés sur le contenu où celui-ci est écrit en Markdown. J'aime aussi utiliser des images pour illustrer mon contenu.",[35,11332,11333],{},"Cependant, les images sont délicates. Pour améliorer les performances, vous voulez les charger en différé (lazy loading), mais le lazy loading provoque inévitablement du Cumulative Layout Shift (CLS) si vous ne réservez pas d'espace. Pour réserver cet espace, le navigateur doit connaître à l'avance les dimensions de l'image. Mais vous ne voulez pas toujours les mêmes dimensions partout, donc pour chaque image vous avez besoin de ses dimensions afin que le navigateur puisse réserver la bonne quantité d'espace.",[35,11335,11336],{},"Au-delà de ça, les images peuvent rapidement devenir volumineuses, et les héberger dans votre dépôt n'est pas idéal. Vous pourriez donc vouloir les transférer vers un bucket dédié. Cela signifie qu'elles seront servies depuis un domaine différent, ce qui, combiné au chargement différé, même avec les bonnes dimensions, laissera un espace vide jusqu'à ce qu'elles soient entièrement chargées.",[130,11338,132,11339,132,11344],{},[134,11340,136,11341,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,11342],{"src":11343,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Fcls-issue.mp4",[143,11345,11346],{},"Exemple de problème de Cumulative Layout Shift (CLS) lors du chargement différé d'une image sans espace réservé.",[35,11348,11349,11350],{},"C'est un problème soluble, mais il nécessite beaucoup de travail manuel. ",[1216,11351,11352],{},"Je déteste le travail manuel et répétitif qui peut être automatisé !",[938,11354,11355],{},[35,11356,11357,11359,11360,11364,11365,215],{},[200,11358,944],{},"\nCet article est dédié au contenu HTML. ",[39,11361,11363],{"href":11362},".\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering","Un autre article"," couvre la gestion du contenu Markdown en utilisant ",[39,11366,11369],{"href":11367,"rel":11368},"https:\u002F\u002Fmarkdown-exit.pages.dev\u002F",[77],"Markdown Exit",[51,11371,11373],{"id":11372},"vite-à-la-rescousse","Vite à la rescousse",[35,11375,11376],{},"La première question qui vient à l'esprit est : Comment savoir si un plugin Vite est le bon outil pour le travail ?",[35,11378,11379,11380],{},"C'est une question importante. Je pourrais simplement vous donner la solution, mais vous ne seriez pas en mesure d'appliquer une approche similaire à vos propres problèmes. ",[1216,11381,11382],{},"Malheureusement, la plupart des tutoriels manquent cette étape.",[35,11384,11385],{},"Analysons le problème en regardant ses entrées et ses sorties.",[35,11387,11388],{},"L'entrée est probablement un extrait HTML brut comme celui-ci :",[192,11390,11392],{"className":590,"code":11391,"language":592,"meta":13,"style":13},"\u003Cdiv>\n  \u003Cimg src=\"\u002Fpath\u002Fto\u002Fimage.jpg\" alt=\"An image\">\n\u003C\u002Fdiv>\n",[156,11393,11394,11403,11433],{"__ignoreMap":13},[200,11395,11396,11398,11401],{"class":202,"line":203},[200,11397,599],{"class":214},[200,11399,11400],{"class":235},"div",[200,11402,647],{"class":214},[200,11404,11405,11407,11409,11411,11413,11415,11418,11420,11422,11424,11426,11429,11431],{"class":202,"line":14},[200,11406,656],{"class":214},[200,11408,1707],{"class":235},[200,11410,10120],{"class":379},[200,11412,449],{"class":214},[200,11414,610],{"class":214},[200,11416,11417],{"class":245},"\u002Fpath\u002Fto\u002Fimage.jpg",[200,11419,610],{"class":214},[200,11421,10148],{"class":379},[200,11423,449],{"class":214},[200,11425,610],{"class":214},[200,11427,11428],{"class":245},"An image",[200,11430,610],{"class":214},[200,11432,647],{"class":214},[200,11434,11435,11437,11439],{"class":202,"line":255},[200,11436,680],{"class":214},[200,11438,11400],{"class":235},[200,11440,647],{"class":214},[35,11442,11443],{},"La sortie devrait être un extrait HTML optimisé comme celui-ci :",[192,11445,11447],{"className":590,"code":11446,"language":592,"meta":13,"style":13},"\u003Cdiv>\n  \u003Cimg\n    src=\"https:\u002F\u002Fcdn.example.com\u002Fpath\u002Fto\u002Fimage-optimized.jpg\"\n    alt=\"An image\"\n    width=\"600\"\n    height=\"400\"\n    loading=\"lazy\"\n    style=\"background-image: url('data:image\u002Fsvg+xml;base64,...'); background-size: cover;\"\n  >\n\u003C\u002Fdiv>\n",[156,11448,11449,11457,11464,11478,11491,11505,11519,11532,11546,11551],{"__ignoreMap":13},[200,11450,11451,11453,11455],{"class":202,"line":203},[200,11452,599],{"class":214},[200,11454,11400],{"class":235},[200,11456,647],{"class":214},[200,11458,11459,11461],{"class":202,"line":14},[200,11460,656],{"class":214},[200,11462,11463],{"class":235},"img\n",[200,11465,11466,11469,11471,11473,11476],{"class":202,"line":255},[200,11467,11468],{"class":379},"    src",[200,11470,449],{"class":214},[200,11472,610],{"class":214},[200,11474,11475],{"class":245},"https:\u002F\u002Fcdn.example.com\u002Fpath\u002Fto\u002Fimage-optimized.jpg",[200,11477,616],{"class":214},[200,11479,11480,11483,11485,11487,11489],{"class":202,"line":272},[200,11481,11482],{"class":379},"    alt",[200,11484,449],{"class":214},[200,11486,610],{"class":214},[200,11488,11428],{"class":245},[200,11490,616],{"class":214},[200,11492,11493,11496,11498,11500,11503],{"class":202,"line":283},[200,11494,11495],{"class":379},"    width",[200,11497,449],{"class":214},[200,11499,610],{"class":214},[200,11501,11502],{"class":245},"600",[200,11504,616],{"class":214},[200,11506,11507,11510,11512,11514,11517],{"class":202,"line":300},[200,11508,11509],{"class":379},"    height",[200,11511,449],{"class":214},[200,11513,610],{"class":214},[200,11515,11516],{"class":245},"400",[200,11518,616],{"class":214},[200,11520,11521,11524,11526,11528,11530],{"class":202,"line":310},[200,11522,11523],{"class":379},"    loading",[200,11525,449],{"class":214},[200,11527,610],{"class":214},[200,11529,9909],{"class":245},[200,11531,616],{"class":214},[200,11533,11534,11537,11539,11541,11544],{"class":202,"line":336},[200,11535,11536],{"class":379},"    style",[200,11538,449],{"class":214},[200,11540,610],{"class":214},[200,11542,11543],{"class":245},"background-image: url('data:image\u002Fsvg+xml;base64,...'); background-size: cover;",[200,11545,616],{"class":214},[200,11547,11548],{"class":202,"line":342},[200,11549,11550],{"class":214},"  >\n",[200,11552,11553,11555,11557],{"class":202,"line":365},[200,11554,680],{"class":214},[200,11556,11400],{"class":235},[200,11558,647],{"class":214},[35,11560,11561],{},"Vite est construit autour d'un pipeline qui traite les fichiers au fur et à mesure qu'ils sont importés. Cela signifie que nous pouvons nous greffer sur le pipeline, détecter quand le code contient une image, et le transformer en conséquence.",[35,11563,11564,11565],{},"Notre cas d'utilisation correspond parfaitement aux capacités de Vite. ",[1216,11566,11567],{},"Les plugins Vite peuvent avoir des effets de bord sur les fichiers importés. Ils débloquent des cas d'utilisation vraiment puissants comme la compression automatique d'images, le redimensionnement, ...",[938,11569,11570],{},[35,11571,11572,11574],{},[200,11573,944],{},"\nCet article suppose que les images sont disponibles localement. La gestion des images distantes ne sera pas couverte ici, mais je donnerai quelques pistes à la fin de l'article.",[51,11576,11578],{"id":11577},"construire-le-plugin","Construire le plugin",[35,11580,11581],{},"Quel est le plan ? C'est bien d'avoir une idée de ce que nous voulons accomplir, mais comment y parvenir ?",[35,11583,11584,11585,11588,11589,11591,11592,11594],{},"Nous devons nous greffer sur le pipeline de Vite et rechercher les templates Vue. Ensuite, nous devons trouver toutes les balises ",[156,11586,11587],{},"\u003Cimg>",", extraire leurs attributs ",[156,11590,11180],{},", charger les images, obtenir leurs dimensions, générer le placeholder flouté, et enfin remplacer les balises ",[156,11593,11587],{}," originales par des versions optimisées.",[938,11596,11597],{},[35,11598,11599,11601],{},[200,11600,944],{},"\nJ'utilise Vue pour tous mes projets, mais la même approche peut être appliquée à n'importe quel framework frontend supporté par Vite.",[35,11603,11604],{},"Cela semble plus facile que ça ne l'est, surtout quand on ne sait pas par où commencer et quel outil utiliser.",[35,11606,11607,11608,11613,11614,215],{},"Pour gérer tout le traitement d'image, j'utilise ",[39,11609,11612],{"href":11610,"rel":11611},"https:\u002F\u002Funpic.pics\u002F",[77],"unpic",". C'est un ensemble fantastique de primitives pour gérer tout ce qui concerne les images. Pour manipuler le code et générer des source maps, j'utilise ",[39,11615,11618],{"href":11616,"rel":11617},"https:\u002F\u002Fgithub.com\u002FRich-Harris\u002Fmagic-string",[77],"MagicString",[35,11620,11621],{},"C'est tout ce dont nous avons besoin !",[35,11623,11624],{},"Maintenant, nous pouvons créer un plugin Vite qui fait exactement ce que nous voulons.",[938,11626,11627],{},[35,11628,11629,11631,11632,215],{},[200,11630,944],{},"\nVous pouvez utiliser un projet Vite + Vue comme point de départ. Le code source du plugin final est disponible sur ",[39,11633,11636],{"href":11634,"rel":11635},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fvite-vue-unpic",[77],"GitHub",[35,11638,11639,11640,11642],{},"Dans le fichier ",[156,11641,1477],{}," de notre projet, créons d'abord un plugin :",[192,11644,11646],{"className":1380,"code":11645,"filename":1477,"language":1383,"meta":13,"style":13},"import vue from '@vitejs\u002Fplugin-vue'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [\n    vue(),\n    (() => {\n      return {\n        name: 'unpic',\n      }\n    })()\n  ],\n})\n",[156,11647,11648,11664,11682,11686,11698,11706,11715,11726,11733,11748,11752,11759,11765],{"__ignoreMap":13},[200,11649,11650,11652,11655,11657,11659,11662],{"class":202,"line":203},[200,11651,972],{"class":206},[200,11653,11654],{"class":210}," vue ",[200,11656,1489],{"class":206},[200,11658,242],{"class":214},[200,11660,11661],{"class":245},"@vitejs\u002Fplugin-vue",[200,11663,991],{"class":214},[200,11665,11666,11668,11670,11672,11674,11676,11678,11680],{"class":202,"line":14},[200,11667,972],{"class":206},[200,11669,318],{"class":214},[200,11671,1505],{"class":210},[200,11673,980],{"class":214},[200,11675,983],{"class":206},[200,11677,242],{"class":214},[200,11679,1514],{"class":245},[200,11681,991],{"class":214},[200,11683,11684],{"class":202,"line":255},[200,11685,528],{"emptyLinePlaceholder":20},[200,11687,11688,11690,11692,11694,11696],{"class":202,"line":272},[200,11689,1390],{"class":206},[200,11691,1393],{"class":206},[200,11693,1505],{"class":223},[200,11695,227],{"class":210},[200,11697,230],{"class":214},[200,11699,11700,11702,11704],{"class":202,"line":283},[200,11701,1537],{"class":235},[200,11703,239],{"class":214},[200,11705,1410],{"class":210},[200,11707,11708,11711,11713],{"class":202,"line":300},[200,11709,11710],{"class":223},"    vue",[200,11712,1549],{"class":210},[200,11714,252],{"class":214},[200,11716,11717,11720,11722,11724],{"class":202,"line":310},[200,11718,11719],{"class":210},"    (",[200,11721,1549],{"class":214},[200,11723,393],{"class":379},[200,11725,280],{"class":214},[200,11727,11728,11731],{"class":202,"line":336},[200,11729,11730],{"class":206},"      return",[200,11732,280],{"class":214},[200,11734,11735,11738,11740,11742,11744,11746],{"class":202,"line":342},[200,11736,11737],{"class":235},"        name",[200,11739,239],{"class":214},[200,11741,242],{"class":214},[200,11743,11612],{"class":245},[200,11745,249],{"class":214},[200,11747,252],{"class":214},[200,11749,11750],{"class":202,"line":365},[200,11751,4493],{"class":214},[200,11753,11754,11756],{"class":202,"line":371},[200,11755,2111],{"class":214},[200,11757,11758],{"class":210},")()\n",[200,11760,11761,11763],{"class":202,"line":398},[200,11762,1427],{"class":210},[200,11764,252],{"class":214},[200,11766,11767,11769],{"class":202,"line":405},[200,11768,413],{"class":214},[200,11770,416],{"class":210},[35,11772,11773],{},"Au minimum, un plugin Vite doit avoir un nom. Notre plugin est une IIFE (Immediately Invoked Function Expression). Cela nous permet d'avoir un contexte pour stocker l'état sans dépendre de variables globales.",[35,11775,11776,11777,11782,11783,11786],{},"Ensuite, nous devons nous greffer sur la bonne partie du pipeline. Il existe ",[39,11778,11781],{"href":11779,"rel":11780},"https:\u002F\u002Frollupjs.org\u002Fplugin-development\u002F#build-hooks",[77],"beaucoup de hooks"," disponibles, mais en pratique, le plus utile est ",[156,11784,11785],{},"transform",". Ce hook est appelé pour chaque fichier importé dans le projet. Il reçoit le code du fichier et son id (chemin). Nous voulons seulement traiter les fichiers Vue, donc nous pouvons filtrer par extension de fichier.",[192,11788,11790],{"className":9755,"code":11789,"filename":1477,"language":1383,"meta":13,"style":13},"(() => {\n  return {\n    name: 'unpic',\n    async transform(code, id) {\n      if (!id.endsWith('.vue')) \u002F\u002F [!code highlight]\n        return\n\n      \u002F\u002F Some magic will happen here\n\n      return {\n        code,\n        map: null,\n      }\n    }\n  }\n})()\n",[156,11791,11792,11802,11808,11823,11843,11871,11876,11880,11885,11889,11895,11902,11912,11916,11920,11924],{"__ignoreMap":13},[200,11793,11794,11796,11798,11800],{"class":202,"line":203},[200,11795,227],{"class":210},[200,11797,1549],{"class":214},[200,11799,393],{"class":379},[200,11801,280],{"class":214},[200,11803,11804,11806],{"class":202,"line":14},[200,11805,3758],{"class":206},[200,11807,280],{"class":214},[200,11809,11810,11813,11815,11817,11819,11821],{"class":202,"line":255},[200,11811,11812],{"class":235},"    name",[200,11814,239],{"class":214},[200,11816,242],{"class":214},[200,11818,11612],{"class":245},[200,11820,249],{"class":214},[200,11822,252],{"class":214},[200,11824,11825,11828,11831,11833,11835,11837,11839,11841],{"class":202,"line":272},[200,11826,11827],{"class":379},"    async",[200,11829,11830],{"class":235}," transform",[200,11832,227],{"class":214},[200,11834,156],{"class":386},[200,11836,549],{"class":214},[200,11838,3239],{"class":386},[200,11840,2104],{"class":214},[200,11842,280],{"class":214},[200,11844,11846,11848,11850,11853,11855,11857,11860,11862,11864,11867,11869],{"class":11845,"line":283},[202,9885],[200,11847,6523],{"class":206},[200,11849,1094],{"class":235},[200,11851,11852],{"class":214},"!",[200,11854,3295],{"class":210},[200,11856,215],{"class":214},[200,11858,11859],{"class":223},"endsWith",[200,11861,227],{"class":235},[200,11863,249],{"class":214},[200,11865,11866],{"class":245},".vue",[200,11868,249],{"class":214},[200,11870,6478],{"class":235},[200,11872,11873],{"class":202,"line":300},[200,11874,11875],{"class":206},"        return\n",[200,11877,11878],{"class":202,"line":310},[200,11879,528],{"emptyLinePlaceholder":20},[200,11881,11882],{"class":202,"line":336},[200,11883,11884],{"class":401},"      \u002F\u002F Some magic will happen here\n",[200,11886,11887],{"class":202,"line":342},[200,11888,528],{"emptyLinePlaceholder":20},[200,11890,11891,11893],{"class":202,"line":365},[200,11892,11730],{"class":206},[200,11894,280],{"class":214},[200,11896,11897,11900],{"class":202,"line":371},[200,11898,11899],{"class":210},"        code",[200,11901,252],{"class":214},[200,11903,11904,11907,11909],{"class":202,"line":398},[200,11905,11906],{"class":235},"        map",[200,11908,239],{"class":214},[200,11910,11911],{"class":214}," null,\n",[200,11913,11914],{"class":202,"line":405},[200,11915,4493],{"class":214},[200,11917,11918],{"class":202,"line":410},[200,11919,4977],{"class":214},[200,11921,11922],{"class":202,"line":860},[200,11923,4505],{"class":214},[200,11925,11926,11928],{"class":202,"line":889},[200,11927,413],{"class":214},[200,11929,11758],{"class":210},[35,11931,11932],{},"Mais nous pouvons le rendre encore plus performant. Appeler notre plugin sur chaque fichier est inutile, surtout avec Rolldown, où la communication entre Rust et JavaScript entraîne un petit surcoût. Pour éviter cela, nous pouvons utiliser des filtres pour ne traiter que les fichiers correspondant à certains critères. Dans notre cas, nous voulons seulement traiter les fichiers Vue.",[192,11934,11936],{"className":9755,"code":11935,"filename":1477,"language":1383,"meta":13,"style":13},"(() => {\n  const imgTagRegex = \u002F\u003Cimg\\s[^>]*src=[\"']([^\"']+)[\"'][^>]*>\u002Fg \u002F\u002F [!code highlight]\n\n  return {\n    name: 'unpic',\n    enforce: 'pre',\n    transform: {\n      filter: {\n        id: \u002F\\.vue$\u002F, \u002F\u002F [!code highlight]\n        code: imgTagRegex, \u002F\u002F [!code highlight]\n      },\n      async handler(code) {},\n    }\n  }\n})()\n",[156,11937,11938,11948,12004,12008,12014,12028,12043,12051,12060,12083,12094,12099,12116,12120,12124],{"__ignoreMap":13},[200,11939,11940,11942,11944,11946],{"class":202,"line":203},[200,11941,227],{"class":210},[200,11943,1549],{"class":214},[200,11945,393],{"class":379},[200,11947,280],{"class":214},[200,11949,11951,11953,11956,11958,11961,11964,11967,11969,11972,11975,11977,11980,11983,11985,11988,11990,11993,11995,11997,11999,12001],{"class":11950,"line":14},[202,9885],[200,11952,3738],{"class":379},[200,11954,11955],{"class":210}," imgTagRegex",[200,11957,509],{"class":214},[200,11959,11960],{"class":214}," \u002F",[200,11962,11963],{"class":245},"\u003Cimg\\s",[200,11965,11966],{"class":214},"[^",[200,11968,674],{"class":245},[200,11970,11971],{"class":214},"]*",[200,11973,11974],{"class":245},"src=",[200,11976,9872],{"class":214},[200,11978,11979],{"class":245},"\"'",[200,11981,11982],{"class":214},"]([^",[200,11984,11979],{"class":245},[200,11986,11987],{"class":214},"]+)[",[200,11989,11979],{"class":245},[200,11991,11992],{"class":214},"][^",[200,11994,674],{"class":245},[200,11996,11971],{"class":214},[200,11998,674],{"class":245},[200,12000,2898],{"class":214},[200,12002,12003],{"class":2060},"g",[200,12005,12006],{"class":202,"line":255},[200,12007,528],{"emptyLinePlaceholder":20},[200,12009,12010,12012],{"class":202,"line":272},[200,12011,3758],{"class":206},[200,12013,280],{"class":214},[200,12015,12016,12018,12020,12022,12024,12026],{"class":202,"line":283},[200,12017,11812],{"class":235},[200,12019,239],{"class":214},[200,12021,242],{"class":214},[200,12023,11612],{"class":245},[200,12025,249],{"class":214},[200,12027,252],{"class":214},[200,12029,12030,12033,12035,12037,12039,12041],{"class":202,"line":300},[200,12031,12032],{"class":235},"    enforce",[200,12034,239],{"class":214},[200,12036,242],{"class":214},[200,12038,192],{"class":245},[200,12040,249],{"class":214},[200,12042,252],{"class":214},[200,12044,12045,12047,12049],{"class":202,"line":310},[200,12046,5051],{"class":235},[200,12048,239],{"class":214},[200,12050,280],{"class":214},[200,12052,12053,12056,12058],{"class":202,"line":336},[200,12054,12055],{"class":235},"      filter",[200,12057,239],{"class":214},[200,12059,280],{"class":214},[200,12061,12063,12066,12068,12070,12073,12076,12079,12081],{"class":12062,"line":342},[202,9885],[200,12064,12065],{"class":235},"        id",[200,12067,239],{"class":214},[200,12069,11960],{"class":214},[200,12071,12072],{"class":210},"\\.",[200,12074,12075],{"class":245},"vue",[200,12077,12078],{"class":206},"$",[200,12080,2898],{"class":214},[200,12082,549],{"class":214},[200,12084,12086,12088,12090,12092],{"class":12085,"line":365},[202,9885],[200,12087,11899],{"class":235},[200,12089,239],{"class":214},[200,12091,11955],{"class":210},[200,12093,549],{"class":214},[200,12095,12096],{"class":202,"line":371},[200,12097,12098],{"class":214},"      },\n",[200,12100,12101,12104,12107,12109,12111,12113],{"class":202,"line":398},[200,12102,12103],{"class":379},"      async",[200,12105,12106],{"class":235}," handler",[200,12108,227],{"class":214},[200,12110,156],{"class":386},[200,12112,2104],{"class":214},[200,12114,12115],{"class":214}," {},\n",[200,12117,12118],{"class":202,"line":405},[200,12119,4977],{"class":214},[200,12121,12122],{"class":202,"line":410},[200,12123,4505],{"class":214},[200,12125,12126,12128],{"class":202,"line":860},[200,12127,413],{"class":214},[200,12129,11758],{"class":210},[35,12131,12132,12133,12135,12136,12138],{},"Ce ",[156,12134,3773],{}," garantit que notre plugin ne s'exécute que sur les fichiers Vue contenant au moins une balise ",[156,12137,11587],{},". Tellement mieux !",[7918,12140,12143,12146,12161,12238,12243,12247,12253,12286,12289,12314,12317,12323,12356,12364,12435,12439,12517,12523,12650,12658,12777,12780,13523,13537,13543,13546,13556,13568,13572,13580,13596,13601,13690,13693,13705,13711,13718,13724,13727,13730,13733,13756,13759,13765,13767],{"text":12141,"href":12142},"Filtres de hook de plugin","https:\u002F\u002Frolldown.rs\u002Fapis\u002Fplugin-hook-filters",[35,12144,12145],{},"Une dernière chose avant de pouvoir commencer la magie : nous devons nous assurer que le plugin s'exécute avant le plugin Vue. Vue transforme les fichiers Vue en fichiers JavaScript, et les fichiers Single-File Component (SFC) sont beaucoup plus simples à traiter. Pour ce faire, nous avons deux options :",[68,12147,12148,12154],{},[71,12149,12150,12151,215],{},"Placer notre plugin avant le plugin Vue dans le tableau ",[156,12152,12153],{},"plugins",[71,12155,12156,12157,12160],{},"Utiliser l'option ",[156,12158,12159],{},"enforce: 'pre'"," dans la définition de notre plugin.",[192,12162,12164],{"className":9755,"code":12163,"filename":1477,"language":1383,"meta":13,"style":13},"(() => {\n  return {\n    name: 'unpic',\n    enforce: 'pre', \u002F\u002F [!code highlight]\n    transform: {\n      \u002F\u002F ...\n    }\n  }\n})()\n",[156,12165,12166,12176,12182,12196,12211,12219,12224,12228,12232],{"__ignoreMap":13},[200,12167,12168,12170,12172,12174],{"class":202,"line":203},[200,12169,227],{"class":210},[200,12171,1549],{"class":214},[200,12173,393],{"class":379},[200,12175,280],{"class":214},[200,12177,12178,12180],{"class":202,"line":14},[200,12179,3758],{"class":206},[200,12181,280],{"class":214},[200,12183,12184,12186,12188,12190,12192,12194],{"class":202,"line":255},[200,12185,11812],{"class":235},[200,12187,239],{"class":214},[200,12189,242],{"class":214},[200,12191,11612],{"class":245},[200,12193,249],{"class":214},[200,12195,252],{"class":214},[200,12197,12199,12201,12203,12205,12207,12209],{"class":12198,"line":272},[202,9885],[200,12200,12032],{"class":235},[200,12202,239],{"class":214},[200,12204,242],{"class":214},[200,12206,192],{"class":245},[200,12208,249],{"class":214},[200,12210,549],{"class":214},[200,12212,12213,12215,12217],{"class":202,"line":283},[200,12214,5051],{"class":235},[200,12216,239],{"class":214},[200,12218,280],{"class":214},[200,12220,12221],{"class":202,"line":300},[200,12222,12223],{"class":401},"      \u002F\u002F ...\n",[200,12225,12226],{"class":202,"line":310},[200,12227,4977],{"class":214},[200,12229,12230],{"class":202,"line":336},[200,12231,4505],{"class":214},[200,12233,12234,12236],{"class":202,"line":342},[200,12235,413],{"class":214},[200,12237,11758],{"class":210},[35,12239,12240,12241,215],{},"Maintenant, nous pouvons commencer à traiter les fichiers Vue pour améliorer les balises ",[156,12242,11587],{},[51,12244,12246],{"id":12245},"transformer-le-code","Transformer le code",[35,12248,12249,12250,12252],{},"Dans le hook ",[156,12251,11785],{},", voici ce qui doit être fait :",[68,12254,12255,12260,12268,12271,12277],{},[71,12256,12257,12258,215],{},"Analyser le code pour trouver toutes les balises ",[156,12259,11587],{},[71,12261,12262,12263,12265,12266,215],{},"Pour chaque balise ",[156,12264,11587],{},", extraire l'attribut ",[156,12267,11180],{},[71,12269,12270],{},"Lire le fichier depuis le système de fichiers.",[71,12272,12273,12274,12276],{},"Traiter l'image avec ",[156,12275,11612],{}," pour obtenir ses dimensions et un placeholder flouté.",[71,12278,12279,12280,12282,12283,12285],{},"Générer et remplacer la balise ",[156,12281,11587],{}," originale par la balise ",[156,12284,11587],{}," optimisée avec tous les attributs nécessaires.",[35,12287,12288],{},"Avant de commencer, nous devons installer les dépendances :",[192,12290,12292],{"className":2166,"code":12291,"language":2168,"meta":13,"style":13},"pnpm install -D @unpic\u002Fpixels @unpic\u002Fplaceholder blurhash magic-string\n",[156,12293,12294],{"__ignoreMap":13},[200,12295,12296,12298,12300,12302,12305,12308,12311],{"class":202,"line":203},[200,12297,1365],{"class":1364},[200,12299,1368],{"class":245},[200,12301,1371],{"class":245},[200,12303,12304],{"class":245}," @unpic\u002Fpixels",[200,12306,12307],{"class":245}," @unpic\u002Fplaceholder",[200,12309,12310],{"class":245}," blurhash",[200,12312,12313],{"class":245}," magic-string\n",[35,12315,12316],{},"Implémentons cela étape par étape.",[68,12318,12319],{},[71,12320,12257,12321,215],{},[156,12322,11587],{},[192,12324,12326],{"className":1380,"code":12325,"filename":1477,"language":1383,"meta":13,"style":13},"\u002F\u002F eslint-disable-next-line prefer-const\nlet match = imgTagRegex.exec(code) \u002F\u002F On réutilise la regex définie plus tôt\n",[156,12327,12328,12333],{"__ignoreMap":13},[200,12329,12330],{"class":202,"line":203},[200,12331,12332],{"class":401},"\u002F\u002F eslint-disable-next-line prefer-const\n",[200,12334,12335,12338,12341,12343,12345,12347,12350,12353],{"class":202,"line":14},[200,12336,12337],{"class":379},"let",[200,12339,12340],{"class":210}," match ",[200,12342,449],{"class":214},[200,12344,11955],{"class":210},[200,12346,215],{"class":214},[200,12348,12349],{"class":223},"exec",[200,12351,12352],{"class":210},"(code) ",[200,12354,12355],{"class":401},"\u002F\u002F On réutilise la regex définie plus tôt\n",[68,12357,12358],{"start":14},[71,12359,12262,12360,12265,12362,215],{},[156,12361,11587],{},[156,12363,11180],{},[192,12365,12367],{"className":1380,"code":12366,"filename":1477,"language":1383,"meta":13,"style":13},"do {\n  const srcValue = match[1]\n\n  match = imgTagRegex.exec(code)\n} while (match !== null)\n",[156,12368,12369,12376,12394,12398,12417],{"__ignoreMap":13},[200,12370,12371,12374],{"class":202,"line":203},[200,12372,12373],{"class":206},"do",[200,12375,280],{"class":214},[200,12377,12378,12380,12383,12385,12388,12390,12392],{"class":202,"line":14},[200,12379,3738],{"class":379},[200,12381,12382],{"class":210}," srcValue",[200,12384,509],{"class":214},[200,12386,12387],{"class":210}," match",[200,12389,9872],{"class":235},[200,12391,3203],{"class":2060},[200,12393,4511],{"class":235},[200,12395,12396],{"class":202,"line":255},[200,12397,528],{"emptyLinePlaceholder":20},[200,12399,12400,12403,12405,12407,12409,12411,12413,12415],{"class":202,"line":272},[200,12401,12402],{"class":210},"  match",[200,12404,509],{"class":214},[200,12406,11955],{"class":210},[200,12408,215],{"class":214},[200,12410,12349],{"class":223},[200,12412,227],{"class":235},[200,12414,156],{"class":210},[200,12416,416],{"class":235},[200,12418,12419,12421,12424,12427,12430,12433],{"class":202,"line":283},[200,12420,413],{"class":214},[200,12422,12423],{"class":206}," while",[200,12425,12426],{"class":210}," (match ",[200,12428,12429],{"class":214},"!==",[200,12431,12432],{"class":214}," null",[200,12434,416],{"class":210},[68,12436,12437],{"start":255},[71,12438,12270],{},[192,12440,12442],{"className":1380,"code":12441,"filename":1477,"language":1383,"meta":13,"style":13},"import { readFile } from 'node:fs\u002Fpromises'\nimport { join } from 'node:path'\n\nconst img = await readFile(join(resolvedConfig.publicDir, srcValue))\n",[156,12443,12444,12464,12484,12488],{"__ignoreMap":13},[200,12445,12446,12448,12450,12453,12455,12457,12459,12462],{"class":202,"line":203},[200,12447,972],{"class":206},[200,12449,318],{"class":214},[200,12451,12452],{"class":210}," readFile",[200,12454,980],{"class":214},[200,12456,983],{"class":206},[200,12458,242],{"class":214},[200,12460,12461],{"class":245},"node:fs\u002Fpromises",[200,12463,991],{"class":214},[200,12465,12466,12468,12470,12473,12475,12477,12479,12482],{"class":202,"line":14},[200,12467,972],{"class":206},[200,12469,318],{"class":214},[200,12471,12472],{"class":210}," join",[200,12474,980],{"class":214},[200,12476,983],{"class":206},[200,12478,242],{"class":214},[200,12480,12481],{"class":245},"node:path",[200,12483,991],{"class":214},[200,12485,12486],{"class":202,"line":255},[200,12487,528],{"emptyLinePlaceholder":20},[200,12489,12490,12492,12494,12496,12498,12500,12502,12504,12507,12509,12512,12514],{"class":202,"line":272},[200,12491,443],{"class":379},[200,12493,10930],{"class":210},[200,12495,449],{"class":214},[200,12497,452],{"class":206},[200,12499,12452],{"class":223},[200,12501,227],{"class":210},[200,12503,6202],{"class":223},[200,12505,12506],{"class":210},"(resolvedConfig",[200,12508,215],{"class":214},[200,12510,12511],{"class":210},"publicDir",[200,12513,549],{"class":214},[200,12515,12516],{"class":210}," srcValue))\n",[68,12518,12519],{"start":272},[71,12520,12273,12521,12276],{},[156,12522,11612],{},[192,12524,12526],{"className":1380,"code":12525,"filename":1477,"language":1383,"meta":13,"style":13},"import { getPixels } from '@unpic\u002Fpixels'\nimport { blurhashToDataUri } from '@unpic\u002Fplaceholder'\nimport { encode } from 'blurhash'\n\nconst data = await getPixels(img)\nconst blurhash = encode(Uint8ClampedArray.from(data.data), data.width, data.height, 4, 4)\n",[156,12527,12528,12547,12565,12584,12588,12602],{"__ignoreMap":13},[200,12529,12530,12532,12534,12536,12538,12540,12542,12545],{"class":202,"line":203},[200,12531,972],{"class":206},[200,12533,318],{"class":214},[200,12535,10976],{"class":210},[200,12537,980],{"class":214},[200,12539,983],{"class":206},[200,12541,242],{"class":214},[200,12543,12544],{"class":245},"@unpic\u002Fpixels",[200,12546,991],{"class":214},[200,12548,12549,12551,12553,12555,12557,12559,12561,12563],{"class":202,"line":14},[200,12550,972],{"class":206},[200,12552,318],{"class":214},[200,12554,11056],{"class":210},[200,12556,980],{"class":214},[200,12558,983],{"class":206},[200,12560,242],{"class":214},[200,12562,11065],{"class":245},[200,12564,991],{"class":214},[200,12566,12567,12569,12571,12573,12575,12577,12579,12582],{"class":202,"line":255},[200,12568,972],{"class":206},[200,12570,318],{"class":214},[200,12572,10995],{"class":210},[200,12574,980],{"class":214},[200,12576,983],{"class":206},[200,12578,242],{"class":214},[200,12580,12581],{"class":245},"blurhash",[200,12583,991],{"class":214},[200,12585,12586],{"class":202,"line":272},[200,12587,528],{"emptyLinePlaceholder":20},[200,12589,12590,12592,12594,12596,12598,12600],{"class":202,"line":283},[200,12591,443],{"class":379},[200,12593,10969],{"class":210},[200,12595,449],{"class":214},[200,12597,452],{"class":206},[200,12599,10976],{"class":223},[200,12601,10979],{"class":210},[200,12603,12604,12606,12608,12610,12612,12614,12616,12618,12620,12622,12624,12626,12628,12630,12632,12634,12636,12638,12640,12642,12644,12646,12648],{"class":202,"line":300},[200,12605,443],{"class":379},[200,12607,10990],{"class":210},[200,12609,449],{"class":214},[200,12611,10995],{"class":223},[200,12613,10998],{"class":210},[200,12615,215],{"class":214},[200,12617,1489],{"class":223},[200,12619,11005],{"class":210},[200,12621,215],{"class":214},[200,12623,11010],{"class":210},[200,12625,549],{"class":214},[200,12627,5056],{"class":210},[200,12629,215],{"class":214},[200,12631,11019],{"class":210},[200,12633,549],{"class":214},[200,12635,5056],{"class":210},[200,12637,215],{"class":214},[200,12639,11028],{"class":210},[200,12641,549],{"class":214},[200,12643,11033],{"class":2060},[200,12645,549],{"class":214},[200,12647,11033],{"class":2060},[200,12649,416],{"class":210},[68,12651,12652],{"start":283},[71,12653,12279,12654,12282,12656,12285],{},[156,12655,11587],{},[156,12657,11587],{},[192,12659,12661],{"className":1380,"code":12660,"filename":1477,"language":1383,"meta":13,"style":13},"import { blurhashToDataUri } from '@unpic\u002Fplaceholder'\n\nconst newImgTag = match[0].replace(\n  \u002F\u003Cimg(\\s+)\u002F,\n  `\u003Cimg$1width=\"${data.width}\" height=\"${data.height}\" style=\"background-size: cover; background-image: url(${blurhashToDataUri(blurhash)});\" loading=\"lazy\" `\n)\n",[156,12662,12663,12681,12685,12707,12724,12773],{"__ignoreMap":13},[200,12664,12665,12667,12669,12671,12673,12675,12677,12679],{"class":202,"line":203},[200,12666,972],{"class":206},[200,12668,318],{"class":214},[200,12670,11056],{"class":210},[200,12672,980],{"class":214},[200,12674,983],{"class":206},[200,12676,242],{"class":214},[200,12678,11065],{"class":245},[200,12680,991],{"class":214},[200,12682,12683],{"class":202,"line":14},[200,12684,528],{"emptyLinePlaceholder":20},[200,12686,12687,12689,12692,12694,12697,12699,12701,12703,12705],{"class":202,"line":255},[200,12688,443],{"class":379},[200,12690,12691],{"class":210}," newImgTag ",[200,12693,449],{"class":214},[200,12695,12696],{"class":210}," match[",[200,12698,3948],{"class":2060},[200,12700,360],{"class":210},[200,12702,215],{"class":214},[200,12704,6148],{"class":223},[200,12706,3130],{"class":210},[200,12708,12709,12712,12715,12717,12719,12722],{"class":202,"line":272},[200,12710,12711],{"class":214},"  \u002F",[200,12713,12714],{"class":245},"\u003Cimg",[200,12716,227],{"class":214},[200,12718,6163],{"class":245},[200,12720,12721],{"class":214},"+)\u002F",[200,12723,252],{"class":214},[200,12725,12726,12729,12732,12734,12737,12739,12741,12743,12746,12748,12750,12752,12754,12756,12759,12761,12763,12765,12767,12770],{"class":202,"line":283},[200,12727,12728],{"class":214},"  `",[200,12730,12731],{"class":245},"\u003Cimg$1width=\"",[200,12733,4868],{"class":214},[200,12735,12736],{"class":210},"data",[200,12738,215],{"class":214},[200,12740,11019],{"class":210},[200,12742,413],{"class":214},[200,12744,12745],{"class":245},"\" height=\"",[200,12747,4868],{"class":214},[200,12749,12736],{"class":210},[200,12751,215],{"class":214},[200,12753,11028],{"class":210},[200,12755,413],{"class":214},[200,12757,12758],{"class":245},"\" style=\"background-size: cover; background-image: url(",[200,12760,4868],{"class":214},[200,12762,11162],{"class":223},[200,12764,11165],{"class":210},[200,12766,413],{"class":214},[200,12768,12769],{"class":245},");\" loading=\"lazy\" ",[200,12771,12772],{"class":214},"`\n",[200,12774,12775],{"class":202,"line":300},[200,12776,416],{"class":210},[35,12778,12779],{},"Après avoir implémenté toutes ces étapes, notre plugin final ressemble à ceci :",[192,12781,12783],{"className":1380,"code":12782,"filename":1477,"language":1383,"meta":13,"style":13},"(() => {\n  let resolvedConfig: ResolvedConfig\n\n  const imgTagRegex = \u002F\u003Cimg\\s[^>]*src=[\"']([^\"']+)[\"'][^>]*>\u002Fg\n\n  return {\n    name: 'unpic',\n    enforce: 'pre',\n    transform: {\n      filter: {\n        id: \u002F\\.vue$\u002F,\n        code: \u002F\u003Cimg\\s[^>]*src=[\"']([^\"']+)[\"'][^>]*>\u002Fg,\n      },\n      async handler(code, id) {\n        const s = new MagicString(code)\n\n        let match = imgTagRegex.exec(code)\n\n        do {\n          const srcValue = match![1]\n\n          const img = await readFile(join(resolvedConfig.publicDir, srcValue))\n          const data = await getPixels(img)\n          const blurhash = encode(Uint8ClampedArray.from(data.data), data.width, data.height, 4, 4)\n\n          const imgTagStart = match!.index\n          const imgTagEnd = imgTagStart + match![0].length\n\n          const newImgTag = match![0].replace(\n            \u002F\u003Cimg(\\s+)\u002F,\n            `\u003Cimg$1width=\"${data.width}\" height=\"${data.height}\" style=\"background-size: cover; background-image: url(${blurhashToDataUri(blurhash)});\" loading=\"lazy\" `\n          )\n\n          s.overwrite(imgTagStart, imgTagEnd, newImgTag)\n\n          match = imgTagRegex.exec(code)\n        } while (match !== null)\n\n        return {\n          code: s.toString(),\n          map: s.generateMap({ hires: true })\n        }\n      }\n    },\n    configResolved(config) {\n      resolvedConfig = config\n    }\n  }\n})()\n",[156,12784,12785,12795,12808,12812,12857,12861,12867,12881,12895,12903,12911,12929,12973,12977,12995,13017,13021,13042,13046,13053,13072,13076,13108,13126,13181,13185,13202,13231,13235,13261,13277,13321,13327,13332,13358,13363,13383,13403,13408,13416,13434,13464,13470,13475,13480,13495,13506,13511,13516],{"__ignoreMap":13},[200,12786,12787,12789,12791,12793],{"class":202,"line":203},[200,12788,227],{"class":210},[200,12790,1549],{"class":214},[200,12792,393],{"class":379},[200,12794,280],{"class":214},[200,12796,12797,12800,12803,12805],{"class":202,"line":14},[200,12798,12799],{"class":379},"  let",[200,12801,12802],{"class":210}," resolvedConfig",[200,12804,239],{"class":214},[200,12806,12807],{"class":1364}," ResolvedConfig\n",[200,12809,12810],{"class":202,"line":255},[200,12811,528],{"emptyLinePlaceholder":20},[200,12813,12814,12816,12818,12820,12822,12824,12826,12828,12830,12832,12834,12836,12838,12840,12842,12844,12846,12848,12850,12852,12854],{"class":202,"line":272},[200,12815,3738],{"class":379},[200,12817,11955],{"class":210},[200,12819,509],{"class":214},[200,12821,11960],{"class":214},[200,12823,11963],{"class":245},[200,12825,11966],{"class":214},[200,12827,674],{"class":245},[200,12829,11971],{"class":214},[200,12831,11974],{"class":245},[200,12833,9872],{"class":214},[200,12835,11979],{"class":245},[200,12837,11982],{"class":214},[200,12839,11979],{"class":245},[200,12841,11987],{"class":214},[200,12843,11979],{"class":245},[200,12845,11992],{"class":214},[200,12847,674],{"class":245},[200,12849,11971],{"class":214},[200,12851,674],{"class":245},[200,12853,2898],{"class":214},[200,12855,12856],{"class":2060},"g\n",[200,12858,12859],{"class":202,"line":283},[200,12860,528],{"emptyLinePlaceholder":20},[200,12862,12863,12865],{"class":202,"line":300},[200,12864,3758],{"class":206},[200,12866,280],{"class":214},[200,12868,12869,12871,12873,12875,12877,12879],{"class":202,"line":310},[200,12870,11812],{"class":235},[200,12872,239],{"class":214},[200,12874,242],{"class":214},[200,12876,11612],{"class":245},[200,12878,249],{"class":214},[200,12880,252],{"class":214},[200,12882,12883,12885,12887,12889,12891,12893],{"class":202,"line":336},[200,12884,12032],{"class":235},[200,12886,239],{"class":214},[200,12888,242],{"class":214},[200,12890,192],{"class":245},[200,12892,249],{"class":214},[200,12894,252],{"class":214},[200,12896,12897,12899,12901],{"class":202,"line":342},[200,12898,5051],{"class":235},[200,12900,239],{"class":214},[200,12902,280],{"class":214},[200,12904,12905,12907,12909],{"class":202,"line":365},[200,12906,12055],{"class":235},[200,12908,239],{"class":214},[200,12910,280],{"class":214},[200,12912,12913,12915,12917,12919,12921,12923,12925,12927],{"class":202,"line":371},[200,12914,12065],{"class":235},[200,12916,239],{"class":214},[200,12918,11960],{"class":214},[200,12920,12072],{"class":210},[200,12922,12075],{"class":245},[200,12924,12078],{"class":206},[200,12926,2898],{"class":214},[200,12928,252],{"class":214},[200,12930,12931,12933,12935,12937,12939,12941,12943,12945,12947,12949,12951,12953,12955,12957,12959,12961,12963,12965,12967,12969,12971],{"class":202,"line":398},[200,12932,11899],{"class":235},[200,12934,239],{"class":214},[200,12936,11960],{"class":214},[200,12938,11963],{"class":245},[200,12940,11966],{"class":214},[200,12942,674],{"class":245},[200,12944,11971],{"class":214},[200,12946,11974],{"class":245},[200,12948,9872],{"class":214},[200,12950,11979],{"class":245},[200,12952,11982],{"class":214},[200,12954,11979],{"class":245},[200,12956,11987],{"class":214},[200,12958,11979],{"class":245},[200,12960,11992],{"class":214},[200,12962,674],{"class":245},[200,12964,11971],{"class":214},[200,12966,674],{"class":245},[200,12968,2898],{"class":214},[200,12970,12003],{"class":2060},[200,12972,252],{"class":214},[200,12974,12975],{"class":202,"line":405},[200,12976,12098],{"class":214},[200,12978,12979,12981,12983,12985,12987,12989,12991,12993],{"class":202,"line":410},[200,12980,12103],{"class":379},[200,12982,12106],{"class":235},[200,12984,227],{"class":214},[200,12986,156],{"class":386},[200,12988,549],{"class":214},[200,12990,3239],{"class":386},[200,12992,2104],{"class":214},[200,12994,280],{"class":214},[200,12996,12997,13000,13003,13005,13008,13011,13013,13015],{"class":202,"line":860},[200,12998,12999],{"class":379},"        const",[200,13001,13002],{"class":210}," s",[200,13004,509],{"class":214},[200,13006,13007],{"class":214}," new",[200,13009,13010],{"class":223}," MagicString",[200,13012,227],{"class":235},[200,13014,156],{"class":210},[200,13016,416],{"class":235},[200,13018,13019],{"class":202,"line":889},[200,13020,528],{"emptyLinePlaceholder":20},[200,13022,13023,13026,13028,13030,13032,13034,13036,13038,13040],{"class":202,"line":899},[200,13024,13025],{"class":379},"        let",[200,13027,12387],{"class":210},[200,13029,509],{"class":214},[200,13031,11955],{"class":210},[200,13033,215],{"class":214},[200,13035,12349],{"class":223},[200,13037,227],{"class":235},[200,13039,156],{"class":210},[200,13041,416],{"class":235},[200,13043,13044],{"class":202,"line":904},[200,13045,528],{"emptyLinePlaceholder":20},[200,13047,13048,13051],{"class":202,"line":930},[200,13049,13050],{"class":206},"        do",[200,13052,280],{"class":214},[200,13054,13055,13058,13060,13062,13064,13066,13068,13070],{"class":202,"line":3655},[200,13056,13057],{"class":379},"          const",[200,13059,12382],{"class":210},[200,13061,509],{"class":214},[200,13063,12387],{"class":210},[200,13065,11852],{"class":214},[200,13067,9872],{"class":235},[200,13069,3203],{"class":2060},[200,13071,4511],{"class":235},[200,13073,13074],{"class":202,"line":3660},[200,13075,528],{"emptyLinePlaceholder":20},[200,13077,13078,13080,13083,13085,13087,13089,13091,13093,13095,13098,13100,13102,13104,13106],{"class":202,"line":4035},[200,13079,13057],{"class":379},[200,13081,13082],{"class":210}," img",[200,13084,509],{"class":214},[200,13086,452],{"class":206},[200,13088,12452],{"class":223},[200,13090,227],{"class":235},[200,13092,6202],{"class":223},[200,13094,227],{"class":235},[200,13096,13097],{"class":210},"resolvedConfig",[200,13099,215],{"class":214},[200,13101,12511],{"class":210},[200,13103,549],{"class":214},[200,13105,12382],{"class":210},[200,13107,584],{"class":235},[200,13109,13110,13112,13114,13116,13118,13120,13122,13124],{"class":202,"line":4490},[200,13111,13057],{"class":379},[200,13113,5056],{"class":210},[200,13115,509],{"class":214},[200,13117,452],{"class":206},[200,13119,10976],{"class":223},[200,13121,227],{"class":235},[200,13123,1707],{"class":210},[200,13125,416],{"class":235},[200,13127,13128,13130,13132,13134,13136,13138,13141,13143,13145,13147,13149,13151,13153,13155,13157,13159,13161,13163,13165,13167,13169,13171,13173,13175,13177,13179],{"class":202,"line":4496},[200,13129,13057],{"class":379},[200,13131,12310],{"class":210},[200,13133,509],{"class":214},[200,13135,10995],{"class":223},[200,13137,227],{"class":235},[200,13139,13140],{"class":210},"Uint8ClampedArray",[200,13142,215],{"class":214},[200,13144,1489],{"class":223},[200,13146,227],{"class":235},[200,13148,12736],{"class":210},[200,13150,215],{"class":214},[200,13152,12736],{"class":210},[200,13154,2104],{"class":235},[200,13156,549],{"class":214},[200,13158,5056],{"class":210},[200,13160,215],{"class":214},[200,13162,11019],{"class":210},[200,13164,549],{"class":214},[200,13166,5056],{"class":210},[200,13168,215],{"class":214},[200,13170,11028],{"class":210},[200,13172,549],{"class":214},[200,13174,11033],{"class":2060},[200,13176,549],{"class":214},[200,13178,11033],{"class":2060},[200,13180,416],{"class":235},[200,13182,13183],{"class":202,"line":4502},[200,13184,528],{"emptyLinePlaceholder":20},[200,13186,13187,13189,13192,13194,13196,13199],{"class":202,"line":4508},[200,13188,13057],{"class":379},[200,13190,13191],{"class":210}," imgTagStart",[200,13193,509],{"class":214},[200,13195,12387],{"class":210},[200,13197,13198],{"class":214},"!.",[200,13200,13201],{"class":210},"index\n",[200,13203,13204,13206,13209,13211,13213,13216,13218,13220,13222,13224,13226,13228],{"class":202,"line":5469},[200,13205,13057],{"class":379},[200,13207,13208],{"class":210}," imgTagEnd",[200,13210,509],{"class":214},[200,13212,13191],{"class":210},[200,13214,13215],{"class":214}," +",[200,13217,12387],{"class":210},[200,13219,11852],{"class":214},[200,13221,9872],{"class":235},[200,13223,3948],{"class":2060},[200,13225,360],{"class":235},[200,13227,215],{"class":214},[200,13229,13230],{"class":210},"length\n",[200,13232,13233],{"class":202,"line":5474},[200,13234,528],{"emptyLinePlaceholder":20},[200,13236,13238,13240,13243,13245,13247,13249,13251,13253,13255,13257,13259],{"class":202,"line":13237},29,[200,13239,13057],{"class":379},[200,13241,13242],{"class":210}," newImgTag",[200,13244,509],{"class":214},[200,13246,12387],{"class":210},[200,13248,11852],{"class":214},[200,13250,9872],{"class":235},[200,13252,3948],{"class":2060},[200,13254,360],{"class":235},[200,13256,215],{"class":214},[200,13258,6148],{"class":223},[200,13260,3130],{"class":235},[200,13262,13264,13267,13269,13271,13273,13275],{"class":202,"line":13263},30,[200,13265,13266],{"class":214},"            \u002F",[200,13268,12714],{"class":245},[200,13270,227],{"class":214},[200,13272,6163],{"class":245},[200,13274,12721],{"class":214},[200,13276,252],{"class":214},[200,13278,13280,13283,13285,13287,13289,13291,13293,13295,13297,13299,13301,13303,13305,13307,13309,13311,13313,13315,13317,13319],{"class":202,"line":13279},31,[200,13281,13282],{"class":214},"            `",[200,13284,12731],{"class":245},[200,13286,4868],{"class":214},[200,13288,12736],{"class":210},[200,13290,215],{"class":214},[200,13292,11019],{"class":210},[200,13294,413],{"class":214},[200,13296,12745],{"class":245},[200,13298,4868],{"class":214},[200,13300,12736],{"class":210},[200,13302,215],{"class":214},[200,13304,11028],{"class":210},[200,13306,413],{"class":214},[200,13308,12758],{"class":245},[200,13310,4868],{"class":214},[200,13312,11162],{"class":223},[200,13314,11165],{"class":210},[200,13316,413],{"class":214},[200,13318,12769],{"class":245},[200,13320,12772],{"class":214},[200,13322,13324],{"class":202,"line":13323},32,[200,13325,13326],{"class":235},"          )\n",[200,13328,13330],{"class":202,"line":13329},33,[200,13331,528],{"emptyLinePlaceholder":20},[200,13333,13335,13338,13340,13343,13345,13348,13350,13352,13354,13356],{"class":202,"line":13334},34,[200,13336,13337],{"class":210},"          s",[200,13339,215],{"class":214},[200,13341,13342],{"class":223},"overwrite",[200,13344,227],{"class":235},[200,13346,13347],{"class":210},"imgTagStart",[200,13349,549],{"class":214},[200,13351,13208],{"class":210},[200,13353,549],{"class":214},[200,13355,13242],{"class":210},[200,13357,416],{"class":235},[200,13359,13361],{"class":202,"line":13360},35,[200,13362,528],{"emptyLinePlaceholder":20},[200,13364,13366,13369,13371,13373,13375,13377,13379,13381],{"class":202,"line":13365},36,[200,13367,13368],{"class":210},"          match",[200,13370,509],{"class":214},[200,13372,11955],{"class":210},[200,13374,215],{"class":214},[200,13376,12349],{"class":223},[200,13378,227],{"class":235},[200,13380,156],{"class":210},[200,13382,416],{"class":235},[200,13384,13386,13389,13391,13393,13396,13399,13401],{"class":202,"line":13385},37,[200,13387,13388],{"class":214},"        }",[200,13390,12423],{"class":206},[200,13392,1094],{"class":235},[200,13394,13395],{"class":210},"match",[200,13397,13398],{"class":214}," !==",[200,13400,12432],{"class":214},[200,13402,416],{"class":235},[200,13404,13406],{"class":202,"line":13405},38,[200,13407,528],{"emptyLinePlaceholder":20},[200,13409,13411,13414],{"class":202,"line":13410},39,[200,13412,13413],{"class":206},"        return",[200,13415,280],{"class":214},[200,13417,13419,13422,13424,13426,13428,13430,13432],{"class":202,"line":13418},40,[200,13420,13421],{"class":235},"          code",[200,13423,239],{"class":214},[200,13425,13002],{"class":210},[200,13427,215],{"class":214},[200,13429,11101],{"class":223},[200,13431,1549],{"class":235},[200,13433,252],{"class":214},[200,13435,13437,13440,13442,13444,13446,13449,13451,13453,13456,13458,13460,13462],{"class":202,"line":13436},41,[200,13438,13439],{"class":235},"          map",[200,13441,239],{"class":214},[200,13443,13002],{"class":210},[200,13445,215],{"class":214},[200,13447,13448],{"class":223},"generateMap",[200,13450,227],{"class":235},[200,13452,2767],{"class":214},[200,13454,13455],{"class":235}," hires",[200,13457,239],{"class":214},[200,13459,2439],{"class":2438},[200,13461,980],{"class":214},[200,13463,416],{"class":235},[200,13465,13467],{"class":202,"line":13466},42,[200,13468,13469],{"class":214},"        }\n",[200,13471,13473],{"class":202,"line":13472},43,[200,13474,4493],{"class":214},[200,13476,13478],{"class":202,"line":13477},44,[200,13479,339],{"class":214},[200,13481,13483,13486,13488,13491,13493],{"class":202,"line":13482},45,[200,13484,13485],{"class":235},"    configResolved",[200,13487,227],{"class":214},[200,13489,13490],{"class":386},"config",[200,13492,2104],{"class":214},[200,13494,280],{"class":214},[200,13496,13498,13501,13503],{"class":202,"line":13497},46,[200,13499,13500],{"class":210},"      resolvedConfig",[200,13502,509],{"class":214},[200,13504,13505],{"class":210}," config\n",[200,13507,13509],{"class":202,"line":13508},47,[200,13510,4977],{"class":214},[200,13512,13514],{"class":202,"line":13513},48,[200,13515,4505],{"class":214},[200,13517,13519,13521],{"class":202,"line":13518},49,[200,13520,413],{"class":214},[200,13522,11758],{"class":210},[938,13524,13525],{},[35,13526,13527,13529,13530,13533,13534,13536],{},[200,13528,944],{},"\nLe hook ",[156,13531,13532],{},"configResolved"," est utilisé pour obtenir la configuration Vite résolue, dont nous avons besoin pour accéder au ",[156,13535,12511],{}," où les images sont stockées.",[35,13538,13539,13540],{},"Le code source complet est également disponible sur ",[1599,13541,215],{"repo":13542},"Barbapapazes\u002Fvite-vue-unpic",[35,13544,13545],{},"Avec ce plugin, nous offrons maintenant une meilleure expérience utilisateur sans ajouter de travail manuel à notre processus de création de contenu.",[130,13547,132,13548,132,13553],{},[134,13549,136,13550,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,13551],{"src":13552,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Foptimized-image.mp4",[143,13554,13555],{},"Exemple d'image optimisée avec espace réservé et chargement différé.",[35,13557,13558,13559,1655,13561,13563,13564,13567],{},"Lorsque la page se charge, l'espace de l'image est déjà réservé grâce aux attributs ",[156,13560,11019],{},[156,13562,11028],{},", et un placeholder flouté est affiché en utilisant le style ",[156,13565,13566],{},"background-image"," jusqu'à ce que l'image complète soit chargée. Cela élimine efficacement les problèmes de CLS liés aux images chargées en différé et améliore l'expérience utilisateur globale en fournissant un repère visuel pendant le chargement de l'image.",[51,13569,13571],{"id":13570},"observer-le-pipeline","Observer le pipeline",[35,13573,13574,13575,215],{},"Nous pouvons observer comment notre plugin interagit avec le pipeline Vite en utilisant ",[39,13576,13579],{"href":13577,"rel":13578},"https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-plugin-inspect",[77],"vite-plugin-inspect",[192,13581,13583],{"className":1354,"code":13582,"language":1357,"meta":13,"style":13},"pnpm add -D vite-plugin-inspect\n",[156,13584,13585],{"__ignoreMap":13},[200,13586,13587,13589,13591,13593],{"class":202,"line":203},[200,13588,1365],{"class":1364},[200,13590,2177],{"class":245},[200,13592,1371],{"class":245},[200,13594,13595],{"class":245}," vite-plugin-inspect\n",[35,13597,13598,13599,2186],{},"Ensuite, ajoutez-le au fichier ",[156,13600,1477],{},[192,13602,13604],{"className":1380,"code":13603,"filename":1477,"language":1383,"meta":13,"style":13},"import { defineConfig } from 'vite'\nimport inspect from 'vite-plugin-inspect'\n\nexport default defineConfig({\n  plugins: [\n    inspect()\n\n    \u002F\u002F ...\n  ]\n})\n",[156,13605,13606,13624,13639,13643,13655,13663,13670,13674,13679,13684],{"__ignoreMap":13},[200,13607,13608,13610,13612,13614,13616,13618,13620,13622],{"class":202,"line":203},[200,13609,972],{"class":206},[200,13611,318],{"class":214},[200,13613,1505],{"class":210},[200,13615,980],{"class":214},[200,13617,983],{"class":206},[200,13619,242],{"class":214},[200,13621,1514],{"class":245},[200,13623,991],{"class":214},[200,13625,13626,13628,13631,13633,13635,13637],{"class":202,"line":14},[200,13627,972],{"class":206},[200,13629,13630],{"class":210}," inspect ",[200,13632,1489],{"class":206},[200,13634,242],{"class":214},[200,13636,13579],{"class":245},[200,13638,991],{"class":214},[200,13640,13641],{"class":202,"line":255},[200,13642,528],{"emptyLinePlaceholder":20},[200,13644,13645,13647,13649,13651,13653],{"class":202,"line":272},[200,13646,1390],{"class":206},[200,13648,1393],{"class":206},[200,13650,1505],{"class":223},[200,13652,227],{"class":210},[200,13654,230],{"class":214},[200,13656,13657,13659,13661],{"class":202,"line":283},[200,13658,1537],{"class":235},[200,13660,239],{"class":214},[200,13662,1410],{"class":210},[200,13664,13665,13668],{"class":202,"line":300},[200,13666,13667],{"class":223},"    inspect",[200,13669,466],{"class":210},[200,13671,13672],{"class":202,"line":310},[200,13673,528],{"emptyLinePlaceholder":20},[200,13675,13676],{"class":202,"line":336},[200,13677,13678],{"class":401},"    \u002F\u002F ...\n",[200,13680,13681],{"class":202,"line":342},[200,13682,13683],{"class":210},"  ]\n",[200,13685,13686,13688],{"class":202,"line":365},[200,13687,413],{"class":214},[200,13689,416],{"class":210},[35,13691,13692],{},"Ensuite, lancez le serveur de développement :",[192,13694,13696],{"className":1354,"code":13695,"language":1357,"meta":13,"style":13},"pnpm dev\n",[156,13697,13698],{"__ignoreMap":13},[200,13699,13700,13702],{"class":202,"line":203},[200,13701,1365],{"class":1364},[200,13703,13704],{"class":245}," dev\n",[35,13706,13707,13708,215],{},"Et ouvrez le panneau d'inspection à ",[156,13709,13710],{},"http:\u002F\u002Flocalhost:5173\u002F__inspect\u002F",[35,13712,13713,13714,13717],{},"Vous pouvez voir tous les fichiers traités par Vite. En cliquant sur ",[156,13715,13716],{},"App.vue",", vous pouvez voir comment notre plugin a transformé le code étape par étape.",[35,13719,13720],{},[1707,13721],{"alt":13722,"src":13723},"Vite Plugin Inspect - Étapes de transformation d'image","\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Fvite-plugin-inspect.webp",[51,13725,13726],{"id":2460},"Aller plus loin",[35,13728,13729],{},"À travers cet exemple relativement simple mais utile, j'espère que vous avez appris la puissance des plugins Vite.",[35,13731,13732],{},"Une fois que vous avez compris l'état d'esprit et les bases, vous pouvez automatiser presque tout dans votre projet. Depuis que j'ai appris à créer des plugins Vite, je les utilise tout le temps pour résoudre des problèmes que je ne savais même pas solubles d'une autre manière.",[35,13734,13735,13736,13741,13742,1655,13747,106,13752,13755],{},"Pour en savoir plus sur les plugins, je recommande vivement de lire le code source de plugins Vite populaires comme ",[39,13737,13740],{"href":13738,"rel":13739},"https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue",[77],"vite-plugin-vue"," ou ",[39,13743,13746],{"href":13744,"rel":13745},"https:\u002F\u002Fgithub.com\u002Funplugin\u002Funplugin-icons",[77],"unplugin-icons",[39,13748,13751],{"href":13749,"rel":13750},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fvitepress\u002Ftree\u002Fmain\u002Fsrc\u002Fnode\u002Fplugins",[77],"ceux de VitePress",[39,13753,13579],{"href":13577,"rel":13754},[77]," aide vraiment à comprendre comment les plugins interagissent entre eux et modifient le code.",[35,13757,13758],{},"De plus, c'est un exemple simple. Si vous avez beaucoup d'images, vous voudrez peut-être ajouter de la mise en cache et du traitement parallèle pour accélérer le temps de construction. Vous pourriez également gérer les images distantes en les téléchargeant avant de les traiter.",[35,13760,13761,13762,13764],{},"En parlant d'images distantes, au lieu de lire le système de fichiers, vous pourriez récupérer les images depuis votre bucket, les traiter, et préfixer l'attribut ",[156,13763,11180],{}," avec l'URL de votre CDN. Cela évite d'avoir à commiter des images dans votre dépôt et garde l'URL du CDN à un seul endroit.",[1158,13766],{},[35,13768,13769],{},"J'envisage de créer une petite conférence sur les plugins Vite. Ils sont sous-estimés et peu documentés. Faites-moi savoir si cela vous intéresserait !",[1170,13771,13772],{},"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 .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 .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 .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}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":13,"searchDepth":14,"depth":14,"links":13774},[13775,13776,13777,13778,13779],{"id":11372,"depth":14,"text":11373},{"id":11577,"depth":14,"text":11578},{"id":12245,"depth":14,"text":12246},{"id":13570,"depth":14,"text":13571},{"id":2460,"depth":14,"text":13726},"2025-11-11","Le Cumulative Layout Shift, lorsqu'il est lié aux images, peut être difficile à gérer. Découvrez comment supprimer le CLS automatiquement.",{},"\u002Ffr\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite","---\ncontentId: a505274a-14a6-471e-95a2-a3dbc64cd170\ntitle: Éliminer le CLS en optimisant les images avec Vite\ndescription: Le Cumulative Layout Shift, lorsqu'il est lié aux images, peut être difficile à gérer. Découvrez comment supprimer le CLS automatiquement.\ndate: 2025-11-11\n---\n\nJ'adore Vite, non seulement parce qu'il est rapide, mais aussi pour son incroyable système de plugins.\n\nÀ la base, un plugin Vite vous permet d'éditer, à la volée, n'importe quel fichier importé, qu'il existe ou non, dans votre projet.\n\nUne fois que vous avez compris cela, tout devient possible. _Tout en tant que plugin Vite !_\n\n---\n\nDepuis des années maintenant, je construis des sites web axés sur le contenu où celui-ci est écrit en Markdown. J'aime aussi utiliser des images pour illustrer mon contenu.\n\nCependant, les images sont délicates. Pour améliorer les performances, vous voulez les charger en différé (lazy loading), mais le lazy loading provoque inévitablement du Cumulative Layout Shift (CLS) si vous ne réservez pas d'espace. Pour réserver cet espace, le navigateur doit connaître à l'avance les dimensions de l'image. Mais vous ne voulez pas toujours les mêmes dimensions partout, donc pour chaque image vous avez besoin de ses dimensions afin que le navigateur puisse réserver la bonne quantité d'espace.\n\nAu-delà de ça, les images peuvent rapidement devenir volumineuses, et les héberger dans votre dépôt n'est pas idéal. Vous pourriez donc vouloir les transférer vers un bucket dédié. Cela signifie qu'elles seront servies depuis un domaine différent, ce qui, combiné au chargement différé, même avec les bonnes dimensions, laissera un espace vide jusqu'à ce qu'elles soient entièrement chargées.\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Fcls-issue.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Exemple de problème de Cumulative Layout Shift (CLS) lors du chargement différé d'une image sans espace réservé.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nC'est un problème soluble, mais il nécessite beaucoup de travail manuel. _Je déteste le travail manuel et répétitif qui peut être automatisé !_\n\n> [!NOTE]\n> Cet article est dédié au contenu HTML. [Un autre article](.\u002Fmarkdown-exit-breaks-the-rules-with-async-rendering.md) couvre la gestion du contenu Markdown en utilisant [Markdown Exit](https:\u002F\u002Fmarkdown-exit.pages.dev\u002F).\n\n## Vite à la rescousse\n\nLa première question qui vient à l'esprit est : Comment savoir si un plugin Vite est le bon outil pour le travail ?\n\nC'est une question importante. Je pourrais simplement vous donner la solution, mais vous ne seriez pas en mesure d'appliquer une approche similaire à vos propres problèmes. _Malheureusement, la plupart des tutoriels manquent cette étape._\n\nAnalysons le problème en regardant ses entrées et ses sorties.\n\nL'entrée est probablement un extrait HTML brut comme celui-ci :\n\n```html\n\u003Cdiv>\n  \u003Cimg src=\"\u002Fpath\u002Fto\u002Fimage.jpg\" alt=\"An image\">\n\u003C\u002Fdiv>\n```\n\nLa sortie devrait être un extrait HTML optimisé comme celui-ci :\n\n```html\n\u003Cdiv>\n  \u003Cimg\n    src=\"https:\u002F\u002Fcdn.example.com\u002Fpath\u002Fto\u002Fimage-optimized.jpg\"\n    alt=\"An image\"\n    width=\"600\"\n    height=\"400\"\n    loading=\"lazy\"\n    style=\"background-image: url('data:image\u002Fsvg+xml;base64,...'); background-size: cover;\"\n  >\n\u003C\u002Fdiv>\n```\n\nVite est construit autour d'un pipeline qui traite les fichiers au fur et à mesure qu'ils sont importés. Cela signifie que nous pouvons nous greffer sur le pipeline, détecter quand le code contient une image, et le transformer en conséquence.\n\nNotre cas d'utilisation correspond parfaitement aux capacités de Vite. _Les plugins Vite peuvent avoir des effets de bord sur les fichiers importés. Ils débloquent des cas d'utilisation vraiment puissants comme la compression automatique d'images, le redimensionnement, ..._\n\n> [!NOTE]\n> Cet article suppose que les images sont disponibles localement. La gestion des images distantes ne sera pas couverte ici, mais je donnerai quelques pistes à la fin de l'article.\n\n## Construire le plugin\n\nQuel est le plan ? C'est bien d'avoir une idée de ce que nous voulons accomplir, mais comment y parvenir ?\n\nNous devons nous greffer sur le pipeline de Vite et rechercher les templates Vue. Ensuite, nous devons trouver toutes les balises `\u003Cimg>`, extraire leurs attributs `src`, charger les images, obtenir leurs dimensions, générer le placeholder flouté, et enfin remplacer les balises `\u003Cimg>` originales par des versions optimisées.\n\n> [!NOTE]\n> J'utilise Vue pour tous mes projets, mais la même approche peut être appliquée à n'importe quel framework frontend supporté par Vite.\n\nCela semble plus facile que ça ne l'est, surtout quand on ne sait pas par où commencer et quel outil utiliser.\n\nPour gérer tout le traitement d'image, j'utilise [unpic](https:\u002F\u002Funpic.pics\u002F). C'est un ensemble fantastique de primitives pour gérer tout ce qui concerne les images. Pour manipuler le code et générer des source maps, j'utilise [MagicString](https:\u002F\u002Fgithub.com\u002FRich-Harris\u002Fmagic-string).\n\nC'est tout ce dont nous avons besoin !\n\nMaintenant, nous pouvons créer un plugin Vite qui fait exactement ce que nous voulons.\n\n> [!NOTE]\n> Vous pouvez utiliser un projet Vite + Vue comme point de départ. Le code source du plugin final est disponible sur [GitHub](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fvite-vue-unpic).\n\nDans le fichier `vite.config.ts` de notre projet, créons d'abord un plugin :\n\n```ts [vite.config.ts]\nimport vue from '@vitejs\u002Fplugin-vue'\nimport { defineConfig } from 'vite'\n\nexport default defineConfig({\n  plugins: [\n    vue(),\n    (() => {\n      return {\n        name: 'unpic',\n      }\n    })()\n  ],\n})\n```\n\nAu minimum, un plugin Vite doit avoir un nom. Notre plugin est une IIFE (Immediately Invoked Function Expression). Cela nous permet d'avoir un contexte pour stocker l'état sans dépendre de variables globales.\n\nEnsuite, nous devons nous greffer sur la bonne partie du pipeline. Il existe [beaucoup de hooks](https:\u002F\u002Frollupjs.org\u002Fplugin-development\u002F#build-hooks) disponibles, mais en pratique, le plus utile est `transform`. Ce hook est appelé pour chaque fichier importé dans le projet. Il reçoit le code du fichier et son id (chemin). Nous voulons seulement traiter les fichiers Vue, donc nous pouvons filtrer par extension de fichier.\n\n```ts [vite.config.ts]\n(() => {\n  return {\n    name: 'unpic',\n    async transform(code, id) {\n      if (!id.endsWith('.vue')) \u002F\u002F [!code highlight]\n        return\n\n      \u002F\u002F Some magic will happen here\n\n      return {\n        code,\n        map: null,\n      }\n    }\n  }\n})()\n```\n\nMais nous pouvons le rendre encore plus performant. Appeler notre plugin sur chaque fichier est inutile, surtout avec Rolldown, où la communication entre Rust et JavaScript entraîne un petit surcoût. Pour éviter cela, nous pouvons utiliser des filtres pour ne traiter que les fichiers correspondant à certains critères. Dans notre cas, nous voulons seulement traiter les fichiers Vue.\n\n```ts [vite.config.ts]\n(() => {\n  const imgTagRegex = \u002F\u003Cimg\\s[^>]*src=[\"']([^\"']+)[\"'][^>]*>\u002Fg \u002F\u002F [!code highlight]\n\n  return {\n    name: 'unpic',\n    enforce: 'pre',\n    transform: {\n      filter: {\n        id: \u002F\\.vue$\u002F, \u002F\u002F [!code highlight]\n        code: imgTagRegex, \u002F\u002F [!code highlight]\n      },\n      async handler(code) {},\n    }\n  }\n})()\n```\n\nCe `filter` garantit que notre plugin ne s'exécute que sur les fichiers Vue contenant au moins une balise `\u003Cimg>`. Tellement mieux !\n\n\u003CCalloutReadMore text=\"Filtres de hook de plugin\" href=\"https:\u002F\u002Frolldown.rs\u002Fapis\u002Fplugin-hook-filters\" \u002F>\n\nUne dernière chose avant de pouvoir commencer la magie : nous devons nous assurer que le plugin s'exécute avant le plugin Vue. Vue transforme les fichiers Vue en fichiers JavaScript, et les fichiers Single-File Component (SFC) sont beaucoup plus simples à traiter. Pour ce faire, nous avons deux options :\n\n1. Placer notre plugin avant le plugin Vue dans le tableau `plugins`.\n2. Utiliser l'option `enforce: 'pre'` dans la définition de notre plugin.\n\n```ts [vite.config.ts]\n(() => {\n  return {\n    name: 'unpic',\n    enforce: 'pre', \u002F\u002F [!code highlight]\n    transform: {\n      \u002F\u002F ...\n    }\n  }\n})()\n```\n\nMaintenant, nous pouvons commencer à traiter les fichiers Vue pour améliorer les balises `\u003Cimg>`.\n\n## Transformer le code\n\nDans le hook `transform`, voici ce qui doit être fait :\n\n1. Analyser le code pour trouver toutes les balises `\u003Cimg>`.\n2. Pour chaque balise `\u003Cimg>`, extraire l'attribut `src`.\n3. Lire le fichier depuis le système de fichiers.\n4. Traiter l'image avec `unpic` pour obtenir ses dimensions et un placeholder flouté.\n5. Générer et remplacer la balise `\u003Cimg>` originale par la balise `\u003Cimg>` optimisée avec tous les attributs nécessaires.\n\nAvant de commencer, nous devons installer les dépendances :\n\n```bash\npnpm install -D @unpic\u002Fpixels @unpic\u002Fplaceholder blurhash magic-string\n```\n\nImplémentons cela étape par étape.\n\n1. Analyser le code pour trouver toutes les balises `\u003Cimg>`.\n\n  ```ts [vite.config.ts]\n  \u002F\u002F eslint-disable-next-line prefer-const\n  let match = imgTagRegex.exec(code) \u002F\u002F On réutilise la regex définie plus tôt\n  ```\n\n2. Pour chaque balise `\u003Cimg>`, extraire l'attribut `src`.\n\n  ```ts [vite.config.ts]\n  do {\n    const srcValue = match[1]\n\n    match = imgTagRegex.exec(code)\n  } while (match !== null)\n  ```\n\n3. Lire le fichier depuis le système de fichiers.\n\n  ```ts [vite.config.ts]\n  import { readFile } from 'node:fs\u002Fpromises'\n  import { join } from 'node:path'\n\n  const img = await readFile(join(resolvedConfig.publicDir, srcValue))\n  ```\n\n4. Traiter l'image avec `unpic` pour obtenir ses dimensions et un placeholder flouté.\n\n  ```ts [vite.config.ts]\n  import { getPixels } from '@unpic\u002Fpixels'\n  import { blurhashToDataUri } from '@unpic\u002Fplaceholder'\n  import { encode } from 'blurhash'\n\n  const data = await getPixels(img)\n  const blurhash = encode(Uint8ClampedArray.from(data.data), data.width, data.height, 4, 4)\n  ```\n\n5. Générer et remplacer la balise `\u003Cimg>` originale par la balise `\u003Cimg>` optimisée avec tous les attributs nécessaires.\n\n  ```ts [vite.config.ts]\n  import { blurhashToDataUri } from '@unpic\u002Fplaceholder'\n\n  const newImgTag = match[0].replace(\n    \u002F\u003Cimg(\\s+)\u002F,\n    `\u003Cimg$1width=\"${data.width}\" height=\"${data.height}\" style=\"background-size: cover; background-image: url(${blurhashToDataUri(blurhash)});\" loading=\"lazy\" `\n  )\n  ```\n\nAprès avoir implémenté toutes ces étapes, notre plugin final ressemble à ceci :\n\n```ts [vite.config.ts]\n(() => {\n  let resolvedConfig: ResolvedConfig\n\n  const imgTagRegex = \u002F\u003Cimg\\s[^>]*src=[\"']([^\"']+)[\"'][^>]*>\u002Fg\n\n  return {\n    name: 'unpic',\n    enforce: 'pre',\n    transform: {\n      filter: {\n        id: \u002F\\.vue$\u002F,\n        code: \u002F\u003Cimg\\s[^>]*src=[\"']([^\"']+)[\"'][^>]*>\u002Fg,\n      },\n      async handler(code, id) {\n        const s = new MagicString(code)\n\n        let match = imgTagRegex.exec(code)\n\n        do {\n          const srcValue = match![1]\n\n          const img = await readFile(join(resolvedConfig.publicDir, srcValue))\n          const data = await getPixels(img)\n          const blurhash = encode(Uint8ClampedArray.from(data.data), data.width, data.height, 4, 4)\n\n          const imgTagStart = match!.index\n          const imgTagEnd = imgTagStart + match![0].length\n\n          const newImgTag = match![0].replace(\n            \u002F\u003Cimg(\\s+)\u002F,\n            `\u003Cimg$1width=\"${data.width}\" height=\"${data.height}\" style=\"background-size: cover; background-image: url(${blurhashToDataUri(blurhash)});\" loading=\"lazy\" `\n          )\n\n          s.overwrite(imgTagStart, imgTagEnd, newImgTag)\n\n          match = imgTagRegex.exec(code)\n        } while (match !== null)\n\n        return {\n          code: s.toString(),\n          map: s.generateMap({ hires: true })\n        }\n      }\n    },\n    configResolved(config) {\n      resolvedConfig = config\n    }\n  }\n})()\n```\n\n> [!NOTE]\n> Le hook `configResolved` est utilisé pour obtenir la configuration Vite résolue, dont nous avons besoin pour accéder au `publicDir` où les images sont stockées.\n\nLe code source complet est également disponible sur \u003CGitHubLink repo=\"Barbapapazes\u002Fvite-vue-unpic\" \u002F>.\n\nAvec ce plugin, nous offrons maintenant une meilleure expérience utilisateur sans ajouter de travail manuel à notre processus de création de contenu.\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Foptimized-image.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Exemple d'image optimisée avec espace réservé et chargement différé.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nLorsque la page se charge, l'espace de l'image est déjà réservé grâce aux attributs `width` et `height`, et un placeholder flouté est affiché en utilisant le style `background-image` jusqu'à ce que l'image complète soit chargée. Cela élimine efficacement les problèmes de CLS liés aux images chargées en différé et améliore l'expérience utilisateur globale en fournissant un repère visuel pendant le chargement de l'image.\n\n## Observer le pipeline\n\nNous pouvons observer comment notre plugin interagit avec le pipeline Vite en utilisant [vite-plugin-inspect](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-plugin-inspect).\n\n```sh\npnpm add -D vite-plugin-inspect\n```\n\nEnsuite, ajoutez-le au fichier `vite.config.ts` :\n\n```ts [vite.config.ts]\nimport { defineConfig } from 'vite'\nimport inspect from 'vite-plugin-inspect'\n\nexport default defineConfig({\n  plugins: [\n    inspect()\n\n    \u002F\u002F ...\n  ]\n})\n```\n\nEnsuite, lancez le serveur de développement :\n\n```sh\npnpm dev\n```\n\nEt ouvrez le panneau d'inspection à `http:\u002F\u002Flocalhost:5173\u002F__inspect\u002F`.\n\nVous pouvez voir tous les fichiers traités par Vite. En cliquant sur `App.vue`, vous pouvez voir comment notre plugin a transformé le code étape par étape.\n\n![Vite Plugin Inspect - Étapes de transformation d'image](\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite\u002Fvite-plugin-inspect.webp)\n\n## Aller plus loin\n\nÀ travers cet exemple relativement simple mais utile, j'espère que vous avez appris la puissance des plugins Vite.\n\nUne fois que vous avez compris l'état d'esprit et les bases, vous pouvez automatiser presque tout dans votre projet. Depuis que j'ai appris à créer des plugins Vite, je les utilise tout le temps pour résoudre des problèmes que je ne savais même pas solubles d'une autre manière.\n\nPour en savoir plus sur les plugins, je recommande vivement de lire le code source de plugins Vite populaires comme [vite-plugin-vue](https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue) ou [unplugin-icons](https:\u002F\u002Fgithub.com\u002Funplugin\u002Funplugin-icons) et [ceux de VitePress](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fvitepress\u002Ftree\u002Fmain\u002Fsrc\u002Fnode\u002Fplugins). [vite-plugin-inspect](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-plugin-inspect) aide vraiment à comprendre comment les plugins interagissent entre eux et modifient le code.\n\nDe plus, c'est un exemple simple. Si vous avez beaucoup d'images, vous voudrez peut-être ajouter de la mise en cache et du traitement parallèle pour accélérer le temps de construction. Vous pourriez également gérer les images distantes en les téléchargeant avant de les traiter.\n\nEn parlant d'images distantes, au lieu de lire le système de fichiers, vous pourriez récupérer les images depuis votre bucket, les traiter, et préfixer l'attribut `src` avec l'URL de votre CDN. Cela évite d'avoir à commiter des images dans votre dépôt et garde l'URL du CDN à un seul endroit.\n\n---\n\nJ'envisage de créer une petite conférence sur les plugins Vite. Ils sont sous-estimés et peu documentés. Faites-moi savoir si cela vous intéresserait !\n",{"title":11311,"description":13781},{"loc":13783,"images":13787},[13788],{"loc":13723},"fr\u002Fposts\u002Ferase-cls-by-automatically-optimizing-images-with-vite","jsajhQBNt6EJEOo1Q1PMAxNYTF9QVsDDRERmz53u9BM",{"id":13792,"title":13793,"body":13794,"contentId":9320,"date":14113,"description":14114,"extension":18,"meta":14115,"navigation":20,"path":14116,"rawbody":14117,"robots":23,"schemaOrg":23,"seo":14118,"sitemap":14119,"stem":14127,"__hash__":14128},"posts\u002Ffr\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux.md","Deux fois en une journée : parler à BDX I\u002FO 2025 à Bordeaux",{"type":7,"value":13795,"toc":14108},[13796,13805,13808,13814,13817,13821,13824,13832,13841,13844,13850,13859,13868,13882,13888,13892,13895,13901,13909,13915,13918,13921,13924,13999,14002,14008,14012,14015,14018,14021,14045,14051,14054,14060,14066,14069,14072,14074,14077,14084,14087,14096,14099,14102],[35,13797,13798,13799,13804],{},"Cette année, et pour la première fois, j'ai participé à ",[39,13800,13803],{"href":13801,"rel":13802},"https:\u002F\u002Fbdxio.fr",[77],"BDX I\u002FO",", une conférence développeurs qui se tient à Bordeaux, en France. J'ai soumis deux talks, et les deux ont été acceptés ! J'étais vraiment heureux d'avoir cette opportunité jusqu'à ce que je réalise que la conférence ne durait qu'une seule journée.",[35,13806,13807],{},"Je devais donner les deux talks le même jour, même la même après-midi ! Le premier était à 13:55, et le second à 15:10. Un planning serré et donc un vrai challenge.",[35,13809,13810,13813],{},[1216,13811,13812],{},"Ne faites pas ça chez vous. Réalisé par un professionnel !"," Blague à part, il faut connaître sa présentation sur le bout des doigts. Le changement de contexte est difficile, et vous ne voulez pas vous tromper. Une fois le premier talk terminé, je devais complètement l'oublier et me reconcentrer sur le second.",[35,13815,13816],{},"Laisse moi te raconter davantage mon voyage à Bordeaux.",[51,13818,13820],{"id":13819},"où-nuxt-est-fabriqué","Où Nuxt est fabriqué",[35,13822,13823],{},"BDX I\u002FO a eu lieu à Bordeaux. Tu connais peut être la ville pour beaucoup de choses, comme son vin.",[35,13825,13826,13827,13831],{},"Mais savais tu que c'est aussi là que ",[39,13828,2934],{"href":13829,"rel":13830},"https:\u002F\u002Fnuxt.com",[77],", le framework, est né ? En octobre 2016, Alexandre et Sébastien Chopin ont commencé à travailler sur Nuxt pour faciliter le server side rendering avec Vue.js.",[35,13833,13834,13835,13840],{},"Le siège de NuxtLabs, la société derrière Nuxt, ",[39,13836,13839],{"href":13837,"rel":13838},"https:\u002F\u002Fnuxtlabs.com\u002F",[77],"racheté par Vercel",", est situé à Bordeaux. J'ai profité de mon voyage pour aller les voir, en vrai cette fois. Je parle avec l'équipe principalement en ligne, via Discord et GitHub.",[35,13842,13843],{},"À Bordeaux, seuls Sébastien et Benjamin sont restés. La plupart de l'équipe opère maintenant à distance. Donc, après être allé à mon auberge, je me suis dirigé vers leurs bureaux.",[35,13845,13846],{},[1707,13847],{"alt":13848,"src":13849},"Bureaux de NuxtLabs à Bordeaux","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fnuxtlabs-office-bordeaux.webp",[35,13851,13852,13853,13858],{},"J'ai trouvé la fameuse plaque Nuxt mais pas la ",[39,13854,13857],{"href":13855,"rel":13856},"https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1986454979763138802",[77],"sonnette",". J'ai envoyé un message à Sébastien mais n'ai pas eu de réponse.",[35,13860,13861,13862,13867],{},"Alors j'ai continué à visiter les environs et après une demi heure, j'ai décidé de réessayer. Cette fois, ",[39,13863,13866],{"href":13864,"rel":13865},"https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1986462410585055599",[77],"j'ai vu la sonnette"," et j'ai sonné. Quelques secondes plus tard, Sébastien a ouvert la porte et m'a accueilli à l'intérieur.",[35,13869,13870,13871,1655,13876,13881],{},"Avec ",[39,13872,13875],{"href":13873,"rel":13874},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fatinux\u002F",[77],"Sébastien",[39,13877,13880],{"href":13878,"rel":13879},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbenjamincanac\u002F",[77],"Benjamin",", nous avons parlé deux heures de Nuxt, Studio, Hub, l'écosystème, Vercel, ... J'ai vraiment apprécié les rencontrer enfin en personne, après toutes ces années d'interactions en ligne.",[35,13883,13884],{},[1707,13885],{"alt":13886,"src":13887},"De droite à gauche : Estéban Soubiran, Sébastien Chopin, Benjamin Canac","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fnuxtlabs-team-bordeaux.webp",[51,13889,13891],{"id":13890},"bdx-io-2025","BDX I\u002FO 2025",[35,13893,13894],{},"BDX I\u002FO est une conférence d'une journée pour développeurs à Bordeaux depuis 2014. Cette année, elle a eu lieu au Palais des Congrès de Bordeaux Lac. Elle attire environ 1 000 participants, 50 conférences réparties sur 8 tracks, et plus de 20 sponsors.",[35,13896,13897],{},[1707,13898],{"alt":13899,"src":13900},"La scène principale à BDX I\u002FO","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fmain-stage-bdx-io.webp",[35,13902,13903,13904,13908],{},"L'endroit était vraiment agréable, avec de grands amphithéâtres, beaucoup de lumière, et une superbe vue sur le lac. Une grande salle était dédiée aux sponsors, avec de la nourriture et des boissons disponibles toute la journée. J'ai vu ",[39,13905,13907],{"href":6953,"rel":13906},[77],"Julien"," de Deezer mixer. Ils ont réussi à créer une piste de danse dans l'espace sponsors ! Et un artiste m'a dessiné une caricature. Vraiment cool !",[35,13910,13911],{},[1707,13912],{"alt":13913,"src":13914},"Espace sponsors et restauration à BDX I\u002FO","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fsponsor-area-bdx-io.webp",[35,13916,13917],{},"La veille de la conférence, il y avait un dîner speakers dans un Doppio Malto près du centre des congrès. Comme d'habitude, c'est toujours un plaisir de rencontrer d'autres speakers, s'interroger sur leurs talks, ou juste discuter de tech et de la vie.",[35,13919,13920],{},"Cette fois-ci, j'ai essayé d'assister à des talks pas directement liés à la technique. Je voulais explorer davantage le monde autour du développement.",[35,13922,13923],{},"Voici donc les talks auxquels j'ai assisté :",[1593,13925,13926],{},[71,13927,13928,13931],{},[6862,13929,13930],{},"Vendredi 7 novembre 2025",[1593,13932,13933,13943,13953,13963,13969,13984,13989],{},[71,13934,13935,6865,13938],{},[6862,13936,13937],{},"Prédire l'imprévisible : comment penser le futur à l'ère de l'IA",[39,13939,13942],{"href":13940,"rel":13941},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fludoviccinquin\u002F",[77],"Ludovic Cinquin",[71,13944,13945,6865,13948],{},[6862,13946,13947],{},"Réécrire le rôle : développeur à l'ère des LLM",[39,13949,13952],{"href":13950,"rel":13951},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fhoraciogonzalez\u002F",[77],"Horacio Gonzalez",[71,13954,13955,6865,13958],{},[6862,13956,13957],{},"Onboarding 2.0 : Réinventer l'intégration des devs",[39,13959,13962],{"href":13960,"rel":13961},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fhafsa-el-maizi\u002F",[77],"Hafsa EL MAIZI",[71,13964,13965,13968],{},[6862,13966,13967],{},"En JavaScript, il y a un paquet pour tout... et souvent pour rien."," par Estéban Soubiran (c'est moi !)",[71,13970,13971,6865,13974,1655,13979],{},[6862,13972,13973],{},"Rex : innover les transports en commun pour accueillir les Jeux Olympiques 2024",[39,13975,13978],{"href":13976,"rel":13977},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fghislain-mokolomboka-3bb86236\u002F",[77],"Ghislain MOKOLOMBOKA",[39,13980,13983],{"href":13981,"rel":13982},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolasjozwiak\u002F",[77],"Nicolas Jozwiak",[71,13985,13986,13988],{},[6862,13987,9137],{}," par Estéban Soubiran (c'est encore moi !)",[71,13990,13991,6865,13994],{},[6862,13992,13993],{},"Parlons d'entretiens techniques",[39,13995,13998],{"href":13996,"rel":13997},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fane-diaz-de-tuesta-37785252\u002F",[77],"Ane Diaz De Tuesta",[35,14000,14001],{},"Avec 8 tracks en parallèle, je sais que j'ai raté beaucoup de super talks, donc je devrai attendre la publication des vidéos.",[35,14003,14004],{},[1707,14005],{"alt":14006,"src":14007},"Photo de groupe des organisateurs de BDX I\u002FO","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fbdx-io-organizers-group-photo.webp",[51,14009,14011],{"id":14010},"parler-deux-fois","Parler deux fois",[35,14013,14014],{},"Donner deux talks est une opportunité. Cela montre la confiance des organisateurs dans mes présentations. J'ai déjà eu cette opportunité durant Devoxx 2025, MiXiT 2025 et BreizhCamp 2025. Mais cette fois, c'était différent parce que les deux talks étaient le même jour, avec seulement 1 heure entre.",[35,14016,14017],{},"Quand j'ai soumis mes talks à BDX I\u002FO, je ne m'attendais pas à ce que les deux soient acceptés. Je n'ai jamais imaginé devoir parler deux fois d'affilée. Donc, quand j'ai reçu deux emails d'acceptation, j'étais à la fois heureux de parler dans une nouvelle conférence et un peu stressé par le planning. Je l'ai pris comme un challenge, et j'adore les challenges.",[35,14019,14020],{},"Ainsi, le 7 novembre 2025, j'ai donné mes deux talks :",[1593,14022,14023,14035],{},[71,14024,14025,14027,14028,215],{},[6862,14026,13967],{}," à 13:55, quinze minutes pour explorer la performance de l'écosystème et montrer des outils pour explorer ",[39,14029,14032],{"href":14030,"rel":14031},"https:\u002F\u002Fe18e.dev\u002F",[77],[156,14033,14034],{},"node_modules",[71,14036,14037,14039,14040,215],{},[6862,14038,9137],{}," à 15:10, quarante cinq minutes pour expliquer les signaux via l'incroyable librairie ",[39,14041,14044],{"href":14042,"rel":14043},"https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals",[77],"Alien Signals",[35,14046,14047],{},[1707,14048],{"alt":14049,"src":14050},"Moi à BDX I\u002FO pendant mon second talk","\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fspeaking-bdx-io.webp",[35,14052,14053],{},"Les deux talks se sont bien passés, et j'ai reçu des retours positifs du public.",[35,14055,14056,14057],{},"C'était la sixième fois que je les donnais, donc je les connais vraiment par coeur. Je sais exactement quoi dire, quand faire une pause, et ce qui arrive quand j'appuie sur la télécommande. Cette familiarité m'a aidé à gérer le planning serré. Honnêtement, je ne recommanderais pas ça si tu ne maîtrises pas parfaitement tes talks. ",[1216,14058,14059],{},"L'entraînement, c'est la clé !",[35,14061,14062,14063,14065],{},"Une fois le premier talk terminé, je devais complètement changer de contexte, oublier les ",[156,14064,14034],{}," JavaScript, et me concentrer sur les signaux. Pour éviter le stress, je suis allé à un talk dans une autre salle, comme si j'étais un participant. Cela m'a aidé à penser à quelque chose d'entièrement différent.",[35,14067,14068],{},"Ensuite, je suis allé dans la salle où je devais donner mon second talk, j'ai installé mon ordinateur portable, et j'ai attendu l'heure de commencer. Je n'ai même pas eu le temps d'être nerveux ou stressé.",[35,14070,14071],{},"Quand j'ai fini, j'étais complètement épuisé avec un mal de tête. Je suis allé à un dernier talk, où je me suis presque endormi assis, j'ai écouté le keynote final, et suis retourné à la gare pour rentrer. J'ai dormi 12+ heures le lendemain pour récupérer. C'est à quel point c'était intense !",[1158,14073],{},[35,14075,14076],{},"Tu pourrais penser que tout était sous contrôle. Mais non !",[35,14078,14079,14080,14083],{},"Quelques minutes avant mon premier talk, j'ai poussé du code en production sur ",[39,14081,7894],{"href":7892,"rel":14082},[77],", l'outil que j'utilise pour rendre mes slides interactives. Je devais aussi créer des questions sur Inalia pour les intégrer à mes slides.",[35,14085,14086],{},"Pourquoi ne pas le faire plus tôt ? Parce qu'honnêtement j'avais tellement de choses à faire avant de partir à Bordeaux que je n'ai pas trouvé le temps. J'ai vu un ami et nous sommes allés au théâtre, participé à un meetup, commencé à préparer les talks, écrit et publié la communication, et plus encore.",[35,14088,14089,14090,14095],{},"La nuit avant la conférence, j'ai dû finir ",[39,14091,14094],{"href":14092,"rel":14093},"https:\u002F\u002Fwww.linkedin.com\u002Ffeed\u002Fupdate\u002Furn:li:activity:7392459559274983425\u002F",[77],"le post de blog sur NuxtLabs"," et ma visite dans leurs bureaux. J'ai dormi moins de 6 heures cette nuit là. Le jour de la conférence était dur, et j'ai pris une petite sieste pendant la pause déjeuner pour survivre l'après midi. À la fin de la journée, je pensais seulement à rentrer et dormir, mais j'avais encore trois heures de train.",[35,14097,14098],{},"Quand tu dois gérer tout seul, c'est malheureusement inévitable.",[35,14100,14101],{},"De l'imagination et la préparation des talks, à la communication autour d'eux, voyager jusqu'aux conférences, et parler, tout est fait sur mon temps libre - soirées et week ends. Il n'y a pas d'entreprise derrière moi pour aider.",[35,14103,14104,14105],{},"Donc tu peux penser que c'est glamour de parler dans des conférences, mais c'est beaucoup de travail et peu de repos. ",[1216,14106,14107],{},"La plupart des gens ne parlent jamais de ce côté de l'histoire.",{"title":13,"searchDepth":14,"depth":14,"links":14109},[14110,14111,14112],{"id":13819,"depth":14,"text":13820},{"id":13890,"depth":14,"text":13891},{"id":14010,"depth":14,"text":14011},"2025-11-10","J'ai eu l'opportunité de présenter deux fois en une seule journée à BDX I\u002FO. C'était un défi unique qui exigeait une maîtrise parfaite de mes présentations.",{},"\u002Ffr\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux","---\ncontentId: 49bf687d-212a-4a3c-af76-ec3c1dac3089\ntitle: \"Deux fois en une journée : parler à BDX I\u002FO 2025 à Bordeaux\"\ndescription: J'ai eu l'opportunité de présenter deux fois en une seule journée à BDX I\u002FO. C'était un défi unique qui exigeait une maîtrise parfaite de mes présentations.\ndate: 2025-11-10\n---\n\nCette année, et pour la première fois, j'ai participé à [BDX I\u002FO](https:\u002F\u002Fbdxio.fr), une conférence développeurs qui se tient à Bordeaux, en France. J'ai soumis deux talks, et les deux ont été acceptés ! J'étais vraiment heureux d'avoir cette opportunité jusqu'à ce que je réalise que la conférence ne durait qu'une seule journée.\n\nJe devais donner les deux talks le même jour, même la même après-midi ! Le premier était à 13:55, et le second à 15:10. Un planning serré et donc un vrai challenge.\n\n_Ne faites pas ça chez vous. Réalisé par un professionnel !_ Blague à part, il faut connaître sa présentation sur le bout des doigts. Le changement de contexte est difficile, et vous ne voulez pas vous tromper. Une fois le premier talk terminé, je devais complètement l'oublier et me reconcentrer sur le second.\n\nLaisse moi te raconter davantage mon voyage à Bordeaux.\n\n## Où Nuxt est fabriqué\n\nBDX I\u002FO a eu lieu à Bordeaux. Tu connais peut être la ville pour beaucoup de choses, comme son vin.\n\nMais savais tu que c'est aussi là que [Nuxt](https:\u002F\u002Fnuxt.com), le framework, est né ? En octobre 2016, Alexandre et Sébastien Chopin ont commencé à travailler sur Nuxt pour faciliter le server side rendering avec Vue.js.\n\nLe siège de NuxtLabs, la société derrière Nuxt, [racheté par Vercel](https:\u002F\u002Fnuxtlabs.com\u002F), est situé à Bordeaux. J'ai profité de mon voyage pour aller les voir, en vrai cette fois. Je parle avec l'équipe principalement en ligne, via Discord et GitHub.\n\nÀ Bordeaux, seuls Sébastien et Benjamin sont restés. La plupart de l'équipe opère maintenant à distance. Donc, après être allé à mon auberge, je me suis dirigé vers leurs bureaux.\n\n![Bureaux de NuxtLabs à Bordeaux](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fnuxtlabs-office-bordeaux.webp)\n\nJ'ai trouvé la fameuse plaque Nuxt mais pas la [sonnette](https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1986454979763138802). J'ai envoyé un message à Sébastien mais n'ai pas eu de réponse.\n\nAlors j'ai continué à visiter les environs et après une demi heure, j'ai décidé de réessayer. Cette fois, [j'ai vu la sonnette](https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1986462410585055599) et j'ai sonné. Quelques secondes plus tard, Sébastien a ouvert la porte et m'a accueilli à l'intérieur.\n\nAvec [Sébastien](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fatinux\u002F) et [Benjamin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbenjamincanac\u002F), nous avons parlé deux heures de Nuxt, Studio, Hub, l'écosystème, Vercel, ... J'ai vraiment apprécié les rencontrer enfin en personne, après toutes ces années d'interactions en ligne.\n\n![De droite à gauche : Estéban Soubiran, Sébastien Chopin, Benjamin Canac](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fnuxtlabs-team-bordeaux.webp)\n\n## BDX I\u002FO 2025\n\nBDX I\u002FO est une conférence d'une journée pour développeurs à Bordeaux depuis 2014. Cette année, elle a eu lieu au Palais des Congrès de Bordeaux Lac. Elle attire environ 1 000 participants, 50 conférences réparties sur 8 tracks, et plus de 20 sponsors.\n\n![La scène principale à BDX I\u002FO](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fmain-stage-bdx-io.webp)\n\nL'endroit était vraiment agréable, avec de grands amphithéâtres, beaucoup de lumière, et une superbe vue sur le lac. Une grande salle était dédiée aux sponsors, avec de la nourriture et des boissons disponibles toute la journée. J'ai vu [Julien](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F) de Deezer mixer. Ils ont réussi à créer une piste de danse dans l'espace sponsors ! Et un artiste m'a dessiné une caricature. Vraiment cool !\n\n![Espace sponsors et restauration à BDX I\u002FO](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fsponsor-area-bdx-io.webp)\n\nLa veille de la conférence, il y avait un dîner speakers dans un Doppio Malto près du centre des congrès. Comme d'habitude, c'est toujours un plaisir de rencontrer d'autres speakers, s'interroger sur leurs talks, ou juste discuter de tech et de la vie.\n\nCette fois-ci, j'ai essayé d'assister à des talks pas directement liés à la technique. Je voulais explorer davantage le monde autour du développement.\n\nVoici donc les talks auxquels j'ai assisté :\n\n- **Vendredi 7 novembre 2025**\n  - **Prédire l'imprévisible : comment penser le futur à l'ère de l'IA** par [Ludovic Cinquin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fludoviccinquin\u002F)\n  - **Réécrire le rôle : développeur à l'ère des LLM** par [Horacio Gonzalez](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fhoraciogonzalez\u002F)\n  - **Onboarding 2.0 : Réinventer l'intégration des devs** par [Hafsa EL MAIZI](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fhafsa-el-maizi\u002F)\n  - **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** par Estéban Soubiran (c'est moi !)\n  - **Rex : innover les transports en commun pour accueillir les Jeux Olympiques 2024** par [Ghislain MOKOLOMBOKA](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fghislain-mokolomboka-3bb86236\u002F) et [Nicolas Jozwiak](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolasjozwiak\u002F)\n  - **La réactivité et les signaux : démystifions la magie du frontend** par Estéban Soubiran (c'est encore moi !)\n  - **Parlons d'entretiens techniques** par [Ane Diaz De Tuesta](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fane-diaz-de-tuesta-37785252\u002F)\n\nAvec 8 tracks en parallèle, je sais que j'ai raté beaucoup de super talks, donc je devrai attendre la publication des vidéos.\n\n![Photo de groupe des organisateurs de BDX I\u002FO](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fbdx-io-organizers-group-photo.webp)\n\n## Parler deux fois\n\nDonner deux talks est une opportunité. Cela montre la confiance des organisateurs dans mes présentations. J'ai déjà eu cette opportunité durant Devoxx 2025, MiXiT 2025 et BreizhCamp 2025. Mais cette fois, c'était différent parce que les deux talks étaient le même jour, avec seulement 1 heure entre.\n\nQuand j'ai soumis mes talks à BDX I\u002FO, je ne m'attendais pas à ce que les deux soient acceptés. Je n'ai jamais imaginé devoir parler deux fois d'affilée. Donc, quand j'ai reçu deux emails d'acceptation, j'étais à la fois heureux de parler dans une nouvelle conférence et un peu stressé par le planning. Je l'ai pris comme un challenge, et j'adore les challenges.\n\nAinsi, le 7 novembre 2025, j'ai donné mes deux talks :\n\n- **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** à 13:55, quinze minutes pour explorer la performance de l'écosystème et montrer des outils pour explorer [`node_modules`](https:\u002F\u002Fe18e.dev\u002F).\n- **La réactivité et les signaux : démystifions la magie du frontend** à 15:10, quarante cinq minutes pour expliquer les signaux via l'incroyable librairie [Alien Signals](https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals).\n\n![Moi à BDX I\u002FO pendant mon second talk](\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux\u002Fspeaking-bdx-io.webp)\n\nLes deux talks se sont bien passés, et j'ai reçu des retours positifs du public.\n\nC'était la sixième fois que je les donnais, donc je les connais vraiment par coeur. Je sais exactement quoi dire, quand faire une pause, et ce qui arrive quand j'appuie sur la télécommande. Cette familiarité m'a aidé à gérer le planning serré. Honnêtement, je ne recommanderais pas ça si tu ne maîtrises pas parfaitement tes talks. _L'entraînement, c'est la clé !_\n\nUne fois le premier talk terminé, je devais complètement changer de contexte, oublier les `node_modules` JavaScript, et me concentrer sur les signaux. Pour éviter le stress, je suis allé à un talk dans une autre salle, comme si j'étais un participant. Cela m'a aidé à penser à quelque chose d'entièrement différent.\n\nEnsuite, je suis allé dans la salle où je devais donner mon second talk, j'ai installé mon ordinateur portable, et j'ai attendu l'heure de commencer. Je n'ai même pas eu le temps d'être nerveux ou stressé.\n\nQuand j'ai fini, j'étais complètement épuisé avec un mal de tête. Je suis allé à un dernier talk, où je me suis presque endormi assis, j'ai écouté le keynote final, et suis retourné à la gare pour rentrer. J'ai dormi 12+ heures le lendemain pour récupérer. C'est à quel point c'était intense !\n\n---\n\nTu pourrais penser que tout était sous contrôle. Mais non !\n\nQuelques minutes avant mon premier talk, j'ai poussé du code en production sur [Inalia](https:\u002F\u002Finalia.app), l'outil que j'utilise pour rendre mes slides interactives. Je devais aussi créer des questions sur Inalia pour les intégrer à mes slides.\n\nPourquoi ne pas le faire plus tôt ? Parce qu'honnêtement j'avais tellement de choses à faire avant de partir à Bordeaux que je n'ai pas trouvé le temps. J'ai vu un ami et nous sommes allés au théâtre, participé à un meetup, commencé à préparer les talks, écrit et publié la communication, et plus encore.\n\nLa nuit avant la conférence, j'ai dû finir [le post de blog sur NuxtLabs](https:\u002F\u002Fwww.linkedin.com\u002Ffeed\u002Fupdate\u002Furn:li:activity:7392459559274983425\u002F) et ma visite dans leurs bureaux. J'ai dormi moins de 6 heures cette nuit là. Le jour de la conférence était dur, et j'ai pris une petite sieste pendant la pause déjeuner pour survivre l'après midi. À la fin de la journée, je pensais seulement à rentrer et dormir, mais j'avais encore trois heures de train.\n\nQuand tu dois gérer tout seul, c'est malheureusement inévitable.\n\nDe l'imagination et la préparation des talks, à la communication autour d'eux, voyager jusqu'aux conférences, et parler, tout est fait sur mon temps libre - soirées et week ends. Il n'y a pas d'entreprise derrière moi pour aider.\n\nDonc tu peux penser que c'est glamour de parler dans des conférences, mais c'est beaucoup de travail et peu de repos. _La plupart des gens ne parlent jamais de ce côté de l'histoire._\n",{"title":13793,"description":14114},{"loc":14116,"images":14120},[14121,14122,14123,14124,14125,14126],{"loc":13849},{"loc":13887},{"loc":13900},{"loc":13914},{"loc":14007},{"loc":14050},"fr\u002Fposts\u002Ftwice-in-one-day-speaking-at-bdx-io-2025-at-bordeaux","yevR0GFSCpIwcp6wdONBEIBGfhmpsQfh5CxiuT2w8b8",{"id":14130,"title":14131,"body":14132,"contentId":9340,"date":16366,"description":16367,"extension":18,"meta":16368,"navigation":20,"path":16369,"rawbody":16370,"robots":23,"schemaOrg":23,"seo":16371,"sitemap":16372,"stem":16375,"__hash__":16376},"posts\u002Ffr\u002Fposts\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong.md","Envie de contribuer à l'open source ? Vous vous y prenez mal",{"type":7,"value":14133,"toc":16360},[14134,14137,14140,14143,14146,14149,14151,14163,14170,14173,14196,14199,14202,14213,14217,14220,14228,14231,14234,14237,14366,14376,14383,14529,14532,14535,14545,14548,14551,14557,14573,14580,15306,15309,15318,15324,15339,15342,15345,15348,15351,15598,15605,15612,15615,15618,15734,15741,15744,15751,15754,15757,15763,15766,15770,15773,15776,15779,15786,15789,16088,16091,16094,16100,16106,16117,16202,16209,16216,16357],[35,14135,14136],{},"Je contribue activement à l'open source depuis 2020. Depuis, j'ai parlé avec beaucoup de développeurs qui veulent contribuer mais ne savent pas par où commencer, demandant souvent une issue sur laquelle travailler.",[35,14138,14139],{},"Je suis profondément convaincu que l'open source, c'est plus que ça et, surtout, que ça ne marche pas comme ça.",[35,14141,14142],{},"Pour moi, l'open source, au fond, est un état d'esprit. C'est la liberté d'explorer, de comprendre, de modifier et d'améliorer le code source du logiciel que tu veux. Bien sûr, ce modèle encourage la collaboration et le partage en permettant à chacun de contribuer, mais il ne t'y oblige pas.",[35,14144,14145],{},"L'open source ne devrait pas, et ne doit jamais, être une fin en soi. Ça fait partie du chemin, pas de la destination. Sinon, tu finiras frustré et insatisfait de ne jamais réussir à contribuer.",[35,14147,14148],{},"Laisse-moi te raconter une histoire pour illustrer ce point.",[1158,14150],{},[35,14152,14153,14154,14159,14160],{},"Je construis un nouveau site appelé ",[39,14155,14158],{"href":14156,"rel":14157},"https:\u002F\u002Finfra.soubiran.dev",[77],"infra.soubiran.dev"," pour documenter ma propre infrastructure. C'est un wiki personnel mais ouvert à tous. ",[1216,14161,14162],{},"Je n'entrerai pas dans les détails, un article dédié viendra plus tard.",[35,14164,14165,14166,14169],{},"Pour le construire, j'ai dû trouver les bons outils. Jusqu'à présent, j'utilisais ",[39,14167,14168],{"href":4715},"VitePress avec un thème personnalisé pour mon blog personnel",", celui que tu es en train de lire. Ça marche très bien, mais j'en ai presque atteint les limites, et avec toutes les idées que j'ai, je sais qu'il pourrait être temps de passer à autre chose.",[35,14171,14172],{},"Alors, j'ai commencé à regarder des alternatives. Honnêtement, il y a deux options :",[68,14174,14175,14187],{},[71,14176,14177,14178,14181,14182],{},"Utiliser ",[39,14179,2934],{"href":13829,"rel":14180},[77]," avec ",[39,14183,14186],{"href":14184,"rel":14185},"https:\u002F\u002Fcontent.nuxt.com",[77],"Nuxt Content",[71,14188,14189,14190,14195],{},"Utiliser Vite avec Vue et quelques ",[39,14191,14194],{"href":14192,"rel":14193},"https:\u002F\u002Funplugin.unjs.io\u002F",[77],"unplugins"," pour gérer les fichiers Markdown.",[35,14197,14198],{},"J'ai choisi la seconde option. Assez drôle, avant de passer à VitePress, j'avais le même dilemme : Nuxt Content, VitePress, ou Vite avec Vue. À l'époque, j'avais choisi VitePress.",[35,14200,14201],{},"Bref, j'ai commencé à construire le site.",[35,14203,14204,14205,14209,14210],{},"Depuis quelques mois, il y a une techno dont je ne peux plus me passer : ",[39,14206,9203],{"href":14207,"rel":14208},"https:\u002F\u002Fui.nuxt.com",[77],". C'est une bibliothèque de composants incroyable réalisée par l'équipe Nuxt. Tous mes projets qui utilisent Vue ou Nuxt utilisent Nuxt UI, et je le pousse régulièrement dans ses retranchements. ",[1216,14211,14212],{},"C'est aussi une des raisons pour lesquelles j'ai décidé de m'éloigner de VitePress, qui n'est pas compatible avec Nuxt UI.",[51,14214,14216],{"id":14215},"construire-avec-des-logiciels-open-source","Construire avec des logiciels open source",[35,14218,14219],{},"Le site que je construis est orienté contenu. Ça signifie que le contenu est principalement textuel et, dans mon cas, toutes les pages sont écrites en Markdown. Pour mettre à jour le site, j'édite les fichiers Markdown puis je reconstruis le site.",[35,14221,14222,14223,215],{},"Les pages sont générées et pré-rendues au moment du build. Il n'y a pas besoin de les générer à l'exécution, puisque chaque utilisateur recevra le même contenu pré-rendu. Pour que ça marche, j'utilise ",[39,14224,14227],{"href":14225,"rel":14226},"https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-ssg",[77],"Vite SSG",[35,14229,14230],{},"C'est un remplaçant pour Vite lors du build du site. Il construit automatiquement le client et le serveur, puis utilise le serveur pour pré-rendre chaque page, comme si un utilisateur la demandait.",[35,14232,14233],{},"Après l'installation, tout fonctionnait comme prévu.",[35,14235,14236],{},"J'ai donc continué à construire le site. J'ai installé Nuxt UI et commencé à le configurer. Pour utiliser Nuxt UI dans une application Vue, il faut utiliser un plugin.",[192,14238,14241],{"className":9755,"code":14239,"filename":14240,"language":1383,"meta":13,"style":13},"import ui from '@nuxt\u002Fui\u002Fvue-plugin'\nimport { ViteSSG } from 'vite-ssg'\nimport App from '.\u002FApp.vue'\n\nexport const createApp = ViteSSG(\n  App,\n  {},\n  ({ app }) => {\n    app.use(ui) \u002F\u002F [!code highlight]\n  },\n)\n","src\u002Fmain.ts",[156,14242,14243,14259,14279,14295,14299,14315,14322,14327,14341,14358,14362],{"__ignoreMap":13},[200,14244,14245,14247,14250,14252,14254,14257],{"class":202,"line":203},[200,14246,972],{"class":206},[200,14248,14249],{"class":210}," ui ",[200,14251,1489],{"class":206},[200,14253,242],{"class":214},[200,14255,14256],{"class":245},"@nuxt\u002Fui\u002Fvue-plugin",[200,14258,991],{"class":214},[200,14260,14261,14263,14265,14268,14270,14272,14274,14277],{"class":202,"line":14},[200,14262,972],{"class":206},[200,14264,318],{"class":214},[200,14266,14267],{"class":210}," ViteSSG",[200,14269,980],{"class":214},[200,14271,983],{"class":206},[200,14273,242],{"class":214},[200,14275,14276],{"class":245},"vite-ssg",[200,14278,991],{"class":214},[200,14280,14281,14283,14286,14288,14290,14293],{"class":202,"line":255},[200,14282,972],{"class":206},[200,14284,14285],{"class":210}," App ",[200,14287,1489],{"class":206},[200,14289,242],{"class":214},[200,14291,14292],{"class":245},".\u002FApp.vue",[200,14294,991],{"class":214},[200,14296,14297],{"class":202,"line":272},[200,14298,528],{"emptyLinePlaceholder":20},[200,14300,14301,14303,14306,14309,14311,14313],{"class":202,"line":283},[200,14302,1390],{"class":206},[200,14304,14305],{"class":379}," const",[200,14307,14308],{"class":210}," createApp ",[200,14310,449],{"class":214},[200,14312,14267],{"class":223},[200,14314,3130],{"class":210},[200,14316,14317,14320],{"class":202,"line":300},[200,14318,14319],{"class":210},"  App",[200,14321,252],{"class":214},[200,14323,14324],{"class":202,"line":310},[200,14325,14326],{"class":214},"  {},\n",[200,14328,14329,14332,14335,14337,14339],{"class":202,"line":336},[200,14330,14331],{"class":214},"  ({",[200,14333,14334],{"class":386}," app",[200,14336,390],{"class":214},[200,14338,393],{"class":379},[200,14340,280],{"class":214},[200,14342,14344,14347,14349,14351,14353,14356],{"class":14343,"line":342},[202,9885],[200,14345,14346],{"class":210},"    app",[200,14348,215],{"class":214},[200,14350,9767],{"class":223},[200,14352,227],{"class":235},[200,14354,14355],{"class":210},"ui",[200,14357,5282],{"class":235},[200,14359,14360],{"class":202,"line":365},[200,14361,368],{"class":214},[200,14363,14364],{"class":202,"line":371},[200,14365,416],{"class":210},[35,14367,14368,14369,14371,14372,14375],{},"Et là, tout a cassé. Pendant le pré-rendu, quelque chose a essayé d'accéder à ",[156,14370,422],{}," pour créer un style : ",[156,14373,14374],{},"const style = document.createElement('style')",". Vraiment étrange.",[35,14377,14378,14379,14382],{},"Lors du pré-rendu, Vite SSG injecte ",[156,14380,14381],{},"import.meta.env.SSR"," pour détecter s'il s'exécute en mode SSR. Ça permet aussi au bundler d'éliminer efficacement le code non utilisé. Donc, pour corriger le bug, j'ai encapsulé l'usage du plugin dans une vérification conditionnelle du mode SSR.",[192,14384,14386],{"className":9755,"code":14385,"filename":14240,"language":1383,"meta":13,"style":13},"import ui from '@nuxt\u002Fui\u002Fvue-plugin'\nimport { ViteSSG } from 'vite-ssg'\nimport App from '.\u002FApp.vue'\n\nexport const createApp = ViteSSG(\n  App,\n  {},\n  ({ app }) => {\n    if (import.meta.env.SSR) { \u002F\u002F [!code highlight]\n      app.use(ui)\n    } \u002F\u002F [!code highlight]\n  },\n)\n",[156,14387,14388,14402,14420,14434,14438,14452,14458,14462,14474,14501,14516,14521,14525],{"__ignoreMap":13},[200,14389,14390,14392,14394,14396,14398,14400],{"class":202,"line":203},[200,14391,972],{"class":206},[200,14393,14249],{"class":210},[200,14395,1489],{"class":206},[200,14397,242],{"class":214},[200,14399,14256],{"class":245},[200,14401,991],{"class":214},[200,14403,14404,14406,14408,14410,14412,14414,14416,14418],{"class":202,"line":14},[200,14405,972],{"class":206},[200,14407,318],{"class":214},[200,14409,14267],{"class":210},[200,14411,980],{"class":214},[200,14413,983],{"class":206},[200,14415,242],{"class":214},[200,14417,14276],{"class":245},[200,14419,991],{"class":214},[200,14421,14422,14424,14426,14428,14430,14432],{"class":202,"line":255},[200,14423,972],{"class":206},[200,14425,14285],{"class":210},[200,14427,1489],{"class":206},[200,14429,242],{"class":214},[200,14431,14292],{"class":245},[200,14433,991],{"class":214},[200,14435,14436],{"class":202,"line":272},[200,14437,528],{"emptyLinePlaceholder":20},[200,14439,14440,14442,14444,14446,14448,14450],{"class":202,"line":283},[200,14441,1390],{"class":206},[200,14443,14305],{"class":379},[200,14445,14308],{"class":210},[200,14447,449],{"class":214},[200,14449,14267],{"class":223},[200,14451,3130],{"class":210},[200,14453,14454,14456],{"class":202,"line":300},[200,14455,14319],{"class":210},[200,14457,252],{"class":214},[200,14459,14460],{"class":202,"line":310},[200,14461,14326],{"class":214},[200,14463,14464,14466,14468,14470,14472],{"class":202,"line":336},[200,14465,14331],{"class":214},[200,14467,14334],{"class":386},[200,14469,390],{"class":214},[200,14471,393],{"class":379},[200,14473,280],{"class":214},[200,14475,14477,14479,14481,14483,14485,14487,14489,14492,14494,14497,14499],{"class":14476,"line":342},[202,9885],[200,14478,5267],{"class":206},[200,14480,1094],{"class":235},[200,14482,972],{"class":206},[200,14484,215],{"class":214},[200,14486,2030],{"class":210},[200,14488,215],{"class":214},[200,14490,14491],{"class":210},"env",[200,14493,215],{"class":214},[200,14495,14496],{"class":210},"SSR",[200,14498,5282],{"class":235},[200,14500,2767],{"class":214},[200,14502,14503,14506,14508,14510,14512,14514],{"class":202,"line":365},[200,14504,14505],{"class":210},"      app",[200,14507,215],{"class":214},[200,14509,9767],{"class":223},[200,14511,227],{"class":235},[200,14513,14355],{"class":210},[200,14515,416],{"class":235},[200,14517,14519],{"class":14518,"line":371},[202,9885],[200,14520,2111],{"class":214},[200,14522,14523],{"class":202,"line":398},[200,14524,368],{"class":214},[200,14526,14527],{"class":202,"line":405},[200,14528,416],{"class":210},[35,14530,14531],{},"À présent, le pré-rendu fonctionnait comme prévu, du moins c'est ce que je pensais.",[35,14533,14534],{},"J'ai écrit quelques pages, créé des composants, et tout poussé en production. La construction s'est bien passée, j'ai ouvert le site et j'ai vu ceci :",[130,14536,132,14537,132,14542],{},[134,14538,136,14539,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,14540],{"src":14541,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fwhant-to-contribute-to-open-source-youre-doing-it-wrong\u002Finfra-soubiran-dev-fouc.mp4",[143,14543,14544],{},"Chargement de mon nouveau site",[35,14546,14547],{},"Tu le vois ?",[35,14549,14550],{},"Avant d'aller plus loin, et si tu l'as remarqué, l'aurais-tu corrigé ? Tu sais que ça va prendre du temps, et tu ne pourras pas continuer à écrire du contenu. C'est un vrai dilemme. Prendre du temps pour quelque chose que tu pourrais considérer comme un détail, ou continuer à travailler sur ton contenu.",[35,14552,14553,14556],{},[6862,14554,14555],{},"Les sites remarquables sont faits de détails",". Pour moi, les détails sont très importants, et il est essentiel d'y prêter attention. Ils peuvent transformer l'expérience utilisateur, améliorer le confort et augmenter la fidélité. Ne les sous-estime jamais.",[35,14558,14559,14560,14565,14566,14569,14570,14572],{},"Pour ceux qui ne le voient pas, ça s'appelle un ",[39,14561,14564],{"href":14562,"rel":14563},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFlash_of_unstyled_content",[77],"FOUC",", un flash de contenu non stylisé. Le contenu apparaît avant que tous les styles ne soient disponibles. Ça arrive généralement quand un script doit ajouter une classe ",[156,14567,14568],{},"dark"," à l'élément HTML. Le site se charge avec le thème par défaut (mode clair), apparaît, puis le JavaScript est analysé et exécuté. Le script ajoute la classe ",[156,14571,14568],{}," et le thème passe en mode sombre. Le mode clair dure quelques centaines de millisecondes, mais c'est suffisant pour être visible. Tout ça est lié à la façon dont un navigateur rend une page.",[35,14574,14575,14576,14579],{},"Ça peut être très compliqué à corriger. En général, ça arrive quand les balises de ton ",[156,14577,14578],{},"head"," ne sont pas correctement ordonnées ou quand le script n'est pas exécuté au bon moment. J'ai donc plongé dans le head de la page générée :",[192,14581,14584],{"className":590,"code":14582,"filename":14583,"language":592,"meta":13,"style":13},"\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Clink rel=\"preconnect\" href=\"https:\u002F\u002Ffonts.gstatic.com\u002F\" crossorigin=\"anonymous\">\n    \u003Clink rel=\"preload\" as=\"style\" onload=\"this.rel='stylesheet'\" href=\"https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=DM Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&amp;family=DM Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&amp;family=Sofia Sans:ital,wght@0,1..1000;1,1..1000&amp;display=swap\">\n    \u003Cmeta charset=\"utf-8\">\n    \u003Cmeta name=\"author\" content=\"Estéban Soubiran\">\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    \u003Ctitle>Estéban's Infra · Estéban Soubiran\u003C\u002Ftitle>\n    \u003Cmeta name=\"msapplication-TileColor\" content=\"#ffffff\">\n    \u003Cmeta name=\"theme-color\" content=\"#ffffff\">\n    \u003Cscript>\n      ;(function () {\n        const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches\n        const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'\n        if (setting === 'dark' || (prefersDark && setting !== 'light'))\n          document.documentElement.classList.toggle('dark', true)\n      })()\n    \u003C\u002Fscript>\n    \u003Cscript type=\"module\" crossorigin=\"\" src=\"\u002Fassets\u002Fapp-IQH-kuhC.js\">\u003C\u002Fscript>\n    \u003Clink rel=\"stylesheet\" crossorigin=\"\" href=\"\u002Fassets\u002Fapp-B27WEjTb.css\">\n    \u003Clink rel=\"modulepreload\" crossorigin=\"\" href=\"\u002Fassets\u002Fpages-DeF1Ma75.js\">\n    \u003Clink rel=\"modulepreload\" crossorigin=\"\" href=\"\u002Fassets\u002FWrapperContent-BVl8okbX.js\">\n    \u003Cmeta property=\"og:title\" content=\"Estéban's Infra\">\n    \u003Cmeta name=\"twitter:title\" content=\"Estéban's Infra\">\n  \u003C\u002Fhead>\n\u003C\u002Fhtml>\n","dist\u002Findex.html",[156,14585,14586,14606,14614,14659,14735,14755,14784,14814,14831,14861,14890,14899,14913,14955,14993,15034,15068,15074,15083,15124,15159,15195,15230,15261,15290,15298],{"__ignoreMap":13},[200,14587,14588,14590,14592,14595,14597,14599,14602,14604],{"class":202,"line":203},[200,14589,599],{"class":214},[200,14591,592],{"class":235},[200,14593,14594],{"class":379}," lang",[200,14596,449],{"class":214},[200,14598,610],{"class":214},[200,14600,14601],{"class":245},"en",[200,14603,610],{"class":214},[200,14605,647],{"class":214},[200,14607,14608,14610,14612],{"class":202,"line":14},[200,14609,656],{"class":214},[200,14611,14578],{"class":235},[200,14613,647],{"class":214},[200,14615,14616,14618,14621,14624,14626,14628,14631,14633,14636,14638,14640,14643,14645,14648,14650,14652,14655,14657],{"class":202,"line":255},[200,14617,803],{"class":214},[200,14619,14620],{"class":235},"link",[200,14622,14623],{"class":379}," rel",[200,14625,449],{"class":214},[200,14627,610],{"class":214},[200,14629,14630],{"class":245},"preconnect",[200,14632,610],{"class":214},[200,14634,14635],{"class":379}," href",[200,14637,449],{"class":214},[200,14639,610],{"class":214},[200,14641,14642],{"class":245},"https:\u002F\u002Ffonts.gstatic.com\u002F",[200,14644,610],{"class":214},[200,14646,14647],{"class":379}," crossorigin",[200,14649,449],{"class":214},[200,14651,610],{"class":214},[200,14653,14654],{"class":245},"anonymous",[200,14656,610],{"class":214},[200,14658,647],{"class":214},[200,14660,14661,14663,14665,14667,14669,14671,14674,14676,14679,14681,14683,14685,14687,14690,14692,14695,14698,14701,14704,14707,14709,14711,14713,14716,14718,14721,14723,14726,14728,14731,14733],{"class":202,"line":272},[200,14662,803],{"class":214},[200,14664,14620],{"class":235},[200,14666,14623],{"class":379},[200,14668,449],{"class":214},[200,14670,610],{"class":214},[200,14672,14673],{"class":245},"preload",[200,14675,610],{"class":214},[200,14677,14678],{"class":379}," as",[200,14680,449],{"class":214},[200,14682,610],{"class":214},[200,14684,1170],{"class":245},[200,14686,610],{"class":214},[200,14688,14689],{"class":379}," onload",[200,14691,449],{"class":214},[200,14693,14694],{"class":214},"\"this.",[200,14696,14697],{"class":210},"rel",[200,14699,14700],{"class":214},"='",[200,14702,14703],{"class":245},"stylesheet",[200,14705,14706],{"class":214},"'\"",[200,14708,14635],{"class":379},[200,14710,449],{"class":214},[200,14712,610],{"class":214},[200,14714,14715],{"class":245},"https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=DM Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000",[200,14717,10130],{"class":210},[200,14719,14720],{"class":245},"family=DM Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500",[200,14722,10130],{"class":210},[200,14724,14725],{"class":245},"family=Sofia Sans:ital,wght@0,1..1000;1,1..1000",[200,14727,10130],{"class":210},[200,14729,14730],{"class":245},"display=swap",[200,14732,610],{"class":214},[200,14734,647],{"class":214},[200,14736,14737,14739,14741,14744,14746,14748,14751,14753],{"class":202,"line":283},[200,14738,803],{"class":214},[200,14740,2030],{"class":235},[200,14742,14743],{"class":379}," charset",[200,14745,449],{"class":214},[200,14747,610],{"class":214},[200,14749,14750],{"class":245},"utf-8",[200,14752,610],{"class":214},[200,14754,647],{"class":214},[200,14756,14757,14759,14761,14763,14765,14767,14770,14772,14774,14776,14778,14780,14782],{"class":202,"line":300},[200,14758,803],{"class":214},[200,14760,2030],{"class":235},[200,14762,700],{"class":379},[200,14764,449],{"class":214},[200,14766,610],{"class":214},[200,14768,14769],{"class":245},"author",[200,14771,610],{"class":214},[200,14773,6053],{"class":379},[200,14775,449],{"class":214},[200,14777,610],{"class":214},[200,14779,7045],{"class":245},[200,14781,610],{"class":214},[200,14783,647],{"class":214},[200,14785,14786,14788,14790,14792,14794,14796,14799,14801,14803,14805,14807,14810,14812],{"class":202,"line":310},[200,14787,803],{"class":214},[200,14789,2030],{"class":235},[200,14791,700],{"class":379},[200,14793,449],{"class":214},[200,14795,610],{"class":214},[200,14797,14798],{"class":245},"viewport",[200,14800,610],{"class":214},[200,14802,6053],{"class":379},[200,14804,449],{"class":214},[200,14806,610],{"class":214},[200,14808,14809],{"class":245},"width=device-width, initial-scale=1",[200,14811,610],{"class":214},[200,14813,647],{"class":214},[200,14815,14816,14818,14820,14822,14825,14827,14829],{"class":202,"line":336},[200,14817,803],{"class":214},[200,14819,3581],{"class":235},[200,14821,674],{"class":214},[200,14823,14824],{"class":210},"Estéban's Infra · Estéban Soubiran",[200,14826,680],{"class":214},[200,14828,3581],{"class":235},[200,14830,647],{"class":214},[200,14832,14833,14835,14837,14839,14841,14843,14846,14848,14850,14852,14854,14857,14859],{"class":202,"line":342},[200,14834,803],{"class":214},[200,14836,2030],{"class":235},[200,14838,700],{"class":379},[200,14840,449],{"class":214},[200,14842,610],{"class":214},[200,14844,14845],{"class":245},"msapplication-TileColor",[200,14847,610],{"class":214},[200,14849,6053],{"class":379},[200,14851,449],{"class":214},[200,14853,610],{"class":214},[200,14855,14856],{"class":245},"#ffffff",[200,14858,610],{"class":214},[200,14860,647],{"class":214},[200,14862,14863,14865,14867,14869,14871,14873,14876,14878,14880,14882,14884,14886,14888],{"class":202,"line":365},[200,14864,803],{"class":214},[200,14866,2030],{"class":235},[200,14868,700],{"class":379},[200,14870,449],{"class":214},[200,14872,610],{"class":214},[200,14874,14875],{"class":245},"theme-color",[200,14877,610],{"class":214},[200,14879,6053],{"class":379},[200,14881,449],{"class":214},[200,14883,610],{"class":214},[200,14885,14856],{"class":245},[200,14887,610],{"class":214},[200,14889,647],{"class":214},[200,14891,14892,14894,14897],{"class":202,"line":371},[200,14893,803],{"class":214},[200,14895,14896],{"class":235},"script",[200,14898,647],{"class":214},[200,14900,14901,14904,14906,14909,14911],{"class":202,"line":398},[200,14902,14903],{"class":214},"      ;",[200,14905,227],{"class":210},[200,14907,14908],{"class":379},"function",[200,14910,3729],{"class":214},[200,14912,280],{"class":214},[200,14914,14915,14917,14920,14922,14925,14927,14930,14933,14935,14937,14939,14941,14943,14946,14948,14950,14952],{"class":202,"line":405},[200,14916,12999],{"class":379},[200,14918,14919],{"class":210}," prefersDark",[200,14921,509],{"class":214},[200,14923,14924],{"class":210}," window",[200,14926,215],{"class":214},[200,14928,14929],{"class":210},"matchMedia",[200,14931,14932],{"class":214}," &&",[200,14934,14924],{"class":210},[200,14936,215],{"class":214},[200,14938,14929],{"class":223},[200,14940,227],{"class":235},[200,14942,249],{"class":214},[200,14944,14945],{"class":245},"(prefers-color-scheme: dark)",[200,14947,249],{"class":214},[200,14949,2104],{"class":235},[200,14951,215],{"class":214},[200,14953,14954],{"class":210},"matches\n",[200,14956,14957,14959,14962,14964,14967,14969,14972,14974,14976,14979,14981,14983,14986,14988,14991],{"class":202,"line":410},[200,14958,12999],{"class":379},[200,14960,14961],{"class":210}," setting",[200,14963,509],{"class":214},[200,14965,14966],{"class":210}," localStorage",[200,14968,215],{"class":214},[200,14970,14971],{"class":223},"getItem",[200,14973,227],{"class":235},[200,14975,249],{"class":214},[200,14977,14978],{"class":245},"vueuse-color-scheme",[200,14980,249],{"class":214},[200,14982,5282],{"class":235},[200,14984,14985],{"class":214},"||",[200,14987,242],{"class":214},[200,14989,14990],{"class":245},"auto",[200,14992,991],{"class":214},[200,14994,14995,14998,15000,15003,15005,15007,15009,15011,15014,15016,15019,15021,15023,15025,15027,15030,15032],{"class":202,"line":860},[200,14996,14997],{"class":206},"        if",[200,14999,1094],{"class":235},[200,15001,15002],{"class":210},"setting",[200,15004,3298],{"class":214},[200,15006,242],{"class":214},[200,15008,14568],{"class":245},[200,15010,249],{"class":214},[200,15012,15013],{"class":214}," ||",[200,15015,1094],{"class":235},[200,15017,15018],{"class":210},"prefersDark",[200,15020,14932],{"class":214},[200,15022,14961],{"class":210},[200,15024,13398],{"class":214},[200,15026,242],{"class":214},[200,15028,15029],{"class":245},"light",[200,15031,249],{"class":214},[200,15033,584],{"class":235},[200,15035,15036,15039,15041,15044,15046,15049,15051,15054,15056,15058,15060,15062,15064,15066],{"class":202,"line":889},[200,15037,15038],{"class":210},"          document",[200,15040,215],{"class":214},[200,15042,15043],{"class":210},"documentElement",[200,15045,215],{"class":214},[200,15047,15048],{"class":210},"classList",[200,15050,215],{"class":214},[200,15052,15053],{"class":223},"toggle",[200,15055,227],{"class":235},[200,15057,249],{"class":214},[200,15059,14568],{"class":245},[200,15061,249],{"class":214},[200,15063,549],{"class":214},[200,15065,2439],{"class":2438},[200,15067,416],{"class":235},[200,15069,15070,15072],{"class":202,"line":899},[200,15071,2647],{"class":214},[200,15073,11758],{"class":210},[200,15075,15076,15079,15081],{"class":202,"line":904},[200,15077,15078],{"class":214},"    \u003C\u002F",[200,15080,14896],{"class":235},[200,15082,647],{"class":214},[200,15084,15085,15087,15089,15091,15093,15095,15098,15100,15102,15104,15107,15109,15111,15113,15116,15118,15120,15122],{"class":202,"line":930},[200,15086,803],{"class":214},[200,15088,14896],{"class":235},[200,15090,321],{"class":379},[200,15092,449],{"class":214},[200,15094,610],{"class":214},[200,15096,15097],{"class":245},"module",[200,15099,610],{"class":214},[200,15101,14647],{"class":379},[200,15103,449],{"class":214},[200,15105,15106],{"class":214},"\"\"",[200,15108,10120],{"class":379},[200,15110,449],{"class":214},[200,15112,610],{"class":214},[200,15114,15115],{"class":245},"\u002Fassets\u002Fapp-IQH-kuhC.js",[200,15117,610],{"class":214},[200,15119,10206],{"class":214},[200,15121,14896],{"class":235},[200,15123,647],{"class":214},[200,15125,15126,15128,15130,15132,15134,15136,15138,15140,15142,15144,15146,15148,15150,15152,15155,15157],{"class":202,"line":3655},[200,15127,803],{"class":214},[200,15129,14620],{"class":235},[200,15131,14623],{"class":379},[200,15133,449],{"class":214},[200,15135,610],{"class":214},[200,15137,14703],{"class":245},[200,15139,610],{"class":214},[200,15141,14647],{"class":379},[200,15143,449],{"class":214},[200,15145,15106],{"class":214},[200,15147,14635],{"class":379},[200,15149,449],{"class":214},[200,15151,610],{"class":214},[200,15153,15154],{"class":245},"\u002Fassets\u002Fapp-B27WEjTb.css",[200,15156,610],{"class":214},[200,15158,647],{"class":214},[200,15160,15161,15163,15165,15167,15169,15171,15174,15176,15178,15180,15182,15184,15186,15188,15191,15193],{"class":202,"line":3660},[200,15162,803],{"class":214},[200,15164,14620],{"class":235},[200,15166,14623],{"class":379},[200,15168,449],{"class":214},[200,15170,610],{"class":214},[200,15172,15173],{"class":245},"modulepreload",[200,15175,610],{"class":214},[200,15177,14647],{"class":379},[200,15179,449],{"class":214},[200,15181,15106],{"class":214},[200,15183,14635],{"class":379},[200,15185,449],{"class":214},[200,15187,610],{"class":214},[200,15189,15190],{"class":245},"\u002Fassets\u002Fpages-DeF1Ma75.js",[200,15192,610],{"class":214},[200,15194,647],{"class":214},[200,15196,15197,15199,15201,15203,15205,15207,15209,15211,15213,15215,15217,15219,15221,15223,15226,15228],{"class":202,"line":4035},[200,15198,803],{"class":214},[200,15200,14620],{"class":235},[200,15202,14623],{"class":379},[200,15204,449],{"class":214},[200,15206,610],{"class":214},[200,15208,15173],{"class":245},[200,15210,610],{"class":214},[200,15212,14647],{"class":379},[200,15214,449],{"class":214},[200,15216,15106],{"class":214},[200,15218,14635],{"class":379},[200,15220,449],{"class":214},[200,15222,610],{"class":214},[200,15224,15225],{"class":245},"\u002Fassets\u002FWrapperContent-BVl8okbX.js",[200,15227,610],{"class":214},[200,15229,647],{"class":214},[200,15231,15232,15234,15236,15239,15241,15243,15246,15248,15250,15252,15254,15257,15259],{"class":202,"line":4490},[200,15233,803],{"class":214},[200,15235,2030],{"class":235},[200,15237,15238],{"class":379}," property",[200,15240,449],{"class":214},[200,15242,610],{"class":214},[200,15244,15245],{"class":245},"og:title",[200,15247,610],{"class":214},[200,15249,6053],{"class":379},[200,15251,449],{"class":214},[200,15253,610],{"class":214},[200,15255,15256],{"class":245},"Estéban's Infra",[200,15258,610],{"class":214},[200,15260,647],{"class":214},[200,15262,15263,15265,15267,15269,15271,15273,15276,15278,15280,15282,15284,15286,15288],{"class":202,"line":4496},[200,15264,803],{"class":214},[200,15266,2030],{"class":235},[200,15268,700],{"class":379},[200,15270,449],{"class":214},[200,15272,610],{"class":214},[200,15274,15275],{"class":245},"twitter:title",[200,15277,610],{"class":214},[200,15279,6053],{"class":379},[200,15281,449],{"class":214},[200,15283,610],{"class":214},[200,15285,15256],{"class":245},[200,15287,610],{"class":214},[200,15289,647],{"class":214},[200,15291,15292,15294,15296],{"class":202,"line":4502},[200,15293,892],{"class":214},[200,15295,14578],{"class":235},[200,15297,647],{"class":214},[200,15299,15300,15302,15304],{"class":202,"line":4508},[200,15301,680],{"class":214},[200,15303,592],{"class":235},[200,15305,647],{"class":214},[35,15307,15308],{},"Et j'ai essayé de comprendre ce qui se passait.",[35,15310,15311,15312,15317],{},"Pour savoir si le head est correctement trié, tu peux utiliser ",[39,15313,15316],{"href":15314,"rel":15315},"https:\u002F\u002Frviscomi.github.io\u002Fcapo.js\u002F",[77],"capo.js",". Après une rapide inspection, j'ai obtenu le résultat suivant :",[35,15319,15320],{},[1707,15321],{"alt":15322,"src":15323},"résultat de capo.js montrant des problèmes dans le head","\u002Fposts\u002Fwhant-to-contribute-to-open-source-youre-doing-it-wrong\u002Fcapo-js-result.webp",[35,15325,15326,15327,15332,15333,15338],{},"Dans la documentation de Vite SSG, il est indiqué d'utiliser ",[39,15328,15331],{"href":15329,"rel":15330},"https:\u002F\u002Funhead.unjs.io",[77],"Unhead",", alors j'ai lu toute la documentation. J'ai découvert qu'Unhead ",[39,15334,15337],{"href":15335,"rel":15336},"https:\u002F\u002Funhead.unjs.io\u002Fdocs\u002Fvue\u002Fhead\u002Fguides\u002Fcore-concepts\u002Fpositions#sort-order",[77],"trie automatiquement le head",", mais si c'est déjà utilisé, pourquoi le head n'est-il pas correctement trié ?",[51,15340,15341],{"id":2814},"Tout ne s'est pas passé comme prévu",[35,15343,15344],{},"Si Unhead est censé trier les éléments du head, le problème pourrait-il venir de Vite SSG ?",[35,15346,15347],{},"Une seule façon de le savoir : déboguer.",[35,15349,15350],{},"Après avoir lu tout le code source de Vite SSG, j'ai trouvé une partie intéressante :",[192,15352,15355],{"className":9755,"code":15353,"filename":15354,"language":1383,"meta":13,"style":13},"\u002F\u002F create jsdom from renderedHTML\nconst jsdom = new JSDOM(renderedHTML)\n\n\u002F\u002F render current page's preloadLinks\nrenderPreloadLinks(jsdom.window.document, ctx.modules || new Set\u003Cstring>(), ssrManifest)\n\n\u002F\u002F render head\nif (head) \u002F\u002F [!code highlight]\n  await renderDOMHead(head, { document: jsdom.window.document }) \u002F\u002F [!code highlight]\n\nconst html = jsdom.serialize()\nlet transformed = (await onPageRendered?.(route, html, appCtx)) || html\nif (beasties)\n  transformed = await beasties.process(transformed)\n\nconst formatted = await formatHtml(transformed, formatting)\n","src\u002Fnode\u002Fbuild.ts",[156,15356,15357,15362,15379,15383,15388,15435,15439,15444,15453,15488,15492,15510,15545,15552,15572,15576],{"__ignoreMap":13},[200,15358,15359],{"class":202,"line":203},[200,15360,15361],{"class":401},"\u002F\u002F create jsdom from renderedHTML\n",[200,15363,15364,15366,15369,15371,15373,15376],{"class":202,"line":14},[200,15365,443],{"class":379},[200,15367,15368],{"class":210}," jsdom ",[200,15370,449],{"class":214},[200,15372,13007],{"class":214},[200,15374,15375],{"class":223}," JSDOM",[200,15377,15378],{"class":210},"(renderedHTML)\n",[200,15380,15381],{"class":202,"line":255},[200,15382,528],{"emptyLinePlaceholder":20},[200,15384,15385],{"class":202,"line":272},[200,15386,15387],{"class":401},"\u002F\u002F render current page's preloadLinks\n",[200,15389,15390,15393,15396,15398,15401,15403,15405,15407,15410,15412,15415,15417,15419,15422,15424,15426,15428,15430,15432],{"class":202,"line":283},[200,15391,15392],{"class":223},"renderPreloadLinks",[200,15394,15395],{"class":210},"(jsdom",[200,15397,215],{"class":214},[200,15399,15400],{"class":210},"window",[200,15402,215],{"class":214},[200,15404,422],{"class":210},[200,15406,549],{"class":214},[200,15408,15409],{"class":210}," ctx",[200,15411,215],{"class":214},[200,15413,15414],{"class":210},"modules ",[200,15416,14985],{"class":214},[200,15418,13007],{"class":214},[200,15420,15421],{"class":223}," Set",[200,15423,599],{"class":214},[200,15425,328],{"class":1364},[200,15427,674],{"class":214},[200,15429,1549],{"class":210},[200,15431,549],{"class":214},[200,15433,15434],{"class":210}," ssrManifest)\n",[200,15436,15437],{"class":202,"line":300},[200,15438,528],{"emptyLinePlaceholder":20},[200,15440,15441],{"class":202,"line":310},[200,15442,15443],{"class":401},"\u002F\u002F render head\n",[200,15445,15447,15450],{"class":15446,"line":336},[202,9885],[200,15448,15449],{"class":206},"if",[200,15451,15452],{"class":210}," (head) ",[200,15454,15456,15458,15461,15464,15466,15468,15470,15472,15475,15477,15479,15481,15484,15486],{"class":15455,"line":342},[202,9885],[200,15457,6177],{"class":206},[200,15459,15460],{"class":223}," renderDOMHead",[200,15462,15463],{"class":210},"(head",[200,15465,549],{"class":214},[200,15467,318],{"class":214},[200,15469,211],{"class":235},[200,15471,239],{"class":214},[200,15473,15474],{"class":210}," jsdom",[200,15476,215],{"class":214},[200,15478,15400],{"class":210},[200,15480,215],{"class":214},[200,15482,15483],{"class":210},"document ",[200,15485,413],{"class":214},[200,15487,5282],{"class":210},[200,15489,15490],{"class":202,"line":365},[200,15491,528],{"emptyLinePlaceholder":20},[200,15493,15494,15496,15499,15501,15503,15505,15508],{"class":202,"line":371},[200,15495,443],{"class":379},[200,15497,15498],{"class":210}," html ",[200,15500,449],{"class":214},[200,15502,15474],{"class":210},[200,15504,215],{"class":214},[200,15506,15507],{"class":223},"serialize",[200,15509,466],{"class":210},[200,15511,15512,15514,15517,15519,15521,15523,15526,15528,15531,15533,15535,15537,15540,15542],{"class":202,"line":398},[200,15513,12337],{"class":379},[200,15515,15516],{"class":210}," transformed ",[200,15518,449],{"class":214},[200,15520,1094],{"class":210},[200,15522,207],{"class":206},[200,15524,15525],{"class":223}," onPageRendered",[200,15527,6536],{"class":214},[200,15529,15530],{"class":210},"(route",[200,15532,549],{"class":214},[200,15534,10290],{"class":210},[200,15536,549],{"class":214},[200,15538,15539],{"class":210}," appCtx)) ",[200,15541,14985],{"class":214},[200,15543,15544],{"class":210}," html\n",[200,15546,15547,15549],{"class":202,"line":405},[200,15548,15449],{"class":206},[200,15550,15551],{"class":210}," (beasties)\n",[200,15553,15554,15557,15559,15561,15564,15566,15569],{"class":202,"line":410},[200,15555,15556],{"class":210},"  transformed ",[200,15558,449],{"class":214},[200,15560,452],{"class":206},[200,15562,15563],{"class":210}," beasties",[200,15565,215],{"class":214},[200,15567,15568],{"class":223},"process",[200,15570,15571],{"class":210},"(transformed)\n",[200,15573,15574],{"class":202,"line":860},[200,15575,528],{"emptyLinePlaceholder":20},[200,15577,15578,15580,15583,15585,15587,15590,15593,15595],{"class":202,"line":889},[200,15579,443],{"class":379},[200,15581,15582],{"class":210}," formatted ",[200,15584,449],{"class":214},[200,15586,452],{"class":206},[200,15588,15589],{"class":223}," formatHtml",[200,15591,15592],{"class":210},"(transformed",[200,15594,549],{"class":214},[200,15596,15597],{"class":210}," formatting)\n",[35,15599,15600,15601,15604],{},"Cette partie est responsable de la génération du HTML avant son enregistrement. La ligne intéressante est ",[156,15602,15603],{},"await renderDOMHead(head, { document: jsdom.window.document })",". C'est la seule partie où le head est utilisé et rendu dans le HTML.",[35,15606,15607,15608,15611],{},"Encore plus intéressant, la fonction vient de ",[156,15609,15610],{},"@unhead\u002Fdom",". Étrange, puisque nous utilisons le SSR, avoir une fonction front côté serveur paraît suspect.",[35,15613,15614],{},"Et j'avais raison. Cette fonction n'optimise pas le head. Elle est censée modifier les éléments du head une fois la page chargée, côté client. Amener toute la logique de tri côté client est complètement inutile.",[35,15616,15617],{},"En lisant la documentation d'Unhead, j'ai découvert la manière recommandée de l'utiliser avec le SSR.",[192,15619,15622],{"className":9755,"code":15620,"filename":15621,"language":1383,"meta":13,"style":13},"import { transformHtmlTemplate } from '@unhead\u002Fvue\u002Fserver' \u002F\u002F [!code highlight]\n\nconst rendered = await render(url)\n\nconst html = await transformHtmlTemplate( \u002F\u002F [!code highlight]\n  rendered.head,\n  template.replace(`\u003C!--app-html-->`, rendered.html ?? '')\n)\n","server.ts",[156,15623,15624,15645,15649,15665,15669,15685,15696,15730],{"__ignoreMap":13},[200,15625,15627,15629,15631,15634,15636,15638,15640,15643],{"class":15626,"line":203},[202,9885],[200,15628,972],{"class":206},[200,15630,318],{"class":214},[200,15632,15633],{"class":210}," transformHtmlTemplate",[200,15635,980],{"class":214},[200,15637,983],{"class":206},[200,15639,242],{"class":214},[200,15641,15642],{"class":245},"@unhead\u002Fvue\u002Fserver",[200,15644,249],{"class":214},[200,15646,15647],{"class":202,"line":14},[200,15648,528],{"emptyLinePlaceholder":20},[200,15650,15651,15653,15656,15658,15660,15662],{"class":202,"line":255},[200,15652,443],{"class":379},[200,15654,15655],{"class":210}," rendered ",[200,15657,449],{"class":214},[200,15659,452],{"class":206},[200,15661,10279],{"class":223},[200,15663,15664],{"class":210},"(url)\n",[200,15666,15667],{"class":202,"line":272},[200,15668,528],{"emptyLinePlaceholder":20},[200,15670,15672,15674,15676,15678,15680,15682],{"class":15671,"line":283},[202,9885],[200,15673,443],{"class":379},[200,15675,15498],{"class":210},[200,15677,449],{"class":214},[200,15679,452],{"class":206},[200,15681,15633],{"class":223},[200,15683,15684],{"class":210},"( ",[200,15686,15687,15690,15692,15694],{"class":202,"line":300},[200,15688,15689],{"class":210},"  rendered",[200,15691,215],{"class":214},[200,15693,14578],{"class":210},[200,15695,252],{"class":214},[200,15697,15698,15701,15703,15705,15707,15709,15712,15714,15716,15719,15721,15724,15726,15728],{"class":202,"line":310},[200,15699,15700],{"class":210},"  template",[200,15702,215],{"class":214},[200,15704,6148],{"class":223},[200,15706,227],{"class":210},[200,15708,3333],{"class":214},[200,15710,15711],{"class":245},"\u003C!--app-html-->",[200,15713,3333],{"class":214},[200,15715,549],{"class":214},[200,15717,15718],{"class":210}," rendered",[200,15720,215],{"class":214},[200,15722,15723],{"class":210},"html ",[200,15725,3836],{"class":214},[200,15727,3909],{"class":214},[200,15729,416],{"class":210},[200,15731,15732],{"class":202,"line":336},[200,15733,416],{"class":210},[35,15735,15736,15737,15740],{},"La fonction ",[156,15738,15739],{},"transformHtmlTemplate"," extrait les éléments actuels du head depuis le template et les injecte dans le head avant de re-render le HTML et de l'injecter dans la page. Ça garantit que le head est correctement trié avant d'être envoyé au client.",[35,15742,15743],{},"Ça semblait être une bonne solution pour corriger le tri du head. J'ai donc créé une PR sur Vite SSG pour améliorer l'intégration avec Unhead.",[35,15745,15746],{},[39,15747,15750],{"href":15748,"rel":15749},"https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-ssg\u002Fpull\u002F464",[77],"refactor: uses unhead\u002Fserver to optimize head",[35,15752,15753],{},"Pour utiliser rapidement ce correctif dans mon projet, j'ai patché Vite SSG en local. Ça m'a permis de continuer à développer mon projet sans attendre que ma PR soit mergée puis publiée.",[35,15755,15756],{},"Mais tout ne s'est pas passé comme prévu.",[35,15758,15759,15760],{},"Le head était presque bien ordonné, mais après ce correctif, le FOUC était toujours là. ",[1216,15761,15762],{},"Triste.",[35,15764,15765],{},"Ça signifie que le problème n'est pas lié au tri du head.",[51,15767,15769],{"id":15768},"enquête-plus-approfondie","Enquête plus approfondie",[35,15771,15772],{},"Le patch de Vite SSG n'est pas inutile, car il améliore le tri du head. Cependant, il n'a pas résolu le FOUC. Il a fallu creuser davantage.",[35,15774,15775],{},"J'ai continué à chercher la cause racine. J'ai essayé d'analyser les performances de l'application pour identifier d'éventuels goulots d'étranglement, conditions de course ou autres problèmes pouvant causer le FOUC. Je n'ai rien trouvé.",[35,15777,15778],{},"J'ai inspecté le CSS et constaté que les styles de la prose s'appliquaient correctement, mais pas ceux de Nuxt UI. Si tu regardes attentivement la vidéo, tu verras la couleur du titre passer du noir au blanc, alors que le texte en dessous est déjà blanc. Les variables CSS de Nuxt UI ne sont pas disponibles lors du premier rendu, mais sont appliquées quand le JavaScript se charge.",[35,15780,15781,15782,15785],{},"Pour confirmer ce soupçon, j'ai vérifié le code source de Nuxt UI où j'ai trouvé un plugin Vue nommé ",[156,15783,15784],{},"colors.ts",". Le même qui a causé le problème de build au début.",[35,15787,15788],{},"Tout menait à ce plugin, alors j'ai pris le temps de le comprendre, et j'ai trouvé la cause racine à la fois du FOUC et du problème de build :",[192,15790,15793],{"className":1380,"code":15791,"filename":15792,"language":1383,"meta":13,"style":13},"const headData: UseHeadInput = {\n  style: [{\n    innerHTML: () => root.value,\n    tagPriority: -2,\n    id: 'nuxt-ui-colors'\n  }]\n}\n\nif (import.meta.client && nuxtApp.isHydrating && !nuxtApp.payload.serverRendered) {\n  const style = document.createElement('style')\n\n  style.innerHTML = root.value\n  style.setAttribute('data-nuxt-ui-colors', '')\n  document.head.appendChild(style)\n\n  headData.script = [{\n    innerHTML: 'document.head.removeChild(document.querySelector(\\'[data-nuxt-ui-colors]\\'))'\n  }]\n}\n\nuseHead(headData)\n","src\u002Fruntime\u002Fplugins\u002Fcolors.ts",[156,15794,15795,15811,15822,15843,15857,15871,15877,15881,15885,15933,15958,15962,15980,16004,16024,16028,16043,16066,16072,16076,16080],{"__ignoreMap":13},[200,15796,15797,15799,15802,15804,15807,15809],{"class":202,"line":203},[200,15798,443],{"class":379},[200,15800,15801],{"class":210}," headData",[200,15803,239],{"class":214},[200,15805,15806],{"class":1364}," UseHeadInput",[200,15808,509],{"class":214},[200,15810,280],{"class":214},[200,15812,15813,15816,15818,15820],{"class":202,"line":14},[200,15814,15815],{"class":235},"  style",[200,15817,239],{"class":214},[200,15819,350],{"class":210},[200,15821,230],{"class":214},[200,15823,15824,15827,15829,15831,15833,15836,15838,15841],{"class":202,"line":255},[200,15825,15826],{"class":223},"    innerHTML",[200,15828,239],{"class":214},[200,15830,3729],{"class":214},[200,15832,393],{"class":379},[200,15834,15835],{"class":210}," root",[200,15837,215],{"class":214},[200,15839,15840],{"class":210},"value",[200,15842,252],{"class":214},[200,15844,15845,15848,15850,15853,15855],{"class":202,"line":272},[200,15846,15847],{"class":235},"    tagPriority",[200,15849,239],{"class":214},[200,15851,15852],{"class":214}," -",[200,15854,4797],{"class":2060},[200,15856,252],{"class":214},[200,15858,15859,15862,15864,15866,15869],{"class":202,"line":283},[200,15860,15861],{"class":235},"    id",[200,15863,239],{"class":214},[200,15865,242],{"class":214},[200,15867,15868],{"class":245},"nuxt-ui-colors",[200,15870,991],{"class":214},[200,15872,15873,15875],{"class":202,"line":300},[200,15874,5095],{"class":214},[200,15876,4511],{"class":210},[200,15878,15879],{"class":202,"line":310},[200,15880,1129],{"class":214},[200,15882,15883],{"class":202,"line":336},[200,15884,528],{"emptyLinePlaceholder":20},[200,15886,15887,15889,15891,15893,15895,15897,15899,15902,15905,15908,15910,15913,15915,15918,15921,15923,15926,15928,15931],{"class":202,"line":342},[200,15888,15449],{"class":206},[200,15890,1094],{"class":210},[200,15892,972],{"class":206},[200,15894,215],{"class":214},[200,15896,2030],{"class":210},[200,15898,215],{"class":214},[200,15900,15901],{"class":210},"client ",[200,15903,15904],{"class":214},"&&",[200,15906,15907],{"class":210}," nuxtApp",[200,15909,215],{"class":214},[200,15911,15912],{"class":210},"isHydrating ",[200,15914,15904],{"class":214},[200,15916,15917],{"class":214}," !",[200,15919,15920],{"class":210},"nuxtApp",[200,15922,215],{"class":214},[200,15924,15925],{"class":210},"payload",[200,15927,215],{"class":214},[200,15929,15930],{"class":210},"serverRendered) ",[200,15932,230],{"class":214},[200,15934,15935,15937,15939,15941,15943,15945,15948,15950,15952,15954,15956],{"class":202,"line":365},[200,15936,3738],{"class":379},[200,15938,10194],{"class":210},[200,15940,509],{"class":214},[200,15942,211],{"class":210},[200,15944,215],{"class":214},[200,15946,15947],{"class":223},"createElement",[200,15949,227],{"class":235},[200,15951,249],{"class":214},[200,15953,1170],{"class":245},[200,15955,249],{"class":214},[200,15957,416],{"class":235},[200,15959,15960],{"class":202,"line":371},[200,15961,528],{"emptyLinePlaceholder":20},[200,15963,15964,15966,15968,15971,15973,15975,15977],{"class":202,"line":398},[200,15965,15815],{"class":210},[200,15967,215],{"class":214},[200,15969,15970],{"class":210},"innerHTML",[200,15972,509],{"class":214},[200,15974,15835],{"class":210},[200,15976,215],{"class":214},[200,15978,15979],{"class":210},"value\n",[200,15981,15982,15984,15986,15989,15991,15993,15996,15998,16000,16002],{"class":202,"line":405},[200,15983,15815],{"class":210},[200,15985,215],{"class":214},[200,15987,15988],{"class":223},"setAttribute",[200,15990,227],{"class":235},[200,15992,249],{"class":214},[200,15994,15995],{"class":245},"data-nuxt-ui-colors",[200,15997,249],{"class":214},[200,15999,549],{"class":214},[200,16001,3909],{"class":214},[200,16003,416],{"class":235},[200,16005,16006,16009,16011,16013,16015,16018,16020,16022],{"class":202,"line":410},[200,16007,16008],{"class":210},"  document",[200,16010,215],{"class":214},[200,16012,14578],{"class":210},[200,16014,215],{"class":214},[200,16016,16017],{"class":223},"appendChild",[200,16019,227],{"class":235},[200,16021,1170],{"class":210},[200,16023,416],{"class":235},[200,16025,16026],{"class":202,"line":860},[200,16027,528],{"emptyLinePlaceholder":20},[200,16029,16030,16033,16035,16037,16039,16041],{"class":202,"line":889},[200,16031,16032],{"class":210},"  headData",[200,16034,215],{"class":214},[200,16036,14896],{"class":210},[200,16038,509],{"class":214},[200,16040,350],{"class":235},[200,16042,230],{"class":214},[200,16044,16045,16047,16049,16051,16054,16057,16060,16062,16064],{"class":202,"line":899},[200,16046,15826],{"class":235},[200,16048,239],{"class":214},[200,16050,242],{"class":214},[200,16052,16053],{"class":245},"document.head.removeChild(document.querySelector(",[200,16055,16056],{"class":210},"\\'",[200,16058,16059],{"class":245},"[data-nuxt-ui-colors]",[200,16061,16056],{"class":210},[200,16063,6131],{"class":245},[200,16065,991],{"class":214},[200,16067,16068,16070],{"class":202,"line":904},[200,16069,5095],{"class":214},[200,16071,4511],{"class":235},[200,16073,16074],{"class":202,"line":930},[200,16075,1129],{"class":214},[200,16077,16078],{"class":202,"line":3655},[200,16079,528],{"emptyLinePlaceholder":20},[200,16081,16082,16085],{"class":202,"line":3660},[200,16083,16084],{"class":223},"useHead",[200,16086,16087],{"class":210},"(headData)\n",[35,16089,16090],{},"Plusieurs problèmes se mêlent ici.",[35,16092,16093],{},"Les sept premières lignes, ainsi que la dernière, servent à injecter les variables CSS de Nuxt UI dans le head, à la fois au build et à l'exécution. En désactivant le plugin, j'ai supprimé ce comportement, donc le FOUC se produit inévitablement.",[35,16095,16096,16097,16099],{},"Le bloc ",[156,16098,15449],{}," crée un élément style et ne doit s'exécuter que côté client, pendant l'hydratation, si le contenu n'est pas rendu côté serveur. Mon app utilise Vite SSG pour être rendue côté serveur, donc ça ne devrait pas arriver.",[35,16101,16102,16103,16105],{},"Alors, c'est quoi ce ",[156,16104,15920],{}," ? Continuons l'enquête.",[35,16107,16108,16109,16113,16114,2186],{},"Au départ, Nuxt UI est fait pour Nuxt, mais grâce au travail de ",[39,16110,16112],{"href":1894,"rel":16111},[77],"Daniel Roe"," et à beaucoup de stubs, Nuxt UI est aussi utilisable en dehors de Nuxt. L'un des stubs est le composable ",[156,16115,16116],{},"useNuxtApp",[192,16118,16121],{"className":1380,"code":16119,"filename":16120,"language":1383,"meta":13,"style":13},"export function useNuxtApp() {\n  return {\n    isHydrating: true,\n    payload: { serverRendered: false },\n    hooks,\n    hook: hooks.hook\n  }\n}\n","src\u002Fruntime\u002Fvue\u002Fstubs.ts",[156,16122,16123,16136,16142,16153,16172,16179,16194,16198],{"__ignoreMap":13},[200,16124,16125,16127,16129,16132,16134],{"class":202,"line":203},[200,16126,1390],{"class":206},[200,16128,5002],{"class":379},[200,16130,16131],{"class":223}," useNuxtApp",[200,16133,1549],{"class":214},[200,16135,280],{"class":214},[200,16137,16138,16140],{"class":202,"line":14},[200,16139,3758],{"class":206},[200,16141,280],{"class":214},[200,16143,16144,16147,16149,16151],{"class":202,"line":255},[200,16145,16146],{"class":235},"    isHydrating",[200,16148,239],{"class":214},[200,16150,2439],{"class":2438},[200,16152,252],{"class":214},[200,16154,16155,16158,16160,16162,16165,16167,16170],{"class":202,"line":272},[200,16156,16157],{"class":235},"    payload",[200,16159,239],{"class":214},[200,16161,318],{"class":214},[200,16163,16164],{"class":235}," serverRendered",[200,16166,239],{"class":214},[200,16168,16169],{"class":2438}," false",[200,16171,333],{"class":214},[200,16173,16174,16177],{"class":202,"line":283},[200,16175,16176],{"class":210},"    hooks",[200,16178,252],{"class":214},[200,16180,16181,16184,16186,16189,16191],{"class":202,"line":300},[200,16182,16183],{"class":235},"    hook",[200,16185,239],{"class":214},[200,16187,16188],{"class":210}," hooks",[200,16190,215],{"class":214},[200,16192,16193],{"class":210},"hook\n",[200,16195,16196],{"class":202,"line":310},[200,16197,4505],{"class":214},[200,16199,16200],{"class":202,"line":336},[200,16201,1129],{"class":214},[35,16203,16204,16205,16208],{},"Le voilà. La propriété ",[156,16206,16207],{},"serverRendered"," est fixée à false, tout le temps. Donc même quand Vite SSG est utilisé, le plugin pense qu'il est dans un navigateur et essaie de créer l'élément style. Évidemment, ça ne marche pas.",[35,16210,16211,16212,16215],{},"Le correctif est vraiment simple. Au lieu de renvoyer toujours false, il faut renvoyer ",[156,16213,16214],{},"import.meta.env.SSR || false"," pour détecter correctement le rendu côté serveur.",[2911,16217,16219,16220,16222,16223,16225,16231,16235,16238,16241,16244,16247,16268,16277,16280],{"href":16218},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F5347","\n  fix(vue): check ",[156,16221,14381],{}," to support ",[156,16224,14276],{},[35,16226,16227,16228],{},"Ensuite, j'ai réactivé le plugin Nuxt UI, le build est passé, et le FOUC avait disparu ! ",[1216,16229,16230],{},"J'adore. Un petit booléen a fait une énorme différence.",[51,16232,16234],{"id":16233},"améliorer-le-head","Améliorer le head",[35,16236,16237],{},"Le build passait et le FOUC avait disparu. J'ai continué à vérifier le head du fichier généré pour m'assurer que tout était en ordre.",[35,16239,16240],{},"Ce n'était pas le cas.",[35,16242,16243],{},"Je pourrais m'arrêter là, mais j'étais lancé dans une série de pull requests, plongé dans Unhead, Nuxt UI et Vite SSG.",[35,16245,16246],{},"J'ai découvert deux choses.",[68,16248,16249,16260],{},[71,16250,16251,16252,16255,16256,16259],{},"Nuxt UI définit un ",[156,16253,16254],{},"tagPriority"," à ",[156,16257,16258],{},"-2"," sur l'élément style lors de son injection. Ça signifie qu'il sera l'un des premiers éléments dans le head au lieu d'être placé automatiquement.",[71,16261,16262,16263,1655,16266,215],{},"Unhead gérait mal l'ordre des scripts ",[156,16264,16265],{},"inlined",[156,16267,15097],{},[35,16269,16270,16271,16276],{},"Pour le premier point, j'ai seulement ",[39,16272,16275],{"href":16273,"rel":16274},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F5347#issuecomment-3472594042",[77],"écrit un commentaire"," pour comprendre pourquoi il était défini. Ça pourrait être un prérequis pour l'intégration Nuxt.",[35,16278,16279],{},"En revanche, pour le second point, sachant que c'était un bug, j'ai ouvert deux pull requests.",[2911,16281,16284],{"title":16282,"href":16283},"fix: inline scripts ordering","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F596",[16285,16286,16289,16292],"pullrequest",{"title":16287,"href":16288},"fix: module scripts","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F600",[35,16290,16291],{},"En travaillant sur ces pull requests, j'ai découvert que la CI ne fonctionnait pas, que certains tests échouaient et que la vérification de type était cassée, alors j'ai créé 3 PR pour tout corriger.",[2911,16293,16296],{"title":16294,"href":16295},"test: update snapshots","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F598",[16285,16297,16300],{"title":16298,"href":16299},"test: fix tsc","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F597",[16285,16301,16304,16307,16310,16315,16319,16322,16328,16331,16334,16337,16339,16345,16348,16354],{"title":16302,"href":16303},"ci: fix","https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F599",[35,16305,16306],{},"Je corrigeais juste un petit bug dans la façon dont Unhead gérait l'ordre des scripts, et j'ai fini par corriger les tests et l'intégration.",[35,16308,16309],{},"Quand tu prends soin des détails, ça finit souvent comme ça, et en faisant des pull requests propres et qui fonctionnent, tu augmentes tes chances d'être accepté.",[35,16311,16312],{},[1216,16313,16314],{},"Je n'ai même pas eu le temps d'écrire un article de blog à leur sujet avant qu'elles ne soient mergées.",[51,16316,16318],{"id":16317},"lopen-source-pas-par-choix","L'open source, pas par choix",[35,16320,16321],{},"Je voulais juste construire mon site pour documenter mon infrastructure. J'ai fini par corriger des bugs dans trois outils et ouvrir 7 pull requests.",[35,16323,16324,16325],{},"Honnêtement, j'adorerais ne jamais avoir à les corriger, et j'aimerais ne jamais avoir à passer du temps à corriger ces bugs. Mais ce n'est pas le contrat implicite de l'open source, ni son état d'esprit. ",[1216,16326,16327],{},"C'est ainsi que je le vois. Tout le monde ne le voit pas comme ça.",[35,16329,16330],{},"Des gens te proposent, gratuitement, et souvent en l'état, leur code pour ton propre usage. Ça te fait gagner du temps et de l'argent et te permet de construire plus vite et mieux. Oui, je passe du temps à corriger des bugs, mais combien de temps ai-je gagné en utilisant des logiciels open source ? À quelle fréquence ai-je pu contribuer à des logiciels open source pour construire l'expérience que je voulais dans mes projets ? Ça ne vaut même pas la peine de commencer à compter. Rendre un peu de ton savoir et de ton temps semble être un échange équitable.",[35,16332,16333],{},"Et c'est normal que les logiciels aient des bugs, des fonctionnalités manquantes ou simplement des cas d'usage non prévus. C'est inhérent au logiciel et ni le logiciel propriétaire ni l'open source n'y échappent.",[35,16335,16336],{},"Cependant, avec l'open source, tu peux changer la donne en ayant cette liberté de contribuer aux logiciels que tu utilises.",[1158,16338],{},[35,16340,16341,16342,215],{},"Construis des choses, n'importe quoi, mais donne vie à tes envies, sur Internet, ne t'arrête pas à ",[156,16343,16344],{},"localhost",[35,16346,16347],{},"En chemin, tu rencontreras des bugs, des fonctionnalités manquantes, ou simplement des choses qui pourraient être améliorées. Quand c'est le cas, n'hésite pas à plonger dans ta stack, à la comprendre, et à essayer de corriger le problème.",[35,16349,16350,16353],{},[6862,16351,16352],{},"C'est comme ça que tu contribues à l'open source",". Pas l'inverse. C'est aussi l'une des meilleures façons d'apprendre.",[35,16355,16356],{},"Cultive l'état d'esprit open source et les opportunités surgiront naturellement.",[1170,16358,16359],{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":13,"searchDepth":14,"depth":14,"links":16361},[16362,16363,16364,16365],{"id":14215,"depth":14,"text":14216},{"id":2814,"depth":14,"text":15341},{"id":15768,"depth":14,"text":15769},{"id":16233,"depth":14,"text":16234},"2025-11-03","On me demande souvent comment contribuer à l'open source et je trouve rarement une réponse satisfaisante. J'ai pris le temps d'y penser. Voici mon point de vue.",{},"\u002Ffr\u002Fposts\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong","---\ncontentId: 919c8df4-e6f7-4dc5-9cda-165d754168ed\ntitle: Envie de contribuer à l'open source ? Vous vous y prenez mal\ndescription: On me demande souvent comment contribuer à l'open source et je trouve rarement une réponse satisfaisante. J'ai pris le temps d'y penser. Voici mon point de vue.\ndate: 2025-11-03\n---\n\nJe contribue activement à l'open source depuis 2020. Depuis, j'ai parlé avec beaucoup de développeurs qui veulent contribuer mais ne savent pas par où commencer, demandant souvent une issue sur laquelle travailler.\n\nJe suis profondément convaincu que l'open source, c'est plus que ça et, surtout, que ça ne marche pas comme ça.\n\nPour moi, l'open source, au fond, est un état d'esprit. C'est la liberté d'explorer, de comprendre, de modifier et d'améliorer le code source du logiciel que tu veux. Bien sûr, ce modèle encourage la collaboration et le partage en permettant à chacun de contribuer, mais il ne t'y oblige pas.\n\nL'open source ne devrait pas, et ne doit jamais, être une fin en soi. Ça fait partie du chemin, pas de la destination. Sinon, tu finiras frustré et insatisfait de ne jamais réussir à contribuer.\n\nLaisse-moi te raconter une histoire pour illustrer ce point.\n\n---\n\nJe construis un nouveau site appelé [infra.soubiran.dev](https:\u002F\u002Finfra.soubiran.dev) pour documenter ma propre infrastructure. C'est un wiki personnel mais ouvert à tous. _Je n'entrerai pas dans les détails, un article dédié viendra plus tard._\n\nPour le construire, j'ai dû trouver les bons outils. Jusqu'à présent, j'utilisais [VitePress avec un thème personnalisé pour mon blog personnel](.\u002Fvitepress-for-a-personal-website-is-that-possible.md), celui que tu es en train de lire. Ça marche très bien, mais j'en ai presque atteint les limites, et avec toutes les idées que j'ai, je sais qu'il pourrait être temps de passer à autre chose.\n\nAlors, j'ai commencé à regarder des alternatives. Honnêtement, il y a deux options :\n\n1. Utiliser [Nuxt](https:\u002F\u002Fnuxt.com) avec [Nuxt Content](https:\u002F\u002Fcontent.nuxt.com)\n2. Utiliser Vite avec Vue et quelques [unplugins](https:\u002F\u002Funplugin.unjs.io\u002F) pour gérer les fichiers Markdown.\n\nJ'ai choisi la seconde option. Assez drôle, avant de passer à VitePress, j'avais le même dilemme : Nuxt Content, VitePress, ou Vite avec Vue. À l'époque, j'avais choisi VitePress.\n\nBref, j'ai commencé à construire le site.\n\nDepuis quelques mois, il y a une techno dont je ne peux plus me passer : [Nuxt UI](https:\u002F\u002Fui.nuxt.com). C'est une bibliothèque de composants incroyable réalisée par l'équipe Nuxt. Tous mes projets qui utilisent Vue ou Nuxt utilisent Nuxt UI, et je le pousse régulièrement dans ses retranchements. _C'est aussi une des raisons pour lesquelles j'ai décidé de m'éloigner de VitePress, qui n'est pas compatible avec Nuxt UI._\n\n## Construire avec des logiciels open source\n\nLe site que je construis est orienté contenu. Ça signifie que le contenu est principalement textuel et, dans mon cas, toutes les pages sont écrites en Markdown. Pour mettre à jour le site, j'édite les fichiers Markdown puis je reconstruis le site.\n\nLes pages sont générées et pré-rendues au moment du build. Il n'y a pas besoin de les générer à l'exécution, puisque chaque utilisateur recevra le même contenu pré-rendu. Pour que ça marche, j'utilise [Vite SSG](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-ssg).\n\nC'est un remplaçant pour Vite lors du build du site. Il construit automatiquement le client et le serveur, puis utilise le serveur pour pré-rendre chaque page, comme si un utilisateur la demandait.\n\nAprès l'installation, tout fonctionnait comme prévu.\n\nJ'ai donc continué à construire le site. J'ai installé Nuxt UI et commencé à le configurer. Pour utiliser Nuxt UI dans une application Vue, il faut utiliser un plugin.\n\n```ts [src\u002Fmain.ts]\nimport ui from '@nuxt\u002Fui\u002Fvue-plugin'\nimport { ViteSSG } from 'vite-ssg'\nimport App from '.\u002FApp.vue'\n\nexport const createApp = ViteSSG(\n  App,\n  {},\n  ({ app }) => {\n    app.use(ui) \u002F\u002F [!code highlight]\n  },\n)\n```\n\nEt là, tout a cassé. Pendant le pré-rendu, quelque chose a essayé d'accéder à `document` pour créer un style : `const style = document.createElement('style')`. Vraiment étrange.\n\nLors du pré-rendu, Vite SSG injecte `import.meta.env.SSR` pour détecter s'il s'exécute en mode SSR. Ça permet aussi au bundler d'éliminer efficacement le code non utilisé. Donc, pour corriger le bug, j'ai encapsulé l'usage du plugin dans une vérification conditionnelle du mode SSR.\n\n```ts [src\u002Fmain.ts]\nimport ui from '@nuxt\u002Fui\u002Fvue-plugin'\nimport { ViteSSG } from 'vite-ssg'\nimport App from '.\u002FApp.vue'\n\nexport const createApp = ViteSSG(\n  App,\n  {},\n  ({ app }) => {\n    if (import.meta.env.SSR) { \u002F\u002F [!code highlight]\n      app.use(ui)\n    } \u002F\u002F [!code highlight]\n  },\n)\n```\n\nÀ présent, le pré-rendu fonctionnait comme prévu, du moins c'est ce que je pensais.\n\nJ'ai écrit quelques pages, créé des composants, et tout poussé en production. La construction s'est bien passée, j'ai ouvert le site et j'ai vu ceci :\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fwhant-to-contribute-to-open-source-youre-doing-it-wrong\u002Finfra-soubiran-dev-fouc.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Chargement de mon nouveau site\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nTu le vois ?\n\nAvant d'aller plus loin, et si tu l'as remarqué, l'aurais-tu corrigé ? Tu sais que ça va prendre du temps, et tu ne pourras pas continuer à écrire du contenu. C'est un vrai dilemme. Prendre du temps pour quelque chose que tu pourrais considérer comme un détail, ou continuer à travailler sur ton contenu.\n\n**Les sites remarquables sont faits de détails**. Pour moi, les détails sont très importants, et il est essentiel d'y prêter attention. Ils peuvent transformer l'expérience utilisateur, améliorer le confort et augmenter la fidélité. Ne les sous-estime jamais.\n\nPour ceux qui ne le voient pas, ça s'appelle un [FOUC](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFlash_of_unstyled_content), un flash de contenu non stylisé. Le contenu apparaît avant que tous les styles ne soient disponibles. Ça arrive généralement quand un script doit ajouter une classe `dark` à l'élément HTML. Le site se charge avec le thème par défaut (mode clair), apparaît, puis le JavaScript est analysé et exécuté. Le script ajoute la classe `dark` et le thème passe en mode sombre. Le mode clair dure quelques centaines de millisecondes, mais c'est suffisant pour être visible. Tout ça est lié à la façon dont un navigateur rend une page.\n\nÇa peut être très compliqué à corriger. En général, ça arrive quand les balises de ton `head` ne sont pas correctement ordonnées ou quand le script n'est pas exécuté au bon moment. J'ai donc plongé dans le head de la page générée :\n\n```html [dist\u002Findex.html]\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Clink rel=\"preconnect\" href=\"https:\u002F\u002Ffonts.gstatic.com\u002F\" crossorigin=\"anonymous\">\n    \u003Clink rel=\"preload\" as=\"style\" onload=\"this.rel='stylesheet'\" href=\"https:\u002F\u002Ffonts.googleapis.com\u002Fcss2?family=DM Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&amp;family=DM Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&amp;family=Sofia Sans:ital,wght@0,1..1000;1,1..1000&amp;display=swap\">\n    \u003Cmeta charset=\"utf-8\">\n    \u003Cmeta name=\"author\" content=\"Estéban Soubiran\">\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    \u003Ctitle>Estéban's Infra · Estéban Soubiran\u003C\u002Ftitle>\n    \u003Cmeta name=\"msapplication-TileColor\" content=\"#ffffff\">\n    \u003Cmeta name=\"theme-color\" content=\"#ffffff\">\n    \u003Cscript>\n      ;(function () {\n        const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches\n        const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'\n        if (setting === 'dark' || (prefersDark && setting !== 'light'))\n          document.documentElement.classList.toggle('dark', true)\n      })()\n    \u003C\u002Fscript>\n    \u003Cscript type=\"module\" crossorigin=\"\" src=\"\u002Fassets\u002Fapp-IQH-kuhC.js\">\u003C\u002Fscript>\n    \u003Clink rel=\"stylesheet\" crossorigin=\"\" href=\"\u002Fassets\u002Fapp-B27WEjTb.css\">\n    \u003Clink rel=\"modulepreload\" crossorigin=\"\" href=\"\u002Fassets\u002Fpages-DeF1Ma75.js\">\n    \u003Clink rel=\"modulepreload\" crossorigin=\"\" href=\"\u002Fassets\u002FWrapperContent-BVl8okbX.js\">\n    \u003Cmeta property=\"og:title\" content=\"Estéban's Infra\">\n    \u003Cmeta name=\"twitter:title\" content=\"Estéban's Infra\">\n  \u003C\u002Fhead>\n\u003C\u002Fhtml>\n```\n\nEt j'ai essayé de comprendre ce qui se passait.\n\nPour savoir si le head est correctement trié, tu peux utiliser [capo.js](https:\u002F\u002Frviscomi.github.io\u002Fcapo.js\u002F). Après une rapide inspection, j'ai obtenu le résultat suivant :\n\n![résultat de capo.js montrant des problèmes dans le head](\u002Fposts\u002Fwhant-to-contribute-to-open-source-youre-doing-it-wrong\u002Fcapo-js-result.webp)\n\nDans la documentation de Vite SSG, il est indiqué d'utiliser [Unhead](https:\u002F\u002Funhead.unjs.io), alors j'ai lu toute la documentation. J'ai découvert qu'Unhead [trie automatiquement le head](https:\u002F\u002Funhead.unjs.io\u002Fdocs\u002Fvue\u002Fhead\u002Fguides\u002Fcore-concepts\u002Fpositions#sort-order), mais si c'est déjà utilisé, pourquoi le head n'est-il pas correctement trié ?\n\n## Tout ne s'est pas passé comme prévu\n\nSi Unhead est censé trier les éléments du head, le problème pourrait-il venir de Vite SSG ?\n\nUne seule façon de le savoir : déboguer.\n\nAprès avoir lu tout le code source de Vite SSG, j'ai trouvé une partie intéressante :\n\n```ts [src\u002Fnode\u002Fbuild.ts]\n\u002F\u002F create jsdom from renderedHTML\nconst jsdom = new JSDOM(renderedHTML)\n\n\u002F\u002F render current page's preloadLinks\nrenderPreloadLinks(jsdom.window.document, ctx.modules || new Set\u003Cstring>(), ssrManifest)\n\n\u002F\u002F render head\nif (head) \u002F\u002F [!code highlight]\n  await renderDOMHead(head, { document: jsdom.window.document }) \u002F\u002F [!code highlight]\n\nconst html = jsdom.serialize()\nlet transformed = (await onPageRendered?.(route, html, appCtx)) || html\nif (beasties)\n  transformed = await beasties.process(transformed)\n\nconst formatted = await formatHtml(transformed, formatting)\n```\n\nCette partie est responsable de la génération du HTML avant son enregistrement. La ligne intéressante est `await renderDOMHead(head, { document: jsdom.window.document })`. C'est la seule partie où le head est utilisé et rendu dans le HTML.\n\nEncore plus intéressant, la fonction vient de `@unhead\u002Fdom`. Étrange, puisque nous utilisons le SSR, avoir une fonction front côté serveur paraît suspect.\n\nEt j'avais raison. Cette fonction n'optimise pas le head. Elle est censée modifier les éléments du head une fois la page chargée, côté client. Amener toute la logique de tri côté client est complètement inutile.\n\nEn lisant la documentation d'Unhead, j'ai découvert la manière recommandée de l'utiliser avec le SSR.\n\n```ts [server.ts]\nimport { transformHtmlTemplate } from '@unhead\u002Fvue\u002Fserver' \u002F\u002F [!code highlight]\n\nconst rendered = await render(url)\n\nconst html = await transformHtmlTemplate( \u002F\u002F [!code highlight]\n  rendered.head,\n  template.replace(`\u003C!--app-html-->`, rendered.html ?? '')\n)\n```\n\nLa fonction `transformHtmlTemplate` extrait les éléments actuels du head depuis le template et les injecte dans le head avant de re-render le HTML et de l'injecter dans la page. Ça garantit que le head est correctement trié avant d'être envoyé au client.\n\nÇa semblait être une bonne solution pour corriger le tri du head. J'ai donc créé une PR sur Vite SSG pour améliorer l'intégration avec Unhead.\n\n[refactor: uses unhead\u002Fserver to optimize head](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-ssg\u002Fpull\u002F464)\n\nPour utiliser rapidement ce correctif dans mon projet, j'ai patché Vite SSG en local. Ça m'a permis de continuer à développer mon projet sans attendre que ma PR soit mergée puis publiée.\n\nMais tout ne s'est pas passé comme prévu.\n\nLe head était presque bien ordonné, mais après ce correctif, le FOUC était toujours là. _Triste._\n\nÇa signifie que le problème n'est pas lié au tri du head.\n\n## Enquête plus approfondie\n\nLe patch de Vite SSG n'est pas inutile, car il améliore le tri du head. Cependant, il n'a pas résolu le FOUC. Il a fallu creuser davantage.\n\nJ'ai continué à chercher la cause racine. J'ai essayé d'analyser les performances de l'application pour identifier d'éventuels goulots d'étranglement, conditions de course ou autres problèmes pouvant causer le FOUC. Je n'ai rien trouvé.\n\nJ'ai inspecté le CSS et constaté que les styles de la prose s'appliquaient correctement, mais pas ceux de Nuxt UI. Si tu regardes attentivement la vidéo, tu verras la couleur du titre passer du noir au blanc, alors que le texte en dessous est déjà blanc. Les variables CSS de Nuxt UI ne sont pas disponibles lors du premier rendu, mais sont appliquées quand le JavaScript se charge.\n\nPour confirmer ce soupçon, j'ai vérifié le code source de Nuxt UI où j'ai trouvé un plugin Vue nommé `colors.ts`. Le même qui a causé le problème de build au début.\n\nTout menait à ce plugin, alors j'ai pris le temps de le comprendre, et j'ai trouvé la cause racine à la fois du FOUC et du problème de build :\n\n```ts [src\u002Fruntime\u002Fplugins\u002Fcolors.ts]\nconst headData: UseHeadInput = {\n  style: [{\n    innerHTML: () => root.value,\n    tagPriority: -2,\n    id: 'nuxt-ui-colors'\n  }]\n}\n\nif (import.meta.client && nuxtApp.isHydrating && !nuxtApp.payload.serverRendered) {\n  const style = document.createElement('style')\n\n  style.innerHTML = root.value\n  style.setAttribute('data-nuxt-ui-colors', '')\n  document.head.appendChild(style)\n\n  headData.script = [{\n    innerHTML: 'document.head.removeChild(document.querySelector(\\'[data-nuxt-ui-colors]\\'))'\n  }]\n}\n\nuseHead(headData)\n```\n\nPlusieurs problèmes se mêlent ici.\n\nLes sept premières lignes, ainsi que la dernière, servent à injecter les variables CSS de Nuxt UI dans le head, à la fois au build et à l'exécution. En désactivant le plugin, j'ai supprimé ce comportement, donc le FOUC se produit inévitablement.\n\nLe bloc `if` crée un élément style et ne doit s'exécuter que côté client, pendant l'hydratation, si le contenu n'est pas rendu côté serveur. Mon app utilise Vite SSG pour être rendue côté serveur, donc ça ne devrait pas arriver.\n\nAlors, c'est quoi ce `nuxtApp` ? Continuons l'enquête.\n\nAu départ, Nuxt UI est fait pour Nuxt, mais grâce au travail de [Daniel Roe](https:\u002F\u002Fgithub.com\u002Fdanielroe) et à beaucoup de stubs, Nuxt UI est aussi utilisable en dehors de Nuxt. L'un des stubs est le composable `useNuxtApp` :\n\n```ts [src\u002Fruntime\u002Fvue\u002Fstubs.ts]\nexport function useNuxtApp() {\n  return {\n    isHydrating: true,\n    payload: { serverRendered: false },\n    hooks,\n    hook: hooks.hook\n  }\n}\n```\n\nLe voilà. La propriété `serverRendered` est fixée à false, tout le temps. Donc même quand Vite SSG est utilisé, le plugin pense qu'il est dans un navigateur et essaie de créer l'élément style. Évidemment, ça ne marche pas.\n\nLe correctif est vraiment simple. Au lieu de renvoyer toujours false, il faut renvoyer `import.meta.env.SSR || false` pour détecter correctement le rendu côté serveur.\n\n\u003CPullRequest href=\"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F5347\">\n  fix(vue): check \u003Ccode>import.meta.env.SSR\u003C\u002Fcode> to support \u003Ccode>vite-ssg\u003C\u002Fcode>\n\u003C\u002FPullRequest>\n\nEnsuite, j'ai réactivé le plugin Nuxt UI, le build est passé, et le FOUC avait disparu ! _J'adore. Un petit booléen a fait une énorme différence._\n\n## Améliorer le head\n\nLe build passait et le FOUC avait disparu. J'ai continué à vérifier le head du fichier généré pour m'assurer que tout était en ordre.\n\nCe n'était pas le cas.\n\nJe pourrais m'arrêter là, mais j'étais lancé dans une série de pull requests, plongé dans Unhead, Nuxt UI et Vite SSG.\n\nJ'ai découvert deux choses.\n\n1. Nuxt UI définit un `tagPriority` à `-2` sur l'élément style lors de son injection. Ça signifie qu'il sera l'un des premiers éléments dans le head au lieu d'être placé automatiquement.\n2. Unhead gérait mal l'ordre des scripts `inlined` et `module`.\n\nPour le premier point, j'ai seulement [écrit un commentaire](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F5347#issuecomment-3472594042) pour comprendre pourquoi il était défini. Ça pourrait être un prérequis pour l'intégration Nuxt.\n\nEn revanche, pour le second point, sachant que c'était un bug, j'ai ouvert deux pull requests.\n\n\u003CPullRequest title=\"fix: inline scripts ordering\" href=\"https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F596\" \u002F>\n\u003CPullRequest title=\"fix: module scripts\" href=\"https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F600\" \u002F>\n\nEn travaillant sur ces pull requests, j'ai découvert que la CI ne fonctionnait pas, que certains tests échouaient et que la vérification de type était cassée, alors j'ai créé 3 PR pour tout corriger.\n\n\u003CPullRequest title=\"test: update snapshots\" href=\"https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F598\" \u002F>\n\u003CPullRequest title=\"test: fix tsc\" href=\"https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F597\" \u002F>\n\u003CPullRequest title=\"ci: fix\" href=\"https:\u002F\u002Fgithub.com\u002Funjs\u002Funhead\u002Fpull\u002F599\" \u002F>\n\nJe corrigeais juste un petit bug dans la façon dont Unhead gérait l'ordre des scripts, et j'ai fini par corriger les tests et l'intégration.\n\nQuand tu prends soin des détails, ça finit souvent comme ça, et en faisant des pull requests propres et qui fonctionnent, tu augmentes tes chances d'être accepté.\n\n_Je n'ai même pas eu le temps d'écrire un article de blog à leur sujet avant qu'elles ne soient mergées._\n\n## L'open source, pas par choix\n\nJe voulais juste construire mon site pour documenter mon infrastructure. J'ai fini par corriger des bugs dans trois outils et ouvrir 7 pull requests.\n\nHonnêtement, j'adorerais ne jamais avoir à les corriger, et j'aimerais ne jamais avoir à passer du temps à corriger ces bugs. Mais ce n'est pas le contrat implicite de l'open source, ni son état d'esprit. _C'est ainsi que je le vois. Tout le monde ne le voit pas comme ça._\n\nDes gens te proposent, gratuitement, et souvent en l'état, leur code pour ton propre usage. Ça te fait gagner du temps et de l'argent et te permet de construire plus vite et mieux. Oui, je passe du temps à corriger des bugs, mais combien de temps ai-je gagné en utilisant des logiciels open source ? À quelle fréquence ai-je pu contribuer à des logiciels open source pour construire l'expérience que je voulais dans mes projets ? Ça ne vaut même pas la peine de commencer à compter. Rendre un peu de ton savoir et de ton temps semble être un échange équitable.\n\nEt c'est normal que les logiciels aient des bugs, des fonctionnalités manquantes ou simplement des cas d'usage non prévus. C'est inhérent au logiciel et ni le logiciel propriétaire ni l'open source n'y échappent.\n\nCependant, avec l'open source, tu peux changer la donne en ayant cette liberté de contribuer aux logiciels que tu utilises.\n\n---\n\nConstruis des choses, n'importe quoi, mais donne vie à tes envies, sur Internet, ne t'arrête pas à `localhost`.\n\nEn chemin, tu rencontreras des bugs, des fonctionnalités manquantes, ou simplement des choses qui pourraient être améliorées. Quand c'est le cas, n'hésite pas à plonger dans ta stack, à la comprendre, et à essayer de corriger le problème.\n\n**C'est comme ça que tu contribues à l'open source**. Pas l'inverse. C'est aussi l'une des meilleures façons d'apprendre.\n\nCultive l'état d'esprit open source et les opportunités surgiront naturellement.\n",{"title":14131,"description":16367},{"loc":16369,"images":16373},[16374],{"loc":15323},"fr\u002Fposts\u002Fwant-to-contribute-to-open-source-youre-doing-it-wrong","aNcA8vGhQaKbB4JjBU1vQSYC8xArqruT5aKuL6h6GZY",{"id":16378,"title":16379,"body":16380,"contentId":9315,"date":16849,"description":16850,"extension":18,"meta":16851,"navigation":20,"path":16852,"rawbody":16853,"robots":23,"schemaOrg":23,"seo":16854,"sitemap":16855,"stem":16869,"__hash__":16870},"posts\u002Ffr\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest.md","Mon aventure à Nantes pour le plus grand DevFest d'Europe",{"type":7,"value":16381,"toc":16844},[16382,16385,16388,16401,16405,16410,16413,16422,16427,16433,16439,16442,16448,16463,16472,16478,16484,16493,16499,16502,16505,16508,16672,16675,16679,16682,16737,16740,16742,16748,16754,16760,16763,16772,16778,16786,16792,16795,16797,16803,16809,16811,16816,16823,16826,16829,16835],[35,16383,16384],{},"DevFest est une série de conférences tech menées par la communauté et organisée par les Google Developer Groups (GDG) partout dans le monde. Chaque événement est organisé par des GDG locaux et propose un mélange de talks, d'ateliers et d'opportunités de networking pour des développeurs de tous niveaux.",[35,16386,16387],{},"Je les ai découverts en cherchant des événements où soumettre mes propositions de talks. Quand on a un talk dans lequel on a mis beaucoup de travail et que les gens l'apprécient, on a vraiment envie de le partager à un public plus large.",[35,16389,16390,16391,16394,16395,1655,16398,16400],{},"Alors après ",[39,16392,16393],{"href":8986},"Devoxx 2025",", j'ai soumis mes deux talks, ",[6862,16396,16397],{},"La réactivité et les signaux: démystifions la magie du frontend",[6862,16399,13967],{},", à de nombreux meetups et conférences. Le 24 juin, j'ai reçu un email de Conference Hall, une plateforme qui héberge des CFP, m'annonçant que l'un de mes talks avait été accepté pour le DevFest Nantes 2025. J'étais tellement heureux de découvrir une nouvelle conférence !",[51,16402,16404],{"id":16403},"devfest-nantes-2025","DevFest Nantes 2025",[35,16406,16407],{},[1216,16408,16409],{},"La vie me conduit plus que je ne la conduis en ce moment. J'ai à peine lu les emails des organisateurs. Je n'avais aucune idée de ce qui m'attendait.",[35,16411,16412],{},"DevFest Nantes est le plus grand DevFest d'Europe: plus de 3 500 participants, 2 jours, 4 tracks, 71 talks et 90 speakers. C'est énorme !",[35,16414,16415,16416,16421],{},"La conférence a commencé par le dîner des speakers le mercredi 15 octobre. Celui-ci a eu lieu au ",[39,16417,16420],{"href":16418,"rel":16419},"https:\u002F\u002Fwww.lesmachines-nantes.fr\u002Fdecouvrir\u002Fle-carrousel-des-mondes-marins\u002F",[77],"Carrousel des Mondes Marins"," où nous avons eu le privilège de le visiter et de faire un tour.",[35,16423,16424],{},[1707,16425],{"alt":16420,"src":16426},"\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fcarrousel-of-the-marine-worlds.webp",[35,16428,16429],{},[1707,16430],{"alt":16431,"src":16432},"Moi, aux commandes de ma propre créature marine","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fme-piloting-my-own-marine-creature.webp",[35,16434,16435,16436],{},"Ensuite, nous avons eu un moment de networking pour rencontrer et échanger avec d'autres speakers. C'est toujours un plaisir de revoir des visages familiers et de découvrir de nouveaux speakers. C'est encore plus vrai quand la nourriture est bonne. ",[1216,16437,16438],{},"Ta batterie sociale doit être pleine avant d'aller à un dîner speakers !",[35,16440,16441],{},"L'événement a démarré le jeudi 16 octobre 2025. Il se tenait au Centre des Congrès de Nantes. Le bâtiment est vraiment impressionnant, avec de très grandes salles. Une chose m'a vraiment impressionné: chaque stand de sponsor était magnifiquement décoré, sur le thème, créant une atmosphère magique très agréable. Même les animations pour gagner des goodies et attirer les gens avaient été créées spécialement pour l'événement. La nourriture était incroyable, avec de quoi manger en quantité et sans temps d'attente. Il y avait même une salle au calme pour que les speakers se détendent.",[35,16443,16444],{},[1707,16445],{"alt":16446,"src":16447},"Centre des Congrès de Nantes","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fnantes-congress-center.webp",[35,16449,16450,16451,16456,16457,16462],{},"En attendant le début d'un talk, je faisais défiler LinkedIn et j'ai vu ",[39,16452,16455],{"href":16453,"rel":16454},"https:\u002F\u002Fwww.linkedin.com\u002Fposts\u002Fxavier-pestel_hey-super-moment-heureux-de-rencontrer-activity-7384490805656387584-psjo",[77],"un post de Xavier"," disant qu'il était au DevFest Nantes. Peut-être que vous ne le connaissez pas. Il a une chaîne YouTube nommée ",[39,16458,16461],{"href":16459,"rel":16460},"https:\u002F\u002Fwww.youtube.com\u002F@xavki",[77],"xavki"," où l'on peut tout apprendre sur le cloud et l'infrastructure. C'est une mine d'or ! Un ami à moi a décroché le job qu'il a aujourd'hui grâce à lui. Je suis donc allé lui dire bonjour.",[35,16464,16465,16466,16471],{},"Honnêtement, je suis resté sans voix pendant quelques secondes. J'étais comme un enfant devant son héros préféré. Pendant qu'on discutait, Stéphane nous a rejoints. Inattendu ! Stéphane a un blog nommé ",[39,16467,16470],{"href":16468,"rel":16469},"https:\u002F\u002Fblog.stephane-robert.info\u002F",[77],"DevSecOps"," qui contient énormément d'informations précieuses. Un moment bluffant.",[35,16473,16474],{},[1707,16475],{"alt":16476,"src":16477},"Stéphane, Xavier et moi pendant le DevFest Nantes","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fstephane-xavier-me.webp",[35,16479,16480,16481],{},"DevFest Nantes, c'est aussi une soirée entre le premier et le deuxième jour. On y rencontre des gens, beaucoup de gens, on mange bien, on danse et on profite simplement du moment. Vous n'aurez pas de photos car ce qui se passe au DevFest Nantes reste au DevFest Nantes. ",[1216,16482,16483],{},"Avec un verre de vin dans la main gauche et une crêpe dans la main droite, difficile de prendre des photos.",[35,16485,16486,16487,16492],{},"Le dernier créneau du vendredi était un spectacle de ",[39,16488,16491],{"href":16489,"rel":16490},"https:\u002F\u002Flafaltazi.com",[77],"La Faltazi",". Quarante-cinq minutes de pur fou rire. Quelle excellente idée de terminer la conférence par du théâtre d'impro ! Une performance incroyable et une fin parfaite.",[35,16494,16495],{},[1707,16496],{"alt":16497,"src":16498},"La Faltazi sur scène au DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fla-faltazi-performing-at-devfest-nantes-2025.webp",[35,16500,16501],{},"Voilà pour la conférence en quelques mots mais qu'en est-il des talks ?",[35,16503,16504],{},"Il y a des choses qui ne changent pas. Jusqu'à la dernière minute, je n'avais pas lu le programme, donc j'ai surtout suivi mon intuition pour choisir les sessions auxquelles assister. Par exemple, j'écris un talk sur l'IA donc j'ai assisté à quelques talks liés à ce sujet, pour voir comment ils l'abordaient et essayer d'apporter quelque chose de nouveau. Je n'étais pas vraiment intéressé par le contenu en soi.",[35,16506,16507],{},"Les talks auxquels j'ai assisté:",[1593,16509,16510,16582],{},[71,16511,16512,16515],{},[6862,16513,16514],{},"Jeudi 16 octobre 2025",[1593,16516,16517,16527,16537,16552,16562,16572],{},[71,16518,16519,6865,16522],{},[6862,16520,16521],{},"Advice Well Taken",[39,16523,16526],{"href":16524,"rel":16525},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdasha-ilina-633005132\u002F",[77],"Dasha Ilina",[71,16528,16529,6865,16532],{},[6862,16530,16531],{},"Ressusciter vos présentations en articles de blog avec AWS Amplify Gen 2 et Amazon Bedrock",[39,16533,16536],{"href":16534,"rel":16535},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fadrien-lebret\u002F",[77],"Adrien Lebret",[71,16538,16539,6865,16542,1655,16547],{},[6862,16540,16541],{},"Révolution TanStack Query : Enfin une bonne gestion d'état",[39,16543,16546],{"href":16544,"rel":16545},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flucas-audart\u002F",[77],"Lucas Audart",[39,16548,16551],{"href":16549,"rel":16550},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmickaelalves\u002F",[77],"Mickael Alves",[71,16553,16554,6865,16557],{},[6862,16555,16556],{},"The future of routing with the Navigation API",[39,16558,16561],{"href":16559,"rel":16560},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fposva\u002F",[77],"Eduardo San Martin Morote",[71,16563,16564,6865,16567],{},[6862,16565,16566],{},"From RAG to riches : les secrets des LLM augmentés par la donnée !",[39,16568,16571],{"href":16569,"rel":16570},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurent-mangin\u002F",[77],"Laurent Mangin",[71,16573,16574,6865,16577],{},[6862,16575,16576],{},"État des Lieux de la Souveraineté du Cloud : Des Clouds Publics aux Clouds Confidentiels",[39,16578,16581],{"href":16579,"rel":16580},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fseifeddinmansri\u002F",[77],"Seifeddin Mansri",[71,16583,16584,16587],{},[6862,16585,16586],{},"Vendredi 17 octobre 2025",[1593,16588,16589,16600,16614,16629,16639,16654,16664],{},[71,16590,16591,6865,16594,16599],{},[6862,16592,16593],{},"Karpenter * Keda : Le duo gagnant du FinOps",[39,16595,16598],{"href":16596,"rel":16597},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgmembre\u002F",[77],"Guillaume Membré"," et Sébastien Fourreau",[71,16601,16602,6865,16605,1655,16609],{},[6862,16603,16604],{},"45 min pour mettre son application à genoux : le guide complet du test de charge",[39,16606,16608],{"href":9596,"rel":16607},[77],"Loïc Ortola",[39,16610,16613],{"href":16611,"rel":16612},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathilde-lorrain\u002F",[77],"Mathilde Lorrain",[71,16615,16616,6865,16619,1655,16624],{},[6862,16617,16618],{},"Limits, Requests, QoS, PriorityClasses, on balaie ce que vous pensiez savoir sur le scheduling dans Kubernetes",[39,16620,16623],{"href":16621,"rel":16622},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdenis-germain\u002F",[77],"Denis Germain",[39,16625,16628],{"href":16626,"rel":16627},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fquentin-joly-it\u002F",[77],"Quentin Joly",[71,16630,16631,6865,16634],{},[6862,16632,16633],{},"Agents intelligents, la nouvelle frontière des LLMs",[39,16635,16638],{"href":16636,"rel":16637},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fglaforge\u002F",[77],"Guillaume Laforge",[71,16640,16641,6865,16644,1655,16649],{},[6862,16642,16643],{},"Optimiser des UIs Web legacy dans un moteur de jeu AAA",[39,16645,16648],{"href":16646,"rel":16647},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcedriccf\u002F",[77],"Cédric Chariere Fiedler",[39,16650,16653],{"href":16651,"rel":16652},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclementgregoire\u002F",[77],"Clément Grégoire",[71,16655,16656,6865,16659],{},[6862,16657,16658],{},"L'aspirant chevalier LynxJS peut-il détrôner monseigneur React Native et le duc de Flutter ?",[39,16660,16663],{"href":16661,"rel":16662},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsimon-bernardin-271633b2\u002F",[77],"Simon Bernardin",[71,16665,16666,6865,16668,16671],{},[6862,16667,13967],{},[39,16669,7045],{"href":7253,"rel":16670},[77]," (c'est moi !)",[35,16673,16674],{},"Et je sais déjà que je regarderai les enregistrements de certains talks que j'ai manqués.",[51,16676,16678],{"id":16677},"coups-de-cœur","Coups de cœur",[35,16680,16681],{},"À chaque conférence, j'aime vraiment mettre en avant quelques talks que j'ai particulièrement appréciés. C'est basé sur l'impact qu'ils ont eu sur moi, la qualité du contenu et la capacité des speakers à engager le public. C'est aussi très personnel et subjectif.",[1593,16683,16684,16715],{},[71,16685,16686,6865,16688,16691,16693,16694,16699,16700,16705,16706,16708,16709,16711],{},[6862,16687,16556],{},[39,16689,16561],{"href":16559,"rel":16690},[77],[7174,16692],{},"Ce talk est très pointu car il y a peu de chances que nous ayons à jouer directement avec la Navigation API. C'est davantage une API pour les auteurs de packages que pour les utilisateurs finaux, comme Eduardo, qui est le créateur de ",[39,16695,16698],{"href":16696,"rel":16697},"https:\u002F\u002Frouter.vuejs.org\u002F",[77],"Vue Router"," et de ",[39,16701,16704],{"href":16702,"rel":16703},"https:\u002F\u002Fpinia.vuejs.org\u002F",[77],"Pinia",". Mais j'ai trouvé vraiment important de comprendre pourquoi cette API existe, les limites de l'actuelle et le potentiel qu'elle a pour l'avenir.",[7174,16707],{},"Ses slides sont truffées d'animations intelligentes, ce qui les rend vraiment faciles à suivre.",[7174,16710],{},[1707,16712],{"alt":16713,"src":16714},"Eduardo San Martin Morote présentant au DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fthe-future-of-routing-with-the-navigation-api.webp",[71,16716,16717,6865,16719,1655,16722,16725,16727,16728,16730,16731,16733],{},[6862,16718,16604],{},[39,16720,16608],{"href":9596,"rel":16721},[77],[39,16723,16613],{"href":16611,"rel":16724},[77],[7174,16726],{},"Ce talk était déjà dans mes coups de cœur de ",[39,16729,16393],{"href":8986},". Je l'inclus à nouveau car je l'ai vu au DevFest Nantes et je l'ai vraiment apprécié. C'est vraiment impressionnant. Ils pourraient parler bien plus de 45 minutes. Le temps passe à toute vitesse pendant leur présentation, elle est bien écrite et très accessible.",[7174,16732],{},[1707,16734],{"alt":16735,"src":16736},"Loïc Ortola et Mathilde Lorrain au DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002F45-min-pour-mettre-son-application-%C3%A0-genoux-le-guide-complet-du-test-de-charge.webp",[35,16738,16739],{},"Je vous recommande vivement de regarder ces talks.",[51,16741,7218],{"id":7217},[35,16743,16744,16745,16747],{},"Mon talk était ",[6862,16746,9152],{},". Mon créneau était le dernier de la journée, le vendredi 17 octobre à 16:50. J'ai eu l'occasion de le jouer plusieurs fois, donc le stress n'était pas un problème. Cependant, après deux longues journées, j'étais vraiment fatigué. Je me suis reposé une heure juste avant mon talk.",[35,16749,16750],{},[1707,16751],{"alt":16752,"src":16753},"Moi et mon café.","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fme-and-my-coffee.webp",[35,16755,16756,16757],{},"Quand je me suis réveillé, j'ai bu un espresso et j'étais reparti. ",[1216,16758,16759],{},"Et je n'aime toujours pas le café.",[35,16761,16762],{},"Il est maintenant temps de monter sur scène.",[35,16764,16765,16766,16771],{},"Avant que je monte sur scène, ",[39,16767,16770],{"href":16768,"rel":16769},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpierre-tibulle-68674812b\u002F",[77],"Pierre"," m'a présenté. C'était la première fois que je vivais ça et c'est vraiment sympa. Il s'est aussi assuré que tout allait bien côté setup. Merci, Pierre !",[35,16773,16774],{},[1707,16775],{"alt":16776,"src":16777},"Pierre me présente au DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fpierre-introducing-me.webp",[35,16779,16780,16781,16785],{},"Ensuite, je suis monté sur scène et j'ai donné mon talk. Un petit selfie avec le public et parlons d'",[39,16782,16784],{"href":14030,"rel":16783},[77],"e18e"," ! L'amphithéâtre était vraiment impressionnant: 800 places ! C'est beaucoup et, de loin, la plus grande salle dans laquelle j'ai parlé.",[35,16787,16788],{},[1707,16789],{"alt":16790,"src":16791},"Mon selfie avec le public","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fmy-selfie-with-the-audience.webp",[35,16793,16794],{},"Tout s'est bien passé et les gens ont apprécié ! Quinze minutes, c'est tellement court, c'est fini juste quand on commence à se sentir bien, mais c'est comme ça.",[1158,16796],{},[35,16798,16799,16800,15917],{},"J'ai vraiment apprécié le DevFest Nantes, certainement ",[6862,16801,16802],{},"la meilleure conférence de l'année",[35,16804,16805],{},[1707,16806],{"alt":16807,"src":16808},"L'équipe GDG Nantes au DevFest Nantes 2025","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fgdg-nantes-team.webp",[1158,16810],{},[35,16812,16813],{},[1216,16814,16815],{},"Partie technique, vous pouvez passer si ça ne vous intéresse pas.",[35,16817,16818,16819,16822],{},"Pour cette conférence, et comme pour les précédentes, j'ai utilisé ",[39,16820,7894],{"href":7892,"rel":16821},[77],", le SaaS que je construis pour rendre les présentations plus interactives. Ça fonctionne bien, ce qui semble normal, et pourtant je suis toujours bluffé d'avoir pu construire une plateforme, tout seul, qui marche tout simplement.",[35,16824,16825],{},"En moins de quinze minutes, plus de 17 000 requêtes (25 000 au total, mais 8 000 mises en cache par Cloudflare) ont frappé mon petit VPS à 4 € (2 vCPU et 4 Go de RAM). Aucun lag, aucun bug.",[35,16827,16828],{},"Pour les plus curieux d'entre vous, voici la répartition des durées de la route utilisée pour envoyer un emoji sur ma slide. Au pic, plus de 100 personnes tentaient d'accéder à l'app en même temps.",[35,16830,16831],{},[1707,16832],{"alt":16833,"src":16834},"Répartition des durées de la route utilisée pour envoyer des réactions en direct","\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fduration-breakdown.webp",[35,16836,16837,16838,16843],{},"En coulisses, le VPS fait aussi tourner Plausible, qui consomme beaucoup de RAM. Je teste des alternatives plus légères. Sur la supervision du VPS, l'utilisation CPU a culminé à 87 %, mais la load average est restée sous contrôle (0,73, 0,68, 0,61). Après quelques tests avec ",[39,16839,16842],{"href":16840,"rel":16841},"https:\u002F\u002Fgithub.com\u002Fmcollina\u002Fautocannon",[77],"autocannon",", le VPS a pu encaisser 41 req\u002Fs avec 100 connexions concurrentes sur 60 secondes en accédant à une page d'overview. Après quelques ajustements, j'ai pu monter les perfs à 47 req\u002Fs, et une fois Plausible arrêté, je m'attends à atteindre 53 req\u002Fs. Donc oui, le VPS est petit, mais il suffit pour l'instant.",{"title":13,"searchDepth":14,"depth":14,"links":16845},[16846,16847,16848],{"id":16403,"depth":14,"text":16404},{"id":16677,"depth":14,"text":16678},{"id":7217,"depth":14,"text":7218},"2025-10-29","Après Devoxx, MiXiT et BreizhCamp, j'ai eu l'opportunité d'assister au DevFest Nantes. Deux jours de talks et de rencontres avec d'autres développeurs.",{},"\u002Ffr\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest","---\ncontentId: da3b2630-1622-49fd-9311-35c9b25c5d54\ntitle: Mon aventure à Nantes pour le plus grand DevFest d'Europe\ndescription: Après Devoxx, MiXiT et BreizhCamp, j'ai eu l'opportunité d'assister au DevFest Nantes. Deux jours de talks et de rencontres avec d'autres développeurs.\ndate: 2025-10-29\n---\n\nDevFest est une série de conférences tech menées par la communauté et organisée par les Google Developer Groups (GDG) partout dans le monde. Chaque événement est organisé par des GDG locaux et propose un mélange de talks, d'ateliers et d'opportunités de networking pour des développeurs de tous niveaux.\n\nJe les ai découverts en cherchant des événements où soumettre mes propositions de talks. Quand on a un talk dans lequel on a mis beaucoup de travail et que les gens l'apprécient, on a vraiment envie de le partager à un public plus large.\n\nAlors après [Devoxx 2025](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md), j'ai soumis mes deux talks, **La réactivité et les signaux: démystifions la magie du frontend** et **En JavaScript, il y a un paquet pour tout... et souvent pour rien.**, à de nombreux meetups et conférences. Le 24 juin, j'ai reçu un email de Conference Hall, une plateforme qui héberge des CFP, m'annonçant que l'un de mes talks avait été accepté pour le DevFest Nantes 2025. J'étais tellement heureux de découvrir une nouvelle conférence !\n\n## DevFest Nantes 2025\n\n_La vie me conduit plus que je ne la conduis en ce moment. J'ai à peine lu les emails des organisateurs. Je n'avais aucune idée de ce qui m'attendait._\n\nDevFest Nantes est le plus grand DevFest d'Europe: plus de 3 500 participants, 2 jours, 4 tracks, 71 talks et 90 speakers. C'est énorme !\n\nLa conférence a commencé par le dîner des speakers le mercredi 15 octobre. Celui-ci a eu lieu au [Carrousel des Mondes Marins](https:\u002F\u002Fwww.lesmachines-nantes.fr\u002Fdecouvrir\u002Fle-carrousel-des-mondes-marins\u002F) où nous avons eu le privilège de le visiter et de faire un tour.\n\n![Carrousel des Mondes Marins](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fcarrousel-of-the-marine-worlds.webp)\n\n![Moi, aux commandes de ma propre créature marine](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fme-piloting-my-own-marine-creature.webp)\n\nEnsuite, nous avons eu un moment de networking pour rencontrer et échanger avec d'autres speakers. C'est toujours un plaisir de revoir des visages familiers et de découvrir de nouveaux speakers. C'est encore plus vrai quand la nourriture est bonne. _Ta batterie sociale doit être pleine avant d'aller à un dîner speakers !_\n\nL'événement a démarré le jeudi 16 octobre 2025. Il se tenait au Centre des Congrès de Nantes. Le bâtiment est vraiment impressionnant, avec de très grandes salles. Une chose m'a vraiment impressionné: chaque stand de sponsor était magnifiquement décoré, sur le thème, créant une atmosphère magique très agréable. Même les animations pour gagner des goodies et attirer les gens avaient été créées spécialement pour l'événement. La nourriture était incroyable, avec de quoi manger en quantité et sans temps d'attente. Il y avait même une salle au calme pour que les speakers se détendent.\n\n![Centre des Congrès de Nantes](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fnantes-congress-center.webp)\n\nEn attendant le début d'un talk, je faisais défiler LinkedIn et j'ai vu [un post de Xavier](https:\u002F\u002Fwww.linkedin.com\u002Fposts\u002Fxavier-pestel_hey-super-moment-heureux-de-rencontrer-activity-7384490805656387584-psjo) disant qu'il était au DevFest Nantes. Peut-être que vous ne le connaissez pas. Il a une chaîne YouTube nommée [xavki](https:\u002F\u002Fwww.youtube.com\u002F@xavki) où l'on peut tout apprendre sur le cloud et l'infrastructure. C'est une mine d'or ! Un ami à moi a décroché le job qu'il a aujourd'hui grâce à lui. Je suis donc allé lui dire bonjour.\n\nHonnêtement, je suis resté sans voix pendant quelques secondes. J'étais comme un enfant devant son héros préféré. Pendant qu'on discutait, Stéphane nous a rejoints. Inattendu ! Stéphane a un blog nommé [DevSecOps](https:\u002F\u002Fblog.stephane-robert.info\u002F) qui contient énormément d'informations précieuses. Un moment bluffant.\n\n![Stéphane, Xavier et moi pendant le DevFest Nantes](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fstephane-xavier-me.webp)\n\nDevFest Nantes, c'est aussi une soirée entre le premier et le deuxième jour. On y rencontre des gens, beaucoup de gens, on mange bien, on danse et on profite simplement du moment. Vous n'aurez pas de photos car ce qui se passe au DevFest Nantes reste au DevFest Nantes. _Avec un verre de vin dans la main gauche et une crêpe dans la main droite, difficile de prendre des photos._\n\nLe dernier créneau du vendredi était un spectacle de [La Faltazi](https:\u002F\u002Flafaltazi.com). Quarante-cinq minutes de pur fou rire. Quelle excellente idée de terminer la conférence par du théâtre d'impro ! Une performance incroyable et une fin parfaite.\n\n![La Faltazi sur scène au DevFest Nantes 2025](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fla-faltazi-performing-at-devfest-nantes-2025.webp)\n\nVoilà pour la conférence en quelques mots mais qu'en est-il des talks ?\n\nIl y a des choses qui ne changent pas. Jusqu'à la dernière minute, je n'avais pas lu le programme, donc j'ai surtout suivi mon intuition pour choisir les sessions auxquelles assister. Par exemple, j'écris un talk sur l'IA donc j'ai assisté à quelques talks liés à ce sujet, pour voir comment ils l'abordaient et essayer d'apporter quelque chose de nouveau. Je n'étais pas vraiment intéressé par le contenu en soi.\n\nLes talks auxquels j'ai assisté:\n\n- **Jeudi 16 octobre 2025**\n  - **Advice Well Taken** par [Dasha Ilina](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdasha-ilina-633005132\u002F)\n  - **Ressusciter vos présentations en articles de blog avec AWS Amplify Gen 2 et Amazon Bedrock** par [Adrien Lebret](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fadrien-lebret\u002F)\n  - **Révolution TanStack Query : Enfin une bonne gestion d'état** par [Lucas Audart](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flucas-audart\u002F) et [Mickael Alves](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmickaelalves\u002F)\n  - **The future of routing with the Navigation API** par [Eduardo San Martin Morote](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fposva\u002F)\n  - **From RAG to riches : les secrets des LLM augmentés par la donnée !** par [Laurent Mangin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaurent-mangin\u002F)\n  - **État des Lieux de la Souveraineté du Cloud : Des Clouds Publics aux Clouds Confidentiels** par [Seifeddin Mansri](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fseifeddinmansri\u002F)\n- **Vendredi 17 octobre 2025**\n  - **Karpenter * Keda : Le duo gagnant du FinOps** par [Guillaume Membré](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgmembre\u002F) et Sébastien Fourreau\n  - **45 min pour mettre son application à genoux : le guide complet du test de charge** par [Loïc Ortola](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F) et [Mathilde Lorrain](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathilde-lorrain\u002F)\n  - **Limits, Requests, QoS, PriorityClasses, on balaie ce que vous pensiez savoir sur le scheduling dans Kubernetes** par [Denis Germain](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdenis-germain\u002F) et [Quentin Joly](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fquentin-joly-it\u002F)\n  - **Agents intelligents, la nouvelle frontière des LLMs** par [Guillaume Laforge](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fglaforge\u002F)\n  - **Optimiser des UIs Web legacy dans un moteur de jeu AAA** par [Cédric Chariere Fiedler](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcedriccf\u002F) et [Clément Grégoire](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclementgregoire\u002F)\n  - **L'aspirant chevalier LynxJS peut-il détrôner monseigneur React Native et le duc de Flutter ?** par [Simon Bernardin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsimon-bernardin-271633b2\u002F)\n  - **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** par [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25) (c'est moi !)\n\nEt je sais déjà que je regarderai les enregistrements de certains talks que j'ai manqués.\n\n## Coups de cœur\n\nÀ chaque conférence, j'aime vraiment mettre en avant quelques talks que j'ai particulièrement appréciés. C'est basé sur l'impact qu'ils ont eu sur moi, la qualité du contenu et la capacité des speakers à engager le public. C'est aussi très personnel et subjectif.\n\n- **The future of routing with the Navigation API** par [Eduardo San Martin Morote](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fposva\u002F)\n\n  Ce talk est très pointu car il y a peu de chances que nous ayons à jouer directement avec la Navigation API. C'est davantage une API pour les auteurs de packages que pour les utilisateurs finaux, comme Eduardo, qui est le créateur de [Vue Router](https:\u002F\u002Frouter.vuejs.org\u002F) et de [Pinia](https:\u002F\u002Fpinia.vuejs.org\u002F). Mais j'ai trouvé vraiment important de comprendre pourquoi cette API existe, les limites de l'actuelle et le potentiel qu'elle a pour l'avenir.\n\n  Ses slides sont truffées d'animations intelligentes, ce qui les rend vraiment faciles à suivre.\n\n  ![Eduardo San Martin Morote présentant au DevFest Nantes 2025](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fthe-future-of-routing-with-the-navigation-api.webp)\n\n- **45 min pour mettre son application à genoux : le guide complet du test de charge** par [Loïc Ortola](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F) et [Mathilde Lorrain](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathilde-lorrain\u002F)\n\n  Ce talk était déjà dans mes coups de cœur de [Devoxx 2025](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md). Je l'inclus à nouveau car je l'ai vu au DevFest Nantes et je l'ai vraiment apprécié. C'est vraiment impressionnant. Ils pourraient parler bien plus de 45 minutes. Le temps passe à toute vitesse pendant leur présentation, elle est bien écrite et très accessible.\n\n  ![Loïc Ortola et Mathilde Lorrain au DevFest Nantes 2025](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002F45-min-pour-mettre-son-application-à-genoux-le-guide-complet-du-test-de-charge.webp)\n\nJe vous recommande vivement de regarder ces talks.\n\n## Mon talk\n\nMon talk était **En JavaScript, il y a un paquet pour tout... et souvent pour rien**. Mon créneau était le dernier de la journée, le vendredi 17 octobre à 16:50. J'ai eu l'occasion de le jouer plusieurs fois, donc le stress n'était pas un problème. Cependant, après deux longues journées, j'étais vraiment fatigué. Je me suis reposé une heure juste avant mon talk.\n\n![Moi et mon café.](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fme-and-my-coffee.webp)\n\nQuand je me suis réveillé, j'ai bu un espresso et j'étais reparti. _Et je n'aime toujours pas le café._\n\nIl est maintenant temps de monter sur scène.\n\nAvant que je monte sur scène, [Pierre](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpierre-tibulle-68674812b\u002F) m'a présenté. C'était la première fois que je vivais ça et c'est vraiment sympa. Il s'est aussi assuré que tout allait bien côté setup. Merci, Pierre !\n\n![Pierre me présente au DevFest Nantes 2025](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fpierre-introducing-me.webp)\n\nEnsuite, je suis monté sur scène et j'ai donné mon talk. Un petit selfie avec le public et parlons d'[e18e](https:\u002F\u002Fe18e.dev\u002F) ! L'amphithéâtre était vraiment impressionnant: 800 places ! C'est beaucoup et, de loin, la plus grande salle dans laquelle j'ai parlé.\n\n![Mon selfie avec le public](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fmy-selfie-with-the-audience.webp)\n\nTout s'est bien passé et les gens ont apprécié ! Quinze minutes, c'est tellement court, c'est fini juste quand on commence à se sentir bien, mais c'est comme ça.\n\n---\n\nJ'ai vraiment apprécié le DevFest Nantes, certainement **la meilleure conférence de l'année** !\n\n![L'équipe GDG Nantes au DevFest Nantes 2025](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fgdg-nantes-team.webp)\n\n---\n\n_Partie technique, vous pouvez passer si ça ne vous intéresse pas._\n\nPour cette conférence, et comme pour les précédentes, j'ai utilisé [Inalia](https:\u002F\u002Finalia.app), le SaaS que je construis pour rendre les présentations plus interactives. Ça fonctionne bien, ce qui semble normal, et pourtant je suis toujours bluffé d'avoir pu construire une plateforme, tout seul, qui marche tout simplement.\n\nEn moins de quinze minutes, plus de 17 000 requêtes (25 000 au total, mais 8 000 mises en cache par Cloudflare) ont frappé mon petit VPS à 4 € (2 vCPU et 4 Go de RAM). Aucun lag, aucun bug.\n\nPour les plus curieux d'entre vous, voici la répartition des durées de la route utilisée pour envoyer un emoji sur ma slide. Au pic, plus de 100 personnes tentaient d'accéder à l'app en même temps.\n\n![Répartition des durées de la route utilisée pour envoyer des réactions en direct](\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest\u002Fduration-breakdown.webp)\n\nEn coulisses, le VPS fait aussi tourner Plausible, qui consomme beaucoup de RAM. Je teste des alternatives plus légères. Sur la supervision du VPS, l'utilisation CPU a culminé à 87 %, mais la load average est restée sous contrôle (0,73, 0,68, 0,61). Après quelques tests avec [autocannon](https:\u002F\u002Fgithub.com\u002Fmcollina\u002Fautocannon), le VPS a pu encaisser 41 req\u002Fs avec 100 connexions concurrentes sur 60 secondes en accédant à une page d'overview. Après quelques ajustements, j'ai pu monter les perfs à 47 req\u002Fs, et une fois Plausible arrêté, je m'attends à atteindre 53 req\u002Fs. Donc oui, le VPS est petit, mais il suffit pour l'instant.\n",{"title":16379,"description":16850},{"loc":16852,"images":16856},[16857,16858,16859,16860,16861,16862,16863,16864,16865,16866,16867,16868],{"loc":16426},{"loc":16432},{"loc":16447},{"loc":16477},{"loc":16498},{"loc":16714},{"loc":16736},{"loc":16753},{"loc":16777},{"loc":16791},{"loc":16808},{"loc":16834},"fr\u002Fposts\u002Fmy-adventure-to-nantes-for-europes-largest-devfest","MIGhsbUcDEWAZDvNzrTMGigDPgP7wtZWYUpvm6TgwtQ",{"id":16872,"title":16873,"body":16874,"contentId":17221,"date":17222,"description":17223,"extension":18,"meta":17224,"navigation":20,"path":17225,"rawbody":17226,"robots":23,"schemaOrg":23,"seo":17227,"sitemap":17228,"stem":17229,"__hash__":17230},"posts\u002Ffr\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web.md","Les agents IA transformeront notre expérience avec le Web",{"type":7,"value":16875,"toc":17213},[16876,16879,16906,16912,16915,16920,16923,16926,16932,16936,16948,16957,16965,16968,16972,16975,16978,16986,16989,16993,16996,17005,17014,17026,17035,17038,17041,17047,17059,17063,17069,17118,17121,17125,17134,17140,17154,17157,17160,17163,17173,17178,17182,17188,17191,17194,17197,17201,17203],[35,16877,16878],{},"L'essor des agents IA est incroyable. Chaque application logicielle, même la documentation, intègre un agent IA ou une fonctionnalité alimentée par l'IA.",[35,16880,16881,16882,16887,16888,16893,16894,16899,16900,16905],{},"Notion, avec sa ",[39,16883,16886],{"href":16884,"rel":16885},"https:\u002F\u002Fwww.notion.com\u002Freleases\u002F2025-09-18",[77],"version 3.0",", la ",[39,16889,16892],{"href":16890,"rel":16891},"https:\u002F\u002Fcursor.com\u002Fdocs",[77],"documentation de Cursor",", les IDE comme VS Code avec ",[39,16895,16898],{"href":16896,"rel":16897},"https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2025\u002F04\u002F07\u002FagentMode",[77],"Copilot Agent"," et tous ses forks, même n8n, avec ",[39,16901,16904],{"href":16902,"rel":16903},"https:\u002F\u002Fn8n.io\u002Fai-agents\u002F",[77],"l'automatisation des workflows avec des agents IA",", intègrent des agents.",[35,16907,16908,16909],{},"Agents, agents, agents. Partout, tout le temps, pour tout. ",[1216,16910,16911],{},"De mon point de vue, parce que la bulle de filtre des réseaux sociaux déforme fortement la perception de la réalité.",[35,16913,16914],{},"Nous ne naviguons plus sur le web. Nous demandons à ChatGPT ou Perplexity de le faire pour nous, et cela change radicalement notre relation avec les logiciels, les services et l'information.",[35,16916,16917],{},[6862,16918,16919],{},"Nous ne faisons plus d'actions. Nous les demandons désormais.",[35,16921,16922],{},"C'est un changement considérable et l'expérience utilisateur est en première ligne. Nous ne parlons pas de changer quelques détails sur la façon dont les applications web sont conçues ; nous parlons d'imaginer quelque chose de complètement nouveau, de zéro, sans précédent.",[35,16924,16925],{},"Nous parlons d'un changement complet des modèles d'utilisation.",[35,16927,16928,16929],{},"Et c'est énorme. ",[1216,16930,16931],{},"Peut-être que j'ai totalement tort et que nous en rirons tous dans quelques années. Mais si c'est l'avenir, alors nous devons être préparés.",[51,16933,16935],{"id":16934},"functions-calling-a-changé-lia","Functions calling a changé l'IA",[35,16937,16938,16939,16944,16945],{},"En juin 2023, il y a plus de deux ans, ",[39,16940,16943],{"href":16941,"rel":16942},"https:\u002F\u002Fopenai.com\u002Findex\u002Ffunction-calling-and-other-api-updates\u002F",[77],"OpenAI a introduit des capacités d'appel de fonctions"," dans leur API. Cela a permis à l'IA de se connecter et d'interagir avec le monde réel. ",[1216,16946,16947],{},"À l'époque, le contexte était de 16k pour les modèles haut de gamme.",[35,16949,16950,16951,16956],{},"C'était principalement utilisé par les éditeurs de code pour améliorer leurs capacités. L'IA pouvait désormais rechercher des informations, créer des fichiers, exécuter des commandes, et plus encore. C'était le début des agents IA. En février 2025, ",[39,16952,16955],{"href":16953,"rel":16954},"https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2025\u002F02\u002F24\u002Fintroducing-copilot-agent-mode",[77],"VS Code a annoncé son mode agent GitHub Copilot",", mais des logiciels comme Cursor ou Windsurf avaient déjà tiré parti de ces capacités depuis des mois.",[35,16958,16959,16964],{},[39,16960,16963],{"href":16961,"rel":16962},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002Fgetting-started\u002Fintro",[77],"MCP",", qui signifie Model Context Protocol, est apparu en novembre 2024. MCP n'est pas directement lié à l'IA. C'est un connecteur entre une application IA et un autre service pour augmenter les capacités de l'IA. L'application IA se connecte au serveur MCP, répertorie ses outils et les injecte à l'IA en utilisant les capacités d'appel de fonctions introduites plus tôt.",[35,16966,16967],{},"C'est intelligent. Cela rend possibles les plugins pour les capacités d'IA, permettant à n'importe quel logiciel d'améliorer n'importe quelle application IA.",[51,16969,16971],{"id":16970},"tout-tourne-autour-de-lidée","Tout tourne autour de l'idée",[35,16973,16974],{},"Donc, nous avons une IA avec des capacités. Comment cela est-il censé changer le monde ?",[35,16976,16977],{},"Honnêtement, je ne peux pas répondre, et je ne vais même pas essayer. Nous parlons de choses qui n'existent pas encore. Tout doit être imaginé, et nous sommes dans cette phase innovante où les idées doivent être définies, testées et analysées.",[35,16979,16980,16981,215],{},"La chance d'échec est élevée, plus élevée que la chance de succès. Mais c'est ainsi que l'innovation fonctionne. Nous sommes au début du ",[39,16982,16985],{"href":16983,"rel":16984},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTechnology_adoption_life_cycle",[77],"cycle de vie d'adoption de la technologie",[35,16987,16988],{},"Nous savons que c'est possible, mais « cela » reste à définir.",[51,16990,16992],{"id":16991},"discuter-avec-le-contenu","Discuter avec le contenu",[35,16994,16995],{},"Deux choses m'ont fait réaliser que quelque chose avait changé.",[35,16997,16998,16999,17004],{},"En mars 2023, ",[39,17000,17003],{"href":17001,"rel":17002},"https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2023\u002F03\u002F30\u002Fvscode-copilot#_embracing-the-chat-view",[77],"VS Code a introduit une vue de chat",", mais elle ne fonctionnait pas bien et ne pouvait pas modifier des fichiers.",[35,17006,17007,17008,17013],{},"Donc, en octobre 2023, j'ai construit une extension VS Code appelée ",[39,17009,17012],{"href":17010,"rel":17011},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Futils-ai",[77],"Utils AI",", me permettant d'exécuter rapidement l'IA sur mes fichiers. C'était un moyen de corriger rapidement des fautes de frappe et d'améliorer le SEO de mes articles ou pages de documentation. Cela a très bien fonctionné.",[35,17015,17016,17017,17022,17023],{},"Ce n'est qu'en novembre 2024 que ",[39,17018,17021],{"href":17019,"rel":17020},"https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2024\u002F11\u002F12\u002Fintroducing-copilot-edits",[77],"VS Code a introduit son mode d'édition avec Copilot Edits",", permettant à l'IA de modifier des fichiers directement. Vous demandez, elle modifie de manière intelligente. ",[1216,17024,17025],{},"Du moins, elle essaie.",[35,17027,17028,17029,17034],{},"En même temps, ",[39,17030,17033],{"href":17031,"rel":17032},"https:\u002F\u002Fblog.google\u002Ftechnology\u002Fai\u002Fnotebooklm-google-ai\u002F",[77],"Google a introduit NotebookLM",", un nouvel outil pour interagir avec du contenu, du texte, des PDFs, des vidéos, en utilisant le langage naturel.",[35,17036,17037],{},"Depuis lors, j'ai découvert quelque chose de nouveau. Vous pouvez littéralement discuter avec votre contenu, avoir une conversation. Époustouflant.",[35,17039,17040],{},"Dans VS Code, j'ai demandé à Copilot de corriger des fautes de frappe, puis de proposer des améliorations de titre, de mettre en gras du texte important, et de me dire si j'avais raté quelque chose ou si une partie était floue. Clairement, mon extension Utils AI est devenue complètement obsolète, mais quelque chose d'encore plus puissant a émergé.",[35,17042,17043,17044],{},"Depuis lors, j'ai une obsession en tête. Comment puis-je créer quelque chose de similaire pour mon propre site web soubiran.dev ? ",[6862,17045,17046],{},"Comment puis-je parler de mon contenu en utilisant le langage naturel ?",[35,17048,17049,17050,17055,17056],{},"Récemment, ",[39,17051,17054],{"href":17052,"rel":17053},"https:\u002F\u002Fx.com\u002Fadamwathan",[77],"ce tweet d'Adam"," au sujet de Notion 3.0 m'a fait réaliser le potentiel d'une barre de chat pour interagir avec un site web pouvait aller bien plus loin qu'une discussion. ",[1216,17057,17058],{},"Gardez cela à l'esprit ; cela fera partie d'un autre article.",[51,17060,17062],{"id":17061},"accepter-léchec","Accepter l'échec",[35,17064,17065,17066],{},"J'ai perdu le compte du nombre de fois où j'ai essayé. Cependant, je peux affirmer avec confiance que chacun a été un échec, car vous ne pouvez toujours pas parler à votre contenu en utilisant le langage naturel. ",[1216,17067,17068],{},"Mais c'est de l'échec que nous apprenons le plus.",[68,17070,17071,17080,17083,17109],{},[71,17072,17073,17074,17079],{},"En 2024, j'ai essayé de créer ",[39,17075,17078],{"href":17076,"rel":17077},"https:\u002F\u002Fpython.langchain.com\u002Fdocs\u002Ftutorials\u002Frag\u002F",[77],"un RAG utilisant LangChain",". Je n'ai jamais créé plus qu'un petit POC sur mon ordinateur.",[71,17081,17082],{},"En février 2025, j'ai construit une barre de recherche pour mon site web. Elle est complètement fonctionnelle et fonctionne bien, mais elle est loin de l'expérience que je cherche.",[71,17084,17085,17086,17091,17092,1655,17097,17102,17103,17108],{},"Plus tard, j'ai essayé d'utiliser un moteur de recherche comme ",[39,17087,17090],{"href":17088,"rel":17089},"https:\u002F\u002Ftypesense.org\u002F",[77],"TypeSense",". J'ai également mis en place ",[39,17093,17096],{"href":17094,"rel":17095},"https:\u002F\u002Ftypesense.org\u002Fdocs\u002Fguide\u002Fsemantic-search.html",[77],"une recherche sémantique",[39,17098,17101],{"href":17099,"rel":17100},"https:\u002F\u002Ftypesense.org\u002Fdocs\u002Fguide\u002Fnatural-language-search.html",[77],"une recherche en langage naturel",". J'ai même essayé ",[39,17104,17107],{"href":17105,"rel":17106},"https:\u002F\u002Fgithub.com\u002Falgolia\u002Fdocsearch",[77],"DocSearch"," pour automatiser le processus. Toutes ces solutions étaient bonnes, mais c'étaient juste des recherches. Même la recherche en langage naturel n'était qu'une recherche. Rien n'était proche d'une expérience de chat.",[71,17110,17111,17112,17117],{},"En avril 2025, ",[39,17113,17116],{"href":17114,"rel":17115},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-autorag-on-cloudflare\u002F",[77],"Cloudflare a introduit AutoRAG"," : une solution RAG entièrement gérée, de l'indexation à l'interrogation. Je l'ai essayé ; le système global fonctionnait bien, mais il était aussi bête qu'une roche.",[35,17119,17120],{},"J'ai encore échoué.",[51,17122,17124],{"id":17123},"la-naissance-dune-idée","La naissance d'une idée",[35,17126,17127,17128,17133],{},"Le 1er octobre 2025, ",[39,17129,17132],{"href":17130,"rel":17131},"https:\u002F\u002Fx.com\u002Fbenjamincanac\u002Fstatus\u002F1973338105391587339",[77],"Benjamin a tweeté au sujet d'une nouvelle recherche IA"," pour Nuxt UI. Je l'ai immédiatement essayé, et c'était exactement ce que j'imaginais pour mon propre site web. Impressionnant progrès !",[35,17135,17136,17137],{},"Dès que je suis rentré chez moi, j'ai commencé à étudier le code pour comprendre comment il a pu obtenir un tel résultat. J'ai essayé encore et encore, j'ai ouvert l'onglet Réseau des Devtools pour comprendre la requête, et j'ai lu attentivement le code pour comprendre chaque mot. ",[1216,17138,17139],{},"Merci à l'équipe Nuxt de l'avoir rendu open-source !",[35,17141,17142,17143,17147,17148,17153],{},"Une idée qui m'est immédiatement venue à l'esprit était celle d'utiliser leur propre ",[39,17144,16963],{"href":17145,"rel":17146},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fblob\u002F6c083ef76fd209e5202b5b649944c8c454e639b7\u002Fdocs\u002Fserver\u002Fapi\u002Fsearch.ts#L8-L14",[77]," avec un ",[39,17149,17152],{"href":17150,"rel":17151},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fblob\u002F6c083ef76fd209e5202b5b649944c8c454e639b7\u002Fdocs\u002Fserver\u002Fapi\u002Fsearch.ts#L42",[77],"agent IA"," pour répondre aux requêtes des utilisateurs. Vous pourriez dire que c'est exagéré, je répondrais que c'est incroyablement puissant.",[35,17155,17156],{},"Il était autour de 21 heures lorsque j'ai eu une idée. Et si je pouvais créer un système similaire pour mon propre site web ?",[35,17158,17159],{},"J'ai commencé à réfléchir à la solution, à imaginer des problèmes et à essayer de tous les résoudre dans ma tête. Vingt minutes plus tard, j'avais toutes les pièces nécessaires pour construire quelque chose. J'ai allumé mon ordinateur, ouvert OBS, lancé un stream, et sept heures plus tard, le POC fonctionnait.",[35,17161,17162],{},"Quel bonheur ! Je n'ai pas de mots pour décrire le sentiment lorsque vous voyez pour la première fois l'IA utiliser ses outils et répondre parfaitement aux requêtes des utilisateurs.",[130,17164,132,17165,132,17170],{},[134,17166,136,17167,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,17168],{"src":17169,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web\u002Fchat-with-soubiran-dev.mp4",[143,17171,17172],{},"Mon agent IA en action.",[35,17174,17175],{},[1216,17176,17177],{},"Pour être juste, les échecs que j'ai rencontrés en cours de route ont été inestimables pour parvenir à cela si rapidement.",[51,17179,17181],{"id":17180},"une-fenêtre-vers-lavenir","Une fenêtre vers l'avenir",[35,17183,17184,17185],{},"Il m'a fallu plus d'un an, près de deux, beaucoup d'échecs, pour construire un prototype fonctionnel de ma vision. Enfin, c'est là. ",[1216,17186,17187],{},"La patience est importante.",[35,17189,17190],{},"C'est impressionnant et stupéfiant de voir ce niveau de précision dans les réponses. L'IA est capable de comprendre parfaitement le sous-texte et de fournir des informations pertinentes.",[35,17192,17193],{},"La partie la plus folle ? Ce n'est que le début et j'ai tant d'idées à explorer pour rendre le système encore plus puissant.",[35,17195,17196],{},"Pour l'instant, cela me permet de discuter avec mon site web, et cela ouvre un nouveau paradigme pour le web. Je l'appelle :",[35,17198,17199],{},[6862,17200,54],{},[1158,17202],{},[35,17204,17205,17206],{},"C'est un concept vraiment excitant. Vous pouvez vous attendre à des articles, des incursions techniques, des streams en direct et même des conférences. ",[1216,17207,17208,17209,17212],{},"Vous êtes un organisateur de meetup à la recherche de conférenciers ? ",[39,17210,17211],{"href":9176},"Contactez-moi !"," car cela pourrait être une conférence.",{"title":13,"searchDepth":14,"depth":14,"links":17214},[17215,17216,17217,17218,17219,17220],{"id":16934,"depth":14,"text":16935},{"id":16970,"depth":14,"text":16971},{"id":16991,"depth":14,"text":16992},{"id":17061,"depth":14,"text":17062},{"id":17123,"depth":14,"text":17124},{"id":17180,"depth":14,"text":17181},"53fec4d8-4c6c-4c1f-8542-88ee81b741ee","2025-10-06","Le web a beaucoup évolué ces dernières années. L'essor de l'IA et des agents IA pourrait complètement changer notre façon d'interagir avec.",{},"\u002Ffr\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web","---\ncontentId: 53fec4d8-4c6c-4c1f-8542-88ee81b741ee\ntitle: \"Les agents IA transformeront notre expérience avec le Web\"\ndescription: \"Le web a beaucoup évolué ces dernières années. L'essor de l'IA et des agents IA pourrait complètement changer notre façon d'interagir avec.\"\ndate: 2025-10-06\n---\n\nL'essor des agents IA est incroyable. Chaque application logicielle, même la documentation, intègre un agent IA ou une fonctionnalité alimentée par l'IA.\n\nNotion, avec sa [version 3.0](https:\u002F\u002Fwww.notion.com\u002Freleases\u002F2025-09-18), la [documentation de Cursor](https:\u002F\u002Fcursor.com\u002Fdocs), les IDE comme VS Code avec [Copilot Agent](https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2025\u002F04\u002F07\u002FagentMode) et tous ses forks, même n8n, avec [l'automatisation des workflows avec des agents IA](https:\u002F\u002Fn8n.io\u002Fai-agents\u002F), intègrent des agents.\n\nAgents, agents, agents. Partout, tout le temps, pour tout. _De mon point de vue, parce que la bulle de filtre des réseaux sociaux déforme fortement la perception de la réalité._\n\nNous ne naviguons plus sur le web. Nous demandons à ChatGPT ou Perplexity de le faire pour nous, et cela change radicalement notre relation avec les logiciels, les services et l'information.\n\n**Nous ne faisons plus d'actions. Nous les demandons désormais.**\n\nC'est un changement considérable et l'expérience utilisateur est en première ligne. Nous ne parlons pas de changer quelques détails sur la façon dont les applications web sont conçues ; nous parlons d'imaginer quelque chose de complètement nouveau, de zéro, sans précédent.\n\nNous parlons d'un changement complet des modèles d'utilisation.\n\nEt c'est énorme. _Peut-être que j'ai totalement tort et que nous en rirons tous dans quelques années. Mais si c'est l'avenir, alors nous devons être préparés._\n\n## Functions calling a changé l'IA\n\nEn juin 2023, il y a plus de deux ans, [OpenAI a introduit des capacités d'appel de fonctions](https:\u002F\u002Fopenai.com\u002Findex\u002Ffunction-calling-and-other-api-updates\u002F) dans leur API. Cela a permis à l'IA de se connecter et d'interagir avec le monde réel. _À l'époque, le contexte était de 16k pour les modèles haut de gamme._\n\nC'était principalement utilisé par les éditeurs de code pour améliorer leurs capacités. L'IA pouvait désormais rechercher des informations, créer des fichiers, exécuter des commandes, et plus encore. C'était le début des agents IA. En février 2025, [VS Code a annoncé son mode agent GitHub Copilot](https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2025\u002F02\u002F24\u002Fintroducing-copilot-agent-mode), mais des logiciels comme Cursor ou Windsurf avaient déjà tiré parti de ces capacités depuis des mois.\n\n[MCP](https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002Fgetting-started\u002Fintro), qui signifie Model Context Protocol, est apparu en novembre 2024. MCP n'est pas directement lié à l'IA. C'est un connecteur entre une application IA et un autre service pour augmenter les capacités de l'IA. L'application IA se connecte au serveur MCP, répertorie ses outils et les injecte à l'IA en utilisant les capacités d'appel de fonctions introduites plus tôt.\n\nC'est intelligent. Cela rend possibles les plugins pour les capacités d'IA, permettant à n'importe quel logiciel d'améliorer n'importe quelle application IA.\n\n## Tout tourne autour de l'idée\n\nDonc, nous avons une IA avec des capacités. Comment cela est-il censé changer le monde ?\n\nHonnêtement, je ne peux pas répondre, et je ne vais même pas essayer. Nous parlons de choses qui n'existent pas encore. Tout doit être imaginé, et nous sommes dans cette phase innovante où les idées doivent être définies, testées et analysées.\n\nLa chance d'échec est élevée, plus élevée que la chance de succès. Mais c'est ainsi que l'innovation fonctionne. Nous sommes au début du [cycle de vie d'adoption de la technologie](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTechnology_adoption_life_cycle).\n\nNous savons que c'est possible, mais « cela » reste à définir.\n\n## Discuter avec le contenu\n\nDeux choses m'ont fait réaliser que quelque chose avait changé.\n\nEn mars 2023, [VS Code a introduit une vue de chat](https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2023\u002F03\u002F30\u002Fvscode-copilot#_embracing-the-chat-view), mais elle ne fonctionnait pas bien et ne pouvait pas modifier des fichiers.\n\nDonc, en octobre 2023, j'ai construit une extension VS Code appelée [Utils AI](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Futils-ai), me permettant d'exécuter rapidement l'IA sur mes fichiers. C'était un moyen de corriger rapidement des fautes de frappe et d'améliorer le SEO de mes articles ou pages de documentation. Cela a très bien fonctionné.\n\nCe n'est qu'en novembre 2024 que [VS Code a introduit son mode d'édition avec Copilot Edits](https:\u002F\u002Fcode.visualstudio.com\u002Fblogs\u002F2024\u002F11\u002F12\u002Fintroducing-copilot-edits), permettant à l'IA de modifier des fichiers directement. Vous demandez, elle modifie de manière intelligente. _Du moins, elle essaie._\n\nEn même temps, [Google a introduit NotebookLM](https:\u002F\u002Fblog.google\u002Ftechnology\u002Fai\u002Fnotebooklm-google-ai\u002F), un nouvel outil pour interagir avec du contenu, du texte, des PDFs, des vidéos, en utilisant le langage naturel.\n\nDepuis lors, j'ai découvert quelque chose de nouveau. Vous pouvez littéralement discuter avec votre contenu, avoir une conversation. Époustouflant.\n\nDans VS Code, j'ai demandé à Copilot de corriger des fautes de frappe, puis de proposer des améliorations de titre, de mettre en gras du texte important, et de me dire si j'avais raté quelque chose ou si une partie était floue. Clairement, mon extension Utils AI est devenue complètement obsolète, mais quelque chose d'encore plus puissant a émergé.\n\nDepuis lors, j'ai une obsession en tête. Comment puis-je créer quelque chose de similaire pour mon propre site web soubiran.dev ? **Comment puis-je parler de mon contenu en utilisant le langage naturel ?**\n\nRécemment, [ce tweet d'Adam](https:\u002F\u002Fx.com\u002Fadamwathan) au sujet de Notion 3.0 m'a fait réaliser le potentiel d'une barre de chat pour interagir avec un site web pouvait aller bien plus loin qu'une discussion. _Gardez cela à l'esprit ; cela fera partie d'un autre article._\n\n## Accepter l'échec\n\nJ'ai perdu le compte du nombre de fois où j'ai essayé. Cependant, je peux affirmer avec confiance que chacun a été un échec, car vous ne pouvez toujours pas parler à votre contenu en utilisant le langage naturel. _Mais c'est de l'échec que nous apprenons le plus._\n\n1. En 2024, j'ai essayé de créer [un RAG utilisant LangChain](https:\u002F\u002Fpython.langchain.com\u002Fdocs\u002Ftutorials\u002Frag\u002F). Je n'ai jamais créé plus qu'un petit POC sur mon ordinateur.\n2. En février 2025, j'ai construit une barre de recherche pour mon site web. Elle est complètement fonctionnelle et fonctionne bien, mais elle est loin de l'expérience que je cherche.\n3. Plus tard, j'ai essayé d'utiliser un moteur de recherche comme [TypeSense](https:\u002F\u002Ftypesense.org\u002F). J'ai également mis en place [une recherche sémantique](https:\u002F\u002Ftypesense.org\u002Fdocs\u002Fguide\u002Fsemantic-search.html) et [une recherche en langage naturel](https:\u002F\u002Ftypesense.org\u002Fdocs\u002Fguide\u002Fnatural-language-search.html). J'ai même essayé [DocSearch](https:\u002F\u002Fgithub.com\u002Falgolia\u002Fdocsearch) pour automatiser le processus. Toutes ces solutions étaient bonnes, mais c'étaient juste des recherches. Même la recherche en langage naturel n'était qu'une recherche. Rien n'était proche d'une expérience de chat.\n4. En avril 2025, [Cloudflare a introduit AutoRAG](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-autorag-on-cloudflare\u002F) : une solution RAG entièrement gérée, de l'indexation à l'interrogation. Je l'ai essayé ; le système global fonctionnait bien, mais il était aussi bête qu'une roche.\n\nJ'ai encore échoué.\n\n## La naissance d'une idée\n\nLe 1er octobre 2025, [Benjamin a tweeté au sujet d'une nouvelle recherche IA](https:\u002F\u002Fx.com\u002Fbenjamincanac\u002Fstatus\u002F1973338105391587339) pour Nuxt UI. Je l'ai immédiatement essayé, et c'était exactement ce que j'imaginais pour mon propre site web. Impressionnant progrès !\n\nDès que je suis rentré chez moi, j'ai commencé à étudier le code pour comprendre comment il a pu obtenir un tel résultat. J'ai essayé encore et encore, j'ai ouvert l'onglet Réseau des Devtools pour comprendre la requête, et j'ai lu attentivement le code pour comprendre chaque mot. _Merci à l'équipe Nuxt de l'avoir rendu open-source !_\n\nUne idée qui m'est immédiatement venue à l'esprit était celle d'utiliser leur propre [MCP](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fblob\u002F6c083ef76fd209e5202b5b649944c8c454e639b7\u002Fdocs\u002Fserver\u002Fapi\u002Fsearch.ts#L8-L14) avec un [agent IA](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fblob\u002F6c083ef76fd209e5202b5b649944c8c454e639b7\u002Fdocs\u002Fserver\u002Fapi\u002Fsearch.ts#L42) pour répondre aux requêtes des utilisateurs. Vous pourriez dire que c'est exagéré, je répondrais que c'est incroyablement puissant.\n\nIl était autour de 21 heures lorsque j'ai eu une idée. Et si je pouvais créer un système similaire pour mon propre site web ?\n\nJ'ai commencé à réfléchir à la solution, à imaginer des problèmes et à essayer de tous les résoudre dans ma tête. Vingt minutes plus tard, j'avais toutes les pièces nécessaires pour construire quelque chose. J'ai allumé mon ordinateur, ouvert OBS, lancé un stream, et sept heures plus tard, le POC fonctionnait.\n\nQuel bonheur ! Je n'ai pas de mots pour décrire le sentiment lorsque vous voyez pour la première fois l'IA utiliser ses outils et répondre parfaitement aux requêtes des utilisateurs.\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web\u002Fchat-with-soubiran-dev.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Mon agent IA en action.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n_Pour être juste, les échecs que j'ai rencontrés en cours de route ont été inestimables pour parvenir à cela si rapidement._\n\n## Une fenêtre vers l'avenir\n\nIl m'a fallu plus d'un an, près de deux, beaucoup d'échecs, pour construire un prototype fonctionnel de ma vision. Enfin, c'est là. _La patience est importante._\n\nC'est impressionnant et stupéfiant de voir ce niveau de précision dans les réponses. L'IA est capable de comprendre parfaitement le sous-texte et de fournir des informations pertinentes.\n\nLa partie la plus folle ? Ce n'est que le début et j'ai tant d'idées à explorer pour rendre le système encore plus puissant.\n\nPour l'instant, cela me permet de discuter avec mon site web, et cela ouvre un nouveau paradigme pour le web. Je l'appelle :\n\n**Site web en tant que barre de recherche**\n\n---\n\nC'est un concept vraiment excitant. Vous pouvez vous attendre à des articles, des incursions techniques, des streams en direct et même des conférences. _Vous êtes un organisateur de meetup à la recherche de conférenciers ? [Contactez-moi !](..\u002Ftalks.md) car cela pourrait être une conférence._\n",{"title":16873,"description":17223},{"loc":17225},"fr\u002Fposts\u002Fai-agents-will-deeply-transform-our-experience-with-the-web","3m2Fi63fHV3FAuu9yhmH-oUqkMaenPo4Ycpdu2-vw1A",{"id":17232,"title":17233,"body":17234,"contentId":9335,"date":17542,"description":17543,"extension":18,"meta":17544,"navigation":20,"path":17545,"rawbody":17546,"robots":23,"schemaOrg":23,"seo":17547,"sitemap":17548,"stem":17549,"__hash__":17550},"posts\u002Ffr\u002Fposts\u002Fsoftware-engineering-a-human-journey-before-anything-else.md","Ingénierie logicielle : Un parcours humain avant tout",{"type":7,"value":17235,"toc":17536},[17236,17244,17249,17252,17255,17257,17264,17267,17269,17277,17282,17285,17288,17291,17295,17298,17307,17310,17319,17328,17331,17342,17345,17349,17363,17366,17369,17372,17375,17378,17381,17384,17387,17390,17422,17425,17432,17436,17439,17442,17445,17448,17451,17460,17464,17473,17476,17479,17482,17485,17488,17506,17508,17523,17525,17531,17533],[35,17237,17238,17239,215],{},"Nuxt UI vient de sortir sa ",[39,17240,17243],{"href":17241,"rel":17242},"https:\u002F\u002Fnuxt.com\u002Fblog\u002Fnuxt-ui-v4",[77],"version 4",[35,17245,17246],{},[1216,17247,17248],{},"Cet article ne parle pas de technologie. C'est une histoire humaine. Vous pouvez continuer à lire.",[35,17250,17251],{},"Vous vous demandez peut-être pourquoi cet article existe. Pour moi, la release de Nuxt UI marque la fin d'un projet qui m'a occupé pendant des mois et m'a donné l'occasion de collaborer avec des personnes du monde entier.",[35,17253,17254],{},"Aujourd'hui, je souhaite partager ce voyage car j'en ai beaucoup parlé sur Twitter, mais à part avec les personnes avec qui j'ai collaboré, je n'ai pas partagé grand-chose.",[1158,17256],{},[35,17258,17259,17260,17263],{},"Il y a quelques semaines, j'ai ",[39,17261,17262],{"href":7947},"publie un article sur ma transition"," allant de l'écriture de code pour le simple plaisir d'écrire à l'utilisation de mes compétences pour créer des moments. Ce changement a radicalement affecté ma vision du développement logiciel, de mon travail et de mes objectifs.",[35,17265,17266],{},"Cette version en fait partie.",[1158,17268],{},[35,17270,17271,17272,2186],{},"Alors, quand ce voyage a-t-il commencé ? Le 7 décembre 2024, j'ai ",[39,17273,17276],{"href":17274,"rel":17275},"https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1865402012629610871",[77],"tweeté",[938,17278,17279],{},[35,17280,17281],{},"Imagine, Nuxt UI but like Shadcn Vue 🫣",[35,17283,17284],{},"Au départ, ce n'était qu'une idée. Je tweet souvent pour tester l'intérêt et la réception.",[35,17286,17287],{},"Ce tweet a été vu plus de 18k fois et aimé 105 fois. Le signal était clair : les gens étaient intéressés. Étrangement, en dehors de ce tweet, je n'avais pas de plan concret ou de vision claire de ce que je voulais construire.",[35,17289,17290],{},"Alors pourquoi l'ai-je tweeté ?",[51,17292,17294],{"id":17293},"le-début","Le Début",[35,17296,17297],{},"Revenons quelques années en arrière pour donner du contexte.",[35,17299,17300,17301,17306],{},"Depuis le premier jour, je suis un grand fan de Nuxt UI. ",[39,17302,17305],{"href":17303,"rel":17304},"https:\u002F\u002Fx.com\u002Fnuxtlabs\u002Fstatus\u002F1658488718230163459",[77],"Le premier jour était le 16 mai 2023",". Étrangement, je me souviens clairement de ce que je faisais lorsque j'ai vu l'annonce sur Twitter : je quittais mon bureau, sur le pont traversant la Seine, sur le point de prendre le tram. Cela pourrait être ordinaire, mais je ne prends jamais le tram pour rentrer chez moi, je prends toujours le bus.",[35,17308,17309],{},"L'annonce ne m'a pas laissé indifférent. J'ai ressenti une petite d'excitation. Nuxt devenait plus qu'un simple framework. Nuxt a toujours été axé sur les détails et une expérience développeur marquée. S'ils pouvaient apporter ce même niveau à leur bibliothèque de composants, ils pouvaient conquérir les développeurs.",[35,17311,17312,17313,17318],{},"Au départ, ce n'était pas Nuxt UI mais NuxtLabs UI. Les composants avaient été extraits de Volta par l'équipe. Nous avons dû attendre la ",[39,17314,17317],{"href":17315,"rel":17316},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Freleases\u002Ftag\u002Fv2.8.0",[77],"version 2.8.0"," avant que NuxtLabs UI soit renommé en Nuxt UI.",[35,17320,17321,17322,17327],{},"Quelques mois plus tard, le 2 novembre 2023, ",[39,17323,17326],{"href":17324,"rel":17325},"https:\u002F\u002Fx.com\u002Fnuxt_js\u002Fstatus\u002F1720123517008556120",[77],"ils ont annoncé Nuxt UI Pro",". Incroyable, mais cela coûtait 199 $. À l'époque, j'étais encore étudiant et, honnêtement, avec tout l'amour que j'ai pour Nuxt, cela représentait beaucoup d'argent pour quelque chose que j'utilisais seulement comme loisir.",[35,17329,17330],{},"Dans la FAQ, ils mentionnaient que les étudiants pouvaient obtenir une réduction. Après avoir essayé les composants Pro de manière intensive, et sans pouvoir les déployer, j'ai contacté l'équipe pour cette réduction le 5 novembre 2023.",[35,17332,17333,17334,17338,17339],{},"Un jour plus tard, ",[39,17335,13875],{"href":17336,"rel":17337},"https:\u002F\u002Fx.com\u002FAtinux",[77]," a répondu avec un code de réduction de 100 % pour me remercier de mes contributions à Nuxt Content, UnJS et Nuxt Studio. Nuxt UI Pro, gratuitement. Je n'y croyais pas. ",[1216,17340,17341],{},"Merci, Sébastien ! Rien que d'y penser me fait sourire.",[35,17343,17344],{},"Revenons à fin 2024.",[51,17346,17348],{"id":17347},"le-besoin","Le Besoin",[35,17350,17351,17352,17357,17358,17362],{},"Au cours des derniers mois, j'ai ",[39,17353,17356],{"href":17354,"rel":17355},"https:\u002F\u002Fsoubiran.dev\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next#:~:text=So%20in%20June%2C%20I%20started%20learning%20Laravel",[77],"appris Laravel",". Avec Laravel, j'ai découvert Inertia et je suis complètement ",[39,17359,17361],{"href":9128,"rel":17360},[77],"tombé amoureux",". Cet outil a changé ma façon de construire des applications.",[35,17364,17365],{},"Dans le même temps, j'ai commencé un emploi où j'avais la liberté de choisir les technologies. J'ai choisi Laravel, Inertia et Vue. Donc, pour mes projets personnels et professionnels, j'ai adopté cette stack.",[35,17367,17368],{},"Rien n'est parfait, et cette stack avait un inconvénient.",[35,17370,17371],{},"Nuxt UI n'était disponible que pour Nuxt et pas aussi personnalisable que je le voulais. C'était frustrant car j'avais appris à aimer les composants et l'expérience développeur qu'ils offraient. Il n'y avait rien de comparable pour Vue.",[35,17373,17374],{},"Mais ne sommes-nous pas des créateurs ? Si cela n'existe pas, pourquoi ne pas le créer ?",[35,17376,17377],{},"Cela m'a conduit à une idée. Shadcn devenait de facto la bibliothèque pour construire des applications React. Elle vient avec un magnifique système de design et un système de distribution intelligent. Les projets possèdent leurs composants, ce qui simplifie la personnalisation au niveau du projet. Cependant, l'expérience développeur lors de l'utilisation de ces composants est loin de l'expérience Nuxt UI.",[35,17379,17380],{},"Pourquoi ne pas créer une bibliothèque similaire à Shadcn pour Vue ? Un clone de Nuxt UI, distribué comme Shadcn, mais pour Vue et compatible avec Inertia. L'expérience développeur de Nuxt UI disponible pour chaque développeur Vue m'a donné des frissons en l'imaginant.",[35,17382,17383],{},"C'est ainsi que le tweet est né.",[35,17385,17386],{},"Avec cette idée en tête et un fort intérêt pour construire cette bibliothèque, je devais me lancer. Construire et maintenir une bibliothèque est un énorme travail, alors j'ai demandé sur Twitter si quelqu'un était intéressé à me rejoindre.",[35,17388,17389],{},"Cinq personnes ont répondu à l'appel, beaucoup plus que je ne l'avais prévu !",[35,17391,17392,17393,3092,17398,3092,17403,3092,17408,9502,17413,17418,17419],{},"Un grand merci à ",[39,17394,17397],{"href":17395,"rel":17396},"https:\u002F\u002Fx.com\u002Fabei557832",[77],"a bei",[39,17399,17402],{"href":17400,"rel":17401},"https:\u002F\u002Fx.com\u002FUnans___",[77],"David Pena",[39,17404,17407],{"href":17405,"rel":17406},"https:\u002F\u002Fx.com\u002Fenzonotario_",[77],"Enzo Notario",[39,17409,17412],{"href":17410,"rel":17411},"https:\u002F\u002Fx.com\u002F_aussieboi",[77],"aussieboi",[39,17414,17417],{"href":17415,"rel":17416},"https:\u002F\u002Fx.com\u002FLyashenkoG",[77],"Grygoriy Lyashenko"," de m'avoir rejoint dans cette aventure ! ",[1216,17420,17421],{},"Si un jour vous venez à Paris, faites-moi signe !",[35,17423,17424],{},"J'avais un plan, une équipe, et nous étions prêts à construire.",[35,17426,17427,17428,17431],{},"Dans le même temps, Nuxt UI progressait vers la version 3. Deux semaines avant mon tweet, une pull request pour rendre Nuxt UI compatible avec Vue 3, créée par ",[39,17429,1896],{"href":1894,"rel":17430},[77],", a été fusionnée. Mais ce n'était toujours pas aussi personnalisable que je le souhaitais, et il n'y avait pas de prise en charge pour Inertia.",[51,17433,17435],{"id":17434},"la-construction","La Construction",[35,17437,17438],{},"Je n'étais pas sûr de la façon d'aborder cela. Je savais que je voulais créer une bibliothèque qui correspondait à l'expérience développeur de Nuxt UI, mais je ne voulais pas simplement la copier.",[35,17440,17441],{},"Ce n'était pas un fork direct de Nuxt UI. Régulièrement, je copiais des composants de Nuxt UI, les adaptais pour les rendre agnostiques à Nuxt, et parfois changeais leur comportement pour correspondre à mes préférences.",[35,17443,17444],{},"L'équipe a fait cela pour chaque composant, chaque composable, chaque fonction utilitaire, et même chaque thème. Nous avons également construit un site de documentation pour permettre aux utilisateurs de copier et coller facilement des extraits de code, avec des modifications de code automatiques si vous utilisiez Inertia ou simplement Vite, et des démos intégrées pour chaque composant.",[35,17446,17447],{},"Nous avons même construit un script bash pour automatiser le processus d'installation et un moyen d'installer uniquement les composants nécessaires avec leurs dépendances.",[35,17449,17450],{},"C'était absolument incroyable à construire et si satisfaisant d'avoir quelque chose de fonctionnel et utilisé dans des projets.",[35,17452,17453,17454,17459],{},"Par exemple, ",[39,17455,17458],{"href":17456,"rel":17457},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcode.soubiran.dev\u002Ftree\u002F53b1811948b1879c8f9d354d76aa144883baaa4d\u002Fsrc\u002Fui",[77],"code.soubiran.dev"," a été construit avec cette bibliothèque. Même mon propre site web, celui sur lequel vous êtes actuellement, l'utilise également.",[51,17461,17463],{"id":17462},"la-vérité","La Vérité",[35,17465,17466,17467,17472],{},"Le 14 avril 2025, ",[39,17468,17471],{"href":17469,"rel":17470},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F3845",[77],"une pull request pour ajouter la prise en charge d'Inertia"," a été acceptée et mergée.",[35,17474,17475],{},"Je devais me rendre à l'évidence : Nuxt UI devenait enfin ce que je voulais construire. La personnalisation n'était pas aussi bonne que de posséder les composants, mais c'était open source. Pourquoi ne pas soumettre quelques contributions ? Je connais chaque composant par cœur et j'avais déjà apporté des améliorations dans mon fork. Je devais juste les porter.",[35,17477,17478],{},"Alors je l'ai fait. Depuis le 1er décembre 2024, j'ai contribué dix-neuf fois.",[35,17480,17481],{},"Maintenir mon fork était honnêtement beaucoup de travail et largement plus que la valeur que j'étais capable d'ajouter. Parfois, il vaut mieux lâcher prise et se concentrer sur ce qui compte vraiment.",[35,17483,17484],{},"Je suis heureux du projet et de l'expérience prise. Ça m'a donné l'occasion d'apprendre en profondeur le code source de Nuxt UI et d'échanger avec l'équipe via des messages Twitter et des issues GitHub. Un aspect amusant de travailler avec une équipe mondiale : vous pouviez vous endormir après avoir envoyé un message et vous réveiller avec une nouvelle demande de tirage !",[35,17486,17487],{},"Maintenant, il est temps d'admettre : Vue UI est officiellement terminé.",[1593,17489,17490,17498],{},[71,17491,17492,17493],{},"Site web : ",[39,17494,17497],{"href":17495,"rel":17496},"https:\u002F\u002Fvue-ui.barbapapazes.dev",[77],"vue-ui.barbapapazes.dev",[71,17499,17500,17501],{},"Code : ",[39,17502,17505],{"href":17503,"rel":17504},"https:\u002F\u002Fgithub.com\u002Fvue-ui-labs\u002Fvue-ui",[77],"Vue UI Labs",[1158,17507],{},[35,17509,17510,17511,3092,17514,9502,17518,17522],{},"Merci à toute l'équipe Nuxt, en particulier à ",[39,17512,13875],{"href":17336,"rel":17513},[77],[39,17515,13880],{"href":17516,"rel":17517},"https:\u002F\u002Fx.com\u002Fbenjamincanac",[77],[39,17519,9366],{"href":17520,"rel":17521},"https:\u002F\u002Fx.com\u002FHugoRCD__",[77]," pour votre travail incroyable sur Nuxt UI. 🫶",[1158,17524],{},[35,17526,17527,17528,215],{},"Une image vaut mille mots, mais parfois mille mots expriment plus que tout. J'espère que cela vous inspirera à entreprendre votre propre voyage. N'oubliez pas : ",[39,17529,17530],{"href":7947},"You don't need to be chosen, you can just do things",[1158,17532],{},[35,17534,17535],{},"De temps en temps, j'aime revenir sur mes articles précédents. J'aime prendre le temps de réfléchir au parcours que j'ai pris. Cela m'aide à apprécier chaque nouveau jour et chaque nouvelle personne que je rencontre en chemin.",{"title":13,"searchDepth":14,"depth":14,"links":17537},[17538,17539,17540,17541],{"id":17293,"depth":14,"text":17294},{"id":17347,"depth":14,"text":17348},{"id":17434,"depth":14,"text":17435},{"id":17462,"depth":14,"text":17463},"2025-09-26","Fusionner le système de distribution de Shadcn avec l'expérience développeur de Nuxt UI. Une idée qui mène à une collaboration incroyable.",{},"\u002Ffr\u002Fposts\u002Fsoftware-engineering-a-human-journey-before-anything-else","---\ncontentId: 83de2151-b1db-48ee-8d5a-eb88e359c337\ntitle: \"Ingénierie logicielle : Un parcours humain avant tout\"\ndescription: Fusionner le système de distribution de Shadcn avec l'expérience développeur de Nuxt UI. Une idée qui mène à une collaboration incroyable.\ndate: 2025-09-26\n---\n\nNuxt UI vient de sortir sa [version 4](https:\u002F\u002Fnuxt.com\u002Fblog\u002Fnuxt-ui-v4).\n\n_Cet article ne parle pas de technologie. C'est une histoire humaine. Vous pouvez continuer à lire._\n\nVous vous demandez peut-être pourquoi cet article existe. Pour moi, la release de Nuxt UI marque la fin d'un projet qui m'a occupé pendant des mois et m'a donné l'occasion de collaborer avec des personnes du monde entier.\n\nAujourd'hui, je souhaite partager ce voyage car j'en ai beaucoup parlé sur Twitter, mais à part avec les personnes avec qui j'ai collaboré, je n'ai pas partagé grand-chose.\n\n---\n\nIl y a quelques semaines, j'ai [publie un article sur ma transition](.\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things.md) allant de l'écriture de code pour le simple plaisir d'écrire à l'utilisation de mes compétences pour créer des moments. Ce changement a radicalement affecté ma vision du développement logiciel, de mon travail et de mes objectifs.\n\nCette version en fait partie.\n\n---\n\nAlors, quand ce voyage a-t-il commencé ? Le 7 décembre 2024, j'ai [tweeté](https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1865402012629610871) :\n\n> Imagine, Nuxt UI but like Shadcn Vue 🫣\n\nAu départ, ce n'était qu'une idée. Je tweet souvent pour tester l'intérêt et la réception.\n\nCe tweet a été vu plus de 18k fois et aimé 105 fois. Le signal était clair : les gens étaient intéressés. Étrangement, en dehors de ce tweet, je n'avais pas de plan concret ou de vision claire de ce que je voulais construire.\n\nAlors pourquoi l'ai-je tweeté ?\n\n## Le Début\n\nRevenons quelques années en arrière pour donner du contexte.\n\nDepuis le premier jour, je suis un grand fan de Nuxt UI. [Le premier jour était le 16 mai 2023](https:\u002F\u002Fx.com\u002Fnuxtlabs\u002Fstatus\u002F1658488718230163459). Étrangement, je me souviens clairement de ce que je faisais lorsque j'ai vu l'annonce sur Twitter : je quittais mon bureau, sur le pont traversant la Seine, sur le point de prendre le tram. Cela pourrait être ordinaire, mais je ne prends jamais le tram pour rentrer chez moi, je prends toujours le bus.\n\nL'annonce ne m'a pas laissé indifférent. J'ai ressenti une petite d'excitation. Nuxt devenait plus qu'un simple framework. Nuxt a toujours été axé sur les détails et une expérience développeur marquée. S'ils pouvaient apporter ce même niveau à leur bibliothèque de composants, ils pouvaient conquérir les développeurs.\n\nAu départ, ce n'était pas Nuxt UI mais NuxtLabs UI. Les composants avaient été extraits de Volta par l'équipe. Nous avons dû attendre la [version 2.8.0](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Freleases\u002Ftag\u002Fv2.8.0) avant que NuxtLabs UI soit renommé en Nuxt UI.\n\nQuelques mois plus tard, le 2 novembre 2023, [ils ont annoncé Nuxt UI Pro](https:\u002F\u002Fx.com\u002Fnuxt_js\u002Fstatus\u002F1720123517008556120). Incroyable, mais cela coûtait 199 $. À l'époque, j'étais encore étudiant et, honnêtement, avec tout l'amour que j'ai pour Nuxt, cela représentait beaucoup d'argent pour quelque chose que j'utilisais seulement comme loisir.\n\nDans la FAQ, ils mentionnaient que les étudiants pouvaient obtenir une réduction. Après avoir essayé les composants Pro de manière intensive, et sans pouvoir les déployer, j'ai contacté l'équipe pour cette réduction le 5 novembre 2023.\n\nUn jour plus tard, [Sébastien](https:\u002F\u002Fx.com\u002FAtinux) a répondu avec un code de réduction de 100 % pour me remercier de mes contributions à Nuxt Content, UnJS et Nuxt Studio. Nuxt UI Pro, gratuitement. Je n'y croyais pas. _Merci, Sébastien ! Rien que d'y penser me fait sourire._\n\nRevenons à fin 2024.\n\n## Le Besoin\n\nAu cours des derniers mois, j'ai [appris Laravel](https:\u002F\u002Fsoubiran.dev\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next#:~:text=So%20in%20June%2C%20I%20started%20learning%20Laravel). Avec Laravel, j'ai découvert Inertia et je suis complètement [tombé amoureux](https:\u002F\u002Ftalks.soubiran.dev\u002F2025-09-17\u002Fnodejs-paris\u002F1). Cet outil a changé ma façon de construire des applications.\n\nDans le même temps, j'ai commencé un emploi où j'avais la liberté de choisir les technologies. J'ai choisi Laravel, Inertia et Vue. Donc, pour mes projets personnels et professionnels, j'ai adopté cette stack.\n\nRien n'est parfait, et cette stack avait un inconvénient.\n\nNuxt UI n'était disponible que pour Nuxt et pas aussi personnalisable que je le voulais. C'était frustrant car j'avais appris à aimer les composants et l'expérience développeur qu'ils offraient. Il n'y avait rien de comparable pour Vue.\n\nMais ne sommes-nous pas des créateurs ? Si cela n'existe pas, pourquoi ne pas le créer ?\n\nCela m'a conduit à une idée. Shadcn devenait de facto la bibliothèque pour construire des applications React. Elle vient avec un magnifique système de design et un système de distribution intelligent. Les projets possèdent leurs composants, ce qui simplifie la personnalisation au niveau du projet. Cependant, l'expérience développeur lors de l'utilisation de ces composants est loin de l'expérience Nuxt UI.\n\nPourquoi ne pas créer une bibliothèque similaire à Shadcn pour Vue ? Un clone de Nuxt UI, distribué comme Shadcn, mais pour Vue et compatible avec Inertia. L'expérience développeur de Nuxt UI disponible pour chaque développeur Vue m'a donné des frissons en l'imaginant.\n\nC'est ainsi que le tweet est né.\n\nAvec cette idée en tête et un fort intérêt pour construire cette bibliothèque, je devais me lancer. Construire et maintenir une bibliothèque est un énorme travail, alors j'ai demandé sur Twitter si quelqu'un était intéressé à me rejoindre.\n\nCinq personnes ont répondu à l'appel, beaucoup plus que je ne l'avais prévu !\n\nUn grand merci à [a bei](https:\u002F\u002Fx.com\u002Fabei557832), [David Pena](https:\u002F\u002Fx.com\u002FUnans___), [Enzo Notario](https:\u002F\u002Fx.com\u002Fenzonotario_), [aussieboi](https:\u002F\u002Fx.com\u002F_aussieboi), et [Grygoriy Lyashenko](https:\u002F\u002Fx.com\u002FLyashenkoG) de m'avoir rejoint dans cette aventure ! _Si un jour vous venez à Paris, faites-moi signe !_\n\nJ'avais un plan, une équipe, et nous étions prêts à construire.\n\nDans le même temps, Nuxt UI progressait vers la version 3. Deux semaines avant mon tweet, une pull request pour rendre Nuxt UI compatible avec Vue 3, créée par [Daniel](https:\u002F\u002Fgithub.com\u002Fdanielroe), a été fusionnée. Mais ce n'était toujours pas aussi personnalisable que je le souhaitais, et il n'y avait pas de prise en charge pour Inertia.\n\n## La Construction\n\nJe n'étais pas sûr de la façon d'aborder cela. Je savais que je voulais créer une bibliothèque qui correspondait à l'expérience développeur de Nuxt UI, mais je ne voulais pas simplement la copier.\n\nCe n'était pas un fork direct de Nuxt UI. Régulièrement, je copiais des composants de Nuxt UI, les adaptais pour les rendre agnostiques à Nuxt, et parfois changeais leur comportement pour correspondre à mes préférences.\n\nL'équipe a fait cela pour chaque composant, chaque composable, chaque fonction utilitaire, et même chaque thème. Nous avons également construit un site de documentation pour permettre aux utilisateurs de copier et coller facilement des extraits de code, avec des modifications de code automatiques si vous utilisiez Inertia ou simplement Vite, et des démos intégrées pour chaque composant.\n\nNous avons même construit un script bash pour automatiser le processus d'installation et un moyen d'installer uniquement les composants nécessaires avec leurs dépendances.\n\nC'était absolument incroyable à construire et si satisfaisant d'avoir quelque chose de fonctionnel et utilisé dans des projets.\n\nPar exemple, [code.soubiran.dev](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fcode.soubiran.dev\u002Ftree\u002F53b1811948b1879c8f9d354d76aa144883baaa4d\u002Fsrc\u002Fui) a été construit avec cette bibliothèque. Même mon propre site web, celui sur lequel vous êtes actuellement, l'utilise également.\n\n## La Vérité\n\nLe 14 avril 2025, [une pull request pour ajouter la prise en charge d'Inertia](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Fpull\u002F3845) a été acceptée et mergée.\n\nJe devais me rendre à l'évidence : Nuxt UI devenait enfin ce que je voulais construire. La personnalisation n'était pas aussi bonne que de posséder les composants, mais c'était open source. Pourquoi ne pas soumettre quelques contributions ? Je connais chaque composant par cœur et j'avais déjà apporté des améliorations dans mon fork. Je devais juste les porter.\n\nAlors je l'ai fait. Depuis le 1er décembre 2024, j'ai contribué dix-neuf fois.\n\nMaintenir mon fork était honnêtement beaucoup de travail et largement plus que la valeur que j'étais capable d'ajouter. Parfois, il vaut mieux lâcher prise et se concentrer sur ce qui compte vraiment.\n\nJe suis heureux du projet et de l'expérience prise. Ça m'a donné l'occasion d'apprendre en profondeur le code source de Nuxt UI et d'échanger avec l'équipe via des messages Twitter et des issues GitHub. Un aspect amusant de travailler avec une équipe mondiale : vous pouviez vous endormir après avoir envoyé un message et vous réveiller avec une nouvelle demande de tirage !\n\nMaintenant, il est temps d'admettre : Vue UI est officiellement terminé.\n\n- Site web : [vue-ui.barbapapazes.dev](https:\u002F\u002Fvue-ui.barbapapazes.dev)\n- Code : [Vue UI Labs](https:\u002F\u002Fgithub.com\u002Fvue-ui-labs\u002Fvue-ui)\n\n---\n\nMerci à toute l'équipe Nuxt, en particulier à [Sébastien](https:\u002F\u002Fx.com\u002FAtinux), [Benjamin](https:\u002F\u002Fx.com\u002Fbenjamincanac), et [Hugo](https:\u002F\u002Fx.com\u002FHugoRCD__) pour votre travail incroyable sur Nuxt UI. 🫶\n\n---\n\nUne image vaut mille mots, mais parfois mille mots expriment plus que tout. J'espère que cela vous inspirera à entreprendre votre propre voyage. N'oubliez pas : [You don't need to be chosen, you can just do things](.\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things.md).\n\n---\n\nDe temps en temps, j'aime revenir sur mes articles précédents. J'aime prendre le temps de réfléchir au parcours que j'ai pris. Cela m'aide à apprécier chaque nouveau jour et chaque nouvelle personne que je rencontre en chemin.\n",{"title":17233,"description":17543},{"loc":17545},"fr\u002Fposts\u002Fsoftware-engineering-a-human-journey-before-anything-else","I9Fq_axJ2_wCpnSCuAT-AoyvHy3jhXcswcKcJc62wzA",{"id":17552,"title":17553,"body":17554,"contentId":9505,"date":17740,"description":17741,"extension":18,"meta":17742,"navigation":20,"path":17743,"rawbody":17744,"robots":23,"schemaOrg":23,"seo":17745,"sitemap":17746,"stem":17747,"__hash__":17748},"posts\u002Ffr\u002Fposts\u002Fyoure-not-a-software-engineer-sorry-but-thats-okay.md","Vous n'êtes pas ingénieur logiciel, et ce n'est pas grave",{"type":7,"value":17555,"toc":17738},[17556,17561,17564,17566,17569,17572,17574,17577,17580,17583,17586,17589,17592,17595,17598,17605,17610,17617,17620,17623,17626,17629,17631,17634,17640,17643,17646,17649,17658,17661,17664,17667,17669,17672,17675,17678,17681,17684,17687,17690,17693,17695,17700,17703,17710,17713,17721,17724,17727],[35,17557,17558],{},[1216,17559,17560],{},"Vous n'êtes pas ingénieur logiciel, désolé, mais ce n'est pas grave.",[35,17562,17563],{},"Je sais, cela peut être difficile à entendre. Mais je suis sûr que vous avez d'autres qualités.",[1158,17565],{},[35,17567,17568],{},"Si vous avez trouvé cet article par vous-même, vous n'êtes peut-être pas la personne ciblée. En revanche, si je vous l'ai envoyé personnellement, vous devriez probablement le lire et prendre un moment pour y réfléchir.",[35,17570,17571],{},"De plus, cet article est axé sur le développement web et les domaines connexes. Je ne parlerai pas d'autres domaines de l'ingénierie logicielle comme les systèmes embarqués ou la science des données.",[1158,17573],{},[35,17575,17576],{},"Avant d'aller plus loin, comment en sommes-nous arrivés là et de quoi discutons-nous ?",[35,17578,17579],{},"Je fais face à un réel défi ici. Alors que le code devient de plus en plus accessible, les gens ont tendance à penser qu'ils peuvent simplement se lancer et commencer à construire des systèmes complexes sans aucune connaissance, que les développeurs de logiciels ne sont qu'un trou financier sans valeur réelle. Je suis juste fatigué d'expliquer encore et encore mon métier. J'ai la liberté de rédiger mes pensées une bonne fois pour toutes de manière claire et de les partager avec quiconque pense qu'il peut faire mon travail. Alors nous y sommes.",[35,17581,17582],{},"Dans le monde d'aujourd'hui, de plus en plus de personnes écrivent du code. Toute la société se numérise. Cela signifie qu'il n'y a jamais eu un besoin aussi important d'écrire des logiciels.",[35,17584,17585],{},"Certaines personnes ont vu une opportunité de gagner de l'argent. Que diriez-vous de dire aux gens qu'ils peuvent devenir riches facilement, d'un bureau, juste en apprenant pendant 2 à 6 mois en vendant une formation ? Super idée ! Les bootcamps sont nés.",[35,17587,17588],{},"C'était une arnaque.",[35,17590,17591],{},"Apprendre à coder est beaucoup plus difficile et prend beaucoup plus de temps que 2 à 6 mois. De plus, seuls les bons développeurs peuvent gagner beaucoup d'argent. Vous ne deviendrez pas riche. Et pire encore, vous n'aurez pas de travail car le marché a été saturé par des développeurs juniors inexpérimentés, espérant trouver un emploi bien rémunéré.",[35,17593,17594],{},"Mais l'histoire ne s'arrête pas là.",[35,17596,17597],{},"Ce mouvement est arrivé avant les LLM. Avec des assistants IA comme ChatGPT ou Claude Code, et des constructeurs IA comme Loveable, V0 ou Bolt.new, la programmation n'a jamais été aussi accessible qu'aujourd'hui, et c'est terrible.",[35,17599,17600,17601,17604],{},"Absolument tout le monde peut maintenant écrire du code. ",[1216,17602,17603],{},"La phrase exacte devrait être \"Absolument tout le monde peut demander à un LLM de générer du code pour eux.\""," La barrière à l'entrée était déjà basse avec des langages comme Python. Maintenant, avec l'IA, les barrières ont disparu.",[35,17606,17607],{},[1216,17608,17609],{},"Ne vous méprenez pas. Si cela peut créer une vocation, ou permettre aux individus de s'exprimer à travers le code sans avoir à gérer toute la complexité, c'est une bonne chose.",[35,17611,17612,17613,17616],{},"La vérité est que ",[6862,17614,17615],{},"l'écriture de code est la partie la plus facile du travail",". Si vous la trouvez difficile, c'est probablement parce que vous êtes au début de votre parcours. Si vous pensez être ingénieur logiciel parce que vous avez suivi un bootcamp ou utilisé des outils IA, vous devriez faire une pause et continuer à lire. J'écris cet article pour vous.",[35,17618,17619],{},"Le monde réel, le monde des adultes, est beaucoup plus complexe qu'une liste de tâches en frontend écrite en HTML et CSS.",[35,17621,17622],{},"Les entreprises ont des besoins commerciaux spécifiques. La plupart du temps, elles ont du mal à les exprimer clairement. Les personnes ayant un besoin ne proviennent pas de ce domaine ; elles ne comprennent pas ce que nous faisons. C'est ok, mais en tant qu'ingénieur logiciel, votre rôle est de faire le lien.",[35,17624,17625],{},"L'un de vos défis est d'extraire le besoin, de le comprendre, de le remettre en question et de construire une vision claire et cohérente à partir de celui-ci. Ensuite, vous devez concevoir une solution appropriée. Cela fait partie de l'emploi d'ingénieur impliqué par le titre \"ingénieur logiciel\".",[35,17627,17628],{},"Les assistants IA et les constructeurs IA ne le feront pas magiquement seuls. Bien sûr, ils peuvent vous aider, mais ils ne comprendront pas tout le contexte commercial car il y a beaucoup de connaissances implicites et de contextes que seul un humain peut saisir.",[1158,17630],{},[35,17632,17633],{},"Maintenant que nous avons tous en tête l'histoire, examinons de plus près le titre \"ingénieur logiciel\".",[35,17635,17636,17637,215],{},"Selon la définition de Wikipédia, ",[1216,17638,17639],{},"l'ingénierie logicielle est une branche de l'informatique et de l'ingénierie axée sur la conception, le développement, le test et la maintenance des applications logicielles. Elle consiste à appliquer des principes d'ingénierie et une expertise en programmation informatique pour développer des systèmes logiciels répondant aux besoins des utilisateurs. Les termes programmeur et codeur se chevauchent avec ingénieur logiciel, mais ils impliquent uniquement l'aspect construction d'une charge de travail d'ingénieur logiciel typique",[35,17641,17642],{},"J'adore vraiment cette définition car elle dit absolument tout sur le travail.",[35,17644,17645],{},"Construire des logiciels implique de la science, à la fois théorique et pratique. Ce n'est pas juste une question de chance ou d'intuition. Il s'agit de comprendre, d'analyser et de résoudre des problèmes. De plus, quatre tâches sont mises en avant : concevoir, développer, tester et maintenir. Lorsque j'ai dit plus tôt que l'écriture de code est la partie la plus facile du travail, c'est parce que c'est seulement une des quatre tâches, même si c'est la plus visible. Et comparé aux autres tâches, écrire du code est un jeu d'enfant.",[35,17647,17648],{},"Et n'oubliez pas que tout cela doit être fait pour résoudre un véritable besoin utilisateur. Sinon, vous écrivez juste du code pour le simple plaisir d'écrire du code, ce qui est inutile. En utilisant des outils IA pour écrire la majeure partie de votre code, et en vous concentrant uniquement sur le code, il y a de fortes chances que vous soyez davantage un programmeur ou un codeur qu'un ingénieur logiciel.",[35,17650,17651,17652,17657],{},"Il y a quelque temps, ",[39,17653,17656],{"href":17654,"rel":17655},"https:\u002F\u002Fsoubiran.dev\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests",[77],"j'ai écrit sur les tests",". Vous devriez le lire pour avoir une première appréciation du sujet, car ce ne sont pas les constructeurs IA qui garantiront la qualité de votre code, et les tests sont vraiment difficiles.",[35,17659,17660],{},"Je ne vais pas approfondir les aspects de conception et de maintenance, mais si vous essayez de me vendre une base de données NoSQL dans une architecture microservices, j'espère que vous avez une solide compréhension des compromis impliqués, et que vous ne suivez pas simplement une tendance ou ne me récitez pas des mots à la mode que vous avez entendus sur un blog de marchand de complexité.",[35,17662,17663],{},"Faire de mauvaises hypothèses et avoir une mauvaise compréhension de l'écosystème conduit à de mauvaises décisions de conception. Cela crée ce que nous appelons de la dette technique. Et comme toute dette, vous devrez la rembourser un jour, et plus vous en accumulez, plus il devient difficile de la gérer. En essayant de construire des logiciels sans les connaissances requises, vous devenez un générateur de dette technique. Rien de plus.",[35,17665,17666],{},"Dans un contexte personnel, ce n'est pas un problème. C'est votre bac à sable, amusez-vous avec. Vous pouvez jeter votre code ou le réécrire. Dans un contexte commercial, les enjeux sont beaucoup plus élevés. De mauvaises décisions de conception peuvent conduire à des échecs de projet, à des ressources gaspillées et à des occasions perdues. Il est crucial de prendre le temps de prendre des décisions éclairées.",[1158,17668],{},[35,17670,17671],{},"Si vous lisez encore, j'espère vous avoir convaincu de la complexité du rôle d'un développeur logiciel. Si ce n'est pas le cas, laissez-moi vous montrer quelque chose.",[35,17673,17674],{},"Les applications modernes ne sont pas simples d'un point de vue externe et selon l'échelle à laquelle vous travaillez, la complexité peut rapidement escalader. Pour cette partie, imaginons une application assez standard avec un trafic gérable. (c'est-à-dire pas à l'échelle de Facebook, Uber ou Twitter)",[35,17676,17677],{},"Votre application a un frontend. Avec ce frontend, écrit en utilisant HTML, CSS, certainement avec un framework comme Tailwind CSS ou Panda CSS, et JavaScript. Pour le JavaScript, vous pouvez utiliser une bibliothèque comme Alpine, Unpoly, HTMX, Hotwire ou Livewire si vous utilisez Laravel. Mais vous pourriez décider d'utiliser un framework plus robuste comme React, Vue.js, Angular, Svelte, etc. Vous pourriez également préférer utiliser un méta-framework comme Next, React Router, Nuxt, SvelteKit. Mais nous n'avons pas encore terminé. Le frontend fournit une interface utilisateur pour offrir une expérience utilisateur à un utilisateur, une personne réelle qui a un besoin réel. Vous devrez donc considérer l'accessibilité, la performance, la réactivité. Cela nécessite de passer un certain temps à réfléchir à l'interface utilisateur (UI) et à l'expérience utilisateur (UX). Honnêtement, c'est beaucoup de travail, donc vous pourriez envisager de choisir une bibliothèque de composants, ou de construire la vôtre sur la base du système de design de votre entreprise. Si vous utilisez Vue, vous pourriez choisir Nuxt UI, PrimeVue, Quasar, Vuetify, Reka UI, Shadcn-vue, Native UI, Volt UI, Daisy UI, Flowbite Vue, Element Plus, Ant Design Vue, Ark UI ou Vuestic UI. Peut-être devrais-je aussi mentionner qu'en fonction de l'architecture, vous pourriez avoir besoin d'une couche de récupération de données comme TanStack Query, et si vous construisez beaucoup de tables, TanStack Tables. Je vous épargne les bibliothèques de gestion de stock et les bibliothèques d'animation, vous pourriez être submergé.",[35,17679,17680],{},"C'est juste le frontend, et je n'ai pas mentionné Vite. Oui, c'est déjà beaucoup.",[35,17682,17683],{},"Maintenant, imaginez le backend, avec tous les différents langages et frameworks disponibles, les systèmes de bases de données avec SQL, NoSQL, TimeSeries, bases de données graphiques et chaque variante pour chacune, les systèmes de queue comme RabbitMQ, Kafka, ou une simple table de base de données, les systèmes de cache comme Redis ou Memcached, toutes les versions gérées fournies par les fournisseurs de cloud, les différentes techniques utilisées pour tester, construire et déployer votre application, en utilisant un pipeline CI\u002FCD, Docker, Compose, Swarm, ou Kubernetes, avec un environnement de staging, et toute la gestion des dépôts. Il y a tant à prendre en compte, tant à apprendre, et tant de nouveautés chaque jour qu'il est instantanément facile de prendre du retard si vous ne restez pas à jour.",[35,17685,17686],{},"Ai-je mentionné la sécurité, l'observabilité, la haute disponibilité, l'évolutivité, l'élasticité ou le déploiement ? Parce que cela fait également partie du travail.",[35,17688,17689],{},"Honnêtement, vous n'avez pas à savoir comment utiliser tout ce que j'ai mentionné. Mais si 90% des mots vous semblent inconnus, alors essayer de faire semblant de savoir ce que vous faites est une terrible idée. Pour vous, pour votre équipe, et pour toute l'entreprise pour laquelle vous travaillez.",[35,17691,17692],{},"La dette technique est réelle. Dire \"je ne sais pas\" est une réponse honnête et complète.",[1158,17694],{},[938,17696,17697],{},[35,17698,17699],{},"L'informatique est devenue une infrastructure, mais cela ne fonctionne pas comme une infrastructure.",[35,17701,17702],{},"Cet article vaut également la peine d'être lu :",[35,17704,17705],{},[39,17706,17709],{"href":17707,"rel":17708},"https:\u002F\u002Fwww.theatlantic.com\u002Ftechnology\u002Farchive\u002F2015\u002F11\u002Fprogrammers-should-not-call-themselves-engineers\u002F414271\u002F",[77],"Programmers: Stop Calling Yourselves Engineers",[35,17711,17712],{},"Et cette vidéo vaut la peine d'être regardée :",[35,17714,17715,17720],{},[39,17716,17719],{"href":17717,"rel":17718},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Zki4d6sHhy4",[77],"Building High-Trust Environments"," par John Drexler",[35,17722,17723],{},"Si vous souhaitez commencer une carrière en tant qu'ingénieur logiciel ou développeur logiciel, je dois vous informer que cela nécessite des sacrifices et de la dévotion. Comme beaucoup d'autres professions, vous ne deviendrez pas bon simplement en lisant ou en regardant une vidéo ou en écoutant un professeur pendant quelques heures. Vous ne deviendrez pas le meilleur sans vous immerger complètement dans le domaine.",[35,17725,17726],{},"Peut-être qu'en voyant un développeur logiciel à l'œuvre, tout semble simple. C'est probablement parce qu'il connaît son travail sans avoir à vous vendre de la complexité pour justifier son existence.",[938,17728,17729],{},[35,17730,17731,17732,17737],{},"Il est difficile de vendre du simple, car le simple semble facile, et qui veut payer pour cela ?\nD'après ",[39,17733,17736],{"href":17734,"rel":17735},"https:\u002F\u002Fworld.hey.com\u002Fdhh\u002Fmerchants-of-complexity-4851301b",[77],"Merchants of Complexity"," de David Heinemeier Hansson",{"title":13,"searchDepth":14,"depth":14,"links":17739},[],"2025-09-04","Mon avis personnel sur le paysage de l'ingénierie logicielle. Vous ne serez peut-être pas d'accord avec moi, et j'aimerais connaître vos pensées.",{},"\u002Ffr\u002Fposts\u002Fyoure-not-a-software-engineer-sorry-but-thats-okay","---\ncontentId: ba80c73f-cf16-41cc-b1f8-bc548c4c82f4\ntitle: \"Vous n'êtes pas ingénieur logiciel, et ce n'est pas grave\"\ndescription: \"Mon avis personnel sur le paysage de l'ingénierie logicielle. Vous ne serez peut-être pas d'accord avec moi, et j'aimerais connaître vos pensées.\"\ndate: 2025-09-04\n---\n\n_Vous n'êtes pas ingénieur logiciel, désolé, mais ce n'est pas grave._\n\nJe sais, cela peut être difficile à entendre. Mais je suis sûr que vous avez d'autres qualités.\n\n---\n\nSi vous avez trouvé cet article par vous-même, vous n'êtes peut-être pas la personne ciblée. En revanche, si je vous l'ai envoyé personnellement, vous devriez probablement le lire et prendre un moment pour y réfléchir.\n\nDe plus, cet article est axé sur le développement web et les domaines connexes. Je ne parlerai pas d'autres domaines de l'ingénierie logicielle comme les systèmes embarqués ou la science des données.\n\n---\n\nAvant d'aller plus loin, comment en sommes-nous arrivés là et de quoi discutons-nous ?\n\nJe fais face à un réel défi ici. Alors que le code devient de plus en plus accessible, les gens ont tendance à penser qu'ils peuvent simplement se lancer et commencer à construire des systèmes complexes sans aucune connaissance, que les développeurs de logiciels ne sont qu'un trou financier sans valeur réelle. Je suis juste fatigué d'expliquer encore et encore mon métier. J'ai la liberté de rédiger mes pensées une bonne fois pour toutes de manière claire et de les partager avec quiconque pense qu'il peut faire mon travail. Alors nous y sommes.\n\nDans le monde d'aujourd'hui, de plus en plus de personnes écrivent du code. Toute la société se numérise. Cela signifie qu'il n'y a jamais eu un besoin aussi important d'écrire des logiciels.\n\nCertaines personnes ont vu une opportunité de gagner de l'argent. Que diriez-vous de dire aux gens qu'ils peuvent devenir riches facilement, d'un bureau, juste en apprenant pendant 2 à 6 mois en vendant une formation ? Super idée ! Les bootcamps sont nés.\n\nC'était une arnaque.\n\nApprendre à coder est beaucoup plus difficile et prend beaucoup plus de temps que 2 à 6 mois. De plus, seuls les bons développeurs peuvent gagner beaucoup d'argent. Vous ne deviendrez pas riche. Et pire encore, vous n'aurez pas de travail car le marché a été saturé par des développeurs juniors inexpérimentés, espérant trouver un emploi bien rémunéré.\n\nMais l'histoire ne s'arrête pas là.\n\nCe mouvement est arrivé avant les LLM. Avec des assistants IA comme ChatGPT ou Claude Code, et des constructeurs IA comme Loveable, V0 ou Bolt.new, la programmation n'a jamais été aussi accessible qu'aujourd'hui, et c'est terrible.\n\nAbsolument tout le monde peut maintenant écrire du code. _La phrase exacte devrait être \"Absolument tout le monde peut demander à un LLM de générer du code pour eux.\"_ La barrière à l'entrée était déjà basse avec des langages comme Python. Maintenant, avec l'IA, les barrières ont disparu.\n\n_Ne vous méprenez pas. Si cela peut créer une vocation, ou permettre aux individus de s'exprimer à travers le code sans avoir à gérer toute la complexité, c'est une bonne chose._\n\nLa vérité est que **l'écriture de code est la partie la plus facile du travail**. Si vous la trouvez difficile, c'est probablement parce que vous êtes au début de votre parcours. Si vous pensez être ingénieur logiciel parce que vous avez suivi un bootcamp ou utilisé des outils IA, vous devriez faire une pause et continuer à lire. J'écris cet article pour vous.\n\nLe monde réel, le monde des adultes, est beaucoup plus complexe qu'une liste de tâches en frontend écrite en HTML et CSS.\n\nLes entreprises ont des besoins commerciaux spécifiques. La plupart du temps, elles ont du mal à les exprimer clairement. Les personnes ayant un besoin ne proviennent pas de ce domaine ; elles ne comprennent pas ce que nous faisons. C'est ok, mais en tant qu'ingénieur logiciel, votre rôle est de faire le lien.\n\nL'un de vos défis est d'extraire le besoin, de le comprendre, de le remettre en question et de construire une vision claire et cohérente à partir de celui-ci. Ensuite, vous devez concevoir une solution appropriée. Cela fait partie de l'emploi d'ingénieur impliqué par le titre \"ingénieur logiciel\".\n\nLes assistants IA et les constructeurs IA ne le feront pas magiquement seuls. Bien sûr, ils peuvent vous aider, mais ils ne comprendront pas tout le contexte commercial car il y a beaucoup de connaissances implicites et de contextes que seul un humain peut saisir.\n\n---\n\nMaintenant que nous avons tous en tête l'histoire, examinons de plus près le titre \"ingénieur logiciel\".\n\nSelon la définition de Wikipédia, _l'ingénierie logicielle est une branche de l'informatique et de l'ingénierie axée sur la conception, le développement, le test et la maintenance des applications logicielles. Elle consiste à appliquer des principes d'ingénierie et une expertise en programmation informatique pour développer des systèmes logiciels répondant aux besoins des utilisateurs. Les termes programmeur et codeur se chevauchent avec ingénieur logiciel, mais ils impliquent uniquement l'aspect construction d'une charge de travail d'ingénieur logiciel typique_.\n\nJ'adore vraiment cette définition car elle dit absolument tout sur le travail.\n\nConstruire des logiciels implique de la science, à la fois théorique et pratique. Ce n'est pas juste une question de chance ou d'intuition. Il s'agit de comprendre, d'analyser et de résoudre des problèmes. De plus, quatre tâches sont mises en avant : concevoir, développer, tester et maintenir. Lorsque j'ai dit plus tôt que l'écriture de code est la partie la plus facile du travail, c'est parce que c'est seulement une des quatre tâches, même si c'est la plus visible. Et comparé aux autres tâches, écrire du code est un jeu d'enfant.\n\nEt n'oubliez pas que tout cela doit être fait pour résoudre un véritable besoin utilisateur. Sinon, vous écrivez juste du code pour le simple plaisir d'écrire du code, ce qui est inutile. En utilisant des outils IA pour écrire la majeure partie de votre code, et en vous concentrant uniquement sur le code, il y a de fortes chances que vous soyez davantage un programmeur ou un codeur qu'un ingénieur logiciel.\n\nIl y a quelque temps, [j'ai écrit sur les tests](https:\u002F\u002Fsoubiran.dev\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests). Vous devriez le lire pour avoir une première appréciation du sujet, car ce ne sont pas les constructeurs IA qui garantiront la qualité de votre code, et les tests sont vraiment difficiles.\n\nJe ne vais pas approfondir les aspects de conception et de maintenance, mais si vous essayez de me vendre une base de données NoSQL dans une architecture microservices, j'espère que vous avez une solide compréhension des compromis impliqués, et que vous ne suivez pas simplement une tendance ou ne me récitez pas des mots à la mode que vous avez entendus sur un blog de marchand de complexité.\n\nFaire de mauvaises hypothèses et avoir une mauvaise compréhension de l'écosystème conduit à de mauvaises décisions de conception. Cela crée ce que nous appelons de la dette technique. Et comme toute dette, vous devrez la rembourser un jour, et plus vous en accumulez, plus il devient difficile de la gérer. En essayant de construire des logiciels sans les connaissances requises, vous devenez un générateur de dette technique. Rien de plus.\n\nDans un contexte personnel, ce n'est pas un problème. C'est votre bac à sable, amusez-vous avec. Vous pouvez jeter votre code ou le réécrire. Dans un contexte commercial, les enjeux sont beaucoup plus élevés. De mauvaises décisions de conception peuvent conduire à des échecs de projet, à des ressources gaspillées et à des occasions perdues. Il est crucial de prendre le temps de prendre des décisions éclairées.\n\n---\n\nSi vous lisez encore, j'espère vous avoir convaincu de la complexité du rôle d'un développeur logiciel. Si ce n'est pas le cas, laissez-moi vous montrer quelque chose.\n\nLes applications modernes ne sont pas simples d'un point de vue externe et selon l'échelle à laquelle vous travaillez, la complexité peut rapidement escalader. Pour cette partie, imaginons une application assez standard avec un trafic gérable. (c'est-à-dire pas à l'échelle de Facebook, Uber ou Twitter)\n\nVotre application a un frontend. Avec ce frontend, écrit en utilisant HTML, CSS, certainement avec un framework comme Tailwind CSS ou Panda CSS, et JavaScript. Pour le JavaScript, vous pouvez utiliser une bibliothèque comme Alpine, Unpoly, HTMX, Hotwire ou Livewire si vous utilisez Laravel. Mais vous pourriez décider d'utiliser un framework plus robuste comme React, Vue.js, Angular, Svelte, etc. Vous pourriez également préférer utiliser un méta-framework comme Next, React Router, Nuxt, SvelteKit. Mais nous n'avons pas encore terminé. Le frontend fournit une interface utilisateur pour offrir une expérience utilisateur à un utilisateur, une personne réelle qui a un besoin réel. Vous devrez donc considérer l'accessibilité, la performance, la réactivité. Cela nécessite de passer un certain temps à réfléchir à l'interface utilisateur (UI) et à l'expérience utilisateur (UX). Honnêtement, c'est beaucoup de travail, donc vous pourriez envisager de choisir une bibliothèque de composants, ou de construire la vôtre sur la base du système de design de votre entreprise. Si vous utilisez Vue, vous pourriez choisir Nuxt UI, PrimeVue, Quasar, Vuetify, Reka UI, Shadcn-vue, Native UI, Volt UI, Daisy UI, Flowbite Vue, Element Plus, Ant Design Vue, Ark UI ou Vuestic UI. Peut-être devrais-je aussi mentionner qu'en fonction de l'architecture, vous pourriez avoir besoin d'une couche de récupération de données comme TanStack Query, et si vous construisez beaucoup de tables, TanStack Tables. Je vous épargne les bibliothèques de gestion de stock et les bibliothèques d'animation, vous pourriez être submergé.\n\nC'est juste le frontend, et je n'ai pas mentionné Vite. Oui, c'est déjà beaucoup.\n\nMaintenant, imaginez le backend, avec tous les différents langages et frameworks disponibles, les systèmes de bases de données avec SQL, NoSQL, TimeSeries, bases de données graphiques et chaque variante pour chacune, les systèmes de queue comme RabbitMQ, Kafka, ou une simple table de base de données, les systèmes de cache comme Redis ou Memcached, toutes les versions gérées fournies par les fournisseurs de cloud, les différentes techniques utilisées pour tester, construire et déployer votre application, en utilisant un pipeline CI\u002FCD, Docker, Compose, Swarm, ou Kubernetes, avec un environnement de staging, et toute la gestion des dépôts. Il y a tant à prendre en compte, tant à apprendre, et tant de nouveautés chaque jour qu'il est instantanément facile de prendre du retard si vous ne restez pas à jour.\n\nAi-je mentionné la sécurité, l'observabilité, la haute disponibilité, l'évolutivité, l'élasticité ou le déploiement ? Parce que cela fait également partie du travail.\n\nHonnêtement, vous n'avez pas à savoir comment utiliser tout ce que j'ai mentionné. Mais si 90% des mots vous semblent inconnus, alors essayer de faire semblant de savoir ce que vous faites est une terrible idée. Pour vous, pour votre équipe, et pour toute l'entreprise pour laquelle vous travaillez.\n\nLa dette technique est réelle. Dire \"je ne sais pas\" est une réponse honnête et complète.\n\n---\n\n> L'informatique est devenue une infrastructure, mais cela ne fonctionne pas comme une infrastructure.\n\nCet article vaut également la peine d'être lu :\n\n[Programmers: Stop Calling Yourselves Engineers](https:\u002F\u002Fwww.theatlantic.com\u002Ftechnology\u002Farchive\u002F2015\u002F11\u002Fprogrammers-should-not-call-themselves-engineers\u002F414271\u002F)\n\nEt cette vidéo vaut la peine d'être regardée :\n\n[Building High-Trust Environments](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Zki4d6sHhy4) par John Drexler\n\nSi vous souhaitez commencer une carrière en tant qu'ingénieur logiciel ou développeur logiciel, je dois vous informer que cela nécessite des sacrifices et de la dévotion. Comme beaucoup d'autres professions, vous ne deviendrez pas bon simplement en lisant ou en regardant une vidéo ou en écoutant un professeur pendant quelques heures. Vous ne deviendrez pas le meilleur sans vous immerger complètement dans le domaine.\n\nPeut-être qu'en voyant un développeur logiciel à l'œuvre, tout semble simple. C'est probablement parce qu'il connaît son travail sans avoir à vous vendre de la complexité pour justifier son existence.\n\n> Il est difficile de vendre du simple, car le simple semble facile, et qui veut payer pour cela ?\n> D'après [Merchants of Complexity](https:\u002F\u002Fworld.hey.com\u002Fdhh\u002Fmerchants-of-complexity-4851301b) de David Heinemeier Hansson\n",{"title":17553,"description":17741},{"loc":17743},"fr\u002Fposts\u002Fyoure-not-a-software-engineer-sorry-but-thats-okay","QcMJUs6LNxSyyCoAM8U2rRgnSaTQLQOUvQyO7ie6EvM",{"id":17750,"title":17751,"body":17752,"contentId":9330,"date":17844,"description":17845,"extension":18,"meta":17846,"navigation":20,"path":17847,"rawbody":17848,"robots":23,"schemaOrg":23,"seo":17849,"sitemap":17850,"stem":17851,"__hash__":17852},"posts\u002Ffr\u002Fposts\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty.md","Être perdu. Un essai pour me retrouver dans l'incertitude.",{"type":7,"value":17753,"toc":17842},[17754,17759,17762,17765,17771,17774,17777,17780,17783,17786,17789,17792,17795,17798,17801,17804,17807,17810,17815,17817,17820,17823,17825,17828,17831,17834,17836,17839],[35,17755,17756],{},[1216,17757,17758],{},"Etre perdu dans sa propre vie. Quelle sensation étrange.",[35,17760,17761],{},"Honnêtement, je ne sais pas où ce post va nous mener. Peut-être que vous ne lirez jamais ces lignes et si vous le faites, il y a des chances que vous ne compreniez pas. C'est une réflexion sur ma vie durant un moment d'incertitude.",[35,17763,17764],{},"Depuis des semaines, j'ai complètement perdu le plaisir de coder au travail. Là où je manquais de temps dans ma journée, j'en ai désormais bien trop, attendant la fin de la journée.",[35,17766,17767,17768],{},"C'est un problème, un vrai. Non seulement parce que je suis complètement improductif, mais aussi parce que cela s'infiltre dans d'autres parties de ma vie. Cela signifie que même mes projets personnels souffrent et, pire encore, me lever de mon canapé devient un véritable effort. Savoir que je vais passer un moment avec des amis, pour me détendre ou faire la fête, est actuellement la seule chose qui me pousse à sortir. ",[1216,17769,17770],{},"Aller au CrossFit est également une bonne raison de bouger.",[35,17772,17773],{},"Faire défiler mes fils d'actualités est tellement plus simple, même si je sais que c'est juste une évasion temporaire, une illusion qui, une fois qu'elle s'est estompée, me laisse dans un état pire qu'avant.",[35,17775,17776],{},"Le pire, c'est que j'ai deux conférences à préparer, un SaaS à terminer et des live streams à réaliser.",[35,17778,17779],{},"Vous direz certainement que je devrais faire une pause. Vous avez peut-être raison. Mais si vous me séparez de mes projets personnels, les choses qui m'ont motivé pendant des années, même si c'est dur en ce moment, que restera-t-il ?",[35,17781,17782],{},"De plus, faire une pause ne résout pas l'origine du problème, seulement les symptômes.",[35,17784,17785],{},"Mais comment est-ce que j'en suis arrivé là ?",[35,17787,17788],{},"C'est un problème aux multiples facettes. J'ai réfléchi et écrit quelques notes à ce sujet pendant deux semaines, et bon sang, cela pourrait être l'un des problèmes les plus difficiles que j'ai jamais rencontrés. Tant de choses sont entrelacées.",[35,17790,17791],{},"Au travail, chaque petite action me demande de déplacer des montagnes. Ce que je peux faire chez moi en une heure ou deux prend maintenant des semaines. Pas des jours, des semaines. La partie la plus épuisante n'est pas vraiment de devoir demander la permission, ou quoi que ce soit d'autre ; c'est plus la charge mentale qui vient avec. Quand vous devez constamment justifier, demander et attendre, le projet stagne inévitablement. Cette stagnation est incroyablement frustrante et démoralisante.",[35,17793,17794],{},"Je crois fermement en cette mentalité de livrer tôt et souvent, même avec le risque d'échec. Il vaut mieux apprendre de petites erreurs que d'attendre le moment parfait qui ne viendra peut-être jamais. Actuellement, cette vitesse est nulle.",[35,17796,17797],{},"Dans le même temps, lors d'un live où j'ai découvert Java avec un professionnel, j'ai réalisé qu'un autre monde existe, et qu'en fin de compte, je ne sais pas grand-chose sur le développement web. Cela peut sembler incroyable, mais cela me fait aussi réaliser combien j'ai à apprendre. La seule façon pour moi d'apprendre tout cela est de pratiquer au sein d'une entreprise spécialisée. Avec cela à l'esprit, et sachant que tout est bien plus difficile qu'il ne devrait l'être au travail, je ressens le besoin de changer quelque chose.",[35,17799,17800],{},"Le problème, c'est que mon travail actuel est une opportunité incroyable. Il n'y a littéralement rien d'autre comme ça en France. Je construis un lanceur spatial. Donc oui, je fais une liste des pour et des contre. Y a-t-il quelque chose de plus difficile que d'avoir le choix ?",[35,17802,17803],{},"Maintenant, ajoutez la suranalyse - penser à toutes les possibilités, analyser chaque détail et tenter de prévoir chaque résultat qui ne se produira jamais.",[35,17805,17806],{},"Le cocktail parfait pour être complètement perdu.",[35,17808,17809],{},"Bienvenue dans mon esprit.",[35,17811,17812],{},[1216,17813,17814],{},"Et, en plus de tout cela, il y a cette discrète distraction. Quelqu'un dont la présence persiste dans mes pensées. Cela ne devrait pas compliquer les choses, mais ça le fait. Mes émotions sont sur un rollercoaster.",[1158,17816],{},[35,17818,17819],{},"La vie est-elle parfois juste ironique, ou essaie-t-elle vraiment d'envoyer un message ? Je me le demande. Récemment, lors d'une conversation dans le métro, quelqu'un a dit qu'il devait être formidable de transformer une passion en métier. Je dois admettre que lorsque toutes les étoiles s'alignent, cela peut être incroyablement gratifiant. Tout le monde parle toujours de cet alignement. J'entends rarement parler du désalignement, des luttes et des doutes. C'est comme un château de cartes - un équilibre fragile qui peut rapidement s'effondrer. Quand cela arrive, tout votre monde, tout ce que vous avez construit pendant des années, peut s'effondrer.",[35,17821,17822],{},"Comme pour tout, c'est une question de compromis. Malheureusement, peu de gens parlent des inconvénients.",[1158,17824],{},[35,17826,17827],{},"Peut-être que tout cela fait partie du processus. Je l'espère. Je l'espère vraiment.",[35,17829,17830],{},"Cette perte de contrôle, pour quelqu'un qui a toujours essayé de contrôler autant que possible, est assez troublante. Je réfléchis beaucoup à tout pour essayer de me rattraper, mais peut-être que la seule façon de s'en sortir est de lâcher prise.",[35,17832,17833],{},"Je verrai. Le temps va faire son œuvre.",[1158,17835],{},[35,17837,17838],{},"J'écris malgré ce chaos, incapable de trouver les bons mots et sans voir la lumière au bout du tunnel. Mais j'ai deux espoirs. Le premier est que faire des exercices d'introspection et essayer de mettre des mots sur mes sentiments m'aide à réduire le brouillard. Le second est d'avoir un aperçu de mes sentiments et pensées actuels, que je pourrai utiliser plus tard pour comprendre comment j'en suis arrivé là et comment avancer.",[35,17840,17841],{},"Peut-être que vous ressentez aussi ce sentiment. Au moins, j'espère que vous le comprenez.",{"title":13,"searchDepth":14,"depth":14,"links":17843},[],"2025-09-02","Parfois, tout se passe bien. Mais quand tout s'effondre, il devient vraiment difficile de retrouver son chemin.",{},"\u002Ffr\u002Fposts\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty","---\ncontentId: 6da35c27-6a73-4b8f-aafa-2aa01e7a1ed3\ntitle: Être perdu. Un essai pour me retrouver dans l'incertitude.\ndescription: Parfois, tout se passe bien. Mais quand tout s'effondre, il devient vraiment difficile de retrouver son chemin.\ndate: 2025-09-02\n---\n\n_Etre perdu dans sa propre vie. Quelle sensation étrange._\n\nHonnêtement, je ne sais pas où ce post va nous mener. Peut-être que vous ne lirez jamais ces lignes et si vous le faites, il y a des chances que vous ne compreniez pas. C'est une réflexion sur ma vie durant un moment d'incertitude.\n\nDepuis des semaines, j'ai complètement perdu le plaisir de coder au travail. Là où je manquais de temps dans ma journée, j'en ai désormais bien trop, attendant la fin de la journée.\n\nC'est un problème, un vrai. Non seulement parce que je suis complètement improductif, mais aussi parce que cela s'infiltre dans d'autres parties de ma vie. Cela signifie que même mes projets personnels souffrent et, pire encore, me lever de mon canapé devient un véritable effort. Savoir que je vais passer un moment avec des amis, pour me détendre ou faire la fête, est actuellement la seule chose qui me pousse à sortir. _Aller au CrossFit est également une bonne raison de bouger._\n\nFaire défiler mes fils d'actualités est tellement plus simple, même si je sais que c'est juste une évasion temporaire, une illusion qui, une fois qu'elle s'est estompée, me laisse dans un état pire qu'avant.\n\nLe pire, c'est que j'ai deux conférences à préparer, un SaaS à terminer et des live streams à réaliser.\n\nVous direz certainement que je devrais faire une pause. Vous avez peut-être raison. Mais si vous me séparez de mes projets personnels, les choses qui m'ont motivé pendant des années, même si c'est dur en ce moment, que restera-t-il ?\n\nDe plus, faire une pause ne résout pas l'origine du problème, seulement les symptômes.\n\nMais comment est-ce que j'en suis arrivé là ?\n\nC'est un problème aux multiples facettes. J'ai réfléchi et écrit quelques notes à ce sujet pendant deux semaines, et bon sang, cela pourrait être l'un des problèmes les plus difficiles que j'ai jamais rencontrés. Tant de choses sont entrelacées.\n\nAu travail, chaque petite action me demande de déplacer des montagnes. Ce que je peux faire chez moi en une heure ou deux prend maintenant des semaines. Pas des jours, des semaines. La partie la plus épuisante n'est pas vraiment de devoir demander la permission, ou quoi que ce soit d'autre ; c'est plus la charge mentale qui vient avec. Quand vous devez constamment justifier, demander et attendre, le projet stagne inévitablement. Cette stagnation est incroyablement frustrante et démoralisante.\n\nJe crois fermement en cette mentalité de livrer tôt et souvent, même avec le risque d'échec. Il vaut mieux apprendre de petites erreurs que d'attendre le moment parfait qui ne viendra peut-être jamais. Actuellement, cette vitesse est nulle.\n\nDans le même temps, lors d'un live où j'ai découvert Java avec un professionnel, j'ai réalisé qu'un autre monde existe, et qu'en fin de compte, je ne sais pas grand-chose sur le développement web. Cela peut sembler incroyable, mais cela me fait aussi réaliser combien j'ai à apprendre. La seule façon pour moi d'apprendre tout cela est de pratiquer au sein d'une entreprise spécialisée. Avec cela à l'esprit, et sachant que tout est bien plus difficile qu'il ne devrait l'être au travail, je ressens le besoin de changer quelque chose.\n\nLe problème, c'est que mon travail actuel est une opportunité incroyable. Il n'y a littéralement rien d'autre comme ça en France. Je construis un lanceur spatial. Donc oui, je fais une liste des pour et des contre. Y a-t-il quelque chose de plus difficile que d'avoir le choix ?\n\nMaintenant, ajoutez la suranalyse - penser à toutes les possibilités, analyser chaque détail et tenter de prévoir chaque résultat qui ne se produira jamais.\n\nLe cocktail parfait pour être complètement perdu.\n\nBienvenue dans mon esprit.\n\n_Et, en plus de tout cela, il y a cette discrète distraction. Quelqu'un dont la présence persiste dans mes pensées. Cela ne devrait pas compliquer les choses, mais ça le fait. Mes émotions sont sur un rollercoaster._\n\n---\n\nLa vie est-elle parfois juste ironique, ou essaie-t-elle vraiment d'envoyer un message ? Je me le demande. Récemment, lors d'une conversation dans le métro, quelqu'un a dit qu'il devait être formidable de transformer une passion en métier. Je dois admettre que lorsque toutes les étoiles s'alignent, cela peut être incroyablement gratifiant. Tout le monde parle toujours de cet alignement. J'entends rarement parler du désalignement, des luttes et des doutes. C'est comme un château de cartes - un équilibre fragile qui peut rapidement s'effondrer. Quand cela arrive, tout votre monde, tout ce que vous avez construit pendant des années, peut s'effondrer.\n\nComme pour tout, c'est une question de compromis. Malheureusement, peu de gens parlent des inconvénients.\n\n---\n\nPeut-être que tout cela fait partie du processus. Je l'espère. Je l'espère vraiment.\n\nCette perte de contrôle, pour quelqu'un qui a toujours essayé de contrôler autant que possible, est assez troublante. Je réfléchis beaucoup à tout pour essayer de me rattraper, mais peut-être que la seule façon de s'en sortir est de lâcher prise.\n\nJe verrai. Le temps va faire son œuvre.\n\n---\n\nJ'écris malgré ce chaos, incapable de trouver les bons mots et sans voir la lumière au bout du tunnel. Mais j'ai deux espoirs. Le premier est que faire des exercices d'introspection et essayer de mettre des mots sur mes sentiments m'aide à réduire le brouillard. Le second est d'avoir un aperçu de mes sentiments et pensées actuels, que je pourrai utiliser plus tard pour comprendre comment j'en suis arrivé là et comment avancer.\n\nPeut-être que vous ressentez aussi ce sentiment. Au moins, j'espère que vous le comprenez.\n",{"title":17751,"description":17845},{"loc":17847},"fr\u002Fposts\u002Fbeing-lost-an-essay-to-find-myself-within-uncertainty","2iwvbOKBw7bqDeKiaJI-TxdrSYDsCLIO_XUkSuaiuBM",{"id":17854,"title":17530,"body":17855,"contentId":5669,"date":18185,"description":18186,"extension":18,"meta":18187,"navigation":20,"path":18188,"rawbody":18189,"robots":23,"schemaOrg":23,"seo":18190,"sitemap":18191,"stem":18192,"__hash__":18193},"posts\u002Ffr\u002Fposts\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things.md",{"type":7,"value":17856,"toc":18180},[17857,17864,17869,17883,17907,17910,17912,17919,17928,17932,17938,17941,17944,17947,17958,17961,17964,17975,17978,17982,17985,17988,17991,18011,18017,18032,18038,18044,18065,18068,18071,18075,18081,18084,18091,18094,18097,18100,18103,18111,18114,18117,18123,18129,18132,18135,18149,18152,18155,18158,18161,18166,18169,18173,18175],[938,17858,17859],{},[35,17860,17861,17863],{},[200,17862,944],{},"\nLe titre n'est volontairement pas traduit. Il m'est important de garder la citation originale pour préserver son impact et sa signification.",[938,17865,17866],{},[35,17867,17868],{},"You don't need permission, you don't need to be chosen, you can just start, and you can just do things.",[35,17870,17871,17872,17877,17878,215],{},"Ce n'est pas de moi. J'aimerais pouvoir la revendiquer. Cette citation vient de ",[39,17873,17876],{"href":17874,"rel":17875},"https:\u002F\u002Fx.com\u002Faarondfrancis",[77],"Aaron Francis"," lors de ",[39,17879,17882],{"href":17880,"rel":17881},"https:\u002F\u002Fyoutu.be\u002Fdgr-WAUgELw",[77],"LaraconUS 2025",[35,17884,17885,17886,17891,17892,17897,17898,17891,17903,17906],{},"Parfois, inexplicablement, certaines phrases, qu'elles viennent d'un ami, d'un livre, d'un film ou d'une présentation, vous frappent. Celle-ci m'a profondément touché, comme la continuation du ",[39,17887,17890],{"href":17888,"rel":17889},"https:\u002F\u002Fyoutu.be\u002FNTMDzGLs8pw?t=3732",[77],"\"we must ship\""," de ",[39,17893,17896],{"href":17894,"rel":17895},"https:\u002F\u002Fx.com\u002Ftaylorotwell",[77],"Taylor Otwell",", le ",[39,17899,17902],{"href":17900,"rel":17901},"https:\u002F\u002Faaronfrancis.com\u002F2024\u002Fwhat-if-you-tried-hard-dac139a5",[77],"\"What if you tried hard?\"",[39,17904,17876],{"href":17874,"rel":17905},[77],", et des années d'expérimentation avec mes propres projets.",[35,17908,17909],{},"Derrière cette citation se cache le pragmatisme, le pragmatisme de construire des choses et de les livrer à de vrais utilisateurs, pas seulement à soi-même. Transformer des idées en réalité, ne pas attendre d'être prêt, mais agir et le faire.",[1158,17911],{},[938,17913,17914],{},[35,17915,17916,17918],{},[200,17917,944],{},"\nC'est mon point de vue personnel que je souhaitais partager. Principalement pour mon futur moi, mais aussi pour vous, si vous le trouvez utile.",[35,17920,17921,17922,17927],{},"Depuis des années, ",[39,17923,17926],{"href":17924,"rel":17925},"https:\u002F\u002Fgithub.com\u002FBarbapapazes",[77],"mon slogan sur GitHub"," est \"I create user experiences on the web and in real life!\" Il n'y a rien sur le code, et rien sur le fait d'être développeur. C'est complètement intentionnel et je trouve cela profondément lié à cette citation et à l'état d'esprit global.",[51,17929,17931],{"id":17930},"suis-je-développeur","Suis-je développeur ?",[35,17933,17934,17935],{},"Avec le temps, je trouve qu'il est difficile de me définir comme développeur. Me réduire, ainsi que tout ce que je crée, à juste \"quelqu'un qui écrit du code\" me blesse vraiment. ",[1216,17936,17937],{},"Je pourrais sérieusement consacrer un article entier à ce sujet.",[35,17939,17940],{},"Vous vous demandez peut-être pourquoi, surtout si vous n'êtes pas du secteur.",[35,17942,17943],{},"Écrire du code est amusant, et j'adore personnellement les défis techniques que nous rencontrons en cours de route. J'adore résoudre des défis très techniques ainsi que regarder des conférences très techniques et partager mes propres défis techniques avec mes pairs. Mais à la fin de la journée, la plupart des gens se moquent des défis techniques auxquels vous avez été confronté ; je ne suis pas sûr qu'ils puissent même les comprendre.",[35,17945,17946],{},"Cela pourrait sembler un détail, mais ça me rend triste. Parce que vous n'avez rien à dire à personne. C'est particulièrement vrai lorsque vos amis ou votre famille ne sont pas développeurs, et que vous êtes le seul ingénieur logiciel, centré sur le web, dans votre entreprise.",[1593,17948,17949,17952,17955],{},[71,17950,17951],{},"Qu'as-tu fait aujourd'hui ? demande l'un de mes collègues.",[71,17953,17954],{},"Oh tu sais, j'ai simplement refondu toute la logique métier interne de l'application en utilisant le DDD pour centraliser les règles métier, en appliquant les règles de calisthénie objet, l'inversion de contrôle, et le principe du \"tell-don't-ask\", pour la rendre plus lisible et maintenable, et grâce au TDD, tout devrait fonctionner comme prévu, je réponds.",[71,17956,17957],{},"Super, ça a l'air bien, dit-il.",[35,17959,17960],{},"Je sais qu'il ne comprend pas vraiment ce que je viens de dire. Je ne lui en veux pas, ce n'est pas son travail. Et je dois admettre que c'est sacrément ennuyeux. Même pour moi. Même si c'est la réalité. J'adore tout ça, mais ce n'est pas ce que les non-spécialistes veulent entendre.",[35,17962,17963],{},"La vérité est que, même aujourd'hui, je ne sais pas comment expliquer ce que je fais sans le réduire à \"J'écris du code\". En même temps, écrire du code n'est pas la partie qui compte le plus pour moi. Sans explications très longues, il est presque impossible de faire passer le message. Et je ne veux pas ennuyer les gens avec ça. Dilemme.",[35,17965,17966,17967],{},"C'est à un point où l'une de mes plus grandes peurs est, lorsqu'il s'agit de rencontrer quelqu'un de nouveau, de devoir répondre à la question \"Que faites-vous dans la vie ?\" Je cherche encore la bonne façon de le formuler. ",[1216,17968,17969,17974],{},[39,17970,17973],{"href":17971,"rel":17972},"https:\u002F\u002Fsoubiran.dev\u002F#:~:text=currently%20working%20for%20a%20French%20space%20company%2C%20%C2%A0MaiaSpace%2C%20as%20an%20aerospace%20engineer",[77],"\"Je construis une fusée.\""," Pas vraiment vrai, pas vraiment faux, mais au moins, facilement compréhensible.",[35,17976,17977],{},"Alors ? Où cela nous mène-t-il ?",[51,17979,17981],{"id":17980},"partagez-vos-passions","Partagez vos passions",[35,17983,17984],{},"Pendant longtemps, j'ai écrit du code juste pour écrire du code. Pas de véritable but, pas de véritable objectif, juste écrire du code. J'en étais heureux et le nombre de lignes écrites devenait une métrique de mon bonheur. Je suis devenu accro à cela, beaucoup trop. Je ne peux pas conseiller d'atteindre ce niveau à quiconque. Toute mon attention était sur le code au point que j'étais complètement en dehors de ma propre vie.",[35,17986,17987],{},"Mais récemment, mon attention a changé. Je me suis complètement éloigné de cela. Je sais pertinemment que les références que j'ai présentées ci-dessus m'ont aidé à réaliser cela.",[35,17989,17990],{},"Avant de vous en dire plus à ce sujet, je souhaite partager une petite histoire personnelle car ce changement a été un cheminement, pas un événement unique qui a cliqué dans ma tête.",[35,17992,17993,17994,1655,17999,18004,18005,18010],{},"Il y a cinq ans, j'étais étudiant en école d'ingénieur. J'étais profondément impliqué dans la vie associative et une opportunité s'est présentée à moi. On m'a demandé de reprendre la plateforme de tutorat visant à aider les étudiants à trouver des tuteurs. Avec deux amis, nous avons commencé à travailler dessus. Beaucoup de travail pour créer une API, une application web utilisant ",[39,17995,17998],{"href":17996,"rel":17997},"https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-client-vue",[77],"Vue",[39,18000,18003],{"href":18001,"rel":18002},"https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-client-react",[77],"React"," (ne me demandez pas pourquoi les deux), et ",[39,18006,18009],{"href":18007,"rel":18008},"https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-mobile",[77],"une application mobile"," utilisant React Native.",[35,18012,18013,18014],{},"Nous avons beaucoup travaillé dessus, de mai à octobre 2020, nous avons mis tout ce que nous avions dedans mais nous n'avons jamais atteint les utilisateurs finaux. Nous ne l'avons jamais livré. Personne ne l'a jamais vu. Nous avons beaucoup appris pendant le processus et l'échec en fait partie. Nous le savions. Mais ça fait quand même mal. ",[1216,18015,18016],{},"Trust the process.",[35,18018,18019,18020,18025,18026,18031],{},"Un an après la fin du projet de tutorat, j'ai commencé un nouveau projet appelé ",[39,18021,18024],{"href":18022,"rel":18023},"https:\u002F\u002Fgithub.com\u002Finsamee-app",[77],"Insamee"," avec un autre ami. C'était la continuité du précédent, mais cette fois, nous voulions construire tout un écosystème autour de cela. Quatre plateformes, ",[39,18027,18030],{"href":18028,"rel":18029},"https:\u002F\u002Fgithub.com\u002Finsamee-app\u002Fteam",[77],"TEAM",", pour faciliter la vie étudiante et croître. Ambitieux.",[35,18033,18034,18035],{},"Sept mois plus tard, en avril 2022, nous avions quelque chose qui fonctionnait. Cependant, vous pouvez deviner comment cela s'est terminé. Nous n'avons jamais atteint les utilisateurs finaux. Nous ne l'avons jamais livré. Malgré cela, j'avais la possibilité de travailler dessus à temps plein grâce à un arrangement scolaire pendant deux mois. Expérience incroyable, je recommande. ",[1216,18036,18037],{},"\"Try hard for a while. You might be surprised by what happens.\", Aaron Francis. Dans ce cas, j'ai eu l'opportunité d'être dispensé de certains cours.",[35,18039,18040,18041],{},"Pour ces deux projets, nous étions beaucoup trop concentrés sur le code plutôt que sur le fait de le livrer. ",[1216,18042,18043],{},"\"We must ship.\", Taylor Otwell. And celebrate.",[35,18045,18046,18047,18052,18053,18058,18059,18064],{},"Quelques mois plus tard, j'ai construit ",[39,18048,18051],{"href":18049,"rel":18050},"https:\u002F\u002Fgithub.com\u002FClassement-des-Associations\u002Fvote.le-classement.fr",[77],"une plateforme de vote"," pour un événement nommé ",[39,18054,18057],{"href":18055,"rel":18056},"https:\u002F\u002Fle-classement.fr\u002F",[77],"Le Classement des Associations",". Cette fois, j'étais seul, et j'avais un objectif clair : le livrer. Pas de place pour échouer, nous l'avions annoncé publiquement. En septembre 2022, ",[39,18060,18063],{"href":18061,"rel":18062},"https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Fla-plateforme-de-vote-du-classement-des-associations",[77],"je l'ai livré"," et pendant un mois, plus de 25 000 votes ont été exprimés. C'était enfin un succès, après des années d'échecs.",[35,18066,18067],{},"Pour la première fois, j'ai créé un véritable moment pour les gens. Ils en parlaient sur les réseaux sociaux, ils le partageaient avec leurs camarades de classe, et ils l'utilisaient. C'était tout numérique, mais sans code, cela n'aurait jamais été possible.",[35,18069,18070],{},"À ce moment-là, quelque chose a commencé à changer mais je n'en étais pas encore conscient.",[51,18072,18074],{"id":18073},"valorisez-les-moments","Valorisez les Moments",[35,18076,18077,18080],{},[6862,18078,18079],{},"Créer et partager des moments"," avec des personnes que j'apprécie vraiment est devenu ma priorité absolue. Cela donne beaucoup plus de sens à ma vie.",[35,18082,18083],{},"Cela me fait me demander comment je peux utiliser ma passion pour le code pour faciliter cela et pour donner vie à ces moments ou en créer de nouveaux.",[35,18085,18086,18087,18090],{},"Avant de plonger là-dedans, je dois reconnaître qu'utiliser toujours du code n'a même pas de sens. ",[6862,18088,18089],{},"Parfois, demander est mieux que tout le reste."," Mais parfois, construire quelque chose peut être ce qui crée plus qu'un moment, une expérience.",[35,18092,18093],{},"C'est exactement ce qui s'est passé récemment.",[35,18095,18096],{},"Il y a quelque chose que je n'ai jamais partagé auparavant, mais j'adore cuisiner, surtout des pâtisseries. Alors que le processus de création d'une pâtisserie est déjà un moment, qui peut être partagé pour devenir encore plus intense, partager cette création avec des amis autour d'une table est, pour moi, la meilleure partie. C'est un moment naturel de connexion et de convivialité.",[35,18098,18099],{},"J'en ai récemment parlé avec deux amis, et j'ai réalisé qu'il était difficile de leur montrer ce que j'étais capable de faire. Bien sûr, j'ai quelques photos, mais parmi toute ma galerie, il faut du temps pour toutes les retrouver. Et je voulais partager plus que de simples photos ; je voulais partager le titre de la recette et une description. Je voulais que tout soit au même endroit, facilement accessible à tous mes amis, sans avoir à leur envoyer une multitude de photos chaque fois que je voulais partager quelque chose.",[35,18101,18102],{},"J'ai donc décidé de construire une plateforme centralisée pour partager mes créations. Un des critères était de m'assurer que seuls mes amis pouvaient y accéder. Il s'avère que j'ai un ami qui possède et maintient son propre fournisseur d'identité pour accéder à ses propres applications. En l'utilisant, j'ai non seulement pu garantir que seuls mes amis pouvaient y accéder, mais j'ai travaillé sur un cas d'utilisation réel avec un ami très proche. C'est tellement amusant de travailler avec des amis sur un projet.",[35,18104,18105,18106,18110],{},"C'était un projet rapide qui m'a donné l'occasion d'utiliser ",[39,18107,18109],{"href":17088,"rel":18108},[77],"Typesense"," pour la recherche. Au fur et à mesure que vous tapez, une requête est envoyée au serveur, et les résultats s'affichent en moins de 100 ms. L'effet \"Wahoo\" est garanti.",[35,18112,18113],{},"Quelques jours plus tard, en repensant à certaines discussions que j'avais eues avec un ami, une autre idée m'est venue à l'esprit.",[35,18115,18116],{},"Cuisiner c'est super, mais ensuite, vous devez manger le gâteau. Chaque week-end, je fais un gâteau et pour être honnête, manger un gâteau entier tout seul prend plusieurs jours et ensuite, un autre gâteau est prêt. Au début, c'était amusant, mais maintenant, ça en devient trop.",[35,18118,18119,18120],{},"Lors de cette conversation que j'ai eue avec elle, elle m'a demandé pourquoi je ne le partageais pas avec mes collègues. J'ai égoïstement répondu. Mais après tout, elle avait raison. Les pâtisseries peuvent être utilisées pour créer des moments, de la même manière que j'utilise le code pour créer des moments. Bien sûr, ils sont différents, mais ce sont tous des moments, et à la fin, vous les partagez avec des personnes que vous appréciez. ",[1216,18121,18122],{},"All the environment can be used to think out of the box.",[35,18124,18125,18126],{},"J'ai donc décidé d'étendre intelligemment cette plateforme à mes collègues. Intelligemment parce que je ne peux pas utiliser le même fournisseur d'identité, et je ne peux pas utiliser celui de mon entreprise. De plus, je ne veux pas avoir à gérer mes créations sur deux plateformes différentes, mais je veux garder les deux plateformes séparées. Différentes bases de code avec des données partagées. ",[1216,18127,18128],{},"Si la solution technique vous intéresse, merci de me le faire savoir dans les commentaires.",[35,18130,18131],{},"Moment parfait. J'avais 15 jours de vacances. J'ai donc dû construire cela en 15 jours, et le livrer à la fin, prêt ou non. J'ai partagé un compte à rebours avec mon équipe, juste avant de partir, pour le rendre officiel et public. Le compte à rebours était réglé pour s'arrêter le 11 août 2025 à 9h00 (UTC+2).",[35,18133,18134],{},"Je devais itérer sur l'expérience que je voulais livrer plusieurs fois pour m'assurer qu'elle soit à la fois utile, utilisable et agréable. J'ai arrêté ma réflexion sur les points suivants :",[1593,18136,18137,18140,18143,18146],{},[71,18138,18139],{},"Permettre aux utilisateurs de voir mes créations,",[71,18141,18142],{},"Permettre aux utilisateurs de suggérer de nouvelles recettes, basées sur de nouvelles idées ou des recettes existantes,",[71,18144,18145],{},"Permettre aux utilisateurs de donner leur avis sur mes créations,",[71,18147,18148],{},"Permettre aux utilisateurs de savoir quand je vais amener le prochain gâteau au bureau.",[35,18150,18151],{},"Seulement 15 jours pour le construire. C'était un défi parce que je devais équilibrer le temps entre la construction de la plateforme et profiter de mes vacances. Ce n'était pas facile, le sommeil était court, mais rappelez-vous : \"Try hard for a while. You might be surprised by what happens.\"",[35,18153,18154],{},"J'ai divisé ma journée en une (petite) session de code, 40 km de vélo, et un cours de wing foil ou de catamaran. J'ai répété ce cycle tous les jours. Parfois, un lever de soleil, parfois la découverte d'une île.",[35,18156,18157],{},"Le dernier jour, après 32 heures de réflexion et d'écriture du code, tout était prêt à être livré.",[35,18159,18160],{},"J'écris ces lignes le dimanche 10 août, la veille du lancement. Tout est prêt à être mis en ligne demain matin pour mes collègues, comme prévu. Je ne peux pas dire si cela fonctionnera, je ne sais pas s'ils aimeront ça, mais j'y ai mis tout ce que j'avais.",[35,18162,18163],{},[6862,18164,18165],{},"I try hard and I ship it.",[35,18167,18168],{},"Et quoi qu'il arrive maintenant, je continuerai à créer des moments pour les personnes qui me tiennent à cœur. Parfois avec un message, parfois avec du code, mais toujours avec passion.",[35,18170,18171],{},[1216,18172,18016],{},[1158,18174],{},[35,18176,18177],{},[1216,18178,18179],{},"Pour Julie et Jordan, sans qui ce projet n'aurait jamais vu le jour. We shipped it!",{"title":13,"searchDepth":14,"depth":14,"links":18181},[18182,18183,18184],{"id":17930,"depth":14,"text":17931},{"id":17980,"depth":14,"text":17981},{"id":18073,"depth":14,"text":18074},"2025-08-09","Récemment, j'ai changé ma vision sur le code que j'écris. Ce n'est plus une fin, mais juste un moyen de créer des moments avec ceux qui me tiennent à cœur.",{},"\u002Ffr\u002Fposts\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things","---\ncontentId: 1cfb53a4-a252-4f64-b569-9a07dbf94e3e\ntitle: \"You don't need to be chosen, you can just do things\"\ndescription: \"Récemment, j'ai changé ma vision sur le code que j'écris. Ce n'est plus une fin, mais juste un moyen de créer des moments avec ceux qui me tiennent à cœur.\"\ndate: 2025-08-09\n---\n\n> [!NOTE]\n> Le titre n'est volontairement pas traduit. Il m'est important de garder la citation originale pour préserver son impact et sa signification.\n\n> You don't need permission, you don't need to be chosen, you can just start, and you can just do things.\n\nCe n'est pas de moi. J'aimerais pouvoir la revendiquer. Cette citation vient de [Aaron Francis](https:\u002F\u002Fx.com\u002Faarondfrancis) lors de [LaraconUS 2025](https:\u002F\u002Fyoutu.be\u002Fdgr-WAUgELw).\n\nParfois, inexplicablement, certaines phrases, qu'elles viennent d'un ami, d'un livre, d'un film ou d'une présentation, vous frappent. Celle-ci m'a profondément touché, comme la continuation du [\"we must ship\"](https:\u002F\u002Fyoutu.be\u002FNTMDzGLs8pw?t=3732) de [Taylor Otwell](https:\u002F\u002Fx.com\u002Ftaylorotwell), le [\"What if you tried hard?\"](https:\u002F\u002Faaronfrancis.com\u002F2024\u002Fwhat-if-you-tried-hard-dac139a5) de [Aaron Francis](https:\u002F\u002Fx.com\u002Faarondfrancis), et des années d'expérimentation avec mes propres projets.\n\nDerrière cette citation se cache le pragmatisme, le pragmatisme de construire des choses et de les livrer à de vrais utilisateurs, pas seulement à soi-même. Transformer des idées en réalité, ne pas attendre d'être prêt, mais agir et le faire.\n\n---\n\n> [!NOTE]\n> C'est mon point de vue personnel que je souhaitais partager. Principalement pour mon futur moi, mais aussi pour vous, si vous le trouvez utile.\n\nDepuis des années, [mon slogan sur GitHub](https:\u002F\u002Fgithub.com\u002FBarbapapazes) est \"I create user experiences on the web and in real life!\" Il n'y a rien sur le code, et rien sur le fait d'être développeur. C'est complètement intentionnel et je trouve cela profondément lié à cette citation et à l'état d'esprit global.\n\n## Suis-je développeur ?\n\nAvec le temps, je trouve qu'il est difficile de me définir comme développeur. Me réduire, ainsi que tout ce que je crée, à juste \"quelqu'un qui écrit du code\" me blesse vraiment. _Je pourrais sérieusement consacrer un article entier à ce sujet._\n\nVous vous demandez peut-être pourquoi, surtout si vous n'êtes pas du secteur.\n\nÉcrire du code est amusant, et j'adore personnellement les défis techniques que nous rencontrons en cours de route. J'adore résoudre des défis très techniques ainsi que regarder des conférences très techniques et partager mes propres défis techniques avec mes pairs. Mais à la fin de la journée, la plupart des gens se moquent des défis techniques auxquels vous avez été confronté ; je ne suis pas sûr qu'ils puissent même les comprendre.\n\nCela pourrait sembler un détail, mais ça me rend triste. Parce que vous n'avez rien à dire à personne. C'est particulièrement vrai lorsque vos amis ou votre famille ne sont pas développeurs, et que vous êtes le seul ingénieur logiciel, centré sur le web, dans votre entreprise.\n\n- Qu'as-tu fait aujourd'hui ? demande l'un de mes collègues.\n- Oh tu sais, j'ai simplement refondu toute la logique métier interne de l'application en utilisant le DDD pour centraliser les règles métier, en appliquant les règles de calisthénie objet, l'inversion de contrôle, et le principe du \"tell-don't-ask\", pour la rendre plus lisible et maintenable, et grâce au TDD, tout devrait fonctionner comme prévu, je réponds.\n- Super, ça a l'air bien, dit-il.\n\nJe sais qu'il ne comprend pas vraiment ce que je viens de dire. Je ne lui en veux pas, ce n'est pas son travail. Et je dois admettre que c'est sacrément ennuyeux. Même pour moi. Même si c'est la réalité. J'adore tout ça, mais ce n'est pas ce que les non-spécialistes veulent entendre.\n\nLa vérité est que, même aujourd'hui, je ne sais pas comment expliquer ce que je fais sans le réduire à \"J'écris du code\". En même temps, écrire du code n'est pas la partie qui compte le plus pour moi. Sans explications très longues, il est presque impossible de faire passer le message. Et je ne veux pas ennuyer les gens avec ça. Dilemme.\n\nC'est à un point où l'une de mes plus grandes peurs est, lorsqu'il s'agit de rencontrer quelqu'un de nouveau, de devoir répondre à la question \"Que faites-vous dans la vie ?\" Je cherche encore la bonne façon de le formuler. _[\"Je construis une fusée.\"](https:\u002F\u002Fsoubiran.dev\u002F#:~:text=currently%20working%20for%20a%20French%20space%20company%2C%20%C2%A0MaiaSpace%2C%20as%20an%20aerospace%20engineer) Pas vraiment vrai, pas vraiment faux, mais au moins, facilement compréhensible._\n\nAlors ? Où cela nous mène-t-il ?\n\n## Partagez vos passions\n\nPendant longtemps, j'ai écrit du code juste pour écrire du code. Pas de véritable but, pas de véritable objectif, juste écrire du code. J'en étais heureux et le nombre de lignes écrites devenait une métrique de mon bonheur. Je suis devenu accro à cela, beaucoup trop. Je ne peux pas conseiller d'atteindre ce niveau à quiconque. Toute mon attention était sur le code au point que j'étais complètement en dehors de ma propre vie.\n\nMais récemment, mon attention a changé. Je me suis complètement éloigné de cela. Je sais pertinemment que les références que j'ai présentées ci-dessus m'ont aidé à réaliser cela.\n\nAvant de vous en dire plus à ce sujet, je souhaite partager une petite histoire personnelle car ce changement a été un cheminement, pas un événement unique qui a cliqué dans ma tête.\n\nIl y a cinq ans, j'étais étudiant en école d'ingénieur. J'étais profondément impliqué dans la vie associative et une opportunité s'est présentée à moi. On m'a demandé de reprendre la plateforme de tutorat visant à aider les étudiants à trouver des tuteurs. Avec deux amis, nous avons commencé à travailler dessus. Beaucoup de travail pour créer une API, une application web utilisant [Vue](https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-client-vue) et [React](https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-client-react) (ne me demandez pas pourquoi les deux), et [une application mobile](https:\u002F\u002Fgithub.com\u002FCampus-INSA-CVL\u002Ftutorinsa-mobile) utilisant React Native.\n\nNous avons beaucoup travaillé dessus, de mai à octobre 2020, nous avons mis tout ce que nous avions dedans mais nous n'avons jamais atteint les utilisateurs finaux. Nous ne l'avons jamais livré. Personne ne l'a jamais vu. Nous avons beaucoup appris pendant le processus et l'échec en fait partie. Nous le savions. Mais ça fait quand même mal. _Trust the process._\n\nUn an après la fin du projet de tutorat, j'ai commencé un nouveau projet appelé [Insamee](https:\u002F\u002Fgithub.com\u002Finsamee-app) avec un autre ami. C'était la continuité du précédent, mais cette fois, nous voulions construire tout un écosystème autour de cela. Quatre plateformes, [TEAM](https:\u002F\u002Fgithub.com\u002Finsamee-app\u002Fteam), pour faciliter la vie étudiante et croître. Ambitieux.\n\nSept mois plus tard, en avril 2022, nous avions quelque chose qui fonctionnait. Cependant, vous pouvez deviner comment cela s'est terminé. Nous n'avons jamais atteint les utilisateurs finaux. Nous ne l'avons jamais livré. Malgré cela, j'avais la possibilité de travailler dessus à temps plein grâce à un arrangement scolaire pendant deux mois. Expérience incroyable, je recommande. _\"Try hard for a while. You might be surprised by what happens.\", Aaron Francis. Dans ce cas, j'ai eu l'opportunité d'être dispensé de certains cours._\n\nPour ces deux projets, nous étions beaucoup trop concentrés sur le code plutôt que sur le fait de le livrer. _\"We must ship.\", Taylor Otwell. And celebrate._\n\nQuelques mois plus tard, j'ai construit [une plateforme de vote](https:\u002F\u002Fgithub.com\u002FClassement-des-Associations\u002Fvote.le-classement.fr) pour un événement nommé [Le Classement des Associations](https:\u002F\u002Fle-classement.fr\u002F). Cette fois, j'étais seul, et j'avais un objectif clair : le livrer. Pas de place pour échouer, nous l'avions annoncé publiquement. En septembre 2022, [je l'ai livré](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Fla-plateforme-de-vote-du-classement-des-associations) et pendant un mois, plus de 25 000 votes ont été exprimés. C'était enfin un succès, après des années d'échecs.\n\nPour la première fois, j'ai créé un véritable moment pour les gens. Ils en parlaient sur les réseaux sociaux, ils le partageaient avec leurs camarades de classe, et ils l'utilisaient. C'était tout numérique, mais sans code, cela n'aurait jamais été possible.\n\nÀ ce moment-là, quelque chose a commencé à changer mais je n'en étais pas encore conscient.\n\n## Valorisez les Moments\n\n**Créer et partager des moments** avec des personnes que j'apprécie vraiment est devenu ma priorité absolue. Cela donne beaucoup plus de sens à ma vie.\n\nCela me fait me demander comment je peux utiliser ma passion pour le code pour faciliter cela et pour donner vie à ces moments ou en créer de nouveaux.\n\nAvant de plonger là-dedans, je dois reconnaître qu'utiliser toujours du code n'a même pas de sens. **Parfois, demander est mieux que tout le reste.** Mais parfois, construire quelque chose peut être ce qui crée plus qu'un moment, une expérience.\n\nC'est exactement ce qui s'est passé récemment.\n\nIl y a quelque chose que je n'ai jamais partagé auparavant, mais j'adore cuisiner, surtout des pâtisseries. Alors que le processus de création d'une pâtisserie est déjà un moment, qui peut être partagé pour devenir encore plus intense, partager cette création avec des amis autour d'une table est, pour moi, la meilleure partie. C'est un moment naturel de connexion et de convivialité.\n\nJ'en ai récemment parlé avec deux amis, et j'ai réalisé qu'il était difficile de leur montrer ce que j'étais capable de faire. Bien sûr, j'ai quelques photos, mais parmi toute ma galerie, il faut du temps pour toutes les retrouver. Et je voulais partager plus que de simples photos ; je voulais partager le titre de la recette et une description. Je voulais que tout soit au même endroit, facilement accessible à tous mes amis, sans avoir à leur envoyer une multitude de photos chaque fois que je voulais partager quelque chose.\n\nJ'ai donc décidé de construire une plateforme centralisée pour partager mes créations. Un des critères était de m'assurer que seuls mes amis pouvaient y accéder. Il s'avère que j'ai un ami qui possède et maintient son propre fournisseur d'identité pour accéder à ses propres applications. En l'utilisant, j'ai non seulement pu garantir que seuls mes amis pouvaient y accéder, mais j'ai travaillé sur un cas d'utilisation réel avec un ami très proche. C'est tellement amusant de travailler avec des amis sur un projet.\n\nC'était un projet rapide qui m'a donné l'occasion d'utiliser [Typesense](https:\u002F\u002Ftypesense.org\u002F) pour la recherche. Au fur et à mesure que vous tapez, une requête est envoyée au serveur, et les résultats s'affichent en moins de 100 ms. L'effet \"Wahoo\" est garanti.\n\nQuelques jours plus tard, en repensant à certaines discussions que j'avais eues avec un ami, une autre idée m'est venue à l'esprit.\n\nCuisiner c'est super, mais ensuite, vous devez manger le gâteau. Chaque week-end, je fais un gâteau et pour être honnête, manger un gâteau entier tout seul prend plusieurs jours et ensuite, un autre gâteau est prêt. Au début, c'était amusant, mais maintenant, ça en devient trop.\n\nLors de cette conversation que j'ai eue avec elle, elle m'a demandé pourquoi je ne le partageais pas avec mes collègues. J'ai égoïstement répondu. Mais après tout, elle avait raison. Les pâtisseries peuvent être utilisées pour créer des moments, de la même manière que j'utilise le code pour créer des moments. Bien sûr, ils sont différents, mais ce sont tous des moments, et à la fin, vous les partagez avec des personnes que vous appréciez. _All the environment can be used to think out of the box._\n\nJ'ai donc décidé d'étendre intelligemment cette plateforme à mes collègues. Intelligemment parce que je ne peux pas utiliser le même fournisseur d'identité, et je ne peux pas utiliser celui de mon entreprise. De plus, je ne veux pas avoir à gérer mes créations sur deux plateformes différentes, mais je veux garder les deux plateformes séparées. Différentes bases de code avec des données partagées. _Si la solution technique vous intéresse, merci de me le faire savoir dans les commentaires._\n\nMoment parfait. J'avais 15 jours de vacances. J'ai donc dû construire cela en 15 jours, et le livrer à la fin, prêt ou non. J'ai partagé un compte à rebours avec mon équipe, juste avant de partir, pour le rendre officiel et public. Le compte à rebours était réglé pour s'arrêter le 11 août 2025 à 9h00 (UTC+2).\n\nJe devais itérer sur l'expérience que je voulais livrer plusieurs fois pour m'assurer qu'elle soit à la fois utile, utilisable et agréable. J'ai arrêté ma réflexion sur les points suivants :\n\n- Permettre aux utilisateurs de voir mes créations,\n- Permettre aux utilisateurs de suggérer de nouvelles recettes, basées sur de nouvelles idées ou des recettes existantes,\n- Permettre aux utilisateurs de donner leur avis sur mes créations,\n- Permettre aux utilisateurs de savoir quand je vais amener le prochain gâteau au bureau.\n\nSeulement 15 jours pour le construire. C'était un défi parce que je devais équilibrer le temps entre la construction de la plateforme et profiter de mes vacances. Ce n'était pas facile, le sommeil était court, mais rappelez-vous : \"Try hard for a while. You might be surprised by what happens.\"\n\nJ'ai divisé ma journée en une (petite) session de code, 40 km de vélo, et un cours de wing foil ou de catamaran. J'ai répété ce cycle tous les jours. Parfois, un lever de soleil, parfois la découverte d'une île.\n\nLe dernier jour, après 32 heures de réflexion et d'écriture du code, tout était prêt à être livré.\n\nJ'écris ces lignes le dimanche 10 août, la veille du lancement. Tout est prêt à être mis en ligne demain matin pour mes collègues, comme prévu. Je ne peux pas dire si cela fonctionnera, je ne sais pas s'ils aimeront ça, mais j'y ai mis tout ce que j'avais.\n\n**I try hard and I ship it.**\n\nEt quoi qu'il arrive maintenant, je continuerai à créer des moments pour les personnes qui me tiennent à cœur. Parfois avec un message, parfois avec du code, mais toujours avec passion.\n\n_Trust the process._\n\n---\n\n_Pour Julie et Jordan, sans qui ce projet n'aurait jamais vu le jour. We shipped it!_\n",{"title":17530,"description":18186},{"loc":18188},"fr\u002Fposts\u002Fyou-dont-need-to-be-chosen-you-can-just-do-things","Km7fH5GnMcZJTikghGgOl_zFhjL7NXRmgYU8Tmgs5Jk",{"id":18195,"title":18196,"body":18197,"contentId":9310,"date":18593,"description":18594,"extension":18,"meta":18595,"navigation":20,"path":18596,"rawbody":18597,"robots":23,"schemaOrg":23,"seo":18598,"sitemap":18599,"stem":18605,"__hash__":18606},"posts\u002Ffr\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker.md","BreizhCamp 2025 : ma première conférence en Bretagne",{"type":7,"value":18198,"toc":18587},[18199,18208,18216,18219,18224,18230,18233,18236,18239,18446],[35,18200,18201,18202,18207],{},"Cette année, j'ai eu le plaisir de me rendre à ",[39,18203,18206],{"href":18204,"rel":18205},"https:\u002F\u002Fwww.breizhcamp.org\u002F",[77],"BreizhCamp 2025"," en Bretagne. C'était un plaisir de présenter en dehors de Paris, pour y découvrir un nouveau public.",[35,18209,18210,18211,13741,18213,18215],{},"BreizhCamp est une conférence plus petite comparée à ",[39,18212,7154],{"href":8986},[39,18214,7497],{"href":7500},", attirant environ 700 participants par jour, avec cinq pistes de conférences en parallèle et 96 intervenants.",[35,18217,18218],{},"Personnellement, je préfère les conférences de petite envergure. Elles offrent plus d'opportunité pour échanger avec les autres participants.",[35,18220,18221],{},[1216,18222,18223],{},"Si quelqu'un de l'équipe de BreizhCamp lit ceci, bravo pour l'organisation ! 🫶",[35,18225,18226],{},[1707,18227],{"alt":18228,"src":18229},"Le lieu pour le petit déjeuner, les pauses café, et les repas","\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fbreizhcamp-2025.jpeg",[51,18231,18206],{"id":18232},"breizhcamp-2025",[35,18234,18235],{},"Si vous avez suivi mon blog, vous savez que je ne consulte jamais le programme avant le matin de la conférence, juste quelques minutes avant qu'elle ne commence. Cette fois n'a pas fait exception. Vous pourriez demander pourquoi, et la raison est simple : cela minimise la charge mentale. Avoir juste quelques minutes pour choisir force une action décisive sans trop réfléchir. Parfait pour moi !",[35,18237,18238],{},"Voici les conférences auxquelles j'ai assisté :",[1593,18240,18241,18302,18394],{},[71,18242,18243,2186,18246],{},[6862,18244,18245],{},"Mercredi 25 juin 2025",[1593,18247,18248,18263,18267,18282],{},[71,18249,18250,6865,18253,1655,18258],{},[6862,18251,18252],{},"Entrez dans le Daggerverse !",[39,18254,18257],{"href":18255,"rel":18256},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-haumont-834a5427\u002F",[77],"Julien Haumont",[39,18259,18262],{"href":18260,"rel":18261},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femeric-martineau\u002F",[77],"Emeric Martineau",[71,18264,18265,13968],{},[6862,18266,9137],{},[71,18268,18269,6865,18272,1655,18277],{},[6862,18270,18271],{},"Redécouvrir la coopération : Atelier de Mob Programming",[39,18273,18276],{"href":18274,"rel":18275},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmanon-carbonnel\u002F",[77],"Manon Carbonnel",[39,18278,18281],{"href":18279,"rel":18280},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthomas-ployon-8bb9a2176\u002F",[77],"Thomas Ployon",[71,18283,18284,6865,18287,3092,18292,1655,18297],{},[6862,18285,18286],{},"Démystifions nos clean architectures",[39,18288,18291],{"href":18289,"rel":18290},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fyann-schepens-a279871a\u002F",[77],"Yann Schepens",[39,18293,18296],{"href":18294,"rel":18295},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-giraud-17a44383\u002F",[77],"Nicolas Giraud",[39,18298,18301],{"href":18299,"rel":18300},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftblaisot\u002F",[77],"Thomas Blaisot",[71,18303,18304,2186,18307],{},[6862,18305,18306],{},"Jeudi 26 juin 2025",[1593,18308,18309,18317,18327,18340,18344,18354,18364,18374,18384],{},[71,18310,18311,6865,18314],{},[6862,18312,18313],{},"Comment nous avons transformé les Restos du Coeur en Cloud Provider",[39,18315,6955],{"href":6953,"rel":18316},[77],[71,18318,18319,6865,18322],{},[6862,18320,18321],{},"HTTP\u002F3 : 36 ans d'histoire du protocole HTTP",[39,18323,18326],{"href":18324,"rel":18325},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fchrisnavas\u002F",[77],"Chris Navas",[71,18328,18329,6865,18332,1655,18335],{},[6862,18330,18331],{},"“Make the change easy” : Apprivoiser le legacy avec les Object Calisthenics",[39,18333,18276],{"href":18274,"rel":18334},[77],[39,18336,18339],{"href":18337,"rel":18338},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgael-salaun\u002F",[77],"Gaël Salaün",[71,18341,18342,13988],{},[6862,18343,13967],{},[71,18345,18346,6865,18349],{},[6862,18347,18348],{},"Amusons-nous avec jj, une alternative à git !",[39,18350,18353],{"href":18351,"rel":18352},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fharold-favereau-32809a255\u002F",[77],"Harold Favereau",[71,18355,18356,6865,18359],{},[6862,18357,18358],{},"Savez-vous vraiment ce qu'est la couleur ? Découvrez la science derrière les pixels 🎨",[39,18360,18363],{"href":18361,"rel":18362},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-sulpis\u002F",[77],"Julien Sulpis",[71,18365,18366,6865,18369],{},[6862,18367,18368],{},"🧑‍🎤🎸 La preuve de programme vous fera apprécier les tests",[39,18370,18373],{"href":18371,"rel":18372},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpunkstarman\u002F",[77],"William Bartlett",[71,18375,18376,6865,18379],{},[6862,18377,18378],{},"Apprend les langues à ton IDE avec LSP",[39,18380,18383],{"href":18381,"rel":18382},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmaxime-reynier\u002F",[77],"Maxime Reynier",[71,18385,18386,6865,18389],{},[6862,18387,18388],{},"Gaming et Industrie : La Double Vie d'Unreal Engine",[39,18390,18393],{"href":18391,"rel":18392},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fenora-landurein-b51368172\u002F",[77],"Enora Landurein",[71,18395,18396,2186,18399],{},[6862,18397,18398],{},"Vendredi 27 juin 2025",[1593,18400,18401,18411,18421,18431],{},[71,18402,18403,6865,18406],{},[6862,18404,18405],{},"Rendre son back générique grâce au pattern composite",[39,18407,18410],{"href":18408,"rel":18409},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-baron-44b256156\u002F",[77],"Alexandre Baron",[71,18412,18413,6865,18416],{},[6862,18414,18415],{},"Modular monolith : la dernière croisade des architectures ?",[39,18417,18420],{"href":18418,"rel":18419},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpiscitellit\u002F",[77],"Thomas Piscitelli",[71,18422,18423,6865,18426],{},[6862,18424,18425],{},"Comment sont implémentés les containers sous Linux ?",[39,18427,18430],{"href":18428,"rel":18429},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fchristophe-quintard\u002F",[77],"Christophe Quintard",[71,18432,18433,6865,18436,1655,18441],{},[6862,18434,18435],{},"Le pattern Hive : une stratégie de modularisation pour votre monolithe modulaire ou vos microservices",[39,18437,18440],{"href":18438,"rel":18439},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-top%C3%A7u\u002F",[77],"Julien Topçu",[39,18442,18445],{"href":18443,"rel":18444},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthomas-p-0664769\u002F",[77],"Thomas Pierrain",[7151,18447,18449,18452,18456,18459,18462,18511,18514,18518,18521,18531,18541,18547,18553,18557,18560,18565,18568,18571,18574,18582],{"icon":7153,"text":9020,"to":18448,"target":7156},"https:\u002F\u002Fwww.youtube.com\u002F@BreizhCamp",[35,18450,18451],{},"Ceci est la liste complète des conférences auxquelles j'ai assisté. Je recommande vivement de consulter la chaîne YouTube de la conférence pour toutes les présentations disponibles.",[51,18453,18455],{"id":18454},"points-forts-des-présentations","Points forts des présentations",[35,18457,18458],{},"J'adore mettre en avant les présentations qui m'ont vraiment marquée, et ce n'est pas ce qui a manqué lors du BreizhCamp 2025, avec une programmation à la fois inspirante et technique.",[35,18460,18461],{},"J'en ai retenu deux qui m'ont particulièrement impressionné :",[1593,18463,18464,18492],{},[71,18465,18466,6865,18468,18471,18473,18474,18479,18480,18485,18486,18488],{},[6862,18467,18313],{},[39,18469,6955],{"href":6953,"rel":18470},[77],[7174,18472],{},"Ce n'était pas ma première fois d'entendre Julien parler du ",[39,18475,18478],{"href":18476,"rel":18477},"https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fcloud-du-coeur\u002Fposts",[77],"Cloud du Coeur",", et c'est toujours un plaisir. Il parvient à livrer des conférences à la fois divertissantes, informatives et techniques. Le travail qu'il réalise pour les ",[39,18481,18484],{"href":18482,"rel":18483},"https:\u002F\u002Fwww.restosducoeur.org\u002F",[77],"Restos du Coeur"," est significatif et impactant. Je vous recommande fortement de visionner sa présentation car elle inspire et illustre comment la technologie peut être utile pour les autres.",[7174,18487],{},[1707,18489],{"alt":18490,"src":18491},"Julien Briault présentant à BreizhCamp 2025","\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fcomment-nous-avons-transforme-les-restos-du-coeur-en-cloud-provider.jpeg",[71,18493,18494,6865,18496,1655,18499,18502,18504,18505,18507],{},[6862,18495,18331],{},[39,18497,18276],{"href":18274,"rel":18498},[77],[39,18500,18339],{"href":18337,"rel":18501},[77],[7174,18503],{},"Cette présentation était un parfait mélange d'insights techniques, de conseils pratiques et d'humour. Les exemples étaient parfaitement bien choisis : assez simples pour être compris, mais suffisamment réalistes pour s'encrer dans le monde réel. Avec cette conférence, j'ai fait une découverte que j'ai l'intention d'appliquer dans mon travail. Les 45 minutes ont filé à une vitesse folle, si bien que la conférence aurait pu durer plus longtemps.",[7174,18506],{},[1707,18508],{"alt":18509,"src":18510},"Manon Carbonnel et Gaël Salaün présentant à BreizhCamp 2025","\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fmake-the-change-easy-apprivoiser-le-legacy-avec-les-object-calisthenics.jpeg",[35,18512,18513],{},"Je recommande vivement de visionner ces présentations. Profitez-en !",[51,18515,18517],{"id":18516},"mes-présentations","Mes présentations",[35,18519,18520],{},"À BreizhCamp 2025, j'étais ravi de présenter deux conférences :",[68,18522,18523,18527],{},[71,18524,18525],{},[6862,18526,9137],{},[71,18528,18529],{},[6862,18530,13967],{},[35,18532,18533,18534,1655,18537,18540],{},"Après les avoir présentées à ",[39,18535,18536],{"href":8986},"Devoxx France 2025",[39,18538,18539],{"href":7500},"MiXiT 2025",", il est appréciable de les présenter à nouveau. Je suis fier de ces présentations, et les présenter est toujours un plaisir. En plus, et maintenant que j'y suis familier, je peux vraiment savourer le moment.",[35,18542,18543,18544],{},"Pour ajouter un peu de piment, je me suis lancé un défi : j'ai pris le train pour Rennes mercredi matin à 6h30, avec ma présentation prévue à 11h00. La veille, j'avais assisté à un meetup de Takima. Du coup, je n'ai dormi que trois heures. J'ai donné le meilleur de moi-même lors de la présentation, mais la session de l'après-midi était difficile. ",[1216,18545,18546],{},"N'essayez pas cela, c'est mémorable, mais pas agréable ni même conseillé !",[35,18548,18549],{},[1707,18550],{"alt":18551,"src":18552},"Moi, présentant à BreizhCamp 2025","\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend.jpeg",[51,18554,18556],{"id":18555},"la-fin-dune-saison","La fin d'une saison",[35,18558,18559],{},"Il y a un dicton :",[938,18561,18562],{},[35,18563,18564],{},"La Bretagne, ça vous gagne !",[35,18566,18567],{},"Clairement, BreizhCamp 2025 m'a conquis. J'ai vraiment apprécié la conférence, rencontrer de nouvelles personnes, et l'ambiance générale. Je reviendrai l'année prochaine et j'espère vous y voir !",[35,18569,18570],{},"En parlant de l'année prochaine, c'était la dernière conférence de la saison 2024\u002F2025 pour moi. C'était une saison incroyable, avec 13 conférences sur 10 mois. Énorme !",[35,18572,18573],{},"La nouvelle saison commence en septembre 2025, et des projets se dessinent déjà. Je peux confirmer une intervention lors d'un meetup à venir à Paris, une conférence à Angers, et quelques nouvelles présentations à annoncer dans les mois à venir.",[35,18575,18576,18577,215],{},"Je continuerai à faire des streams sur Twitch, avec des sessions de code, de création de diapositives et des surprises. Restez à l'écoute et suivez-moi sur ",[39,18578,18581],{"href":18579,"rel":18580},"https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes",[77],"Twitch",[35,18583,18584],{},[1216,18585,18586],{},"La fin d'une chose est le début d'une autre. Profitons du voyage !",{"title":13,"searchDepth":14,"depth":14,"links":18588},[18589,18590,18591,18592],{"id":18232,"depth":14,"text":18206},{"id":18454,"depth":14,"text":18455},{"id":18516,"depth":14,"text":18517},{"id":18555,"depth":14,"text":18556},"2025-06-29","Toujours un plaisir de présenter en dehors de Paris, BreizhCamp 2025 a été une excellente occasion de rencontrer un nouveau public et de réseauter.",{},"\u002Ffr\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker","---\ncontentId: 6f54e585-a2ec-4508-a863-5a0726f23744\ntitle: \"BreizhCamp 2025 : ma première conférence en Bretagne\"\ndescription: \"Toujours un plaisir de présenter en dehors de Paris, BreizhCamp 2025 a été une excellente occasion de rencontrer un nouveau public et de réseauter.\"\ndate: 2025-06-29\n---\n\nCette année, j'ai eu le plaisir de me rendre à [BreizhCamp 2025](https:\u002F\u002Fwww.breizhcamp.org\u002F) en Bretagne. C'était un plaisir de présenter en dehors de Paris, pour y découvrir un nouveau public.\n\nBreizhCamp est une conférence plus petite comparée à [Devoxx France](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md) ou [MiXiT](.\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md), attirant environ 700 participants par jour, avec cinq pistes de conférences en parallèle et 96 intervenants.\n\nPersonnellement, je préfère les conférences de petite envergure. Elles offrent plus d'opportunité pour échanger avec les autres participants.\n\n_Si quelqu'un de l'équipe de BreizhCamp lit ceci, bravo pour l'organisation ! 🫶_\n\n![Le lieu pour le petit déjeuner, les pauses café, et les repas](\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fbreizhcamp-2025.jpeg)\n\n## BreizhCamp 2025\n\nSi vous avez suivi mon blog, vous savez que je ne consulte jamais le programme avant le matin de la conférence, juste quelques minutes avant qu'elle ne commence. Cette fois n'a pas fait exception. Vous pourriez demander pourquoi, et la raison est simple : cela minimise la charge mentale. Avoir juste quelques minutes pour choisir force une action décisive sans trop réfléchir. Parfait pour moi !\n\nVoici les conférences auxquelles j'ai assisté :\n\n- **Mercredi 25 juin 2025** :\n  - **Entrez dans le Daggerverse !** par [Julien Haumont](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-haumont-834a5427\u002F) et [Emeric Martineau](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femeric-martineau\u002F)\n  - **La réactivité et les signaux : démystifions la magie du frontend** par Estéban Soubiran (c'est moi !)\n  - **Redécouvrir la coopération : Atelier de Mob Programming** par [Manon Carbonnel](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmanon-carbonnel\u002F) et [Thomas Ployon](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthomas-ployon-8bb9a2176\u002F)\n  - **Démystifions nos clean architectures** par [Yann Schepens](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fyann-schepens-a279871a\u002F), [Nicolas Giraud](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolas-giraud-17a44383\u002F) et [Thomas Blaisot](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftblaisot\u002F)\n\n- **Jeudi 26 juin 2025** :\n  - **Comment nous avons transformé les Restos du Coeur en Cloud Provider** par [Julien Briault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F)\n  - **HTTP\u002F3 : 36 ans d'histoire du protocole HTTP** par [Chris Navas](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fchrisnavas\u002F)\n  - **“Make the change easy” : Apprivoiser le legacy avec les Object Calisthenics** par [Manon Carbonnel](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmanon-carbonnel\u002F) et [Gaël Salaün](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgael-salaun\u002F)\n  - **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** par Estéban Soubiran (c'est encore moi !)\n  - **Amusons-nous avec jj, une alternative à git !** par [Harold Favereau](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fharold-favereau-32809a255\u002F)\n  - **Savez-vous vraiment ce qu'est la couleur ? Découvrez la science derrière les pixels 🎨** par [Julien Sulpis](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-sulpis\u002F)\n  - **🧑‍🎤🎸 La preuve de programme vous fera apprécier les tests** par [William Bartlett](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpunkstarman\u002F)\n  - **Apprend les langues à ton IDE avec LSP** par [Maxime Reynier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmaxime-reynier\u002F)\n  - **Gaming et Industrie : La Double Vie d'Unreal Engine** par [Enora Landurein](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fenora-landurein-b51368172\u002F)\n\n- **Vendredi 27 juin 2025** :\n  - **Rendre son back générique grâce au pattern composite** par [Alexandre Baron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Falexandre-baron-44b256156\u002F)\n  - **Modular monolith : la dernière croisade des architectures ?** par [Thomas Piscitelli](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpiscitellit\u002F)\n  - **Comment sont implémentés les containers sous Linux ?** par [Christophe Quintard](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fchristophe-quintard\u002F)\n  - **Le pattern Hive : une stratégie de modularisation pour votre monolithe modulaire ou vos microservices** par [Julien Topçu](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-topçu\u002F) et [Thomas Pierrain](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthomas-p-0664769\u002F)\n\n\u003CCallout icon=\"i-simple-icons-youtube\" text=\"BreizhCamp\" to=\"https:\u002F\u002Fwww.youtube.com\u002F@BreizhCamp\" target=\"_blank\" \u002F>\n\nCeci est la liste complète des conférences auxquelles j'ai assisté. Je recommande vivement de consulter la chaîne YouTube de la conférence pour toutes les présentations disponibles.\n\n## Points forts des présentations\n\nJ'adore mettre en avant les présentations qui m'ont vraiment marquée, et ce n'est pas ce qui a manqué lors du BreizhCamp 2025, avec une programmation à la fois inspirante et technique.\n\nJ'en ai retenu deux qui m'ont particulièrement impressionné :\n\n- **Comment nous avons transformé les Restos du Coeur en Cloud Provider** par [Julien Briault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F)\n\n  Ce n'était pas ma première fois d'entendre Julien parler du [Cloud du Coeur](https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fcloud-du-coeur\u002Fposts), et c'est toujours un plaisir. Il parvient à livrer des conférences à la fois divertissantes, informatives et techniques. Le travail qu'il réalise pour les [Restos du Coeur](https:\u002F\u002Fwww.restosducoeur.org\u002F) est significatif et impactant. Je vous recommande fortement de visionner sa présentation car elle inspire et illustre comment la technologie peut être utile pour les autres.\n\n  ![Julien Briault présentant à BreizhCamp 2025](\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fcomment-nous-avons-transforme-les-restos-du-coeur-en-cloud-provider.jpeg)\n\n- **“Make the change easy” : Apprivoiser le legacy avec les Object Calisthenics** par [Manon Carbonnel](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmanon-carbonnel\u002F) et [Gaël Salaün](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgael-salaun\u002F)\n\n  Cette présentation était un parfait mélange d'insights techniques, de conseils pratiques et d'humour. Les exemples étaient parfaitement bien choisis : assez simples pour être compris, mais suffisamment réalistes pour s'encrer dans le monde réel. Avec cette conférence, j'ai fait une découverte que j'ai l'intention d'appliquer dans mon travail. Les 45 minutes ont filé à une vitesse folle, si bien que la conférence aurait pu durer plus longtemps.\n\n  ![Manon Carbonnel et Gaël Salaün présentant à BreizhCamp 2025](\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fmake-the-change-easy-apprivoiser-le-legacy-avec-les-object-calisthenics.jpeg)\n\nJe recommande vivement de visionner ces présentations. Profitez-en !\n\n## Mes présentations\n\nÀ BreizhCamp 2025, j'étais ravi de présenter deux conférences :\n\n1. **La réactivité et les signaux : démystifions la magie du frontend**\n2. **En JavaScript, il y a un paquet pour tout... et souvent pour rien.**\n\nAprès les avoir présentées à [Devoxx France 2025](.\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md) et [MiXiT 2025](.\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md), il est appréciable de les présenter à nouveau. Je suis fier de ces présentations, et les présenter est toujours un plaisir. En plus, et maintenant que j'y suis familier, je peux vraiment savourer le moment.\n\nPour ajouter un peu de piment, je me suis lancé un défi : j'ai pris le train pour Rennes mercredi matin à 6h30, avec ma présentation prévue à 11h00. La veille, j'avais assisté à un meetup de Takima. Du coup, je n'ai dormi que trois heures. J'ai donné le meilleur de moi-même lors de la présentation, mais la session de l'après-midi était difficile. _N'essayez pas cela, c'est mémorable, mais pas agréable ni même conseillé !_\n\n![Moi, présentant à BreizhCamp 2025](\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend.jpeg)\n\n## La fin d'une saison\n\nIl y a un dicton :\n\n> La Bretagne, ça vous gagne !\n\nClairement, BreizhCamp 2025 m'a conquis. J'ai vraiment apprécié la conférence, rencontrer de nouvelles personnes, et l'ambiance générale. Je reviendrai l'année prochaine et j'espère vous y voir !\n\nEn parlant de l'année prochaine, c'était la dernière conférence de la saison 2024\u002F2025 pour moi. C'était une saison incroyable, avec 13 conférences sur 10 mois. Énorme !\n\nLa nouvelle saison commence en septembre 2025, et des projets se dessinent déjà. Je peux confirmer une intervention lors d'un meetup à venir à Paris, une conférence à Angers, et quelques nouvelles présentations à annoncer dans les mois à venir.\n\nJe continuerai à faire des streams sur Twitch, avec des sessions de code, de création de diapositives et des surprises. Restez à l'écoute et suivez-moi sur [Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes).\n\n_La fin d'une chose est le début d'une autre. Profitons du voyage !_\n",{"title":18196,"description":18594},{"loc":18596,"images":18600},[18601,18602,18603,18604],{"loc":18229},{"loc":18491},{"loc":18510},{"loc":18552},"fr\u002Fposts\u002Fbreizhcamp-2025-my-first-time-in-brittany-as-a-speaker","Gmu5600aT78zf5p18xvMiRSqxeskkmaSxAREmkerPjw",{"id":18608,"title":18609,"body":18610,"contentId":9280,"date":19381,"description":19382,"extension":18,"meta":19383,"navigation":20,"path":19384,"rawbody":19385,"robots":23,"schemaOrg":23,"seo":19386,"sitemap":19387,"stem":19388,"__hash__":19389},"posts\u002Ffr\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux.md","Pinia Colada, les modales et l'expérience utilisateur",{"type":7,"value":18611,"toc":19375},[18612,18623,18638,18644,18648,18651,18661,18664,18667,18673,18676,18682,18913,18919,18941,18947,19019,19028,19038,19042,19045,19059,19065,19069,19083,19088,19091,19094,19324,19336,19346,19350,19353,19361,19367,19372],[35,18613,18614,18615,18622],{},"Depuis plusieurs mois, j'utilise ",[39,18616,18619],{"href":18617,"rel":18618},"https:\u002F\u002Fpinia-colada.esm.dev\u002F",[77],[6862,18620,18621],{},"Pinia Colada",". Je l'ai découvert en recherchant une solution élégante pour récupérer des données sur mon site personnel (celui que vous lisez actuellement). Depuis, je l'ai utilisé dans tous mes projets qui nécessitent des données d'une API. C'est facile à utiliser, ça fonctionne parfaitement et cela me permet de créer de meilleures expériences grâce à sa mise en cache intégrée, sa fonctionnalité de revalidation des données en arrière-plan, et la facilité de mettre en œuvre des optimistic updates.",[35,18624,18625,18627,18628,18633,18634,18637],{},[6862,18626,18621],{}," est une couche de récupération de données pour ",[39,18629,18631],{"href":16702,"rel":18630},[77],[6862,18632,16704],{},", le store pour ",[6862,18635,18636],{},"Vue.js",", rendant la gestion d'état asynchrone d'une simplicité enfantine. Vous devriez vraiment l'essayer. Je ne peux plus commencer un nouveau projet sans lui désormais.",[35,18639,18640,18641,18643],{},"Cependant, j'ai récemment rencontré une difficulté en utilisant ",[6862,18642,18621],{}," dans une modale. Laissez-moi vous expliquer le problème et comment je l'ai résolu.",[51,18645,18647],{"id":18646},"le-problème","Le Problème",[35,18649,18650],{},"Avant de me plonger dans l'explication, regardez la vidéo suivante :",[130,18652,132,18653,132,18658],{},[134,18654,136,18655,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,18656],{"src":18657,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux\u002Fthe-problem.mp4",[143,18659,18660],{},"La modale se ferme avant que les données ne soient mises à jour.",[35,18662,18663],{},"Vous voyez le problème ?",[35,18665,18666],{},"Lorsque je soumets le formulaire dans la modale, le bouton \"Soumettre\" est désactivé et un indicateur de chargement apparaît. Jusqu'ici, tout va bien. Mais ensuite, la modale se ferme avant que les données ne soient mises à jour sur la page. L'utilisateur doit attendre, mais aucun retour ne s'affiche, ce qui nuit à l'expérience utilisateur. L'utilisateur ne sait pas si l'action a été réussie.",[35,18668,18669,18670],{},"Vous pouvez trouver le code de cette vidéo sur ",[1599,18671,215],{"repo":18672},"barbapapazes\u002Fpinia-colada-await-invalidate-queries",[35,18674,18675],{},"Plongeons dans les détails techniques du problème et ses causes.",[35,18677,18678,18679,18681],{},"Le code ",[6862,18680,18621],{}," que j'ai écrit pour muter les données est le suivant :",[192,18683,18685],{"className":1380,"code":18684,"language":1383,"meta":13,"style":13},"const open = ref(false)\nconst { mutate: createComment, isLoading: isCreatingComment } = useMutation({\n  mutation: () => $fetch('\u002Fapi\u002Fcomments', {\n    method: 'POST',\n    body: comment.value\n  }),\n  onSettled: () => {\n    queryCache.invalidateQueries({ key: ['comments'] })\n  },\n  onSuccess: () => {\n    comment.value.text = ''\n    open.value = false\n  }\n})\n",[156,18686,18687,18706,18741,18768,18784,18798,18806,18819,18854,18858,18871,18889,18903,18907],{"__ignoreMap":13},[200,18688,18689,18691,18694,18696,18699,18701,18704],{"class":202,"line":203},[200,18690,443],{"class":379},[200,18692,18693],{"class":210}," open ",[200,18695,449],{"class":214},[200,18697,18698],{"class":223}," ref",[200,18700,227],{"class":210},[200,18702,18703],{"class":2438},"false",[200,18705,416],{"class":210},[200,18707,18708,18710,18712,18715,18717,18720,18722,18725,18727,18730,18732,18734,18737,18739],{"class":202,"line":14},[200,18709,443],{"class":379},[200,18711,318],{"class":214},[200,18713,18714],{"class":235}," mutate",[200,18716,239],{"class":214},[200,18718,18719],{"class":210}," createComment",[200,18721,549],{"class":214},[200,18723,18724],{"class":235}," isLoading",[200,18726,239],{"class":214},[200,18728,18729],{"class":210}," isCreatingComment ",[200,18731,413],{"class":214},[200,18733,509],{"class":214},[200,18735,18736],{"class":223}," useMutation",[200,18738,227],{"class":210},[200,18740,230],{"class":214},[200,18742,18743,18746,18748,18750,18752,18755,18757,18759,18762,18764,18766],{"class":202,"line":255},[200,18744,18745],{"class":223},"  mutation",[200,18747,239],{"class":214},[200,18749,3729],{"class":214},[200,18751,393],{"class":379},[200,18753,18754],{"class":223}," $fetch",[200,18756,227],{"class":210},[200,18758,249],{"class":214},[200,18760,18761],{"class":245},"\u002Fapi\u002Fcomments",[200,18763,249],{"class":214},[200,18765,549],{"class":214},[200,18767,280],{"class":214},[200,18769,18770,18773,18775,18777,18780,18782],{"class":202,"line":272},[200,18771,18772],{"class":235},"    method",[200,18774,239],{"class":214},[200,18776,242],{"class":214},[200,18778,18779],{"class":245},"POST",[200,18781,249],{"class":214},[200,18783,252],{"class":214},[200,18785,18786,18789,18791,18794,18796],{"class":202,"line":283},[200,18787,18788],{"class":235},"    body",[200,18790,239],{"class":214},[200,18792,18793],{"class":210}," comment",[200,18795,215],{"class":214},[200,18797,15979],{"class":210},[200,18799,18800,18802,18804],{"class":202,"line":300},[200,18801,5095],{"class":214},[200,18803,2104],{"class":210},[200,18805,252],{"class":214},[200,18807,18808,18811,18813,18815,18817],{"class":202,"line":310},[200,18809,18810],{"class":223},"  onSettled",[200,18812,239],{"class":214},[200,18814,3729],{"class":214},[200,18816,393],{"class":379},[200,18818,280],{"class":214},[200,18820,18821,18824,18826,18829,18831,18833,18836,18838,18840,18842,18845,18847,18850,18852],{"class":202,"line":336},[200,18822,18823],{"class":210},"    queryCache",[200,18825,215],{"class":214},[200,18827,18828],{"class":223},"invalidateQueries",[200,18830,227],{"class":235},[200,18832,2767],{"class":214},[200,18834,18835],{"class":235}," key",[200,18837,239],{"class":214},[200,18839,350],{"class":235},[200,18841,249],{"class":214},[200,18843,18844],{"class":245},"comments",[200,18846,249],{"class":214},[200,18848,18849],{"class":235},"] ",[200,18851,413],{"class":214},[200,18853,416],{"class":235},[200,18855,18856],{"class":202,"line":342},[200,18857,368],{"class":214},[200,18859,18860,18863,18865,18867,18869],{"class":202,"line":365},[200,18861,18862],{"class":223},"  onSuccess",[200,18864,239],{"class":214},[200,18866,3729],{"class":214},[200,18868,393],{"class":379},[200,18870,280],{"class":214},[200,18872,18873,18876,18878,18880,18882,18884,18886],{"class":202,"line":371},[200,18874,18875],{"class":210},"    comment",[200,18877,215],{"class":214},[200,18879,15840],{"class":210},[200,18881,215],{"class":214},[200,18883,697],{"class":210},[200,18885,509],{"class":214},[200,18887,18888],{"class":214}," ''\n",[200,18890,18891,18894,18896,18898,18900],{"class":202,"line":398},[200,18892,18893],{"class":210},"    open",[200,18895,215],{"class":214},[200,18897,15840],{"class":210},[200,18899,509],{"class":214},[200,18901,18902],{"class":2438}," false\n",[200,18904,18905],{"class":202,"line":405},[200,18906,4505],{"class":214},[200,18908,18909,18911],{"class":202,"line":410},[200,18910,413],{"class":214},[200,18912,416],{"class":210},[35,18914,10674,18915,18918],{},[156,18916,18917],{},"open"," contrôle la visibilité de la modale.",[35,18920,18921,18922,18925,18926,18929,18930,18933,18934,18937,18938,18940],{},"L'utilisateur ouvre la modale, remplit le formulaire et clique sur le bouton \"Soumettre\". La mutation ",[156,18923,18924],{},"createComment"," est alors appelée, envoyant des données à l'API via la fonction ",[156,18927,18928],{},"mutate",". En cas de succès, la fonction ",[156,18931,18932],{},"onSuccess"," réinitialise le formulaire et ferme la modale. Enfin, la fonction ",[156,18935,18936],{},"onSettled"," invalide la requête ",[156,18939,18844],{}," pour récupérer à nouveau les données de l'API.",[35,18942,18943,18944,18946],{},"La requête ",[6862,18945,18621],{}," ressemble à ceci :",[192,18948,18950],{"className":1380,"code":18949,"language":1383,"meta":13,"style":13},"const { state } = useQuery({\n  key: ['comments'],\n  query: () => $fetch('\u002Fapi\u002Fcomments')\n})\n",[156,18951,18952,18972,18991,19013],{"__ignoreMap":13},[200,18953,18954,18956,18958,18961,18963,18965,18968,18970],{"class":202,"line":203},[200,18955,443],{"class":379},[200,18957,318],{"class":214},[200,18959,18960],{"class":210}," state ",[200,18962,413],{"class":214},[200,18964,509],{"class":214},[200,18966,18967],{"class":223}," useQuery",[200,18969,227],{"class":210},[200,18971,230],{"class":214},[200,18973,18974,18977,18979,18981,18983,18985,18987,18989],{"class":202,"line":14},[200,18975,18976],{"class":235},"  key",[200,18978,239],{"class":214},[200,18980,350],{"class":210},[200,18982,249],{"class":214},[200,18984,18844],{"class":245},[200,18986,249],{"class":214},[200,18988,360],{"class":210},[200,18990,252],{"class":214},[200,18992,18993,18995,18997,18999,19001,19003,19005,19007,19009,19011],{"class":202,"line":255},[200,18994,6719],{"class":223},[200,18996,239],{"class":214},[200,18998,3729],{"class":214},[200,19000,393],{"class":379},[200,19002,18754],{"class":223},[200,19004,227],{"class":210},[200,19006,249],{"class":214},[200,19008,18761],{"class":245},[200,19010,249],{"class":214},[200,19012,416],{"class":210},[200,19014,19015,19017],{"class":202,"line":272},[200,19016,413],{"class":214},[200,19018,416],{"class":210},[35,19020,19021,19022,19024,19025,19027],{},"Le problème est que ",[156,19023,18936],{},", qui invalide la requête et récupère à nouveau les données, est appelé après ",[156,19026,18932],{},", ce qui ferme la modale. Par conséquent, l'utilisateur voit la modale se fermer avant que les données n'apparaissent sur la page, ce qui crée de la confusion. \"Où est mon commentaire ? Ça a fonctionné ?\"",[35,19029,19030,19031,19034,19035,19037],{},"Serait-il possible de déplacer ",[156,19032,19033],{},"open.value = false"," vers ",[156,19036,18936],{},", après l'invalidation de la requête ? Malheureusement, non.",[51,19039,19041],{"id":19040},"solutions-multiples","Solutions Multiples",[35,19043,19044],{},"Pour résoudre le problème, deux solutions me sont venues à l'esprit :",[1593,19046,19047,19053],{},[71,19048,19049,19052],{},[6862,19050,19051],{},"Mises à Jour Optimistes"," : C'est la meilleure solution pour l'expérience utilisateur, mettant immédiatement à jour l'interface, mais cela nécessite un code plus complexe. Ce n'est pas toujours faisable, surtout si l'API renvoie des données différentes de celles qui ont été envoyées. Par exemple, avec un système de commentaire permettant le markdown, l'API renvoie le markdown formaté, mais l'utilisateur envoie du markdown brut. Dans de tels cas, les mises à jour optimistes ne sont pas viables, et un état de chargement est la seule solution.",[71,19054,19055,19058],{},[6862,19056,19057],{},"État de Chargement"," : C'est la solution la plus simple. Désactiver le bouton \"Soumettre\", afficher un indicateur de chargement et attendre la réponse de l'API. C'est la plus commune et la plus facile à mettre en œuvre. Cependant, dans une modale, cela nécessite des ajustements par rapport à un formulaire mis en ligne dans une page.",[35,19060,19061,19062,19064],{},"Pour mon projet, les mises à jour optimistes n'étaient pas une option, j'ai donc dû utiliser l'état de chargement. Pourtant, lors de sa mise en œuvre, j'ai rencontré ce problème. J'ai beau avoir lu la documentation ",[6862,19063,18621],{}," plusieurs fois, je n'avais pas de solution. À un moment, une idée a fini par germer. J'ai sauté dans la documentation pour voir si ma compréhension pouvait être une solution viable.",[51,19066,19068],{"id":19067},"attendre-les-requêtes-invalidées","Attendre les Requêtes Invalidées",[35,19070,19071,19072,19082],{},"La documentation comprend une section sur ",[39,19073,19076,19077,19079,19080,610],{"href":19074,"rel":19075},"https:\u002F\u002Fpinia-colada.esm.dev\u002Fguide\u002Fquery-invalidation.html#To-await-or-not-to-await",[77],"\"To ",[156,19078,207],{}," or not to ",[156,19081,207],{}," qui indique :",[938,19084,19085],{},[35,19086,19087],{},"In mutations, it's possible to await within the different hooks. This will effectively delay the resolution or rejection of the mutation and its asyncStatus.",[35,19089,19090],{},"C'était la clé. Je n'avais jamais compris le cas d'usage de l'attente des requêtes invalidées, mais c'est devenu clair avec ce problème. En attendant les requêtes invalidées, je pouvais retarder la fermeture de la modale jusqu'à ce que les données soient récupérées et disponibles sur la page.",[35,19092,19093],{},"Voici le code pour mettre en œuvre cette solution :",[192,19095,19097],{"className":1380,"code":19096,"language":1383,"meta":13,"style":13},"const open = ref(false)\nconst { mutate: createComment, isLoading: isCreatingComment } = useMutation({\n  mutation: () => $fetch('\u002Fapi\u002Fcomments', {\n    method: 'POST',\n    body: comment.value\n  }),\n  onSettled: async (_, error) => {\n    await queryCache.invalidateQueries({ key: ['comments'] })\n\n    if (!error) {\n      comment.value.text = ''\n      open.value = false\n    }\n  },\n})\n",[156,19098,19099,19115,19145,19169,19183,19195,19203,19227,19261,19265,19280,19297,19310,19314,19318],{"__ignoreMap":13},[200,19100,19101,19103,19105,19107,19109,19111,19113],{"class":202,"line":203},[200,19102,443],{"class":379},[200,19104,18693],{"class":210},[200,19106,449],{"class":214},[200,19108,18698],{"class":223},[200,19110,227],{"class":210},[200,19112,18703],{"class":2438},[200,19114,416],{"class":210},[200,19116,19117,19119,19121,19123,19125,19127,19129,19131,19133,19135,19137,19139,19141,19143],{"class":202,"line":14},[200,19118,443],{"class":379},[200,19120,318],{"class":214},[200,19122,18714],{"class":235},[200,19124,239],{"class":214},[200,19126,18719],{"class":210},[200,19128,549],{"class":214},[200,19130,18724],{"class":235},[200,19132,239],{"class":214},[200,19134,18729],{"class":210},[200,19136,413],{"class":214},[200,19138,509],{"class":214},[200,19140,18736],{"class":223},[200,19142,227],{"class":210},[200,19144,230],{"class":214},[200,19146,19147,19149,19151,19153,19155,19157,19159,19161,19163,19165,19167],{"class":202,"line":255},[200,19148,18745],{"class":223},[200,19150,239],{"class":214},[200,19152,3729],{"class":214},[200,19154,393],{"class":379},[200,19156,18754],{"class":223},[200,19158,227],{"class":210},[200,19160,249],{"class":214},[200,19162,18761],{"class":245},[200,19164,249],{"class":214},[200,19166,549],{"class":214},[200,19168,280],{"class":214},[200,19170,19171,19173,19175,19177,19179,19181],{"class":202,"line":272},[200,19172,18772],{"class":235},[200,19174,239],{"class":214},[200,19176,242],{"class":214},[200,19178,18779],{"class":245},[200,19180,249],{"class":214},[200,19182,252],{"class":214},[200,19184,19185,19187,19189,19191,19193],{"class":202,"line":283},[200,19186,18788],{"class":235},[200,19188,239],{"class":214},[200,19190,18793],{"class":210},[200,19192,215],{"class":214},[200,19194,15979],{"class":210},[200,19196,19197,19199,19201],{"class":202,"line":300},[200,19198,5095],{"class":214},[200,19200,2104],{"class":210},[200,19202,252],{"class":214},[200,19204,19205,19207,19209,19211,19213,19216,19218,19221,19223,19225],{"class":202,"line":310},[200,19206,18810],{"class":223},[200,19208,239],{"class":214},[200,19210,380],{"class":379},[200,19212,1094],{"class":214},[200,19214,19215],{"class":386},"_",[200,19217,549],{"class":214},[200,19219,19220],{"class":386}," error",[200,19222,2104],{"class":214},[200,19224,393],{"class":379},[200,19226,280],{"class":214},[200,19228,19229,19232,19235,19237,19239,19241,19243,19245,19247,19249,19251,19253,19255,19257,19259],{"class":202,"line":336},[200,19230,19231],{"class":206},"    await",[200,19233,19234],{"class":210}," queryCache",[200,19236,215],{"class":214},[200,19238,18828],{"class":223},[200,19240,227],{"class":235},[200,19242,2767],{"class":214},[200,19244,18835],{"class":235},[200,19246,239],{"class":214},[200,19248,350],{"class":235},[200,19250,249],{"class":214},[200,19252,18844],{"class":245},[200,19254,249],{"class":214},[200,19256,18849],{"class":235},[200,19258,413],{"class":214},[200,19260,416],{"class":235},[200,19262,19263],{"class":202,"line":342},[200,19264,528],{"emptyLinePlaceholder":20},[200,19266,19267,19269,19271,19273,19276,19278],{"class":202,"line":365},[200,19268,5267],{"class":206},[200,19270,1094],{"class":235},[200,19272,11852],{"class":214},[200,19274,19275],{"class":210},"error",[200,19277,5282],{"class":235},[200,19279,230],{"class":214},[200,19281,19282,19285,19287,19289,19291,19293,19295],{"class":202,"line":371},[200,19283,19284],{"class":210},"      comment",[200,19286,215],{"class":214},[200,19288,15840],{"class":210},[200,19290,215],{"class":214},[200,19292,697],{"class":210},[200,19294,509],{"class":214},[200,19296,18888],{"class":214},[200,19298,19299,19302,19304,19306,19308],{"class":202,"line":398},[200,19300,19301],{"class":210},"      open",[200,19303,215],{"class":214},[200,19305,15840],{"class":210},[200,19307,509],{"class":214},[200,19309,18902],{"class":2438},[200,19311,19312],{"class":202,"line":405},[200,19313,4977],{"class":214},[200,19315,19316],{"class":202,"line":410},[200,19317,368],{"class":214},[200,19319,19320,19322],{"class":202,"line":860},[200,19321,413],{"class":214},[200,19323,416],{"class":210},[35,19325,19326,19327,19329,19330,19332,19333,19335],{},"Au lieu d'utiliser ",[156,19328,18932],{}," pour fermer la modale, je l'ai déplacé vers ",[156,19331,18936],{},", ajoutant un ",[156,19334,207],{}," avant l'invalidation du cache. Ainsi, la modale ne se ferme qu'après que les données aient été récupérées à nouveau et affichées sur la page. Simple, élégant et efficace.",[130,19337,132,19338,132,19343],{},[134,19339,136,19340,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,19341],{"src":19342,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux\u002Fthe-solution.mp4",[143,19344,19345],{},"La modale se ferme après que les données soient mises à jour.",[51,19347,19349],{"id":19348},"enfin","Enfin",[35,19351,19352],{},"Ce petit problème souligne deux points importants :",[1593,19354,19355,19358],{},[71,19356,19357],{},"Chaque détail impacte l'expérience utilisateur. Ces petites nuances peuvent créer une grande différence à la fin.",[71,19359,19360],{},"Lire la documentation, encore et encore, même si cela peut sembler impertinent au départ, peut aider à résoudre des problèmes comme je l'ai fait ici.",[35,19362,19363,19364,19366],{},"J'espère que cet article clarifie l'utilisation de ",[6862,19365,18621],{}," dans des modales sans dégrader l'expérience utilisateur. Si vous avez des questions ou des suggestions, n'hésitez pas à laisser un commentaire ci-dessous.",[35,19368,19369,19370],{},"Pour voir le code en action, vous pouvez le trouver sur ",[1599,19371,215],{"repo":18672},[1170,19373,19374],{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .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 .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}",{"title":13,"searchDepth":14,"depth":14,"links":19376},[19377,19378,19379,19380],{"id":18646,"depth":14,"text":18647},{"id":19040,"depth":14,"text":19041},{"id":19067,"depth":14,"text":19068},{"id":19348,"depth":14,"text":19349},"2025-06-07","Loin d'être évident mais essentiel pour offrir une bonne expérience utilisateur, vous comprendrez quand attendre les requêtes invalidées dans Pinia Colada.",{},"\u002Ffr\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux","---\ncontentId: 11e74f5a-eed4-4419-a931-1c659ebd7ff3\ntitle: Pinia Colada, les modales et l'expérience utilisateur\ndescription: Loin d'être évident mais essentiel pour offrir une bonne expérience utilisateur, vous comprendrez quand attendre les requêtes invalidées dans Pinia Colada.\ndate: 2025-06-07\n---\n\nDepuis plusieurs mois, j'utilise [**Pinia Colada**](https:\u002F\u002Fpinia-colada.esm.dev\u002F). Je l'ai découvert en recherchant une solution élégante pour récupérer des données sur mon site personnel (celui que vous lisez actuellement). Depuis, je l'ai utilisé dans tous mes projets qui nécessitent des données d'une API. C'est facile à utiliser, ça fonctionne parfaitement et cela me permet de créer de meilleures expériences grâce à sa mise en cache intégrée, sa fonctionnalité de revalidation des données en arrière-plan, et la facilité de mettre en œuvre des optimistic updates.\n\n**Pinia Colada** est une couche de récupération de données pour [**Pinia**](https:\u002F\u002Fpinia.vuejs.org\u002F), le store pour **Vue.js**, rendant la gestion d'état asynchrone d'une simplicité enfantine. Vous devriez vraiment l'essayer. Je ne peux plus commencer un nouveau projet sans lui désormais.\n\nCependant, j'ai récemment rencontré une difficulté en utilisant **Pinia Colada** dans une modale. Laissez-moi vous expliquer le problème et comment je l'ai résolu.\n\n## Le Problème\n\nAvant de me plonger dans l'explication, regardez la vidéo suivante :\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux\u002Fthe-problem.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>La modale se ferme avant que les données ne soient mises à jour.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nVous voyez le problème ?\n\nLorsque je soumets le formulaire dans la modale, le bouton \"Soumettre\" est désactivé et un indicateur de chargement apparaît. Jusqu'ici, tout va bien. Mais ensuite, la modale se ferme avant que les données ne soient mises à jour sur la page. L'utilisateur doit attendre, mais aucun retour ne s'affiche, ce qui nuit à l'expérience utilisateur. L'utilisateur ne sait pas si l'action a été réussie.\n\nVous pouvez trouver le code de cette vidéo sur \u003CGitHubLink repo=\"barbapapazes\u002Fpinia-colada-await-invalidate-queries\" \u002F>.\n\nPlongeons dans les détails techniques du problème et ses causes.\n\nLe code **Pinia Colada** que j'ai écrit pour muter les données est le suivant :\n\n```ts\nconst open = ref(false)\nconst { mutate: createComment, isLoading: isCreatingComment } = useMutation({\n  mutation: () => $fetch('\u002Fapi\u002Fcomments', {\n    method: 'POST',\n    body: comment.value\n  }),\n  onSettled: () => {\n    queryCache.invalidateQueries({ key: ['comments'] })\n  },\n  onSuccess: () => {\n    comment.value.text = ''\n    open.value = false\n  }\n})\n```\n\nLa variable `open` contrôle la visibilité de la modale.\n\nL'utilisateur ouvre la modale, remplit le formulaire et clique sur le bouton \"Soumettre\". La mutation `createComment` est alors appelée, envoyant des données à l'API via la fonction `mutate`. En cas de succès, la fonction `onSuccess` réinitialise le formulaire et ferme la modale. Enfin, la fonction `onSettled` invalide la requête `comments` pour récupérer à nouveau les données de l'API.\n\nLa requête **Pinia Colada** ressemble à ceci :\n\n```ts\nconst { state } = useQuery({\n  key: ['comments'],\n  query: () => $fetch('\u002Fapi\u002Fcomments')\n})\n```\n\nLe problème est que `onSettled`, qui invalide la requête et récupère à nouveau les données, est appelé après `onSuccess`, ce qui ferme la modale. Par conséquent, l'utilisateur voit la modale se fermer avant que les données n'apparaissent sur la page, ce qui crée de la confusion. \"Où est mon commentaire ? Ça a fonctionné ?\"\n\nSerait-il possible de déplacer `open.value = false` vers `onSettled`, après l'invalidation de la requête ? Malheureusement, non.\n\n## Solutions Multiples\n\nPour résoudre le problème, deux solutions me sont venues à l'esprit :\n\n- **Mises à Jour Optimistes** : C'est la meilleure solution pour l'expérience utilisateur, mettant immédiatement à jour l'interface, mais cela nécessite un code plus complexe. Ce n'est pas toujours faisable, surtout si l'API renvoie des données différentes de celles qui ont été envoyées. Par exemple, avec un système de commentaire permettant le markdown, l'API renvoie le markdown formaté, mais l'utilisateur envoie du markdown brut. Dans de tels cas, les mises à jour optimistes ne sont pas viables, et un état de chargement est la seule solution.\n- **État de Chargement** : C'est la solution la plus simple. Désactiver le bouton \"Soumettre\", afficher un indicateur de chargement et attendre la réponse de l'API. C'est la plus commune et la plus facile à mettre en œuvre. Cependant, dans une modale, cela nécessite des ajustements par rapport à un formulaire mis en ligne dans une page.\n\nPour mon projet, les mises à jour optimistes n'étaient pas une option, j'ai donc dû utiliser l'état de chargement. Pourtant, lors de sa mise en œuvre, j'ai rencontré ce problème. J'ai beau avoir lu la documentation **Pinia Colada** plusieurs fois, je n'avais pas de solution. À un moment, une idée a fini par germer. J'ai sauté dans la documentation pour voir si ma compréhension pouvait être une solution viable.\n\n## Attendre les Requêtes Invalidées\n\nLa documentation comprend une section sur [\"To `await` or not to `await`\"](https:\u002F\u002Fpinia-colada.esm.dev\u002Fguide\u002Fquery-invalidation.html#To-await-or-not-to-await) qui indique :\n\n> In mutations, it's possible to await within the different hooks. This will effectively delay the resolution or rejection of the mutation and its asyncStatus.\n\nC'était la clé. Je n'avais jamais compris le cas d'usage de l'attente des requêtes invalidées, mais c'est devenu clair avec ce problème. En attendant les requêtes invalidées, je pouvais retarder la fermeture de la modale jusqu'à ce que les données soient récupérées et disponibles sur la page.\n\nVoici le code pour mettre en œuvre cette solution :\n\n```ts\nconst open = ref(false)\nconst { mutate: createComment, isLoading: isCreatingComment } = useMutation({\n  mutation: () => $fetch('\u002Fapi\u002Fcomments', {\n    method: 'POST',\n    body: comment.value\n  }),\n  onSettled: async (_, error) => {\n    await queryCache.invalidateQueries({ key: ['comments'] })\n\n    if (!error) {\n      comment.value.text = ''\n      open.value = false\n    }\n  },\n})\n```\n\nAu lieu d'utiliser `onSuccess` pour fermer la modale, je l'ai déplacé vers `onSettled`, ajoutant un `await` avant l'invalidation du cache. Ainsi, la modale ne se ferme qu'après que les données aient été récupérées à nouveau et affichées sur la page. Simple, élégant et efficace.\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux\u002Fthe-solution.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>La modale se ferme après que les données soient mises à jour.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n## Enfin\n\nCe petit problème souligne deux points importants :\n\n- Chaque détail impacte l'expérience utilisateur. Ces petites nuances peuvent créer une grande différence à la fin.\n- Lire la documentation, encore et encore, même si cela peut sembler impertinent au départ, peut aider à résoudre des problèmes comme je l'ai fait ici.\n\nJ'espère que cet article clarifie l'utilisation de **Pinia Colada** dans des modales sans dégrader l'expérience utilisateur. Si vous avez des questions ou des suggestions, n'hésitez pas à laisser un commentaire ci-dessous.\n\nPour voir le code en action, vous pouvez le trouver sur \u003CGitHubLink repo=\"barbapapazes\u002Fpinia-colada-await-invalidate-queries\" \u002F>.\n",{"title":18609,"description":19382},{"loc":19384},"fr\u002Fposts\u002Fusing-pinia-colada-in-modals-without-spoiling-the-ux","K1ODev9Xyqz0RGMHPX5UqJa72vtnLGYDXt1pd1jfzOo",{"id":19391,"title":19392,"body":19393,"contentId":9275,"date":20430,"description":20431,"extension":18,"meta":20432,"navigation":20,"path":20433,"rawbody":20434,"robots":23,"schemaOrg":23,"seo":20435,"sitemap":20436,"stem":20437,"__hash__":20438},"posts\u002Ffr\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website.md","Un serveur Model Context Protocol (MCP) pour mon site web",{"type":7,"value":19394,"toc":20424},[19395,19401,19416,19428,19432,19435,19465,19468,19505,19511,19517,19526,19540,19545,19549,19552,19558,19590,19684,19687,19691,19702,20347,20350,20367,20373,20376,20387,20391,20394,20404,20411,20418,20421],[35,19396,19397,19400],{},[6862,19398,19399],{},"Les Modèles de Langage de Grande Taille"," (LLMs) sont omniprésents ces jours-ci. Le rythme des avancées est stupéfiant, et il est difficile de suivre les mises à jour constantes, c'est tout simplement fou !",[35,19402,19403,19404,19407,19408,19415],{},"Au milieu de cette avalanche d'informations, j'ai trouvé le concept d'un ",[6862,19405,19406],{},"agent",", particulièrement avec le ",[39,19409,19412],{"href":19410,"rel":19411},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction",[77],[6862,19413,19414],{},"Model Context Protocol"," (MCP), particulièrement intrigant et prometteur. J'ai donc décidé d'expérimenter avec cela et de voir comment cela pourrait améliorer mon flux de travail.",[938,19417,19418],{},[35,19419,19420,19422,19423,215],{},[200,19421,944],{},"\nVous préférez regarder une vidéo ? Consultez la version vidéo de cet article sur ",[39,19424,19427],{"href":19425,"rel":19426},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ",[77],"YouTube",[51,19429,19431],{"id":19430},"le-défi-des-identifiants","Le défi des identifiants",[35,19433,19434],{},"Actuellement, mon site web se compose de deux composants clés :",[1593,19436,19437,19451],{},[71,19438,19439,19440,19443,19444,19450],{},"Le ",[6862,19441,19442],{},"frontend",", construit avec ",[39,19445,19448],{"href":19446,"rel":19447},"https:\u002F\u002Fvitepress.dev",[77],[6862,19449,2929],{},", où chaque page est un fichier Markdown.",[71,19452,19439,19453,19456,19457,19464],{},[6862,19454,19455],{},"backend",", une application ",[39,19458,19461],{"href":19459,"rel":19460},"https:\u002F\u002Flaravel.com",[77],[6862,19462,19463],{},"Laravel"," qui ajoute des fonctionnalités supplémentaires.",[35,19466,19467],{},"Pour garantir que le bon contenu est servi et stocké pour chaque page frontend, je dois partager un identifiant entre ces composants. Chaque fichier Markdown a un identifiant unique dans son frontmatter.",[192,19469,19472],{"className":5903,"code":19470,"filename":19471,"language":18,"meta":13,"style":13},"---\nid: 1\ntitle:\ndescription:\n---\n","src\u002Fposts\u002Fa-simple-markdown-file.md",[156,19473,19474,19479,19488,19495,19501],{"__ignoreMap":13},[200,19475,19476],{"class":202,"line":203},[200,19477,19478],{"class":214},"---\n",[200,19480,19481,19483,19485],{"class":202,"line":14},[200,19482,3295],{"class":235},[200,19484,239],{"class":214},[200,19486,19487],{"class":2060}," 1\n",[200,19489,19490,19492],{"class":202,"line":255},[200,19491,3581],{"class":235},[200,19493,19494],{"class":214},":\n",[200,19496,19497,19499],{"class":202,"line":272},[200,19498,3597],{"class":235},[200,19500,19494],{"class":214},[200,19502,19503],{"class":202,"line":283},[200,19504,19478],{"class":214},[35,19506,19507,19508,215],{},"Cet identifiant récupère le contenu correspondant du backend lors du chargement de la page. Pour que tout fonctionne sans accroc, le backend doit également stocker cet identifiant dans la base de données. Sans cela, il est impossible de maintenir une base de données relationnelle fonctionnelle, car ma base de données comprend une table ",[156,19509,19510],{},"posts",[35,19512,19513,19514,19516],{},"Pour obtenir ces identifiants, le backend récupère régulièrement un fichier JSON généré au moment de la construction par ",[6862,19515,2929],{},". Ce fichier contient tous les fichiers Markdown et leur frontmatter. Cette approche fonctionne bien, mais il y a un hic.",[35,19518,19519,19520,19522,19523,19525],{},"Lorsque j'écris un nouveau fichier Markdown, je dois accéder à la page d'administration du backend, trier la table ",[156,19521,19510],{}," par la colonne ",[156,19524,3295],{},", et trouver le dernier identifiant utilisé. Ensuite, j'écris cet identifiant, incrémenté de un, dans le fichier sur lequel je travaille. C'est une tâche fastidieuse qui n'apporte aucune réelle valeur et consomme du temps, un processus que je déteste profondément.",[35,19527,19528,19529,19532,19533,19536,19537],{},"Simultanément, j'utilise fréquemment les ",[6862,19530,19531],{},"LLMs"," pour améliorer le contenu technique de mes articles, y compris le ",[6862,19534,19535],{},"SEO",", la grammaire et l'orthographe. Cela m'a amené à me demander : ",[6862,19538,19539],{},"Puis-je intégrer un agent dans le processus LLM actuel pour m'assurer que l'identifiant est toujours correct ?",[35,19541,19542],{},[1216,19543,19544],{},"Oui !",[51,19546,19548],{"id":19547},"mise-en-place-dune-nouvelle-route-backend","Mise en place d'une nouvelle route backend",[35,19550,19551],{},"Les serveurs MCP ne sont pas mystiques ; ils fournissent un moyen pour les LLMs d'interagir avec des sources de données. Ainsi, je dois créer une route API backend pour renvoyer le prochain identifiant de publication. Cette route sera accessible par le serveur MCP via la capacité d'outils.",[35,19553,19554,19555,19557],{},"Créer cette route API dans mon application ",[6862,19556,19463],{}," et configurer un contrôleur invocable est assez simple :",[192,19559,19564],{"className":19560,"code":19561,"filename":19562,"language":19563,"meta":13,"style":13},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","use App\\Http\\Controllers\\NextPostIdController;\nuse Illuminate\\Support\\Facades\\Route;\n\nRoute::get('posts\u002Fnext-id', NextPostIdController::class)\n    ->name('posts.next-id');\n","routes\u002Fapi.php","php",[156,19565,19566,19571,19576,19580,19585],{"__ignoreMap":13},[200,19567,19568],{"class":202,"line":203},[200,19569,19570],{},"use App\\Http\\Controllers\\NextPostIdController;\n",[200,19572,19573],{"class":202,"line":14},[200,19574,19575],{},"use Illuminate\\Support\\Facades\\Route;\n",[200,19577,19578],{"class":202,"line":255},[200,19579,528],{"emptyLinePlaceholder":20},[200,19581,19582],{"class":202,"line":272},[200,19583,19584],{},"Route::get('posts\u002Fnext-id', NextPostIdController::class)\n",[200,19586,19587],{"class":202,"line":283},[200,19588,19589],{},"    ->name('posts.next-id');\n",[192,19591,19594],{"className":19560,"code":19592,"filename":19593,"language":19563,"meta":13,"style":13},"\u003C?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\Post;\nuse Illuminate\\Http\\JsonResponse;\n\nclass NextPostIdController extends Controller\n{\n    \u002F**\n     * Handle the incoming request.\n     *\u002F\n    public function __invoke(): JsonResponse\n    {\n        $nextPostId = Post::max('id') + 1;\n\n        return response()->json(['next_post_id' => $nextPostId]);\n    }\n}\n","app\u002FHttp\u002FControllers\u002FNextPostIdController.php",[156,19595,19596,19601,19605,19610,19614,19619,19624,19628,19633,19637,19642,19647,19652,19657,19662,19667,19671,19676,19680],{"__ignoreMap":13},[200,19597,19598],{"class":202,"line":203},[200,19599,19600],{},"\u003C?php\n",[200,19602,19603],{"class":202,"line":14},[200,19604,528],{"emptyLinePlaceholder":20},[200,19606,19607],{"class":202,"line":255},[200,19608,19609],{},"namespace App\\Http\\Controllers;\n",[200,19611,19612],{"class":202,"line":272},[200,19613,528],{"emptyLinePlaceholder":20},[200,19615,19616],{"class":202,"line":283},[200,19617,19618],{},"use App\\Models\\Post;\n",[200,19620,19621],{"class":202,"line":300},[200,19622,19623],{},"use Illuminate\\Http\\JsonResponse;\n",[200,19625,19626],{"class":202,"line":310},[200,19627,528],{"emptyLinePlaceholder":20},[200,19629,19630],{"class":202,"line":336},[200,19631,19632],{},"class NextPostIdController extends Controller\n",[200,19634,19635],{"class":202,"line":342},[200,19636,230],{},[200,19638,19639],{"class":202,"line":365},[200,19640,19641],{},"    \u002F**\n",[200,19643,19644],{"class":202,"line":371},[200,19645,19646],{},"     * Handle the incoming request.\n",[200,19648,19649],{"class":202,"line":398},[200,19650,19651],{},"     *\u002F\n",[200,19653,19654],{"class":202,"line":405},[200,19655,19656],{},"    public function __invoke(): JsonResponse\n",[200,19658,19659],{"class":202,"line":410},[200,19660,19661],{},"    {\n",[200,19663,19664],{"class":202,"line":860},[200,19665,19666],{},"        $nextPostId = Post::max('id') + 1;\n",[200,19668,19669],{"class":202,"line":889},[200,19670,528],{"emptyLinePlaceholder":20},[200,19672,19673],{"class":202,"line":899},[200,19674,19675],{},"        return response()->json(['next_post_id' => $nextPostId]);\n",[200,19677,19678],{"class":202,"line":904},[200,19679,4977],{},[200,19681,19682],{"class":202,"line":930},[200,19683,1129],{},[35,19685,19686],{},"Cette configuration, bien que simple, est cruciale pour un serveur MCP efficace.",[51,19688,19690],{"id":19689},"configuration-du-serveur-mcp","Configuration du serveur MCP",[35,19692,19693,19694,19701],{},"Pour le serveur MCP, j'utilise le ",[39,19695,19698],{"href":19696,"rel":19697},"https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk",[77],[6862,19699,19700],{},"SDK TypeScript"," officiel. C'est simple et très efficace.",[192,19703,19705],{"className":1380,"code":19704,"filename":10026,"language":1383,"meta":13,"style":13},"import { Server } from '@modelcontextprotocol\u002Fsdk\u002Fserver\u002Findex.js'\nimport { StdioServerTransport } from '@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js'\nimport {\n  CallToolRequestSchema,\n  ListToolsRequestSchema\n} from '@modelcontextprotocol\u002Fsdk\u002Ftypes.js'\nimport { ofetch } from 'ofetch'\nimport { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\n\nconst server = new Server(\n  {\n    name: 'mcp.soubiran.dev',\n    version: '0.0.0'\n  },\n  {\n    capabilities: {\n      tools: {}\n    }\n  }\n)\n\nconst GetNextId = z.object({})\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n  return {\n    tools: [\n      {\n        name: 'next_post_id',\n        description: 'Obtenir le prochain ID de publication pour un post sur soubiran.dev',\n        inputSchema: zodToJsonSchema(GetNextId),\n      },\n    ]\n  }\n})\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n  if (request.params.name === 'next_post_id') {\n    try {\n      const data = await ofetch\u003C{ next_post_id: number }>('http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fposts\u002Fnext-id')\n\n      return {\n        content: [{\n          type: 'text',\n          text: data.next_post_id\n        }]\n      }\n    }\n    catch (error) {\n      throw new Error('Erreur lors de la récupération du prochain ID de post')\n    }\n  }\n\n  throw new Error('Outil inconnu')\n})\n\nconst transport = new StdioServerTransport()\nawait server.connect(transport)\n",[156,19706,19707,19727,19747,19753,19760,19765,19778,19797,19816,19836,19840,19855,19859,19874,19888,19892,19896,19905,19915,19919,19923,19927,19931,19953,19957,19979,19985,19994,19998,20013,20029,20047,20051,20055,20059,20065,20069,20094,20125,20132,20170,20174,20180,20191,20205,20218,20224,20228,20232,20245,20267,20272,20277,20282,20303,20310,20315,20331],{"__ignoreMap":13},[200,19708,19709,19711,19713,19716,19718,19720,19722,19725],{"class":202,"line":203},[200,19710,972],{"class":206},[200,19712,318],{"class":214},[200,19714,19715],{"class":210}," Server",[200,19717,980],{"class":214},[200,19719,983],{"class":206},[200,19721,242],{"class":214},[200,19723,19724],{"class":245},"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Findex.js",[200,19726,991],{"class":214},[200,19728,19729,19731,19733,19736,19738,19740,19742,19745],{"class":202,"line":14},[200,19730,972],{"class":206},[200,19732,318],{"class":214},[200,19734,19735],{"class":210}," StdioServerTransport",[200,19737,980],{"class":214},[200,19739,983],{"class":206},[200,19741,242],{"class":214},[200,19743,19744],{"class":245},"@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js",[200,19746,991],{"class":214},[200,19748,19749,19751],{"class":202,"line":255},[200,19750,972],{"class":206},[200,19752,280],{"class":214},[200,19754,19755,19758],{"class":202,"line":272},[200,19756,19757],{"class":210},"  CallToolRequestSchema",[200,19759,252],{"class":214},[200,19761,19762],{"class":202,"line":283},[200,19763,19764],{"class":210},"  ListToolsRequestSchema\n",[200,19766,19767,19769,19771,19773,19776],{"class":202,"line":300},[200,19768,413],{"class":214},[200,19770,983],{"class":206},[200,19772,242],{"class":214},[200,19774,19775],{"class":245},"@modelcontextprotocol\u002Fsdk\u002Ftypes.js",[200,19777,991],{"class":214},[200,19779,19780,19782,19784,19786,19788,19790,19792,19795],{"class":202,"line":310},[200,19781,972],{"class":206},[200,19783,318],{"class":214},[200,19785,4858],{"class":210},[200,19787,980],{"class":214},[200,19789,983],{"class":206},[200,19791,242],{"class":214},[200,19793,19794],{"class":245},"ofetch",[200,19796,991],{"class":214},[200,19798,19799,19801,19803,19805,19807,19809,19811,19814],{"class":202,"line":336},[200,19800,972],{"class":206},[200,19802,318],{"class":214},[200,19804,3180],{"class":210},[200,19806,980],{"class":214},[200,19808,983],{"class":206},[200,19810,242],{"class":214},[200,19812,19813],{"class":245},"zod",[200,19815,991],{"class":214},[200,19817,19818,19820,19822,19825,19827,19829,19831,19834],{"class":202,"line":342},[200,19819,972],{"class":206},[200,19821,318],{"class":214},[200,19823,19824],{"class":210}," zodToJsonSchema",[200,19826,980],{"class":214},[200,19828,983],{"class":206},[200,19830,242],{"class":214},[200,19832,19833],{"class":245},"zod-to-json-schema",[200,19835,991],{"class":214},[200,19837,19838],{"class":202,"line":365},[200,19839,528],{"emptyLinePlaceholder":20},[200,19841,19842,19844,19847,19849,19851,19853],{"class":202,"line":371},[200,19843,443],{"class":379},[200,19845,19846],{"class":210}," server ",[200,19848,449],{"class":214},[200,19850,13007],{"class":214},[200,19852,19715],{"class":223},[200,19854,3130],{"class":210},[200,19856,19857],{"class":202,"line":398},[200,19858,3146],{"class":214},[200,19860,19861,19863,19865,19867,19870,19872],{"class":202,"line":405},[200,19862,11812],{"class":235},[200,19864,239],{"class":214},[200,19866,242],{"class":214},[200,19868,19869],{"class":245},"mcp.soubiran.dev",[200,19871,249],{"class":214},[200,19873,252],{"class":214},[200,19875,19876,19879,19881,19883,19886],{"class":202,"line":410},[200,19877,19878],{"class":235},"    version",[200,19880,239],{"class":214},[200,19882,242],{"class":214},[200,19884,19885],{"class":245},"0.0.0",[200,19887,991],{"class":214},[200,19889,19890],{"class":202,"line":860},[200,19891,368],{"class":214},[200,19893,19894],{"class":202,"line":889},[200,19895,3146],{"class":214},[200,19897,19898,19901,19903],{"class":202,"line":899},[200,19899,19900],{"class":235},"    capabilities",[200,19902,239],{"class":214},[200,19904,280],{"class":214},[200,19906,19907,19910,19912],{"class":202,"line":904},[200,19908,19909],{"class":235},"      tools",[200,19911,239],{"class":214},[200,19913,19914],{"class":214}," {}\n",[200,19916,19917],{"class":202,"line":930},[200,19918,4977],{"class":214},[200,19920,19921],{"class":202,"line":3655},[200,19922,4505],{"class":214},[200,19924,19925],{"class":202,"line":3660},[200,19926,416],{"class":210},[200,19928,19929],{"class":202,"line":4035},[200,19930,528],{"emptyLinePlaceholder":20},[200,19932,19933,19935,19938,19940,19942,19944,19946,19948,19951],{"class":202,"line":4490},[200,19934,443],{"class":379},[200,19936,19937],{"class":210}," GetNextId ",[200,19939,449],{"class":214},[200,19941,3180],{"class":210},[200,19943,215],{"class":214},[200,19945,293],{"class":223},[200,19947,227],{"class":210},[200,19949,19950],{"class":214},"{}",[200,19952,416],{"class":210},[200,19954,19955],{"class":202,"line":4496},[200,19956,528],{"emptyLinePlaceholder":20},[200,19958,19959,19961,19963,19966,19969,19971,19973,19975,19977],{"class":202,"line":4502},[200,19960,3123],{"class":210},[200,19962,215],{"class":214},[200,19964,19965],{"class":223},"setRequestHandler",[200,19967,19968],{"class":210},"(ListToolsRequestSchema",[200,19970,549],{"class":214},[200,19972,380],{"class":379},[200,19974,3729],{"class":214},[200,19976,393],{"class":379},[200,19978,280],{"class":214},[200,19980,19981,19983],{"class":202,"line":4508},[200,19982,3758],{"class":206},[200,19984,280],{"class":214},[200,19986,19987,19990,19992],{"class":202,"line":5469},[200,19988,19989],{"class":235},"    tools",[200,19991,239],{"class":214},[200,19993,1410],{"class":235},[200,19995,19996],{"class":202,"line":5474},[200,19997,4457],{"class":214},[200,19999,20000,20002,20004,20006,20009,20011],{"class":202,"line":13237},[200,20001,11737],{"class":235},[200,20003,239],{"class":214},[200,20005,242],{"class":214},[200,20007,20008],{"class":245},"next_post_id",[200,20010,249],{"class":214},[200,20012,252],{"class":214},[200,20014,20015,20018,20020,20022,20025,20027],{"class":202,"line":13263},[200,20016,20017],{"class":235},"        description",[200,20019,239],{"class":214},[200,20021,242],{"class":214},[200,20023,20024],{"class":245},"Obtenir le prochain ID de publication pour un post sur soubiran.dev",[200,20026,249],{"class":214},[200,20028,252],{"class":214},[200,20030,20031,20034,20036,20038,20040,20043,20045],{"class":202,"line":13279},[200,20032,20033],{"class":235},"        inputSchema",[200,20035,239],{"class":214},[200,20037,19824],{"class":223},[200,20039,227],{"class":235},[200,20041,20042],{"class":210},"GetNextId",[200,20044,2104],{"class":235},[200,20046,252],{"class":214},[200,20048,20049],{"class":202,"line":13323},[200,20050,12098],{"class":214},[200,20052,20053],{"class":202,"line":13329},[200,20054,4499],{"class":235},[200,20056,20057],{"class":202,"line":13334},[200,20058,4505],{"class":214},[200,20060,20061,20063],{"class":202,"line":13360},[200,20062,413],{"class":214},[200,20064,416],{"class":210},[200,20066,20067],{"class":202,"line":13365},[200,20068,528],{"emptyLinePlaceholder":20},[200,20070,20071,20073,20075,20077,20080,20082,20084,20086,20088,20090,20092],{"class":202,"line":13385},[200,20072,3123],{"class":210},[200,20074,215],{"class":214},[200,20076,19965],{"class":223},[200,20078,20079],{"class":210},"(CallToolRequestSchema",[200,20081,549],{"class":214},[200,20083,380],{"class":379},[200,20085,1094],{"class":214},[200,20087,6675],{"class":386},[200,20089,2104],{"class":214},[200,20091,393],{"class":379},[200,20093,280],{"class":214},[200,20095,20096,20099,20101,20103,20105,20108,20110,20113,20115,20117,20119,20121,20123],{"class":202,"line":13405},[200,20097,20098],{"class":206},"  if",[200,20100,1094],{"class":235},[200,20102,6675],{"class":210},[200,20104,215],{"class":214},[200,20106,20107],{"class":210},"params",[200,20109,215],{"class":214},[200,20111,20112],{"class":210},"name",[200,20114,3298],{"class":214},[200,20116,242],{"class":214},[200,20118,20008],{"class":245},[200,20120,249],{"class":214},[200,20122,5282],{"class":235},[200,20124,230],{"class":214},[200,20126,20127,20130],{"class":202,"line":13410},[200,20128,20129],{"class":206},"    try",[200,20131,280],{"class":214},[200,20133,20134,20137,20139,20141,20143,20145,20148,20151,20153,20156,20159,20161,20163,20166,20168],{"class":202,"line":13418},[200,20135,20136],{"class":379},"      const",[200,20138,5056],{"class":210},[200,20140,509],{"class":214},[200,20142,452],{"class":206},[200,20144,4858],{"class":223},[200,20146,20147],{"class":214},"\u003C{",[200,20149,20150],{"class":235}," next_post_id",[200,20152,239],{"class":214},[200,20154,20155],{"class":1364}," number",[200,20157,20158],{"class":214}," }>",[200,20160,227],{"class":235},[200,20162,249],{"class":214},[200,20164,20165],{"class":245},"http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fposts\u002Fnext-id",[200,20167,249],{"class":214},[200,20169,416],{"class":235},[200,20171,20172],{"class":202,"line":13436},[200,20173,528],{"emptyLinePlaceholder":20},[200,20175,20176,20178],{"class":202,"line":13466},[200,20177,11730],{"class":206},[200,20179,280],{"class":214},[200,20181,20182,20185,20187,20189],{"class":202,"line":13472},[200,20183,20184],{"class":235},"        content",[200,20186,239],{"class":214},[200,20188,350],{"class":235},[200,20190,230],{"class":214},[200,20192,20193,20195,20197,20199,20201,20203],{"class":202,"line":13477},[200,20194,4929],{"class":235},[200,20196,239],{"class":214},[200,20198,242],{"class":214},[200,20200,697],{"class":245},[200,20202,249],{"class":214},[200,20204,252],{"class":214},[200,20206,20207,20209,20211,20213,20215],{"class":202,"line":13482},[200,20208,4944],{"class":235},[200,20210,239],{"class":214},[200,20212,5056],{"class":210},[200,20214,215],{"class":214},[200,20216,20217],{"class":210},"next_post_id\n",[200,20219,20220,20222],{"class":202,"line":13497},[200,20221,13388],{"class":214},[200,20223,4511],{"class":235},[200,20225,20226],{"class":202,"line":13508},[200,20227,4493],{"class":214},[200,20229,20230],{"class":202,"line":13513},[200,20231,4977],{"class":214},[200,20233,20234,20237,20239,20241,20243],{"class":202,"line":13518},[200,20235,20236],{"class":206},"    catch",[200,20238,1094],{"class":235},[200,20240,19275],{"class":210},[200,20242,5282],{"class":235},[200,20244,230],{"class":214},[200,20246,20248,20251,20253,20256,20258,20260,20263,20265],{"class":202,"line":20247},50,[200,20249,20250],{"class":206},"      throw",[200,20252,13007],{"class":214},[200,20254,20255],{"class":223}," Error",[200,20257,227],{"class":235},[200,20259,249],{"class":214},[200,20261,20262],{"class":245},"Erreur lors de la récupération du prochain ID de post",[200,20264,249],{"class":214},[200,20266,416],{"class":235},[200,20268,20270],{"class":202,"line":20269},51,[200,20271,4977],{"class":214},[200,20273,20275],{"class":202,"line":20274},52,[200,20276,4505],{"class":214},[200,20278,20280],{"class":202,"line":20279},53,[200,20281,528],{"emptyLinePlaceholder":20},[200,20283,20285,20288,20290,20292,20294,20296,20299,20301],{"class":202,"line":20284},54,[200,20286,20287],{"class":206},"  throw",[200,20289,13007],{"class":214},[200,20291,20255],{"class":223},[200,20293,227],{"class":235},[200,20295,249],{"class":214},[200,20297,20298],{"class":245},"Outil inconnu",[200,20300,249],{"class":214},[200,20302,416],{"class":235},[200,20304,20306,20308],{"class":202,"line":20305},55,[200,20307,413],{"class":214},[200,20309,416],{"class":210},[200,20311,20313],{"class":202,"line":20312},56,[200,20314,528],{"emptyLinePlaceholder":20},[200,20316,20318,20320,20323,20325,20327,20329],{"class":202,"line":20317},57,[200,20319,443],{"class":379},[200,20321,20322],{"class":210}," transport ",[200,20324,449],{"class":214},[200,20326,13007],{"class":214},[200,20328,19735],{"class":223},[200,20330,466],{"class":210},[200,20332,20334,20336,20339,20341,20344],{"class":202,"line":20333},58,[200,20335,207],{"class":206},[200,20337,20338],{"class":210}," server",[200,20340,215],{"class":214},[200,20342,20343],{"class":223},"connect",[200,20345,20346],{"class":210},"(transport)\n",[35,20348,20349],{},"Ce code établit un serveur MCP STDIO avec deux gestionnaires de requêtes clés :",[68,20351,20352,20361],{},[71,20353,20354,20355,20358,20359,215],{},"Le gestionnaire ",[156,20356,20357],{},"ListToolsRequest"," renvoie les outils disponibles à l'agent. Actuellement, il y a un outil : ",[156,20360,20008],{},[71,20362,20354,20363,20366],{},[156,20364,20365],{},"CallToolRequest"," est activé lorsque l'agent utilise l'outil. Il appelle la route API backend pour récupérer le prochain ID de post.",[35,20368,20369,20370,20372],{},"Le processus d'appel d'outil est simple ; il récupère le prochain ID de post depuis la route API backend que j'ai définie, en utilisant la bibliothèque ",[156,20371,19794],{}," pour plus de facilité. L'ID de publication est ensuite renvoyé sous forme de nombre.",[35,20374,20375],{},"Par conséquent, tout agent invoquant cet outil recevra le prochain ID de publication en réponse dans son contexte. Cela me permet de rédiger des fichiers Markdown sans me soucier des IDs de post, l'agent s'en occupe parfaitement !",[938,20377,20378],{},[35,20379,20380,20382,20383,215],{},[200,20381,944],{},"\nPour le code complet, regardez la ",[39,20384,20386],{"href":19425,"rel":20385},[77],"vidéo sur YouTube",[51,20388,20390],{"id":20389},"ça-fonctionne","Ça fonctionne ?",[35,20392,20393],{},"Oui, cela fonctionne parfaitement ! Voyez-le en action :",[130,20395,132,20396,132,20401],{},[134,20397,136,20398,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,20399],{"src":20400,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website\u002Fworking-mcp-soubiran-dev.mp4",[143,20402,20403],{},"Demande à l'agent d'améliorer un fichier et d'insérer automatiquement l'ID correct en utilisant le prochain ID de publication du serveur MCP.",[35,20405,20406,20407,20410],{},"Avec ce ",[6862,20408,20409],{},"serveur MCP"," opérationnel, je peux désormais interagir avec mes fichiers Markdown tout en fournissant un contexte à l'agent. Cela rationalise mon flux de travail, me permettant de me concentrer sur le contenu plutôt que sur les détails techniques. Mon objectif ultime est d'accélérer mon processus d'écriture, le rendant plus efficace et agréable, et ce logiciel est une étape vers cet objectif.",[35,20412,20413,20414,20417],{},"Je suis impressionné par la facilité d'utilisation du SDK MCP et son intégration avec ",[6862,20415,20416],{},"VS Code",". Il permet de créer rapidement de nouveaux agents et d'incorporer facilement le flux de travail.",[35,20419,20420],{},"Je suis impatient d'explorer les possibilités que cela ouvre. L'avenir des agents est prometteur, et je recommande vivement d'expérimenter avec eux !",[1170,20422,20423],{},"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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":13,"searchDepth":14,"depth":14,"links":20425},[20426,20427,20428,20429],{"id":19430,"depth":14,"text":19431},{"id":19547,"depth":14,"text":19548},{"id":19689,"depth":14,"text":19690},{"id":20389,"depth":14,"text":20390},"2025-05-10","Explorer le potentiel du Model Context Protocol (MCP) en l'intégrant dans mon flux de travail et en optimisant les opérations backend de mon site web.",{},"\u002Ffr\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website","---\ncontentId: 5326cb9f-fd51-4d43-bff8-2cee2a995d89\ntitle: \"Un serveur Model Context Protocol (MCP) pour mon site web\"\ndescription: \"Explorer le potentiel du Model Context Protocol (MCP) en l'intégrant dans mon flux de travail et en optimisant les opérations backend de mon site web.\"\ndate: 2025-05-10\n---\n\n**Les Modèles de Langage de Grande Taille** (LLMs) sont omniprésents ces jours-ci. Le rythme des avancées est stupéfiant, et il est difficile de suivre les mises à jour constantes, c'est tout simplement fou !\n\nAu milieu de cette avalanche d'informations, j'ai trouvé le concept d'un **agent**, particulièrement avec le [**Model Context Protocol**](https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction) (MCP), particulièrement intrigant et prometteur. J'ai donc décidé d'expérimenter avec cela et de voir comment cela pourrait améliorer mon flux de travail.\n\n> [!NOTE]\n> Vous préférez regarder une vidéo ? Consultez la version vidéo de cet article sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ).\n\n## Le défi des identifiants\n\nActuellement, mon site web se compose de deux composants clés :\n\n- Le **frontend**, construit avec [**VitePress**](https:\u002F\u002Fvitepress.dev), où chaque page est un fichier Markdown.\n- Le **backend**, une application [**Laravel**](https:\u002F\u002Flaravel.com) qui ajoute des fonctionnalités supplémentaires.\n\nPour garantir que le bon contenu est servi et stocké pour chaque page frontend, je dois partager un identifiant entre ces composants. Chaque fichier Markdown a un identifiant unique dans son frontmatter.\n\n```md [src\u002Fposts\u002Fa-simple-markdown-file.md]\n---\nid: 1\ntitle:\ndescription:\n---\n```\n\nCet identifiant récupère le contenu correspondant du backend lors du chargement de la page. Pour que tout fonctionne sans accroc, le backend doit également stocker cet identifiant dans la base de données. Sans cela, il est impossible de maintenir une base de données relationnelle fonctionnelle, car ma base de données comprend une table `posts`.\n\nPour obtenir ces identifiants, le backend récupère régulièrement un fichier JSON généré au moment de la construction par **VitePress**. Ce fichier contient tous les fichiers Markdown et leur frontmatter. Cette approche fonctionne bien, mais il y a un hic.\n\nLorsque j'écris un nouveau fichier Markdown, je dois accéder à la page d'administration du backend, trier la table `posts` par la colonne `id`, et trouver le dernier identifiant utilisé. Ensuite, j'écris cet identifiant, incrémenté de un, dans le fichier sur lequel je travaille. C'est une tâche fastidieuse qui n'apporte aucune réelle valeur et consomme du temps, un processus que je déteste profondément.\n\nSimultanément, j'utilise fréquemment les **LLMs** pour améliorer le contenu technique de mes articles, y compris le **SEO**, la grammaire et l'orthographe. Cela m'a amené à me demander : **Puis-je intégrer un agent dans le processus LLM actuel pour m'assurer que l'identifiant est toujours correct ?**\n\n_Oui !_\n\n## Mise en place d'une nouvelle route backend\n\nLes serveurs MCP ne sont pas mystiques ; ils fournissent un moyen pour les LLMs d'interagir avec des sources de données. Ainsi, je dois créer une route API backend pour renvoyer le prochain identifiant de publication. Cette route sera accessible par le serveur MCP via la capacité d'outils.\n\nCréer cette route API dans mon application **Laravel** et configurer un contrôleur invocable est assez simple :\n\n```php [routes\u002Fapi.php]\nuse App\\Http\\Controllers\\NextPostIdController;\nuse Illuminate\\Support\\Facades\\Route;\n\nRoute::get('posts\u002Fnext-id', NextPostIdController::class)\n    ->name('posts.next-id');\n```\n\n```php [app\u002FHttp\u002FControllers\u002FNextPostIdController.php]\n\u003C?php\n\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\Post;\nuse Illuminate\\Http\\JsonResponse;\n\nclass NextPostIdController extends Controller\n{\n    \u002F**\n     * Handle the incoming request.\n     *\u002F\n    public function __invoke(): JsonResponse\n    {\n        $nextPostId = Post::max('id') + 1;\n\n        return response()->json(['next_post_id' => $nextPostId]);\n    }\n}\n```\n\nCette configuration, bien que simple, est cruciale pour un serveur MCP efficace.\n\n## Configuration du serveur MCP\n\nPour le serveur MCP, j'utilise le [**SDK TypeScript**](https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk) officiel. C'est simple et très efficace.\n\n```ts [src\u002Findex.ts]\nimport { Server } from '@modelcontextprotocol\u002Fsdk\u002Fserver\u002Findex.js'\nimport { StdioServerTransport } from '@modelcontextprotocol\u002Fsdk\u002Fserver\u002Fstdio.js'\nimport {\n  CallToolRequestSchema,\n  ListToolsRequestSchema\n} from '@modelcontextprotocol\u002Fsdk\u002Ftypes.js'\nimport { ofetch } from 'ofetch'\nimport { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\n\nconst server = new Server(\n  {\n    name: 'mcp.soubiran.dev',\n    version: '0.0.0'\n  },\n  {\n    capabilities: {\n      tools: {}\n    }\n  }\n)\n\nconst GetNextId = z.object({})\n\nserver.setRequestHandler(ListToolsRequestSchema, async () => {\n  return {\n    tools: [\n      {\n        name: 'next_post_id',\n        description: 'Obtenir le prochain ID de publication pour un post sur soubiran.dev',\n        inputSchema: zodToJsonSchema(GetNextId),\n      },\n    ]\n  }\n})\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n  if (request.params.name === 'next_post_id') {\n    try {\n      const data = await ofetch\u003C{ next_post_id: number }>('http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fposts\u002Fnext-id')\n\n      return {\n        content: [{\n          type: 'text',\n          text: data.next_post_id\n        }]\n      }\n    }\n    catch (error) {\n      throw new Error('Erreur lors de la récupération du prochain ID de post')\n    }\n  }\n\n  throw new Error('Outil inconnu')\n})\n\nconst transport = new StdioServerTransport()\nawait server.connect(transport)\n```\n\nCe code établit un serveur MCP STDIO avec deux gestionnaires de requêtes clés :\n\n1. Le gestionnaire `ListToolsRequest` renvoie les outils disponibles à l'agent. Actuellement, il y a un outil : `next_post_id`.\n2. Le gestionnaire `CallToolRequest` est activé lorsque l'agent utilise l'outil. Il appelle la route API backend pour récupérer le prochain ID de post.\n\nLe processus d'appel d'outil est simple ; il récupère le prochain ID de post depuis la route API backend que j'ai définie, en utilisant la bibliothèque `ofetch` pour plus de facilité. L'ID de publication est ensuite renvoyé sous forme de nombre.\n\nPar conséquent, tout agent invoquant cet outil recevra le prochain ID de publication en réponse dans son contexte. Cela me permet de rédiger des fichiers Markdown sans me soucier des IDs de post, l'agent s'en occupe parfaitement !\n\n> [!NOTE]\n> Pour le code complet, regardez la [vidéo sur YouTube](https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=NnLIYresumQ).\n\n## Ça fonctionne ?\n\nOui, cela fonctionne parfaitement ! Voyez-le en action :\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website\u002Fworking-mcp-soubiran-dev.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Demande à l'agent d'améliorer un fichier et d'insérer automatiquement l'ID correct en utilisant le prochain ID de publication du serveur MCP.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nAvec ce **serveur MCP** opérationnel, je peux désormais interagir avec mes fichiers Markdown tout en fournissant un contexte à l'agent. Cela rationalise mon flux de travail, me permettant de me concentrer sur le contenu plutôt que sur les détails techniques. Mon objectif ultime est d'accélérer mon processus d'écriture, le rendant plus efficace et agréable, et ce logiciel est une étape vers cet objectif.\n\nJe suis impressionné par la facilité d'utilisation du SDK MCP et son intégration avec **VS Code**. Il permet de créer rapidement de nouveaux agents et d'incorporer facilement le flux de travail.\n\nJe suis impatient d'explorer les possibilités que cela ouvre. L'avenir des agents est prometteur, et je recommande vivement d'expérimenter avec eux !\n",{"title":19392,"description":20431},{"loc":20433},"fr\u002Fposts\u002Fa-model-context-protocol-mcp-server-for-my-website","ygVysYx8b1QIWhqlAjROxmI7h27lDmb-L2qGfHZbKVM",{"id":20440,"title":20441,"body":20442,"contentId":9305,"date":20826,"description":20827,"extension":18,"meta":20828,"navigation":20,"path":20829,"rawbody":20830,"robots":23,"schemaOrg":23,"seo":20831,"sitemap":20832,"stem":20844,"__hash__":20845},"posts\u002Ffr\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025.md","La conférence tech qui parle éthique et diversité : MiXiT",{"type":7,"value":20443,"toc":20820},[20444,20447,20453,20459,20465,20470,20473,20476,20479,20621],[35,20445,20446],{},"Cette année, les choses évoluent. Ma première expérience de conférence a été Devoxx France en 2023, et jusqu'à présent, je n'avais jamais participé à une conférence en dehors de Paris. Cependant, voyager, rencontrer de nouvelles personnes, découvrir leurs passions et partager les miennes a toujours été quelque chose que j'apprécie.",[35,20448,20449,20450,20452],{},"Lorsque l'opportunité de prendre la parole à ",[6862,20451,7497],{}," s'est présentée, j'ai rapidement soumis plusieurs propositions. Quelques semaines plus tard, j'ai reçu un email plaisant : j'avais été sélectionné pour parler à l'édition 2025, délivrant non pas une, mais deux interventions. J'étais ravi d'assister à cette conférence et, pour la première fois, faire connaissance avec une nouvelle communauté.",[35,20454,20455],{},[1707,20456],{"alt":20457,"src":20458},"Moi devant le Parc des Hauteurs à Lyon.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fme-in-front-of-parc-des-hauteurs.webp",[35,20460,20461],{},[1707,20462],{"alt":20463,"src":20464},"Vue de Lyon depuis le Parc des Hauteurs.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Flyon-from-parc-des-hauteurs.webp",[35,20466,20467],{},[1216,20468,20469],{},"Merci, MiXiT, et à tous pour cette conférence incroyable ! 🫶",[51,20471,18539],{"id":20472},"mixit-2025",[35,20474,20475],{},"Je dois confesser que je n'ai rien lu ni préparé pour cette conférence jusqu'à chaque matin. J'ai choisi d'apprécier le moment sans stress, ce qui a résulté en une totale absence de continuité dans tout ce que j'ai regardé - tout était basé sur l'intuition.",[35,20477,20478],{},"Voici la liste des interventions auxquelles j'ai assisté :",[1593,20480,20481,20566],{},[71,20482,20483,20486],{},[6862,20484,20485],{},"Mardi 29 avril 2025",[1593,20487,20488,20498,20506,20514,20524,20534,20549,20556],{},[71,20489,20490,6865,20493],{},[6862,20491,20492],{},"Robustesse : une nouvelle culture Tech ?",[39,20494,20497],{"href":20495,"rel":20496},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Folivier-hamant-a55149153\u002F",[77],"Olivier Hamant",[71,20499,20500,6865,20503],{},[6862,20501,20502],{},"LLMs, toutes ces questions que vous n'avez jamais osé poser à leur sujet",[39,20504,16638],{"href":16636,"rel":20505},[77],[71,20507,20508,6865,20511],{},[6862,20509,20510],{},"Stimuler l'Innovation de Votre Entreprise Grâce à une Plateforme Basée sur l'IA Générative",[39,20512,7030],{"href":7028,"rel":20513},[77],[71,20515,20516,6865,20519],{},[6862,20517,20518],{},"Nos manières de compter, périls économiques, démocratiques et écologiques.",[39,20520,20523],{"href":20521,"rel":20522},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fval%C3%A9rie-charolles-88779594\u002F",[77],"Valérie Charolles",[71,20525,20526,6865,20529],{},[6862,20527,20528],{},"Quand le Terminal dévore la UI : TUI pour tout le monde !",[39,20530,20533],{"href":20531,"rel":20532},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthierrychantier\u002F",[77],"Thierry Chantier",[71,20535,20536,6865,20539,1655,20544],{},[6862,20537,20538],{},"Rendre transparents les changements des CGUs avec Open Terms Archive",[39,20540,20543],{"href":20541,"rel":20542},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmattisg\u002F",[77],"Matti Schneider",[39,20545,20548],{"href":20546,"rel":20547},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclementbiron\u002F",[77],"Clément Biron",[71,20550,20551,6865,20553],{},[6862,20552,13967],{},[39,20554,7045],{"href":7043,"rel":20555},[77],[71,20557,20558,6865,20561],{},[6862,20559,20560],{},"Design : sabotage, hacking & politique.",[39,20562,20565],{"href":20563,"rel":20564},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgeoffreydorne\u002F",[77],"Geoffrey Dorne",[71,20567,20568,20571],{},[6862,20569,20570],{},"Mercredi 30 avril 2025",[1593,20572,20573,20583,20590,20596,20606],{},[71,20574,20575,6865,20578],{},[6862,20576,20577],{},"Faut-il changer d'ère numérique pour préserver la démocratie ?",[39,20579,20582],{"href":20580,"rel":20581},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdavid-chavalarias\u002F",[77],"David Chavalarias",[71,20584,20585,6865,20587],{},[6862,20586,9137],{},[39,20588,7045],{"href":7043,"rel":20589},[77],[71,20591,20592,20595],{},[6862,20593,20594],{},"Mapping the critical infrastructure sustaining our understanding of the Earth"," par Codrina Maria Illie",[71,20597,20598,6865,20601],{},[6862,20599,20600],{},"L'aventure d'une requête HTTP - ou le chemin de la vie des devs",[39,20602,20605],{"href":20603,"rel":20604},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmartinpascal\u002F",[77],"Pascal Martin",[71,20607,20608,6865,20611,1655,20616],{},[6862,20609,20610],{},"Les super-pouvoirs méconnus de Docker Desktop",[39,20612,20615],{"href":20613,"rel":20614},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fguillaumelours\u002F",[77],"Guillaume Lours",[39,20617,20620],{"href":20618,"rel":20619},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fphcharriere\u002F",[77],"Philippe Charrière",[7151,20622,20624,20627,20630,20636,20642,20648,20652,20655,20714,20717,20721,20729,20737,20747,20753,20757],{"w-full":13,"icon":7153,"text":7497,"to":20623,"target":7156},"https:\u002F\u002Fwww.youtube.com\u002F@mixitconf",[35,20625,20626],{},"Il y avait des sujets vraiment fascinants à la conférence, et honnêtement, certains m'ont époustouflé.",[35,20628,20629],{},"Assister à une conférence, c'est aussi une question de réseautage, et avec le dîner des intervenants lundi soir et le cocktail mardi soir, j'ai eu la chance de discuter avec de nombreuses personnes sur bon nombre de sujets - exactement ce que j'aime faire.",[35,20631,20632],{},[1707,20633],{"alt":20634,"src":20635},"Mon verre de vin pendant le dîner des intervenants. C'est la seule photo que j'ai prise. Oups !","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fspeakers-dinner.webp",[35,20637,20638],{},[1707,20639],{"alt":20640,"src":20641},"L'hôtel de ville de Lyon, où le cocktail a eu lieu.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Ftown-hall.webp",[35,20643,20644],{},[1707,20645],{"alt":20646,"src":20647},"Le cocktail à l'hôtel de ville de Lyon.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fcocktail-party.webp",[51,20649,20651],{"id":20650},"mes-interventions-préférées","Mes Interventions Préférées",[35,20653,20654],{},"Certaines interventions ont particulièrement retenu mon attention ou résonné en moi. Voici quelques points forts :",[68,20656,20657,20673,20698],{},[71,20658,20659,6865,20661,20664,20666,20667,20669],{},[6862,20660,20510],{},[39,20662,7030],{"href":7028,"rel":20663},[77],[7174,20665],{},"Intégrer l'IA dans votre entreprise est désormais absolument essentiel. C'est s'adapter ou périr. Lors de son intervention de 45 minutes, Philippe Laumay a expliqué comment son département R&D a mis en place un laboratoire pour permettre à chaque employé d'expérimenter avec l'IA générative. Les objectifs étaient d'éduquer l'entreprise sur cette technologie inévitable et de leur permettre de découvrir son potentiel pour l'intégrer dans les workflows. C'était véritablement impressionnant, montrant des années de travail avec des chiffres soutenant l'efficacité de cette approche. Si vous envisagez d'intégrer l'IA dans votre entreprise, cette intervention est incontournable.",[7174,20668],{},[1707,20670],{"alt":20671,"src":20672},"Photo de Philippe Laumay lors de son intervention.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fstimuler-linnovation-de-votre-entreprise-grace-a-une-plateforme-basee-sur-lia-generative.webp",[71,20674,20675,6865,20677,1655,20680,20683,20685,20686,20691,20692,20694],{},[6862,20676,20538],{},[39,20678,20543],{"href":20541,"rel":20679},[77],[39,20681,20548],{"href":20546,"rel":20682},[77],[7174,20684],{},"Nous avons tous reçu des courriels nous informant des changements dans les conditions de service. Mais qui les lit ? Comment vérifions-nous les changements réels ? Cette intervention a couvert un projet open-source appelé ",[39,20687,20690],{"href":20688,"rel":20689},"https:\u002F\u002Fopentermsarchive.org\u002Fen\u002F",[77],"Open Terms Archive",", visant à rendre les conditions de service des grandes entreprises technologiques transparentes en enregistrant publiquement chaque version pour un contrôle démocratique. Ils ont exposé des changements dans les dernières conditions de Meta, notamment en ce qui concerne la liberté d'expression, et j'ai été vraiment surpris par leurs découvertes.",[7174,20693],{},[1707,20695],{"alt":20696,"src":20697},"Photo de Matti Schneider et Clément Biron lors de leur intervention.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Frendre-transparents-les-changements-des-cgus-avec-open-terms-archive.webp",[71,20699,20700,6865,20702,20705,20707,20708,20710],{},[6862,20701,20600],{},[39,20703,20605],{"href":20603,"rel":20704},[77],[7174,20706],{},"Pendant ce créneau, j'étais incertain sur l'intervention à laquelle assister. Sur un coup de tête, j'ai choisi celle-ci, ne connaissant pas l'intervenant et ayant à peine consulté le résumé. Cela s'est avéré être un excellent choix. Pascal Martin a ouvert avec une question : \"Que se passe-t-il lorsque vous appuyez sur entrer dans votre navigateur ?\" Cela a conduit à une explication captivante de 45 minutes des complexités du développement web. C'était éclairant, et je prévois de partager la vidéo avec quiconque pense que le web se limite simplement à HTML, CSS, et JavaScript.",[7174,20709],{},[1707,20711],{"alt":20712,"src":20713},"Photo de Pascal Martin lors de son intervention.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Flaventure-dune-requete-http-ou-le-chemin-de-la-vie-des-devs.webp",[35,20715,20716],{},"Je vous recommande vivement de regarder ces interventions.",[51,20718,20720],{"id":20719},"parler-de-signaux-aliens-dans-un-podcast","Parler de Signaux Aliens dans un Podcast",[35,20722,20723,20724,20728],{},"Cette année, MiXiT a lancé un podcast pour interviewer les intervenants sur leurs présentations. À partir de maintenant et jusqu'à l'édition 2026, ils publieront un nouvel épisode de 30 minutes toutes les trois semaines. C'est une stratégie astucieuse pour maintenir l'engagement de la communauté et continuer à apprendre de la conférence. Le podcast s'appelle ",[39,20725,8040],{"href":20726,"rel":20727},"https:\u002F\u002Fopen.spotify.com\u002Fshow\u002F1VjQM9KdqFVRRFzkgPf7b3",[77],", et je recommande fortement de s'abonner.",[35,20730,20731,20732,20736],{},"J'ai eu l'honneur de participer, discutant de ",[39,20733,20735],{"href":14042,"rel":20734},[77],"Signaux Aliens",". C'était une expérience nouvelle mais merveilleuse ; le temps a filé si vite en parlant de ce sujet que j'adore.",[35,20738,20739],{},[1216,20740,20741,20742,20746],{},"Initialement, je devais discuter de ",[39,20743,20745],{"href":14030,"rel":20744},[77],"Performance de l'Écosystème",", mais après l'intervention sur la réactivité et les signaux en frontend, ils m'ont demandé de couvrir cela à la place. J'étais ravi de le faire.",[35,20748,20749],{},[1707,20750],{"alt":20751,"src":20752},"Avec l'équipe du podcast après l'enregistrement.","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fpodcast.webp",[51,20754,20756],{"id":20755},"mes-interventions","Mes Interventions",[7151,20758,20762,20765,20793,20799],{"icon":20759,"text":20760,"href":20761},"i-ph-presentation-duotone","Je suis ouvert aux invitations si vous souhaitez que je parle à votre conférence!","mailto:esteban@soubiran.dev",[35,20763,20764],{},"J'ai été choisi pour présenter deux interventions :",[68,20766,20767,20778],{},[71,20768,20769,20771,20772,20774,20775,20777],{},[6862,20770,9137],{}," (Français, \"The Reactivity and Signals: Demystifying the Magic of Frontend\" en anglais)",[7174,20773],{},"C'est mon intervention phare, celle que j'ai passé des semaines à préparer. Heureusement, elle était déjà prête car je l'ai présentée récemment. L'objectif était simple : explorer et comprendre en profondeur la bibliothèque ",[6862,20776,14044],{}," à travers des explications réelles de code. Cette intervention est destinée à ceux qui recherchent une connaissance approfondie de la réactivité en JavaScript. La salle était pleine, ce qui m'étonne toujours étant donné la profondeur technique du sujet.",[71,20779,20780,20782,20783,20785,20786,20789,20790,20792],{},[6862,20781,13967],{}," (Français, \"In JavaScript, there is a package for everything... and often for nothing\" en anglais)",[7174,20784],{},"Cette intervention porte sur ",[39,20787,20745],{"href":14030,"rel":20788},[77],", une initiative visant à nettoyer, accélérer et améliorer l'écosystème JavaScript, en particulier les paquets. J'ai présenté le projet et les outils pour aider quiconque à comprendre ses ",[156,20791,14034],{}," et à procéder à des améliorations. Préparer et délivrer cette intervention de 15 minutes a été un vrai plaisir, et les retours indiquent que les participants ont acquis de nouvelles perspectives.",[35,20794,20795],{},[1707,20796],{"alt":20797,"src":20798},"Moi, devant le public","\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fme-in-front-of-audience.webp",[7151,20800,20802,20806,20809,20812,20817],{"icon":20759,"text":20801,"href":1833,"target":7156},"Découvrez les diapositives de mes interventions",[51,20803,20805],{"id":20804},"continuons","Continuons",[35,20807,20808],{},"J'adore ce mode de vie - voyager à travers la France, rencontrer des gens, découvrir leurs passions et partager les miennes. J'ai donc décidé de soumettre des propositions à six autres conférences en 2025. Cela peut sembler beaucoup, mais cela me motive tellement et permet d'éveiller son esprit. Chaque retour d'une conférence me remplit d'idées nouvelles, même s'il y a rarement assez de temps pour les explorer toutes. 🫠",[35,20810,20811],{},"Vous pourriez penser que soumettre mes propositions me laissera encore moins de temps en raison des besoins de préparation. Cependant, je l'ai fait de manière stratégique. J'ai proposé des interventions déjà préparées ; quand vous passez des mois à les perfectionner, vous voulez les présenter plusieurs fois !",[35,20813,20814],{},[1216,20815,20816],{},"La première fois est toujours la plus difficile, mais tout devient plus fluide par la suite.",[35,20818,20819],{},"Avec ce temps retrouvé, peut-être pourrais-je reprendre mes streams sur Twitch (en français). Seriez-vous au rendez-vous ? 🫣",{"title":13,"searchDepth":14,"depth":14,"links":20821},[20822,20823,20824,20825],{"id":20472,"depth":14,"text":18539},{"id":20650,"depth":14,"text":20651},{"id":20719,"depth":14,"text":20720},{"id":20755,"depth":14,"text":20756},"2025-04-30","Explorer mes insights et mes expériences à MiXiT 2025, où j'ai donné deux interventions et fait connaissance avec une nouvelle communauté.",{},"\u002Ffr\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025","---\ncontentId: 1f871ffb-d9c9-4b71-bc6b-bf260ceb227a\ntitle: \"La conférence tech qui parle éthique et diversité : MiXiT\"\ndescription: \"Explorer mes insights et mes expériences à MiXiT 2025, où j'ai donné deux interventions et fait connaissance avec une nouvelle communauté.\"\ndate: 2025-04-30\n---\n\nCette année, les choses évoluent. Ma première expérience de conférence a été Devoxx France en 2023, et jusqu'à présent, je n'avais jamais participé à une conférence en dehors de Paris. Cependant, voyager, rencontrer de nouvelles personnes, découvrir leurs passions et partager les miennes a toujours été quelque chose que j'apprécie.\n\nLorsque l'opportunité de prendre la parole à **MiXiT** s'est présentée, j'ai rapidement soumis plusieurs propositions. Quelques semaines plus tard, j'ai reçu un email plaisant : j'avais été sélectionné pour parler à l'édition 2025, délivrant non pas une, mais deux interventions. J'étais ravi d'assister à cette conférence et, pour la première fois, faire connaissance avec une nouvelle communauté.\n\n![Moi devant le Parc des Hauteurs à Lyon.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fme-in-front-of-parc-des-hauteurs.webp)\n\n![Vue de Lyon depuis le Parc des Hauteurs.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Flyon-from-parc-des-hauteurs.webp)\n\n_Merci, MiXiT, et à tous pour cette conférence incroyable ! 🫶_\n\n## MiXiT 2025\n\nJe dois confesser que je n'ai rien lu ni préparé pour cette conférence jusqu'à chaque matin. J'ai choisi d'apprécier le moment sans stress, ce qui a résulté en une totale absence de continuité dans tout ce que j'ai regardé - tout était basé sur l'intuition.\n\nVoici la liste des interventions auxquelles j'ai assisté :\n\n- **Mardi 29 avril 2025**\n  - **Robustesse : une nouvelle culture Tech ?** par [Olivier Hamant](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Folivier-hamant-a55149153\u002F)\n  - **LLMs, toutes ces questions que vous n'avez jamais osé poser à leur sujet** par [Guillaume Laforge](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fglaforge\u002F)\n  - **Stimuler l'Innovation de Votre Entreprise Grâce à une Plateforme Basée sur l'IA Générative** par [Philippe Laumay](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaumay\u002F)\n  - **Nos manières de compter, périls économiques, démocratiques et écologiques.** par [Valérie Charolles](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fval%C3%A9rie-charolles-88779594\u002F)\n  - **Quand le Terminal dévore la UI : TUI pour tout le monde !** par [Thierry Chantier](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthierrychantier\u002F)\n  - **Rendre transparents les changements des CGUs avec Open Terms Archive** par [Matti Schneider](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmattisg\u002F) et [Clément Biron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclementbiron\u002F)\n  - **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** par [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n  - **Design : sabotage, hacking & politique.** par [Geoffrey Dorne](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fgeoffreydorne\u002F)\n\n- **Mercredi 30 avril 2025**\n  - **Faut-il changer d'ère numérique pour préserver la démocratie ?** par [David Chavalarias](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdavid-chavalarias\u002F)\n  - **La réactivité et les signaux : démystifions la magie du frontend** par [Estéban Soubiran](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F)\n  - **Mapping the critical infrastructure sustaining our understanding of the Earth** par Codrina Maria Illie\n  - **L'aventure d'une requête HTTP - ou le chemin de la vie des devs** par [Pascal Martin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmartinpascal\u002F)\n  - **Les super-pouvoirs méconnus de Docker Desktop** par [Guillaume Lours](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fguillaumelours\u002F) et [Philippe Charrière](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fphcharriere\u002F)\n\n\u003CCallout w-full icon=\"i-simple-icons-youtube\" text=\"MiXiT\" to=\"https:\u002F\u002Fwww.youtube.com\u002F@mixitconf\" target=\"_blank\" \u002F>\n\nIl y avait des sujets vraiment fascinants à la conférence, et honnêtement, certains m'ont époustouflé.\n\nAssister à une conférence, c'est aussi une question de réseautage, et avec le dîner des intervenants lundi soir et le cocktail mardi soir, j'ai eu la chance de discuter avec de nombreuses personnes sur bon nombre de sujets - exactement ce que j'aime faire.\n\n![Mon verre de vin pendant le dîner des intervenants. C'est la seule photo que j'ai prise. Oups !](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fspeakers-dinner.webp)\n\n![L'hôtel de ville de Lyon, où le cocktail a eu lieu.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Ftown-hall.webp)\n\n![Le cocktail à l'hôtel de ville de Lyon.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fcocktail-party.webp)\n\n## Mes Interventions Préférées\n\nCertaines interventions ont particulièrement retenu mon attention ou résonné en moi. Voici quelques points forts :\n\n1. **Stimuler l'Innovation de Votre Entreprise Grâce à une Plateforme Basée sur l'IA Générative** par [Philippe Laumay](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Flaumay\u002F)\n\n   Intégrer l'IA dans votre entreprise est désormais absolument essentiel. C'est s'adapter ou périr. Lors de son intervention de 45 minutes, Philippe Laumay a expliqué comment son département R&D a mis en place un laboratoire pour permettre à chaque employé d'expérimenter avec l'IA générative. Les objectifs étaient d'éduquer l'entreprise sur cette technologie inévitable et de leur permettre de découvrir son potentiel pour l'intégrer dans les workflows. C'était véritablement impressionnant, montrant des années de travail avec des chiffres soutenant l'efficacité de cette approche. Si vous envisagez d'intégrer l'IA dans votre entreprise, cette intervention est incontournable.\n\n   ![Photo de Philippe Laumay lors de son intervention.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fstimuler-linnovation-de-votre-entreprise-grace-a-une-plateforme-basee-sur-lia-generative.webp)\n\n2. **Rendre transparents les changements des CGUs avec Open Terms Archive** par [Matti Schneider](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmattisg\u002F) et [Clément Biron](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclementbiron\u002F)\n\n   Nous avons tous reçu des courriels nous informant des changements dans les conditions de service. Mais qui les lit ? Comment vérifions-nous les changements réels ? Cette intervention a couvert un projet open-source appelé [Open Terms Archive](https:\u002F\u002Fopentermsarchive.org\u002Fen\u002F), visant à rendre les conditions de service des grandes entreprises technologiques transparentes en enregistrant publiquement chaque version pour un contrôle démocratique. Ils ont exposé des changements dans les dernières conditions de Meta, notamment en ce qui concerne la liberté d'expression, et j'ai été vraiment surpris par leurs découvertes.\n\n   ![Photo de Matti Schneider et Clément Biron lors de leur intervention.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Frendre-transparents-les-changements-des-cgus-avec-open-terms-archive.webp)\n\n3. **L'aventure d'une requête HTTP - ou le chemin de la vie des devs** par [Pascal Martin](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmartinpascal\u002F)\n\n   Pendant ce créneau, j'étais incertain sur l'intervention à laquelle assister. Sur un coup de tête, j'ai choisi celle-ci, ne connaissant pas l'intervenant et ayant à peine consulté le résumé. Cela s'est avéré être un excellent choix. Pascal Martin a ouvert avec une question : \"Que se passe-t-il lorsque vous appuyez sur entrer dans votre navigateur ?\" Cela a conduit à une explication captivante de 45 minutes des complexités du développement web. C'était éclairant, et je prévois de partager la vidéo avec quiconque pense que le web se limite simplement à HTML, CSS, et JavaScript.\n\n   ![Photo de Pascal Martin lors de son intervention.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Flaventure-dune-requete-http-ou-le-chemin-de-la-vie-des-devs.webp)\n\nJe vous recommande vivement de regarder ces interventions.\n\n## Parler de Signaux Aliens dans un Podcast\n\nCette année, MiXiT a lancé un podcast pour interviewer les intervenants sur leurs présentations. À partir de maintenant et jusqu'à l'édition 2026, ils publieront un nouvel épisode de 30 minutes toutes les trois semaines. C'est une stratégie astucieuse pour maintenir l'engagement de la communauté et continuer à apprendre de la conférence. Le podcast s'appelle [MiXiT On Air](https:\u002F\u002Fopen.spotify.com\u002Fshow\u002F1VjQM9KdqFVRRFzkgPf7b3), et je recommande fortement de s'abonner.\n\nJ'ai eu l'honneur de participer, discutant de [Signaux Aliens](https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals). C'était une expérience nouvelle mais merveilleuse ; le temps a filé si vite en parlant de ce sujet que j'adore.\n\n_Initialement, je devais discuter de [Performance de l'Écosystème](https:\u002F\u002Fe18e.dev\u002F), mais après l'intervention sur la réactivité et les signaux en frontend, ils m'ont demandé de couvrir cela à la place. J'étais ravi de le faire._\n\n![Avec l'équipe du podcast après l'enregistrement.](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fpodcast.webp)\n\n## Mes Interventions\n\n\u003CCallout icon=\"i-ph-presentation-duotone\" text=\"Je suis ouvert aux invitations si vous souhaitez que je parle à votre conférence!\" href=\"mailto:esteban@soubiran.dev\" \u002F>\n\nJ'ai été choisi pour présenter deux interventions :\n\n1. **La réactivité et les signaux : démystifions la magie du frontend** (Français, \"The Reactivity and Signals: Demystifying the Magic of Frontend\" en anglais)\n\n   C'est mon intervention phare, celle que j'ai passé des semaines à préparer. Heureusement, elle était déjà prête car je l'ai présentée récemment. L'objectif était simple : explorer et comprendre en profondeur la bibliothèque **Alien Signals** à travers des explications réelles de code. Cette intervention est destinée à ceux qui recherchent une connaissance approfondie de la réactivité en JavaScript. La salle était pleine, ce qui m'étonne toujours étant donné la profondeur technique du sujet.\n\n2. **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** (Français, \"In JavaScript, there is a package for everything... and often for nothing\" en anglais)\n\n   Cette intervention porte sur [Performance de l'Écosystème](https:\u002F\u002Fe18e.dev\u002F), une initiative visant à nettoyer, accélérer et améliorer l'écosystème JavaScript, en particulier les paquets. J'ai présenté le projet et les outils pour aider quiconque à comprendre ses `node_modules` et à procéder à des améliorations. Préparer et délivrer cette intervention de 15 minutes a été un vrai plaisir, et les retours indiquent que les participants ont acquis de nouvelles perspectives.\n\n![Moi, devant le public](\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025\u002Fme-in-front-of-audience.webp)\n\n\u003CCallout icon=\"i-ph-presentation-duotone\" text=\"Découvrez les diapositives de mes interventions\" href=\"\u002Ffr\u002Ftalks\" target=\"_blank\" \u002F>\n\n## Continuons\n\nJ'adore ce mode de vie - voyager à travers la France, rencontrer des gens, découvrir leurs passions et partager les miennes. J'ai donc décidé de soumettre des propositions à six autres conférences en 2025. Cela peut sembler beaucoup, mais cela me motive tellement et permet d'éveiller son esprit. Chaque retour d'une conférence me remplit d'idées nouvelles, même s'il y a rarement assez de temps pour les explorer toutes. 🫠\n\nVous pourriez penser que soumettre mes propositions me laissera encore moins de temps en raison des besoins de préparation. Cependant, je l'ai fait de manière stratégique. J'ai proposé des interventions déjà préparées ; quand vous passez des mois à les perfectionner, vous voulez les présenter plusieurs fois !\n\n_La première fois est toujours la plus difficile, mais tout devient plus fluide par la suite._\n\nAvec ce temps retrouvé, peut-être pourrais-je reprendre mes streams sur Twitch (en français). Seriez-vous au rendez-vous ? 🫣\n",{"title":20441,"description":20827},{"loc":20829,"images":20833},[20834,20835,20836,20837,20838,20839,20840,20841,20842,20843],{"loc":20458},{"loc":20464},{"loc":20635},{"loc":20641},{"loc":20647},{"loc":20672},{"loc":20697},{"loc":20713},{"loc":20752},{"loc":20798},"fr\u002Fposts\u002Fthe-conference-for-ethics-and-diversity-in-tech-mixit-2025","JiFSZBDK_QjvQ0S0ybW4o4_-seob2PbvNxNNPpzlXEM",{"id":20847,"title":20848,"body":20849,"contentId":9300,"date":21356,"description":21357,"extension":18,"meta":21358,"navigation":20,"path":21359,"rawbody":21360,"robots":23,"schemaOrg":23,"seo":21361,"sitemap":21362,"stem":21377,"__hash__":21378},"posts\u002Ffr\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special.md","Au-delà de la tech : un Devoxx France 2025 spécial",{"type":7,"value":20850,"toc":21353},[20851,20857],[35,20852,20853,20854,20856],{},"Cette année, j'ai eu le plaisir d'assister à ",[6862,20855,7154],{}," en tant qu'intervenant pour la troisième fois.",[7918,20858,20860,20863,20868,20874,20878,20881,20884,20897,20900,20903,21029,21035],{"id":20859},"f3e97a6a-ce47-42b8-8493-06eb37f8e964",[35,20861,20862],{},"Cependant, cette année était différente des deux précédentes. Je me sentais plus à l'aise, sans la pression de donner deux conférences ou la nécessité d'assister à toutes les sessions. J'ai rencontré des individus incroyables qui ont rendu cet événement véritablement spécial et mémorable.",[35,20864,20865],{},[1216,20866,20867],{},"Devoxx France est une conférence qui se tient à Paris, France, et qui propose trois jours de conférences, 70 exposants, plus de 300 intervenants, et 4 500 participants.",[35,20869,20870],{},[1707,20871],{"alt":20872,"src":20873},"Ouverture de Devoxx France.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fdevoxx-france-opening.webp",[51,20875,20877],{"id":20876},"mon-emploi-du-temps","Mon emploi du temps",[35,20879,20880],{},"Comme toujours, je préparais mon emploi du temps chaque matin en attendant la première session. Arriver 45 minutes en avance pour être le premier dans la file d'attente me donnait le temps de planifier ma journée !",[35,20882,20883],{},"Tu te demandes peut-être pourquoi j'arrive si tôt. La raison est simple : chaque jour commence par deux keynotes, mais l'amphithéâtre principal ne compte que 800 places. Bien qu'elles soient diffusées dans d'autres salles, je préfère être dans la salle principale pour voir les intervenants en personne et profiter de l'atmosphère. Pour sécuriser la meilleure place, je dois arriver tôt, car la file d'attente est déjà longue 40 minutes avant le début de la première session.",[35,20885,20886,20887,3092,20890,1655,20893,20896],{},"Cette année, de nombreuses sessions étaient axées sur ",[6862,20888,20889],{},"l'IA",[6862,20891,20892],{},"les LLM",[6862,20894,20895],{},"les agents",". Il est fascinant de voir l'évolution de l'IA reflétée dans les titres des sessions. En 2023, il était question de \"l'IA\" et de \"LLM\" ; en 2024, de \"l'IA\" et de \"RAG\" (Retrieval-Augmented Generation) ; et en 2025, de \"l'IA\" et des \"agents\". C'est incroyable à quel point le paysage change rapidement !",[35,20898,20899],{},"Pourtant, j'ai tout fait pour éviter les sessions sur l'IA et les LLM, car ce sujet est déjà omniprésent. Je cherchais des sessions avec des thèmes moins conventionnels.",[35,20901,20902],{},"Voici l'ensemble des conférences que j'ai pu voir pour les trois jours de Devoxx France 2025 :",[68,20904,20905,20939,20988],{},[71,20906,20907,20908],{},"Mercredi 16 avril\n",[1593,20909,20910,20916,20921,20927,20933],{},[71,20911,20912,20915],{},[6862,20913,20914],{},"L'Intelligence Artificielle n'existe pas"," par Luc Julia",[71,20917,20918,20915],{},[6862,20919,20920],{},"Luc Julia sans filtre : l'IA, la tech et les idées reçues",[71,20922,20923,20926],{},[6862,20924,20925],{},"Arcane: Quand la technologie n'est pas le produit: une série d'animation"," par Philippe Llerena",[71,20928,20929,20932],{},[6862,20930,20931],{},"Comment nous avons transformé les Restos du Cœur en Cloud Provider"," par Julien Briault et Stéphane Trognon",[71,20934,20935,20938],{},[6862,20936,20937],{},"\"Ça marche dans mon .devcontainer\""," par Benoit Moussaud",[71,20940,20941,20942],{},"Jeudi 17 avril\n",[1593,20943,20944,20950,20956,20962,20968,20974,20980],{},[71,20945,20946,20949],{},[6862,20947,20948],{},"Language, IA et propagande : la guerre des récits a déjà commencé"," par Elodie Mielczareck",[71,20951,20952,20955],{},[6862,20953,20954],{},"La territorialisation des infrastructures comme levier de pouvoir"," par Ophélie Coelho",[71,20957,20958,20961],{},[6862,20959,20960],{},"Les clés de l'architecture pour les devs"," par Christian Sperandio et Cyrille Martraire",[71,20963,20964,20967],{},[6862,20965,20966],{},"Design System : Du Chaos à la Cohérence avec style !"," par Claire Gourcy",[71,20969,20970,20973],{},[6862,20971,20972],{},"On met en place une stack React props et complète"," par Olivier Abdelnour",[71,20975,20976,20979],{},[6862,20977,20978],{},"Load-testons M6+ pour préparer l'Euro 2024"," par Paul-Henri Pillet et Benoit Colin",[71,20981,20982,20984,20985,2104],{},[6862,20983,9137],{}," par Estéban Soubiran (",[1216,20986,20987],{},"c'est moi !",[71,20989,20990,20991],{},"Vendredi 18 avril\n",[1593,20992,20993,20999,21005,21010,21017,21023],{},[71,20994,20995,20998],{},[6862,20996,20997],{},"Plongez dans l'Ère Quantique : décryptez et anticipez la révolution à venir"," par Fanny Bouton",[71,21000,21001,21004],{},[6862,21002,21003],{},"Les LLM rêvent-ils de cavaliers électriques ?"," par Thibaut Giraud",[71,21006,21007,21009],{},[6862,21008,16604],{}," par Loïc Ortola et Mathilde Lorrain",[71,21011,21012,20984,21014,2104],{},[6862,21013,13967],{},[1216,21015,21016],{},"c'est moi, encore !",[71,21018,21019,21022],{},[6862,21020,21021],{},"Construire une IA agentique avec les Structured Outputs, Function Calling et MCP"," par Julien Dubois",[71,21024,21025,21028],{},[6862,21026,21027],{},"Optimisation des requêtes PostgreSQL : Parlons Performance !"," par Laetitia Avrot",[35,21030,21031,21032,215],{},"J'ai dû faire des choix et j'ai probablement manqué d'excellentes conférences. Heureusement, les sessions sont enregistrées et disponibles sur la ",[6862,21033,21034],{},"chaîne YouTube de Devoxx France",[7151,21036,21037,21064,21069,21073,21076,21200,21203,21206,21209,21274],{"w-full":13,"icon":7153,"text":7154,"to":7155,"target":7156},[35,21038,21039,21040,21045,21046,21051,21052,21057,21058,21063],{},"Cette année, j'ai assisté à moins de sessions que les années précédentes, préférant passer plus de temps pour échanger avec les personnes présentes. Un des moments que j'ai fortement apprécié a été ma visite au stand de Clever Cloud, où j'ai eu une conversation fascinante et passionnante avec ",[39,21041,21044],{"href":21042,"rel":21043},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcnivolle\u002F",[77],"Clément Nivolle",", co-fondateur de Clever Cloud. Nous avons discuté de sujets allant de l'écosystème cloud français à l'influence des hyperscalers sur la politique. Au stand de Microsoft, j'ai discuté avec ",[39,21047,21050],{"href":21048,"rel":21049},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbaptisteohanes\u002F",[77],"Baptiste Ohanes"," de Microsoft, du cloud et de l'IA. J'ai également renoué des liens avec ",[39,21053,21056],{"href":21054,"rel":21055},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftugdualgrall\u002F",[77],"Tugdual Grall",", que j'avais rencontré à ",[39,21059,21062],{"href":21060,"rel":21061},"https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023#github-galaxy",[77],"GitHub Galaxy 2023",". Il a présenté les nouvelles fonctionnalités de GitHub Copilot, comme le mode Agent. En voyant sa démo, il était clair qu'en ne pas utilisant l'IA, on manque de nombreuses avancées. Merci à tous pour ces discussions formidables !",[35,21065,21066],{},[1216,21067,21068],{},"Si tu assistes à des conférences, ne néglige pas la valeur du réseautage : cela peut être plus passionnant que les sessions elles-mêmes.",[51,21070,21072],{"id":21071},"les-conférences-que-jai-adorées","Les conférences que j'ai adorées",[35,21074,21075],{},"J'ai eu le privilège d'assister à de nombreuses conférences exceptionnelles, et voici quelques-unes de mes préférées (sans ordre particulier) :",[68,21077,21078,21107,21134,21169],{},[71,21079,21080,6865,21082,21087,21089,21094,21095,21100,21101,21103],{},[6862,21081,20925],{},[39,21083,21086],{"href":21084,"rel":21085},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fphilippellerena\u002F",[77],"Philippe Llerena",[7174,21088],{},[39,21090,21093],{"href":21091,"rel":21092},"https:\u002F\u002Fwww.arcane.com\u002Fen-us\u002F",[77],"Arcane"," est un phénomène, une véritable œuvre d'art. Il est génial de présenter une conférence sur Arcane. Philippe, le CTO de ",[39,21096,21099],{"href":21097,"rel":21098},"https:\u002F\u002Fforticheprod.com\u002F",[77],"Fortiche",", le studio derrière Arcane, a expliqué le rôle crucial que joue la technologie dans sa production. Il était rafraîchissant de voir comment les développeurs peuvent être de véritable support aux artistes et non livrer une nouvelle application.",[7174,21102],{},[1707,21104],{"alt":21105,"src":21106},"Photo de Philippe Llerena lors de sa présentation.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Farcane-quand-la-technologie-n-est-pas-le-produit-une-serie-d-animation.webp",[71,21108,21109,6865,21111,1655,21114,21119,21121,21122,21127,21128,21130],{},[6862,21110,20931],{},[39,21112,6955],{"href":6953,"rel":21113},[77],[39,21115,21118],{"href":21116,"rel":21117},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fstephanetrognon\u002F",[77],"Stéphane Trognon",[7174,21120],{},"Les ",[39,21123,21126],{"href":21124,"rel":21125},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRestaurants_du_C%C5%93ur",[77],"Restos du Cœur"," sont une association caritative française fournissant une aide alimentaire. Dans cette conférence de trois heures, Julien et Stéphane ont partagé l'histoire derrière la transformation de leur système informatique. Le lien entre une association caritative et un fournisseur de cloud peut sembler flou, mais avec plus de 35 000 personnes servies quotidiennement, et chaque euro équivalant à un repas, un fournisseur coûteux n'était pas une option. Ce parcours a conduit à la création d'un fournisseur de cloud interne. Cela illustre l'utilisation de la technologie pour des solutions humanitaires. Leur travail est véritablement inspirant, et j'espère que le projet continuera de croître.",[7174,21129],{},[1707,21131],{"alt":21132,"src":21133},"Photo de Julien Briault et Stéphane Trognon lors de leur présentation.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fcomment-nous-avons-transforme-les-restos-du-coeur-en-cloud-provider.webp",[71,21135,21136,6865,21138,21143,21145,21146,21151,21152,21157,21158,215,21163,21165],{},[6862,21137,20966],{},[39,21139,21142],{"href":21140,"rel":21141},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclaire-gourcy-3197861a\u002F",[77],"Claire Gourcy",[7174,21144],{},"Claire, une designer UX-UI chez ",[39,21147,21150],{"href":21148,"rel":21149},"https:\u002F\u002Fwww.michelin.com",[77],"Michelin",", a partagé le parcours derrière la création du système de design de Michelin, qui unifie l'expérience à travers tous les produits, services et outils. Cela m'a rappelé le défi auquel ",[39,21153,21156],{"href":21154,"rel":21155},"https:\u002F\u002Fwww.infomaniak.com\u002F",[77],"Infomaniak"," est confronté pour unifier ses plateformes et services web. Il était impressionnant de voir l'évolution du projet, d'une initiative à une mise en œuvre complète du ",[39,21159,21162],{"href":21160,"rel":21161},"https:\u002F\u002Fdesignsystem.michelin.com\u002F",[77],"Motion Design System",[7174,21164],{},[1707,21166],{"alt":21167,"src":21168},"Photo de Claire Gourcy et Bibendum lors de sa présentation.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fdesign-system-du-chaos-a-la-coherence-avec-style.webp",[71,21170,21171,6865,21173,1655,21176,21179,21181,21182,21187,21188,21190,21194,21196],{},[6862,21172,16604],{},[39,21174,16608],{"href":9596,"rel":21175},[77],[39,21177,16613],{"href":16611,"rel":21178},[77],[7174,21180],{},"Cette conférence était une masterclass. Le contenu était incroyablement perspicace, j'ai beaucoup appris, et la présentation de Loïc et Mathilde était impressionnante. Leur collaboration, leur humour avec le moment ",[39,21183,21186],{"href":21184,"rel":21185},"https:\u002F\u002Fwww.youtube.com\u002Fuser\u002Fbref",[77],"Bref"," et l'utilisation à la fois de diapositives, du code, du refactoring et des exemples concrets ont rendu la présentation engageante. Les conseils pratiques ont montré la puissance des tests de charge.",[7174,21189],{},[1707,21191],{"alt":21192,"src":21193},"Photo de Loïc Ortola et Mathilde Lorrain lors de leur présentation.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002F45-min-pour-mettre-son-application-a-genoux-le-guide-complet-du-test-de-charge.webp",[7174,21195],{},[1707,21197],{"alt":21198,"src":21199},"Photo de Loïc Ortola et Mathilde prenant un selfie avec le public. (Sur LinkedIn, vous pouvez trouver le selfie avec le public.)","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Floic-ortola-mathilde-lorrain-selfie.webp",[35,21201,21202],{},"Je recommande vivement de visionner ces conférences.",[51,21204,21205],{"id":20755},"Mes interventions",[35,21207,21208],{},"Cette année, j'ai eu le plaisir de donner deux conférences, ce qui a impliqué beaucoup de plaisir et de travail acharné ! 🫠",[68,21210,21211,21254],{},[71,21212,21213,21215,21217,21218,21220,21221,21223,21227,21229,21230,21234,21235,21237,21238,21240,21244,21246,21247,21249,21250,21253],{},[6862,21214,9137],{},[7174,21216],{},"C'était ma conférence phare, et j'ai passé des semaines à la préparer (et je l'améliore continuellement pour de futures présentations). L'objectif était simple : explorer et comprendre en profondeur la bibliothèque ",[6862,21219,14044],{}," à travers des explications de code réelles. Cette conférence cible ceux qui cherchent à comprendre la réactivité en JavaScript. La salle pleine indiquait un vif intérêt, avec plus de 200 participants. Quand on m'a informé qu'il n'y avait plus de places libres cinq minutes avant ma conférence, mon niveau de stress a augmenté. Mais une fois que j'ai commencé, je suis rentré dans ma zone, et le stress a disparu.",[7174,21222],{},[1707,21224],{"alt":21225,"src":21226},"Photo de moi avant ma présentation.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend.webp",[7174,21228],{},"Après un an de travail, c'était la première véritable utilisation de ",[39,21231,7894],{"href":21232,"rel":21233},"https:\u002F\u002Finalia.app\u002F",[77],", le SaaS que je développe pour améliorer les conférences avec des interactions en temps réel. Une fonctionnalité permet au public de réagir avec des émojis, partageant leurs pensées et sentiments en temps réel. Voir 🍆, 🍑, et 👅 à l'écran était amusant, nécessitant parfois que je me concentre ailleurs que sur mes diapositives ! 😆",[7174,21236],{},"Plus de 20 000 réactions par émoji ont été envoyées pendant la présentation ! 🤯",[7174,21239],{},[1707,21241],{"alt":21242,"src":21243},"Photo de ma présentation couverte d'émojis.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend-emoji.webp",[7174,21245],{},"Lorsque le public utilise efficacement les réactions, cela devient transformateur. Je peux sentir quand ils sont perdus, ennuyés ou réellement intéressés.",[7174,21248],{},"Comprendre ",[6862,21251,21252],{},"Align Signals"," et construire cette plateforme a été laborieux mais en valait la peine. Les retours ont été extrêmement positifs, malgré le contenu technique et approfondi. Merci à tous pour vos retours et votre enthousiasme.",[71,21255,21256,21258,21260,21261,20789,21265,21267,21268,21270],{},[6862,21257,13967],{},[7174,21259],{},"Cette présentation se concentre sur ",[39,21262,21264],{"href":14030,"rel":21263},[77],"Ecosystem Performance",[156,21266,14034],{}," et à faire des améliorations. Préparer et présenter cette conférence de 15 minutes a été amusant, et les retours montrent que les participants ont appris quelque chose de nouveau.",[7174,21269],{},[1707,21271],{"alt":21272,"src":21273},"Photo du public pendant ma présentation.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fen-javascript-il-y-a-un-paquet-pour-tout-et-souvent-pour-rien.webp",[7151,21275,21278,21282,21285,21288,21295,21305,21311,21317,21323,21329,21334,21338,21350],{"icon":20759,"text":21276,"href":21277,"target":7156},"Découvrez les diapositives de mes présentations","\u002Ftalks",[51,21279,21281],{"id":21280},"remerciements-spéciaux-à-takima","Remerciements spéciaux à Takima",[35,21283,21284],{},"Parfois, une seule rencontre peut transformer complètement votre expérience.",[35,21286,21287],{},"Je ne suis pas la personne la plus extravertie, et j'ai toujours trouvé qu'il était difficile d'engager la conversation avec de nouvelles personnes, même l'approche des exposants peut être difficile. Au fil des années, j'ai assisté à des meetups, conférences et autres événements seul, car mes amis ne sont pas des développeurs web. Bien que j'apprécie d'aller à ces rassemblements, cela peut être un peu épuisant d'y aller seul, surtout lors d'un événement de trois jours comme Devoxx France où le réseautage joue un grand rôle, ou pendant la soirée de rencontre où les gens sont au cœur du moment.",[35,21289,21290,21291,21294],{},"Tout cela a changé lorsque j'ai rencontré l'équipe incroyable de ",[39,21292,9589],{"href":9587,"rel":21293},[77]," lors de leurs événements TakiMeet. Ils ont assisté à ma présentation, et leur stand avait l'air d'un quartier général, un endroit où j'avais toujours quelqu'un avec qui discuter ou même un endroit pour m'asseoir et me détendre, même avec des batteries sociales vides. Cette année à Devoxx a été absolument incroyable !",[35,21296,21297,21298,21300,21301,21304],{},"Merci à la formidable équipe de ",[6862,21299,9589],{}," - avec des remerciements particuliers à ",[1216,21302,21303],{},"Mathilde, Camille et Cyrielle"," - pour les conversations que nous avons partagées. J'ai vraiment apprécié tous vous rencontrer et j'espère à bientôt. 🫶",[35,21306,21307],{},[1707,21308],{"alt":21309,"src":21310},"Photo du stand Takima à Devoxx France 2025.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-stand.webp",[35,21312,21313],{},[1707,21314],{"alt":21315,"src":21316},"Une partie de l'équipe Takima regardant une présentation d'un de leurs collègues.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-team.webp",[35,21318,21319],{},[1707,21320],{"alt":21321,"src":21322},"Stand Takima pendant le tirage au sort pour gagner des écouteurs, des enceintes et des formations.","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-game.webp",[35,21324,21325],{},[1707,21326],{"alt":21327,"src":21328},"Bidendum, une partie de l'équipe Takima, et moi !","\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-bidendum.webp",[35,21330,21331],{},[1216,21332,21333],{},"Tout tourne autour des gens !",[51,21335,21337],{"id":21336},"à-suivre","À suivre",[35,21339,21340,21341,21345,21346,21349],{},"Devoxx France a été un régale, mais l'aventure ne s'arrête pas là. À la fin du mois d'avril, j'assisterai à ",[39,21342,7497],{"href":21343,"rel":21344},"https:\u002F\u002Fmixitconf.org\u002Ffr\u002F",[77]," pour donner la même présentation sur la réactivité et les signaux. Le voyage continue en juin avec ",[39,21347,9020],{"href":18204,"rel":21348},[77],". Si vous êtes dans le coin, n'hésitez pas à venir dire bonjour !",[35,21351,21352],{},"Bon code ! 👋",{"title":13,"searchDepth":14,"depth":14,"links":21354},[21355],{"id":20876,"depth":14,"text":20877},"2025-04-18","Un récapitulatif de mon expérience à Devoxx France 2025, axé sur les personnes et les connexions qui ont rendu l'événement mémorable.",{},"\u002Ffr\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special","---\ncontentId: 29c1e474-c428-4506-9d4b-23b420bf9f64\ntitle: \"Au-delà de la tech : un Devoxx France 2025 spécial\"\ndescription: \"Un récapitulatif de mon expérience à Devoxx France 2025, axé sur les personnes et les connexions qui ont rendu l'événement mémorable.\"\ndate: 2025-04-18\n---\n\nCette année, j'ai eu le plaisir d'assister à **Devoxx France** en tant qu'intervenant pour la troisième fois.\n\n\u003CCalloutReadMore id=\"f3e97a6a-ce47-42b8-8493-06eb37f8e964\" \u002F>\n\nCependant, cette année était différente des deux précédentes. Je me sentais plus à l'aise, sans la pression de donner deux conférences ou la nécessité d'assister à toutes les sessions. J'ai rencontré des individus incroyables qui ont rendu cet événement véritablement spécial et mémorable.\n\n_Devoxx France est une conférence qui se tient à Paris, France, et qui propose trois jours de conférences, 70 exposants, plus de 300 intervenants, et 4 500 participants._\n\n![Ouverture de Devoxx France.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fdevoxx-france-opening.webp)\n\n## Mon emploi du temps\n\nComme toujours, je préparais mon emploi du temps chaque matin en attendant la première session. Arriver 45 minutes en avance pour être le premier dans la file d'attente me donnait le temps de planifier ma journée !\n\nTu te demandes peut-être pourquoi j'arrive si tôt. La raison est simple : chaque jour commence par deux keynotes, mais l'amphithéâtre principal ne compte que 800 places. Bien qu'elles soient diffusées dans d'autres salles, je préfère être dans la salle principale pour voir les intervenants en personne et profiter de l'atmosphère. Pour sécuriser la meilleure place, je dois arriver tôt, car la file d'attente est déjà longue 40 minutes avant le début de la première session.\n\nCette année, de nombreuses sessions étaient axées sur **l'IA**, **les LLM** et **les agents**. Il est fascinant de voir l'évolution de l'IA reflétée dans les titres des sessions. En 2023, il était question de \"l'IA\" et de \"LLM\" ; en 2024, de \"l'IA\" et de \"RAG\" (Retrieval-Augmented Generation) ; et en 2025, de \"l'IA\" et des \"agents\". C'est incroyable à quel point le paysage change rapidement !\n\nPourtant, j'ai tout fait pour éviter les sessions sur l'IA et les LLM, car ce sujet est déjà omniprésent. Je cherchais des sessions avec des thèmes moins conventionnels.\n\nVoici l'ensemble des conférences que j'ai pu voir pour les trois jours de Devoxx France 2025 :\n\n1. Mercredi 16 avril\n   - **L'Intelligence Artificielle n'existe pas** par Luc Julia\n   - **Luc Julia sans filtre : l'IA, la tech et les idées reçues** par Luc Julia\n   - **Arcane: Quand la technologie n'est pas le produit: une série d'animation** par Philippe Llerena\n   - **Comment nous avons transformé les Restos du Cœur en Cloud Provider** par Julien Briault et Stéphane Trognon\n   - **\"Ça marche dans mon .devcontainer\"** par Benoit Moussaud\n2. Jeudi 17 avril\n   - **Language, IA et propagande : la guerre des récits a déjà commencé** par Elodie Mielczareck\n   - **La territorialisation des infrastructures comme levier de pouvoir** par Ophélie Coelho\n   - **Les clés de l'architecture pour les devs** par Christian Sperandio et Cyrille Martraire\n   - **Design System : Du Chaos à la Cohérence avec style !** par Claire Gourcy\n   - **On met en place une stack React props et complète** par Olivier Abdelnour\n   - **Load-testons M6+ pour préparer l'Euro 2024** par Paul-Henri Pillet et Benoit Colin\n   - **La réactivité et les signaux : démystifions la magie du frontend** par Estéban Soubiran (_c'est moi !_)\n3. Vendredi 18 avril\n   - **Plongez dans l'Ère Quantique : décryptez et anticipez la révolution à venir** par Fanny Bouton\n   - **Les LLM rêvent-ils de cavaliers électriques ?** par Thibaut Giraud\n   - **45 min pour mettre son application à genoux : le guide complet du test de charge** par Loïc Ortola et Mathilde Lorrain\n   - **En JavaScript, il y a un paquet pour tout... et souvent pour rien.** par Estéban Soubiran (_c'est moi, encore !_)\n   - **Construire une IA agentique avec les Structured Outputs, Function Calling et MCP** par Julien Dubois\n   - **Optimisation des requêtes PostgreSQL : Parlons Performance !** par Laetitia Avrot\n\nJ'ai dû faire des choix et j'ai probablement manqué d'excellentes conférences. Heureusement, les sessions sont enregistrées et disponibles sur la **chaîne YouTube de Devoxx France**.\n\n\u003CCallout w-full icon=\"i-simple-icons-youtube\" text=\"Devoxx France\" to=\"https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos\" target=\"_blank\" \u002F>\n\nCette année, j'ai assisté à moins de sessions que les années précédentes, préférant passer plus de temps pour échanger avec les personnes présentes. Un des moments que j'ai fortement apprécié a été ma visite au stand de Clever Cloud, où j'ai eu une conversation fascinante et passionnante avec [Clément Nivolle](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fcnivolle\u002F), co-fondateur de Clever Cloud. Nous avons discuté de sujets allant de l'écosystème cloud français à l'influence des hyperscalers sur la politique. Au stand de Microsoft, j'ai discuté avec [Baptiste Ohanes](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbaptisteohanes\u002F) de Microsoft, du cloud et de l'IA. J'ai également renoué des liens avec [Tugdual Grall](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Ftugdualgrall\u002F), que j'avais rencontré à [GitHub Galaxy 2023](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023#github-galaxy). Il a présenté les nouvelles fonctionnalités de GitHub Copilot, comme le mode Agent. En voyant sa démo, il était clair qu'en ne pas utilisant l'IA, on manque de nombreuses avancées. Merci à tous pour ces discussions formidables !\n\n_Si tu assistes à des conférences, ne néglige pas la valeur du réseautage : cela peut être plus passionnant que les sessions elles-mêmes._\n\n## Les conférences que j'ai adorées\n\nJ'ai eu le privilège d'assister à de nombreuses conférences exceptionnelles, et voici quelques-unes de mes préférées (sans ordre particulier) :\n\n1. **Arcane: Quand la technologie n'est pas le produit: une série d'animation** par [Philippe Llerena](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fphilippellerena\u002F)\n\n   [Arcane](https:\u002F\u002Fwww.arcane.com\u002Fen-us\u002F) est un phénomène, une véritable œuvre d'art. Il est génial de présenter une conférence sur Arcane. Philippe, le CTO de [Fortiche](https:\u002F\u002Fforticheprod.com\u002F), le studio derrière Arcane, a expliqué le rôle crucial que joue la technologie dans sa production. Il était rafraîchissant de voir comment les développeurs peuvent être de véritable support aux artistes et non livrer une nouvelle application.\n\n   ![Photo de Philippe Llerena lors de sa présentation.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Farcane-quand-la-technologie-n-est-pas-le-produit-une-serie-d-animation.webp)\n\n2. **Comment nous avons transformé les Restos du Cœur en Cloud Provider** par [Julien Briault](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjulien-briault-441539137\u002F) et [Stéphane Trognon](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fstephanetrognon\u002F)\n\n   Les [Restos du Cœur](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRestaurants_du_C%C5%93ur) sont une association caritative française fournissant une aide alimentaire. Dans cette conférence de trois heures, Julien et Stéphane ont partagé l'histoire derrière la transformation de leur système informatique. Le lien entre une association caritative et un fournisseur de cloud peut sembler flou, mais avec plus de 35 000 personnes servies quotidiennement, et chaque euro équivalant à un repas, un fournisseur coûteux n'était pas une option. Ce parcours a conduit à la création d'un fournisseur de cloud interne. Cela illustre l'utilisation de la technologie pour des solutions humanitaires. Leur travail est véritablement inspirant, et j'espère que le projet continuera de croître.\n\n   ![Photo de Julien Briault et Stéphane Trognon lors de leur présentation.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fcomment-nous-avons-transforme-les-restos-du-coeur-en-cloud-provider.webp)\n\n3. **Design System : Du Chaos à la Cohérence avec style !** par [Claire Gourcy](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fclaire-gourcy-3197861a\u002F)\n\n   Claire, une designer UX-UI chez [Michelin](https:\u002F\u002Fwww.michelin.com), a partagé le parcours derrière la création du système de design de Michelin, qui unifie l'expérience à travers tous les produits, services et outils. Cela m'a rappelé le défi auquel [Infomaniak](https:\u002F\u002Fwww.infomaniak.com\u002F) est confronté pour unifier ses plateformes et services web. Il était impressionnant de voir l'évolution du projet, d'une initiative à une mise en œuvre complète du [Motion Design System](https:\u002F\u002Fdesignsystem.michelin.com\u002F).\n\n   ![Photo de Claire Gourcy et Bibendum lors de sa présentation.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fdesign-system-du-chaos-a-la-coherence-avec-style.webp)\n\n4. **45 min pour mettre son application à genoux : le guide complet du test de charge** par [Loïc Ortola](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Floicortola\u002F) et [Mathilde Lorrain](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmathilde-lorrain\u002F)\n\n   Cette conférence était une masterclass. Le contenu était incroyablement perspicace, j'ai beaucoup appris, et la présentation de Loïc et Mathilde était impressionnante. Leur collaboration, leur humour avec le moment [Bref](https:\u002F\u002Fwww.youtube.com\u002Fuser\u002Fbref) et l'utilisation à la fois de diapositives, du code, du refactoring et des exemples concrets ont rendu la présentation engageante. Les conseils pratiques ont montré la puissance des tests de charge.\n\n   ![Photo de Loïc Ortola et Mathilde Lorrain lors de leur présentation.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002F45-min-pour-mettre-son-application-a-genoux-le-guide-complet-du-test-de-charge.webp)\n\n   ![Photo de Loïc Ortola et Mathilde prenant un selfie avec le public. (Sur LinkedIn, vous pouvez trouver le selfie avec le public.)](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Floic-ortola-mathilde-lorrain-selfie.webp)\n\nJe recommande vivement de visionner ces conférences.\n\n## Mes interventions\n\nCette année, j'ai eu le plaisir de donner deux conférences, ce qui a impliqué beaucoup de plaisir et de travail acharné ! 🫠\n\n1. **La réactivité et les signaux : démystifions la magie du frontend**\n\n   C'était ma conférence phare, et j'ai passé des semaines à la préparer (et je l'améliore continuellement pour de futures présentations). L'objectif était simple : explorer et comprendre en profondeur la bibliothèque **Alien Signals** à travers des explications de code réelles. Cette conférence cible ceux qui cherchent à comprendre la réactivité en JavaScript. La salle pleine indiquait un vif intérêt, avec plus de 200 participants. Quand on m'a informé qu'il n'y avait plus de places libres cinq minutes avant ma conférence, mon niveau de stress a augmenté. Mais une fois que j'ai commencé, je suis rentré dans ma zone, et le stress a disparu.\n\n   ![Photo de moi avant ma présentation.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend.webp)\n\n   Après un an de travail, c'était la première véritable utilisation de [Inalia](https:\u002F\u002Finalia.app\u002F), le SaaS que je développe pour améliorer les conférences avec des interactions en temps réel. Une fonctionnalité permet au public de réagir avec des émojis, partageant leurs pensées et sentiments en temps réel. Voir 🍆, 🍑, et 👅 à l'écran était amusant, nécessitant parfois que je me concentre ailleurs que sur mes diapositives ! 😆\n\n   Plus de 20 000 réactions par émoji ont été envoyées pendant la présentation ! 🤯\n\n   ![Photo de ma présentation couverte d'émojis.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fla-reactivite-et-les-signaux-demystifions-la-magie-du-frontend-emoji.webp)\n\n   Lorsque le public utilise efficacement les réactions, cela devient transformateur. Je peux sentir quand ils sont perdus, ennuyés ou réellement intéressés.\n\n   Comprendre **Align Signals** et construire cette plateforme a été laborieux mais en valait la peine. Les retours ont été extrêmement positifs, malgré le contenu technique et approfondi. Merci à tous pour vos retours et votre enthousiasme.\n\n2. **En JavaScript, il y a un paquet pour tout... et souvent pour rien.**\n\n   Cette présentation se concentre sur [Ecosystem Performance](https:\u002F\u002Fe18e.dev\u002F), une initiative visant à nettoyer, accélérer et améliorer l'écosystème JavaScript, en particulier les paquets. J'ai présenté le projet et les outils pour aider quiconque à comprendre ses `node_modules` et à faire des améliorations. Préparer et présenter cette conférence de 15 minutes a été amusant, et les retours montrent que les participants ont appris quelque chose de nouveau.\n\n   ![Photo du public pendant ma présentation.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Fen-javascript-il-y-a-un-paquet-pour-tout-et-souvent-pour-rien.webp)\n\n\u003CCallout icon=\"i-ph-presentation-duotone\" text=\"Découvrez les diapositives de mes présentations\" href=\"\u002Ftalks\" target=\"_blank\" \u002F>\n\n## Remerciements spéciaux à Takima\n\nParfois, une seule rencontre peut transformer complètement votre expérience.\n\nJe ne suis pas la personne la plus extravertie, et j'ai toujours trouvé qu'il était difficile d'engager la conversation avec de nouvelles personnes, même l'approche des exposants peut être difficile. Au fil des années, j'ai assisté à des meetups, conférences et autres événements seul, car mes amis ne sont pas des développeurs web. Bien que j'apprécie d'aller à ces rassemblements, cela peut être un peu épuisant d'y aller seul, surtout lors d'un événement de trois jours comme Devoxx France où le réseautage joue un grand rôle, ou pendant la soirée de rencontre où les gens sont au cœur du moment.\n\nTout cela a changé lorsque j'ai rencontré l'équipe incroyable de [Takima](https:\u002F\u002Fwww.takima.fr\u002F) lors de leurs événements TakiMeet. Ils ont assisté à ma présentation, et leur stand avait l'air d'un quartier général, un endroit où j'avais toujours quelqu'un avec qui discuter ou même un endroit pour m'asseoir et me détendre, même avec des batteries sociales vides. Cette année à Devoxx a été absolument incroyable !\n\nMerci à la formidable équipe de **Takima** - avec des remerciements particuliers à _Mathilde, Camille et Cyrielle_ - pour les conversations que nous avons partagées. J'ai vraiment apprécié tous vous rencontrer et j'espère à bientôt. 🫶\n\n![Photo du stand Takima à Devoxx France 2025.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-stand.webp)\n\n![Une partie de l'équipe Takima regardant une présentation d'un de leurs collègues.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-team.webp)\n\n![Stand Takima pendant le tirage au sort pour gagner des écouteurs, des enceintes et des formations.](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-game.webp)\n\n![Bidendum, une partie de l'équipe Takima, et moi !](\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special\u002Ftakima-bidendum.webp)\n\n_Tout tourne autour des gens !_\n\n## À suivre\n\nDevoxx France a été un régale, mais l'aventure ne s'arrête pas là. À la fin du mois d'avril, j'assisterai à [MiXiT](https:\u002F\u002Fmixitconf.org\u002Ffr\u002F) pour donner la même présentation sur la réactivité et les signaux. Le voyage continue en juin avec [BreizhCamp](https:\u002F\u002Fwww.breizhcamp.org\u002F). Si vous êtes dans le coin, n'hésitez pas à venir dire bonjour !\n\nBon code ! 👋\n",{"title":20848,"description":21357},{"loc":21359,"images":21363},[21364,21365,21366,21367,21368,21369,21370,21371,21372,21373,21374,21375,21376],{"loc":20873},{"loc":21106},{"loc":21133},{"loc":21168},{"loc":21193},{"loc":21199},{"loc":21226},{"loc":21243},{"loc":21273},{"loc":21310},{"loc":21316},{"loc":21322},{"loc":21328},"fr\u002Fposts\u002Fbeyond-the-tech-people-who-made-devoxx-france-2025-special","On67dGHQMAYL7JDQKIIsRRWK9i2xo9YnI_rhBK9fWu8",{"id":21380,"title":21381,"body":21382,"contentId":9270,"date":24204,"description":24205,"extension":18,"meta":24206,"navigation":20,"path":24207,"rawbody":24208,"robots":23,"schemaOrg":23,"seo":24209,"sitemap":24210,"stem":24211,"__hash__":24212},"posts\u002Ffr\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs.md","Les boîtes de dialogue dans l'expérience utilisateur",{"type":7,"value":21383,"toc":24196},[21384,21391,24193],[35,21385,21386,21387,21390],{},"Autant que je me souvienne, ce défi a été le plus persistant depuis que j'ai commencé le ",[6862,21388,21389],{},"développement web",". Récemment, j'ai découvert une méthode pour construire une bibliothèque de composants, ce qui a été un long processus. Cependant, résoudre les subtilités de la gestion des boîtes de dialogue s'est avéré encore plus vaste, complexe, et crucial en raison de leur présence omniprésente dans chaque application.",[7918,21392,21394,21401,21405,21418,21421,21436,21439,21449,21452,21455,21545,21628,21631,21634,21637,21679,21682,21685,21695,21699,21706,21790,21793,21801,22019,22022,22026,22029,22035,22145,22148,22311,22314,22439,22630,22633,22884,22906,22910,22917,22924,23080,23083,23100,23195,23198,23217,23374,23381,23384,23388,23391,23393,23638,23982,24116,24118,24130,24143,24147,24150,24167,24190],{"id":21393},"75df6bff-de40-4056-af23-fae2401cd21a",[35,21395,21396,21397,21400],{},"Aujourd'hui, je suis ravi de partager la solution que j'ai trouvée, ",[1216,21398,21399],{},"je ne l'ai pas développée",", pour gérer efficacement les boîtes de dialogue au sein de vos applications. Cette solution découle de plusieurs années d'expérience, et j'espère qu'elle vous sera aussi bénéfique qu'elle l'a été pour moi.",[51,21402,21404],{"id":21403},"comprendre-le-problème","Comprendre le problème",[35,21406,21407,21408,3092,21411,9502,21414,21417],{},"Avant de plonger dans le problème, alignons-nous sur ce dont nous parlons. Cet article se concentre sur les ",[6862,21409,21410],{},"boîtes de dialogue",[6862,21412,21413],{},"modaux",[6862,21415,21416],{},"slideover",". Chacun sert à afficher des informations ou à solliciter une confirmation, différant principalement en termes de présentation et d'utilisation.",[35,21419,21420],{},"Essayez d'ouvrir la boîte de dialogue en cliquant sur le bouton ci-dessous.",[11400,21422,132,21426],{"className":21423},[21424,21425],"flex","justify-center",[21427,21428,136,21432],"modal",{"title":21429,"description":21430,":ui":21431},"Boîte de Dialogue","Ceci est une boîte de dialogue","{ content: 'min-w-[280px]' }",[909,21433,21435],{"label":21434},"Ouvrir la Boîte de Dialogue","\n  \n",[35,21437,21438],{},"De même, vous pouvez ouvrir un slideover en cliquant sur le bouton.",[11400,21440,132,21442],{"className":21441},[21424,21425],[21416,21443,136,21446],{"title":21444,"description":21445,":ui":21431},"Slideover","Ceci est un slideover",[909,21447,21435],{"label":21448},"Ouvrir le Slideover",[35,21450,21451],{},"Le design et les types de dialogues peuvent sembler secondaires ; l'aspect important réside dans le code sous-jacent. Examinons-le :",[35,21453,21454],{},"::: code-group",[192,21456,21460],{"className":21457,"code":21458,"filename":21459,"language":12075,"meta":13,"style":13},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Ctemplate>\n  \u003CModal\n    title=\"Boîte de Dialogue\"\n    description=\"Ceci est une boîte de dialogue\"\n  >\n    \u003CButton label=\"Ouvrir la Boîte de Dialogue\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FModal.vue",[156,21461,21462,21471,21478,21491,21503,21507,21528,21537],{"__ignoreMap":13},[200,21463,21464,21466,21469],{"class":202,"line":203},[200,21465,599],{"class":214},[200,21467,21468],{"class":235},"template",[200,21470,647],{"class":214},[200,21472,21473,21475],{"class":202,"line":14},[200,21474,656],{"class":214},[200,21476,21477],{"class":235},"Modal\n",[200,21479,21480,21483,21485,21487,21489],{"class":202,"line":255},[200,21481,21482],{"class":379},"    title",[200,21484,449],{"class":214},[200,21486,610],{"class":214},[200,21488,21429],{"class":245},[200,21490,616],{"class":214},[200,21492,21493,21495,21497,21499,21501],{"class":202,"line":272},[200,21494,3151],{"class":379},[200,21496,449],{"class":214},[200,21498,610],{"class":214},[200,21500,21430],{"class":245},[200,21502,616],{"class":214},[200,21504,21505],{"class":202,"line":283},[200,21506,11550],{"class":214},[200,21508,21509,21511,21514,21517,21519,21521,21523,21525],{"class":202,"line":300},[200,21510,803],{"class":214},[200,21512,21513],{"class":235},"Button",[200,21515,21516],{"class":379}," label",[200,21518,449],{"class":214},[200,21520,610],{"class":214},[200,21522,21434],{"class":245},[200,21524,610],{"class":214},[200,21526,21527],{"class":214}," \u002F>\n",[200,21529,21530,21532,21535],{"class":202,"line":310},[200,21531,892],{"class":214},[200,21533,21534],{"class":235},"Modal",[200,21536,647],{"class":214},[200,21538,21539,21541,21543],{"class":202,"line":336},[200,21540,680],{"class":214},[200,21542,21468],{"class":235},[200,21544,647],{"class":214},[192,21546,21549],{"className":21457,"code":21547,"filename":21548,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CSlideover\n    title=\"Slideover\"\n    description=\"Ceci est un slideover\"\n  >\n    \u003CButton label=\"Ouvrir le Slideover\" \u002F>\n  \u003C\u002FSlideover>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FSlideover.vue",[156,21550,21551,21559,21566,21578,21590,21594,21612,21620],{"__ignoreMap":13},[200,21552,21553,21555,21557],{"class":202,"line":203},[200,21554,599],{"class":214},[200,21556,21468],{"class":235},[200,21558,647],{"class":214},[200,21560,21561,21563],{"class":202,"line":14},[200,21562,656],{"class":214},[200,21564,21565],{"class":235},"Slideover\n",[200,21567,21568,21570,21572,21574,21576],{"class":202,"line":255},[200,21569,21482],{"class":379},[200,21571,449],{"class":214},[200,21573,610],{"class":214},[200,21575,21444],{"class":245},[200,21577,616],{"class":214},[200,21579,21580,21582,21584,21586,21588],{"class":202,"line":272},[200,21581,3151],{"class":379},[200,21583,449],{"class":214},[200,21585,610],{"class":214},[200,21587,21445],{"class":245},[200,21589,616],{"class":214},[200,21591,21592],{"class":202,"line":283},[200,21593,11550],{"class":214},[200,21595,21596,21598,21600,21602,21604,21606,21608,21610],{"class":202,"line":300},[200,21597,803],{"class":214},[200,21599,21513],{"class":235},[200,21601,21516],{"class":379},[200,21603,449],{"class":214},[200,21605,610],{"class":214},[200,21607,21448],{"class":245},[200,21609,610],{"class":214},[200,21611,21527],{"class":214},[200,21613,21614,21616,21618],{"class":202,"line":310},[200,21615,892],{"class":214},[200,21617,21444],{"class":235},[200,21619,647],{"class":214},[200,21621,21622,21624,21626],{"class":202,"line":336},[200,21623,680],{"class":214},[200,21625,21468],{"class":235},[200,21627,647],{"class":214},[35,21629,21630],{},":::",[35,21632,21633],{},"Ils sont très similaires, reposant sur le même composant en coulisses. La véritable intrigue réside dans la manière dont ce code est utilisé. Ces exemples illustrent que les boîtes de dialogue s'intègrent parfaitement comme tout élément HTML, ce qui est le cœur du problème.",[35,21635,21636],{},"Les boîtes de dialogue sont un type particulier de composant dans une application :",[68,21638,21639,21642,21656],{},[71,21640,21641],{},"Ils peuvent être invoqués de n'importe où dans l'application.",[71,21643,21644,21645],{},"Ils s'ouvrent par divers moyens, tels que :",[1593,21646,21647,21650,21653],{},[71,21648,21649],{},"Clics de bouton",[71,21651,21652],{},"Pressions de touches",[71,21654,21655],{},"Navigations dans les routes",[71,21657,21658,21659,21676,21678],{},"La nature omniprésente rend des applications sans boîtes de dialogue presque impraticables sans sacrifier l'expérience utilisateur :",[1593,21660,21661,21664,21667,21670,21673],{},[71,21662,21663],{},"Création d'éléments",[71,21665,21666],{},"Modification d'éléments",[71,21668,21669],{},"Confirmations d'action",[71,21671,21672],{},"Visualisations d'éléments",[71,21674,21675],{},"Etc.",[7174,21677],{},"Chaque action peut mener à des conséquences immédiates ou attendre une réponse du serveur, ce qui complique les possibilités d'utilisation des boîtes de dialogue.",[35,21680,21681],{},"En raison de ces facteurs, les boîtes de dialogue nécessitent une attention minutieuse lors du développement et de l'utilisation ; sinon, ils et leur logique peuvent rapidement devenir chaotiques.",[35,21683,21684],{},"De plus, j'ai toujours eu une préférence personnelle pour permettre l'empilement de boîtes de dialogue avec des animations fluides, comme ceci :",[130,21686,132,21687,132,21692],{},[134,21688,136,21689,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,21690],{"src":21691,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs\u002Fmodal-stacking.mp4",[143,21693,21694],{},"Empiler des boîtes de dialogue avec une animation élégante.",[51,21696,21698],{"id":21697},"boîte-de-dialogue-en-ligne","Boîte de dialogue en ligne",[35,21700,21701,21702,21705],{},"Une première tentative pour relever ce défi est via des ",[6862,21703,21704],{},"boîtes de dialogue en ligne",". C'est une approche répandue et qui convient aux cas simples. La stratégie consiste à incorporer un composant modal directement dans le template nécessitant celui-ci, comme vu précédemment.",[192,21707,21709],{"className":21457,"code":21708,"filename":21459,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CModal\n    title=\"Dialogue\"\n    description=\"Ceci est un dialogue\"\n  >\n    \u003CButton label=\"Ouvrir le Dialogue\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n",[156,21710,21711,21719,21725,21738,21751,21755,21774,21782],{"__ignoreMap":13},[200,21712,21713,21715,21717],{"class":202,"line":203},[200,21714,599],{"class":214},[200,21716,21468],{"class":235},[200,21718,647],{"class":214},[200,21720,21721,21723],{"class":202,"line":14},[200,21722,656],{"class":214},[200,21724,21477],{"class":235},[200,21726,21727,21729,21731,21733,21736],{"class":202,"line":255},[200,21728,21482],{"class":379},[200,21730,449],{"class":214},[200,21732,610],{"class":214},[200,21734,21735],{"class":245},"Dialogue",[200,21737,616],{"class":214},[200,21739,21740,21742,21744,21746,21749],{"class":202,"line":272},[200,21741,3151],{"class":379},[200,21743,449],{"class":214},[200,21745,610],{"class":214},[200,21747,21748],{"class":245},"Ceci est un dialogue",[200,21750,616],{"class":214},[200,21752,21753],{"class":202,"line":283},[200,21754,11550],{"class":214},[200,21756,21757,21759,21761,21763,21765,21767,21770,21772],{"class":202,"line":300},[200,21758,803],{"class":214},[200,21760,21513],{"class":235},[200,21762,21516],{"class":379},[200,21764,449],{"class":214},[200,21766,610],{"class":214},[200,21768,21769],{"class":245},"Ouvrir le Dialogue",[200,21771,610],{"class":214},[200,21773,21527],{"class":214},[200,21775,21776,21778,21780],{"class":202,"line":310},[200,21777,892],{"class":214},[200,21779,21534],{"class":235},[200,21781,647],{"class":214},[200,21783,21784,21786,21788],{"class":202,"line":336},[200,21785,680],{"class":214},[200,21787,21468],{"class":235},[200,21789,647],{"class":214},[35,21791,21792],{},"Bien que l'implémentation soit simple, cette approche présente au moins deux inconvénients significatifs :",[68,21794,21795,21798],{},[71,21796,21797],{},"Les composants de boîte de dialogue deviennent étroitement couplés avec le composant qui les utilise, compliquant la compréhension et le refactoring.",[71,21799,21800],{},"Il devient coûteux, en terme de code, d'ajouter une nouvelle boîte de dialogue, surtout lorsqu'on ouvre les ouvre sans boutons ou en découplant les templates d'une boîte de dialogue. Un exemple d'ouverture d'une boîte dialogue par pression de touche suit :",[192,21802,21805],{"className":21457,"code":21803,"filename":21804,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport { ref } from 'vue'\n\nconst isDialogOpen = ref(false)\n\ndefineShortcuts({\n  meta_k: () => {\n    isDialogOpen.value = !isDialogOpen.value\n  }\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CModal\n      v-model:open=\"isDialogOpen\"\n      title=\"Boîte de Dialogue\"\n      description=\"Ceci est une boîte de dialogue\"\n    \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FInlineModal.vue",[156,21806,21807,21828,21846,21850,21867,21871,21880,21893,21913,21917,21923,21931,21935,21943,21951,21957,21974,21986,21998,22003,22011],{"__ignoreMap":13},[200,21808,21809,21811,21813,21815,21817,21819,21821,21823,21826],{"class":202,"line":203},[200,21810,599],{"class":214},[200,21812,14896],{"class":235},[200,21814,14594],{"class":379},[200,21816,449],{"class":214},[200,21818,610],{"class":214},[200,21820,1383],{"class":245},[200,21822,610],{"class":214},[200,21824,21825],{"class":379}," setup",[200,21827,647],{"class":214},[200,21829,21830,21832,21834,21836,21838,21840,21842,21844],{"class":202,"line":14},[200,21831,972],{"class":206},[200,21833,318],{"class":214},[200,21835,18698],{"class":210},[200,21837,980],{"class":214},[200,21839,983],{"class":206},[200,21841,242],{"class":214},[200,21843,12075],{"class":245},[200,21845,991],{"class":214},[200,21847,21848],{"class":202,"line":255},[200,21849,528],{"emptyLinePlaceholder":20},[200,21851,21852,21854,21857,21859,21861,21863,21865],{"class":202,"line":272},[200,21853,443],{"class":379},[200,21855,21856],{"class":210}," isDialogOpen ",[200,21858,449],{"class":214},[200,21860,18698],{"class":223},[200,21862,227],{"class":210},[200,21864,18703],{"class":2438},[200,21866,416],{"class":210},[200,21868,21869],{"class":202,"line":283},[200,21870,528],{"emptyLinePlaceholder":20},[200,21872,21873,21876,21878],{"class":202,"line":300},[200,21874,21875],{"class":223},"defineShortcuts",[200,21877,227],{"class":210},[200,21879,230],{"class":214},[200,21881,21882,21885,21887,21889,21891],{"class":202,"line":310},[200,21883,21884],{"class":223},"  meta_k",[200,21886,239],{"class":214},[200,21888,3729],{"class":214},[200,21890,393],{"class":379},[200,21892,280],{"class":214},[200,21894,21895,21898,21900,21902,21904,21906,21909,21911],{"class":202,"line":336},[200,21896,21897],{"class":210},"    isDialogOpen",[200,21899,215],{"class":214},[200,21901,15840],{"class":210},[200,21903,509],{"class":214},[200,21905,15917],{"class":214},[200,21907,21908],{"class":210},"isDialogOpen",[200,21910,215],{"class":214},[200,21912,15979],{"class":210},[200,21914,21915],{"class":202,"line":342},[200,21916,4505],{"class":214},[200,21918,21919,21921],{"class":202,"line":365},[200,21920,413],{"class":214},[200,21922,416],{"class":210},[200,21924,21925,21927,21929],{"class":202,"line":371},[200,21926,680],{"class":214},[200,21928,14896],{"class":235},[200,21930,647],{"class":214},[200,21932,21933],{"class":202,"line":398},[200,21934,528],{"emptyLinePlaceholder":20},[200,21936,21937,21939,21941],{"class":202,"line":405},[200,21938,599],{"class":214},[200,21940,21468],{"class":235},[200,21942,647],{"class":214},[200,21944,21945,21947,21949],{"class":202,"line":410},[200,21946,656],{"class":214},[200,21948,11400],{"class":235},[200,21950,647],{"class":214},[200,21952,21953,21955],{"class":202,"line":860},[200,21954,803],{"class":214},[200,21956,21477],{"class":235},[200,21958,21959,21962,21964,21966,21968,21970,21972],{"class":202,"line":889},[200,21960,21961],{"class":379},"      v-model",[200,21963,239],{"class":214},[200,21965,18917],{"class":379},[200,21967,449],{"class":214},[200,21969,610],{"class":214},[200,21971,21908],{"class":210},[200,21973,616],{"class":214},[200,21975,21976,21978,21980,21982,21984],{"class":202,"line":899},[200,21977,3572],{"class":379},[200,21979,449],{"class":214},[200,21981,610],{"class":214},[200,21983,21429],{"class":245},[200,21985,616],{"class":214},[200,21987,21988,21990,21992,21994,21996],{"class":202,"line":904},[200,21989,3588],{"class":379},[200,21991,449],{"class":214},[200,21993,610],{"class":214},[200,21995,21430],{"class":245},[200,21997,616],{"class":214},[200,21999,22000],{"class":202,"line":930},[200,22001,22002],{"class":214},"    \u002F>\n",[200,22004,22005,22007,22009],{"class":202,"line":3655},[200,22006,892],{"class":214},[200,22008,11400],{"class":235},[200,22010,647],{"class":214},[200,22012,22013,22015,22017],{"class":202,"line":3660},[200,22014,680],{"class":214},[200,22016,21468],{"class":235},[200,22018,647],{"class":214},[35,22020,22021],{},"Cette méthode devient rapidement encombrante et difficile à maintenir au fil du temps. Plusieurs boîtes de dialogue intensifient le code répétitif, nécessitant des ajouts répétés dans chaque composant utilisant la boîte de dialogue.",[51,22023,22025],{"id":22024},"boîtes-de-dialogue-globales","Boîtes de dialogue globales",[35,22027,22028],{},"Améliorer la gestion des boîtes de dialogue implique d'en ouvrir programatiquement de manière universelle, découplant leur invocation de toute relation directe avec un composant. Ce modèle facilite le refactoring du code en séparant physiquement les boîtes de dialogue des composants, améliorant la réutilisabilité à travers plusieurs composants sans incorporations redondantes.",[35,22030,22031,22032,22034],{},"Un fichier ",[156,22033,13716],{}," pourrait ressembler à ceci :",[192,22036,22040],{"className":22037,"code":22038,"filename":22039,"language":12075,"meta":13,"style":13},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight has-highlighted","\u003Ctemplate>\n  \u003Cdiv>\n    \u003CLayout>\n      \u003CRouterView \u002F>\n    \u003C\u002FLayout>\n\n    \u003CModalFeature1 \u002F> \u003C!-- [!code highlight] -->\n    \u003CModalFeature2 \u002F> \u003C!-- [!code highlight] -->\n    \u003CModalFeature3 \u002F> \u003C!-- [!code highlight] -->\n\n    \u003C!-- Et ainsi de suite... -->\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","src\u002FApp.vue",[156,22041,22042,22050,22058,22067,22077,22085,22089,22100,22110,22120,22124,22129,22137],{"__ignoreMap":13},[200,22043,22044,22046,22048],{"class":202,"line":203},[200,22045,599],{"class":214},[200,22047,21468],{"class":235},[200,22049,647],{"class":214},[200,22051,22052,22054,22056],{"class":202,"line":14},[200,22053,656],{"class":214},[200,22055,11400],{"class":235},[200,22057,647],{"class":214},[200,22059,22060,22062,22065],{"class":202,"line":255},[200,22061,803],{"class":214},[200,22063,22064],{"class":235},"Layout",[200,22066,647],{"class":214},[200,22068,22069,22072,22075],{"class":202,"line":272},[200,22070,22071],{"class":214},"      \u003C",[200,22073,22074],{"class":235},"RouterView",[200,22076,21527],{"class":214},[200,22078,22079,22081,22083],{"class":202,"line":283},[200,22080,15078],{"class":214},[200,22082,22064],{"class":235},[200,22084,647],{"class":214},[200,22086,22087],{"class":202,"line":300},[200,22088,528],{"emptyLinePlaceholder":20},[200,22090,22092,22094,22097],{"class":22091,"line":310},[202,9885],[200,22093,803],{"class":214},[200,22095,22096],{"class":235},"ModalFeature1",[200,22098,22099],{"class":214}," \u002F>",[200,22101,22103,22105,22108],{"class":22102,"line":336},[202,9885],[200,22104,803],{"class":214},[200,22106,22107],{"class":235},"ModalFeature2",[200,22109,22099],{"class":214},[200,22111,22113,22115,22118],{"class":22112,"line":342},[202,9885],[200,22114,803],{"class":214},[200,22116,22117],{"class":235},"ModalFeature3",[200,22119,22099],{"class":214},[200,22121,22122],{"class":202,"line":365},[200,22123,528],{"emptyLinePlaceholder":20},[200,22125,22126],{"class":202,"line":371},[200,22127,22128],{"class":401},"    \u003C!-- Et ainsi de suite... -->\n",[200,22130,22131,22133,22135],{"class":202,"line":398},[200,22132,892],{"class":214},[200,22134,11400],{"class":235},[200,22136,647],{"class":214},[200,22138,22139,22141,22143],{"class":202,"line":405},[200,22140,680],{"class":214},[200,22142,21468],{"class":235},[200,22144,647],{"class":214},[35,22146,22147],{},"Atteindre cette configuration exige de partager une variable entre le composant ouvrant la boîte de dialogue et la boîte de dialogue ell-même. Dans Vue, une approche simple, bien que pas idéale, est de créer une composable externalisant son état.",[192,22149,22152],{"className":9755,"code":22150,"filename":22151,"language":1383,"meta":13,"style":13},"import { ref } from 'vue'\n\nconst isOpen = ref(false) \u002F\u002F [!code highlight]\n\nexport function useDialog() {\n  function open() {\n    isOpen.value = true\n  }\n\n  function close() {\n    isOpen.value = false\n  }\n\n  return {\n    isOpen,\n\n    open,\n    close\n  }\n}\n","src\u002Fcomposables\u002FuseDialog.ts",[156,22153,22154,22172,22176,22194,22198,22211,22223,22237,22241,22245,22256,22268,22272,22276,22282,22288,22292,22298,22303,22307],{"__ignoreMap":13},[200,22155,22156,22158,22160,22162,22164,22166,22168,22170],{"class":202,"line":203},[200,22157,972],{"class":206},[200,22159,318],{"class":214},[200,22161,18698],{"class":210},[200,22163,980],{"class":214},[200,22165,983],{"class":206},[200,22167,242],{"class":214},[200,22169,12075],{"class":245},[200,22171,991],{"class":214},[200,22173,22174],{"class":202,"line":14},[200,22175,528],{"emptyLinePlaceholder":20},[200,22177,22179,22181,22184,22186,22188,22190,22192],{"class":22178,"line":255},[202,9885],[200,22180,443],{"class":379},[200,22182,22183],{"class":210}," isOpen ",[200,22185,449],{"class":214},[200,22187,18698],{"class":223},[200,22189,227],{"class":210},[200,22191,18703],{"class":2438},[200,22193,5282],{"class":210},[200,22195,22196],{"class":202,"line":272},[200,22197,528],{"emptyLinePlaceholder":20},[200,22199,22200,22202,22204,22207,22209],{"class":202,"line":283},[200,22201,1390],{"class":206},[200,22203,5002],{"class":379},[200,22205,22206],{"class":223}," useDialog",[200,22208,1549],{"class":214},[200,22210,280],{"class":214},[200,22212,22213,22216,22219,22221],{"class":202,"line":300},[200,22214,22215],{"class":379},"  function",[200,22217,22218],{"class":223}," open",[200,22220,1549],{"class":214},[200,22222,280],{"class":214},[200,22224,22225,22228,22230,22232,22234],{"class":202,"line":310},[200,22226,22227],{"class":210},"    isOpen",[200,22229,215],{"class":214},[200,22231,15840],{"class":210},[200,22233,509],{"class":214},[200,22235,22236],{"class":2438}," true\n",[200,22238,22239],{"class":202,"line":336},[200,22240,4505],{"class":214},[200,22242,22243],{"class":202,"line":342},[200,22244,528],{"emptyLinePlaceholder":20},[200,22246,22247,22249,22252,22254],{"class":202,"line":365},[200,22248,22215],{"class":379},[200,22250,22251],{"class":223}," close",[200,22253,1549],{"class":214},[200,22255,280],{"class":214},[200,22257,22258,22260,22262,22264,22266],{"class":202,"line":371},[200,22259,22227],{"class":210},[200,22261,215],{"class":214},[200,22263,15840],{"class":210},[200,22265,509],{"class":214},[200,22267,18902],{"class":2438},[200,22269,22270],{"class":202,"line":398},[200,22271,4505],{"class":214},[200,22273,22274],{"class":202,"line":405},[200,22275,528],{"emptyLinePlaceholder":20},[200,22277,22278,22280],{"class":202,"line":410},[200,22279,3758],{"class":206},[200,22281,280],{"class":214},[200,22283,22284,22286],{"class":202,"line":860},[200,22285,22227],{"class":210},[200,22287,252],{"class":214},[200,22289,22290],{"class":202,"line":889},[200,22291,528],{"emptyLinePlaceholder":20},[200,22293,22294,22296],{"class":202,"line":899},[200,22295,18893],{"class":210},[200,22297,252],{"class":214},[200,22299,22300],{"class":202,"line":904},[200,22301,22302],{"class":210},"    close\n",[200,22304,22305],{"class":202,"line":930},[200,22306,4505],{"class":214},[200,22308,22309],{"class":202,"line":3655},[200,22310,1129],{"class":214},[35,22312,22313],{},"Utilisez ce composable pour le composant ouvrant la boîte de dialogue et la boîte de dialogue elle-même.",[192,22315,22317],{"className":22037,"code":22316,"filename":22039,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport { useDialog } from '@\u002Fcomposables\u002FuseDialog'\n\nconst { open } = useDialog() \u002F\u002F [!code highlight]\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CButton label=\"Ouvrir la Boîte de Dialogue\" @click=\"open\" \u002F>\n\u003C\u002Ftemplate>\n",[156,22318,22319,22339,22358,22362,22379,22387,22391,22399,22431],{"__ignoreMap":13},[200,22320,22321,22323,22325,22327,22329,22331,22333,22335,22337],{"class":202,"line":203},[200,22322,599],{"class":214},[200,22324,14896],{"class":235},[200,22326,14594],{"class":379},[200,22328,449],{"class":214},[200,22330,610],{"class":214},[200,22332,1383],{"class":245},[200,22334,610],{"class":214},[200,22336,21825],{"class":379},[200,22338,647],{"class":214},[200,22340,22341,22343,22345,22347,22349,22351,22353,22356],{"class":202,"line":14},[200,22342,972],{"class":206},[200,22344,318],{"class":214},[200,22346,22206],{"class":210},[200,22348,980],{"class":214},[200,22350,983],{"class":206},[200,22352,242],{"class":214},[200,22354,22355],{"class":245},"@\u002Fcomposables\u002FuseDialog",[200,22357,991],{"class":214},[200,22359,22360],{"class":202,"line":255},[200,22361,528],{"emptyLinePlaceholder":20},[200,22363,22365,22367,22369,22371,22373,22375,22377],{"class":22364,"line":272},[202,9885],[200,22366,443],{"class":379},[200,22368,318],{"class":214},[200,22370,18693],{"class":210},[200,22372,413],{"class":214},[200,22374,509],{"class":214},[200,22376,22206],{"class":223},[200,22378,6332],{"class":210},[200,22380,22381,22383,22385],{"class":202,"line":283},[200,22382,680],{"class":214},[200,22384,14896],{"class":235},[200,22386,647],{"class":214},[200,22388,22389],{"class":202,"line":300},[200,22390,528],{"emptyLinePlaceholder":20},[200,22392,22393,22395,22397],{"class":202,"line":310},[200,22394,599],{"class":214},[200,22396,21468],{"class":235},[200,22398,647],{"class":214},[200,22400,22401,22403,22405,22407,22409,22411,22413,22415,22418,22421,22423,22425,22427,22429],{"class":202,"line":336},[200,22402,656],{"class":214},[200,22404,21513],{"class":235},[200,22406,21516],{"class":379},[200,22408,449],{"class":214},[200,22410,610],{"class":214},[200,22412,21434],{"class":245},[200,22414,610],{"class":214},[200,22416,22417],{"class":214}," @",[200,22419,22420],{"class":379},"click",[200,22422,449],{"class":214},[200,22424,610],{"class":214},[200,22426,18917],{"class":210},[200,22428,610],{"class":214},[200,22430,21527],{"class":214},[200,22432,22433,22435,22437],{"class":202,"line":342},[200,22434,680],{"class":214},[200,22436,21468],{"class":235},[200,22438,647],{"class":214},[192,22440,22443],{"className":22037,"code":22441,"filename":22442,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport { useDialog } from '@\u002Fcomposables\u002FuseDialog'\n\nconst { isOpen, close } = useDialog() \u002F\u002F [!code highlight]\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CModal\n    v-model:open=\"isOpen\"\n    title=\"Boîte de Dialogue\"\n    description=\"Ceci est une boîte de dialogue\"\n  >\n    \u003CButton label=\"Fermer\" @click=\"close\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FModalFeature1.vue",[156,22444,22445,22465,22483,22487,22510,22518,22522,22530,22536,22554,22566,22578,22582,22614,22622],{"__ignoreMap":13},[200,22446,22447,22449,22451,22453,22455,22457,22459,22461,22463],{"class":202,"line":203},[200,22448,599],{"class":214},[200,22450,14896],{"class":235},[200,22452,14594],{"class":379},[200,22454,449],{"class":214},[200,22456,610],{"class":214},[200,22458,1383],{"class":245},[200,22460,610],{"class":214},[200,22462,21825],{"class":379},[200,22464,647],{"class":214},[200,22466,22467,22469,22471,22473,22475,22477,22479,22481],{"class":202,"line":14},[200,22468,972],{"class":206},[200,22470,318],{"class":214},[200,22472,22206],{"class":210},[200,22474,980],{"class":214},[200,22476,983],{"class":206},[200,22478,242],{"class":214},[200,22480,22355],{"class":245},[200,22482,991],{"class":214},[200,22484,22485],{"class":202,"line":255},[200,22486,528],{"emptyLinePlaceholder":20},[200,22488,22490,22492,22494,22497,22499,22502,22504,22506,22508],{"class":22489,"line":272},[202,9885],[200,22491,443],{"class":379},[200,22493,318],{"class":214},[200,22495,22496],{"class":210}," isOpen",[200,22498,549],{"class":214},[200,22500,22501],{"class":210}," close ",[200,22503,413],{"class":214},[200,22505,509],{"class":214},[200,22507,22206],{"class":223},[200,22509,6332],{"class":210},[200,22511,22512,22514,22516],{"class":202,"line":283},[200,22513,680],{"class":214},[200,22515,14896],{"class":235},[200,22517,647],{"class":214},[200,22519,22520],{"class":202,"line":300},[200,22521,528],{"emptyLinePlaceholder":20},[200,22523,22524,22526,22528],{"class":202,"line":310},[200,22525,599],{"class":214},[200,22527,21468],{"class":235},[200,22529,647],{"class":214},[200,22531,22532,22534],{"class":202,"line":336},[200,22533,656],{"class":214},[200,22535,21477],{"class":235},[200,22537,22538,22541,22543,22545,22547,22549,22552],{"class":202,"line":342},[200,22539,22540],{"class":379},"    v-model",[200,22542,239],{"class":214},[200,22544,18917],{"class":379},[200,22546,449],{"class":214},[200,22548,610],{"class":214},[200,22550,22551],{"class":210},"isOpen",[200,22553,616],{"class":214},[200,22555,22556,22558,22560,22562,22564],{"class":202,"line":365},[200,22557,21482],{"class":379},[200,22559,449],{"class":214},[200,22561,610],{"class":214},[200,22563,21429],{"class":245},[200,22565,616],{"class":214},[200,22567,22568,22570,22572,22574,22576],{"class":202,"line":371},[200,22569,3151],{"class":379},[200,22571,449],{"class":214},[200,22573,610],{"class":214},[200,22575,21430],{"class":245},[200,22577,616],{"class":214},[200,22579,22580],{"class":202,"line":398},[200,22581,11550],{"class":214},[200,22583,22584,22586,22588,22590,22592,22594,22597,22599,22601,22603,22605,22607,22610,22612],{"class":202,"line":405},[200,22585,803],{"class":214},[200,22587,21513],{"class":235},[200,22589,21516],{"class":379},[200,22591,449],{"class":214},[200,22593,610],{"class":214},[200,22595,22596],{"class":245},"Fermer",[200,22598,610],{"class":214},[200,22600,22417],{"class":214},[200,22602,22420],{"class":379},[200,22604,449],{"class":214},[200,22606,610],{"class":214},[200,22608,22609],{"class":210},"close",[200,22611,610],{"class":214},[200,22613,21527],{"class":214},[200,22615,22616,22618,22620],{"class":202,"line":410},[200,22617,892],{"class":214},[200,22619,21534],{"class":235},[200,22621,647],{"class":214},[200,22623,22624,22626,22628],{"class":202,"line":860},[200,22625,680],{"class":214},[200,22627,21468],{"class":235},[200,22629,647],{"class":214},[35,22631,22632],{},"Cette approche est certainement supérieure à la méthode en ligne mais présente tout de même des inconvénients :",[68,22634,22635,22638],{},[71,22636,22637],{},"L'inclusion de boîtes de dialogue au niveau de l'application (ou de la route) crée une redondance de code.",[71,22639,22640,22641],{},"La gestion des boîtes de dialogue devient complexe, particulièrement avec de nombreuses boîtes de dialogue ou propriétés, chacune nécessitant un passage à travers la composable.",[192,22642,22644],{"className":9755,"code":22643,"language":1383,"meta":13,"style":13},"import { ref } from 'vue'\n\nconst isOpen = ref(false)\nconst title = ref('') \u002F\u002F [!code highlight]\nconst description = ref('') \u002F\u002F [!code highlight]\n\nexport function useDialog() {\n  function open(newTitle: string, newDescription: string) {\n    title.value = newTitle\n    description.value = newDescription\n    isOpen.value = true\n  }\n\n  function close() {\n    isOpen.value = false\n  }\n\n  return {\n    isOpen,\n    title,\n    description,\n    open,\n    close\n  }\n}\n",[156,22645,22646,22664,22668,22684,22703,22721,22725,22737,22766,22779,22792,22804,22808,22812,22822,22834,22838,22842,22848,22854,22860,22866,22872,22876,22880],{"__ignoreMap":13},[200,22647,22648,22650,22652,22654,22656,22658,22660,22662],{"class":202,"line":203},[200,22649,972],{"class":206},[200,22651,318],{"class":214},[200,22653,18698],{"class":210},[200,22655,980],{"class":214},[200,22657,983],{"class":206},[200,22659,242],{"class":214},[200,22661,12075],{"class":245},[200,22663,991],{"class":214},[200,22665,22666],{"class":202,"line":14},[200,22667,528],{"emptyLinePlaceholder":20},[200,22669,22670,22672,22674,22676,22678,22680,22682],{"class":202,"line":255},[200,22671,443],{"class":379},[200,22673,22183],{"class":210},[200,22675,449],{"class":214},[200,22677,18698],{"class":223},[200,22679,227],{"class":210},[200,22681,18703],{"class":2438},[200,22683,416],{"class":210},[200,22685,22687,22689,22692,22694,22696,22698,22701],{"class":22686,"line":272},[202,9885],[200,22688,443],{"class":379},[200,22690,22691],{"class":210}," title ",[200,22693,449],{"class":214},[200,22695,18698],{"class":223},[200,22697,227],{"class":210},[200,22699,22700],{"class":214},"''",[200,22702,5282],{"class":210},[200,22704,22706,22708,22711,22713,22715,22717,22719],{"class":22705,"line":283},[202,9885],[200,22707,443],{"class":379},[200,22709,22710],{"class":210}," description ",[200,22712,449],{"class":214},[200,22714,18698],{"class":223},[200,22716,227],{"class":210},[200,22718,22700],{"class":214},[200,22720,5282],{"class":210},[200,22722,22723],{"class":202,"line":300},[200,22724,528],{"emptyLinePlaceholder":20},[200,22726,22727,22729,22731,22733,22735],{"class":202,"line":310},[200,22728,1390],{"class":206},[200,22730,5002],{"class":379},[200,22732,22206],{"class":223},[200,22734,1549],{"class":214},[200,22736,280],{"class":214},[200,22738,22739,22741,22743,22745,22748,22750,22753,22755,22758,22760,22762,22764],{"class":202,"line":336},[200,22740,22215],{"class":379},[200,22742,22218],{"class":223},[200,22744,227],{"class":214},[200,22746,22747],{"class":386},"newTitle",[200,22749,239],{"class":214},[200,22751,22752],{"class":1364}," string",[200,22754,549],{"class":214},[200,22756,22757],{"class":386}," newDescription",[200,22759,239],{"class":214},[200,22761,22752],{"class":1364},[200,22763,2104],{"class":214},[200,22765,280],{"class":214},[200,22767,22768,22770,22772,22774,22776],{"class":202,"line":342},[200,22769,21482],{"class":210},[200,22771,215],{"class":214},[200,22773,15840],{"class":210},[200,22775,509],{"class":214},[200,22777,22778],{"class":210}," newTitle\n",[200,22780,22781,22783,22785,22787,22789],{"class":202,"line":365},[200,22782,3151],{"class":210},[200,22784,215],{"class":214},[200,22786,15840],{"class":210},[200,22788,509],{"class":214},[200,22790,22791],{"class":210}," newDescription\n",[200,22793,22794,22796,22798,22800,22802],{"class":202,"line":371},[200,22795,22227],{"class":210},[200,22797,215],{"class":214},[200,22799,15840],{"class":210},[200,22801,509],{"class":214},[200,22803,22236],{"class":2438},[200,22805,22806],{"class":202,"line":398},[200,22807,4505],{"class":214},[200,22809,22810],{"class":202,"line":405},[200,22811,528],{"emptyLinePlaceholder":20},[200,22813,22814,22816,22818,22820],{"class":202,"line":410},[200,22815,22215],{"class":379},[200,22817,22251],{"class":223},[200,22819,1549],{"class":214},[200,22821,280],{"class":214},[200,22823,22824,22826,22828,22830,22832],{"class":202,"line":860},[200,22825,22227],{"class":210},[200,22827,215],{"class":214},[200,22829,15840],{"class":210},[200,22831,509],{"class":214},[200,22833,18902],{"class":2438},[200,22835,22836],{"class":202,"line":889},[200,22837,4505],{"class":214},[200,22839,22840],{"class":202,"line":899},[200,22841,528],{"emptyLinePlaceholder":20},[200,22843,22844,22846],{"class":202,"line":904},[200,22845,3758],{"class":206},[200,22847,280],{"class":214},[200,22849,22850,22852],{"class":202,"line":930},[200,22851,22227],{"class":210},[200,22853,252],{"class":214},[200,22855,22856,22858],{"class":202,"line":3655},[200,22857,21482],{"class":210},[200,22859,252],{"class":214},[200,22861,22862,22864],{"class":202,"line":3660},[200,22863,3151],{"class":210},[200,22865,252],{"class":214},[200,22867,22868,22870],{"class":202,"line":4035},[200,22869,18893],{"class":210},[200,22871,252],{"class":214},[200,22873,22874],{"class":202,"line":4490},[200,22875,22302],{"class":210},[200,22877,22878],{"class":202,"line":4496},[200,22879,4505],{"class":214},[200,22881,22882],{"class":202,"line":4502},[200,22883,1129],{"class":214},[938,22885,22886],{},[35,22887,22888,22890,22891,17891,22898,22905],{},[200,22889,944],{},"\nSimplifiez l'écriture des composables avec ",[39,22892,22895],{"href":22893,"rel":22894},"https:\u002F\u002Fvueuse.org\u002Fshared\u002FcreateSharedComposable\u002F",[77],[156,22896,22897],{},"createSharedComposable",[39,22899,22902],{"href":22900,"rel":22901},"https:\u002F\u002Fvueuse.org\u002F",[77],[156,22903,22904],{},"@vueuse\u002Fcore"," pour créer un composable singleton, partageant automatiquement le même état entre tous les composants qui l'utilisent.",[51,22907,22909],{"id":22908},"dialogues-programmatiques","Dialogues programmatiques",[35,22911,22912,22913,22916],{},"La méthode la plus récente et profondément efficace que j'ai découverte implique ",[6862,22914,22915],{},"la gestion programmatique des boîtes de dialogue",". Cette méthode résout les difficultés mentionnées plus tôt en permettant un contrôle complet des boîtes de dialogue par le biais du code plutôt que des templates.",[35,22918,22919,22920,22923],{},"Ayant utilisé des boîtes de dialogue programmatiques dans ",[6862,22921,22922],{},"Angular"," avec Angular Material, j'ai apprécié sa capacité à simplifier le code.",[192,22925,22928],{"className":1380,"code":22926,"filename":22927,"language":1383,"meta":13,"style":13},"const dialogRef = dialog.open(UserProfileComponent, {\n  height: '400px',\n  width: '600px',\n})\n\ndialogRef.afterClosed().subscribe((result) => {\n  console.log(`Résultat du dialogue : ${result}`) \u002F\u002F Pizza !\n})\n\ndialogRef.close('Pizza !')\n","Angular Material Dialog",[156,22929,22930,22953,22969,22985,22991,22995,23024,23051,23057,23061],{"__ignoreMap":13},[200,22931,22932,22934,22937,22939,22942,22944,22946,22949,22951],{"class":202,"line":203},[200,22933,443],{"class":379},[200,22935,22936],{"class":210}," dialogRef ",[200,22938,449],{"class":214},[200,22940,22941],{"class":210}," dialog",[200,22943,215],{"class":214},[200,22945,18917],{"class":223},[200,22947,22948],{"class":210},"(UserProfileComponent",[200,22950,549],{"class":214},[200,22952,280],{"class":214},[200,22954,22955,22958,22960,22962,22965,22967],{"class":202,"line":14},[200,22956,22957],{"class":235},"  height",[200,22959,239],{"class":214},[200,22961,242],{"class":214},[200,22963,22964],{"class":245},"400px",[200,22966,249],{"class":214},[200,22968,252],{"class":214},[200,22970,22971,22974,22976,22978,22981,22983],{"class":202,"line":255},[200,22972,22973],{"class":235},"  width",[200,22975,239],{"class":214},[200,22977,242],{"class":214},[200,22979,22980],{"class":245},"600px",[200,22982,249],{"class":214},[200,22984,252],{"class":214},[200,22986,22987,22989],{"class":202,"line":272},[200,22988,413],{"class":214},[200,22990,416],{"class":210},[200,22992,22993],{"class":202,"line":283},[200,22994,528],{"emptyLinePlaceholder":20},[200,22996,22997,23000,23002,23005,23007,23009,23012,23014,23016,23018,23020,23022],{"class":202,"line":300},[200,22998,22999],{"class":210},"dialogRef",[200,23001,215],{"class":214},[200,23003,23004],{"class":223},"afterClosed",[200,23006,1549],{"class":210},[200,23008,215],{"class":214},[200,23010,23011],{"class":223},"subscribe",[200,23013,227],{"class":210},[200,23015,227],{"class":214},[200,23017,3546],{"class":386},[200,23019,2104],{"class":214},[200,23021,393],{"class":379},[200,23023,280],{"class":214},[200,23025,23026,23028,23030,23032,23034,23036,23039,23041,23043,23046,23048],{"class":202,"line":310},[200,23027,1112],{"class":210},[200,23029,215],{"class":214},[200,23031,1117],{"class":223},[200,23033,227],{"class":235},[200,23035,3333],{"class":214},[200,23037,23038],{"class":245},"Résultat du dialogue : ",[200,23040,4868],{"class":214},[200,23042,3546],{"class":210},[200,23044,23045],{"class":214},"}`",[200,23047,5282],{"class":235},[200,23049,23050],{"class":401},"\u002F\u002F Pizza !\n",[200,23052,23053,23055],{"class":202,"line":336},[200,23054,413],{"class":214},[200,23056,416],{"class":210},[200,23058,23059],{"class":202,"line":342},[200,23060,528],{"emptyLinePlaceholder":20},[200,23062,23063,23065,23067,23069,23071,23073,23076,23078],{"class":202,"line":365},[200,23064,22999],{"class":210},[200,23066,215],{"class":214},[200,23068,22609],{"class":223},[200,23070,227],{"class":210},[200,23072,249],{"class":214},[200,23074,23075],{"class":245},"Pizza !",[200,23077,249],{"class":214},[200,23079,416],{"class":210},[35,23081,23082],{},"Pendant longtemps, j'ai espéré d'une fonctionnalité similaire dans Vue.",[35,23084,23085,23086,23090,23091,23095,23096,23099],{},"Ce temps est désormais arrivé, lorsque ",[39,23087,2934],{"href":23088,"rel":23089},"https:\u002F\u002Fnuxt.com\u002F",[77]," a sorti la version 3 de ",[39,23092,9203],{"href":23093,"rel":23094},"https:\u002F\u002Fui.nuxt.com\u002F",[77]," avec la composable ",[156,23097,23098],{},"useOverlay",". Ce composable partagé par l'application permet d'ouvrir des boîtes de dialogue de n'importe quel endroit, imitant la fonctionnalité d'Angular Material.",[192,23101,23103],{"className":21457,"code":23102,"filename":22039,"language":12075,"meta":13,"style":13},"\u003Cscript setup lang=\"ts\">\nconst overlay = useOverlay()\n\nasync function openModal() {\n  overlay\n    .create(MyModal)\n    .open()\n}\n\u003C\u002Fscript>\n",[156,23104,23105,23125,23139,23143,23156,23161,23175,23183,23187],{"__ignoreMap":13},[200,23106,23107,23109,23111,23113,23115,23117,23119,23121,23123],{"class":202,"line":203},[200,23108,599],{"class":214},[200,23110,14896],{"class":235},[200,23112,21825],{"class":379},[200,23114,14594],{"class":379},[200,23116,449],{"class":214},[200,23118,610],{"class":214},[200,23120,1383],{"class":245},[200,23122,610],{"class":214},[200,23124,647],{"class":214},[200,23126,23127,23129,23132,23134,23137],{"class":202,"line":14},[200,23128,443],{"class":379},[200,23130,23131],{"class":210}," overlay ",[200,23133,449],{"class":214},[200,23135,23136],{"class":223}," useOverlay",[200,23138,466],{"class":210},[200,23140,23141],{"class":202,"line":255},[200,23142,528],{"emptyLinePlaceholder":20},[200,23144,23145,23147,23149,23152,23154],{"class":202,"line":272},[200,23146,3726],{"class":379},[200,23148,5002],{"class":379},[200,23150,23151],{"class":223}," openModal",[200,23153,1549],{"class":214},[200,23155,280],{"class":214},[200,23157,23158],{"class":202,"line":283},[200,23159,23160],{"class":210},"  overlay\n",[200,23162,23163,23165,23168,23170,23173],{"class":202,"line":300},[200,23164,3770],{"class":214},[200,23166,23167],{"class":223},"create",[200,23169,227],{"class":235},[200,23171,23172],{"class":210},"MyModal",[200,23174,416],{"class":235},[200,23176,23177,23179,23181],{"class":202,"line":310},[200,23178,3770],{"class":214},[200,23180,18917],{"class":223},[200,23182,466],{"class":235},[200,23184,23185],{"class":202,"line":336},[200,23186,1129],{"class":214},[200,23188,23189,23191,23193],{"class":202,"line":342},[200,23190,680],{"class":214},[200,23192,14896],{"class":235},[200,23194,647],{"class":214},[35,23196,23197],{},"Magnifique, n'est-ce pas ? 😍",[35,23199,23200,23201,23208,23209,23216],{},"Sous le capot, cette méthode utilise la directive ",[39,23202,23205],{"href":23203,"rel":23204},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fessentials\u002Fcomponent-basics.html#dynamic-components",[77],[156,23206,23207],{},"component"," avec une simple boucle ",[39,23210,23213],{"href":23211,"rel":23212},"https:\u002F\u002Fvuejs.org\u002Fapi\u002Fbuilt-in-directives.html#v-for",[77],[156,23214,23215],{},"v-for"," pour rendre les boîtes de dialogue.",[192,23218,23221],{"className":21457,"code":23219,"filename":23220,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst { overlays, close } = useOverlay()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Ccomponent\n    :is=\"overlay.component\"\n    v-for=\"overlay in overlays\"\n    :key=\"overlay.id\"\n    v-bind=\"overlay.props\"\n    v-model:open=\"overlay.modelValue\"\n  \u002F>\n\u003C\u002Ftemplate>\n","OverlayProvider.vue",[156,23222,23223,23243,23264,23272,23276,23284,23291,23305,23319,23333,23347,23361,23366],{"__ignoreMap":13},[200,23224,23225,23227,23229,23231,23233,23235,23237,23239,23241],{"class":202,"line":203},[200,23226,599],{"class":214},[200,23228,14896],{"class":235},[200,23230,14594],{"class":379},[200,23232,449],{"class":214},[200,23234,610],{"class":214},[200,23236,1383],{"class":245},[200,23238,610],{"class":214},[200,23240,21825],{"class":379},[200,23242,647],{"class":214},[200,23244,23245,23247,23249,23252,23254,23256,23258,23260,23262],{"class":202,"line":14},[200,23246,443],{"class":379},[200,23248,318],{"class":214},[200,23250,23251],{"class":210}," overlays",[200,23253,549],{"class":214},[200,23255,22501],{"class":210},[200,23257,413],{"class":214},[200,23259,509],{"class":214},[200,23261,23136],{"class":223},[200,23263,466],{"class":210},[200,23265,23266,23268,23270],{"class":202,"line":255},[200,23267,680],{"class":214},[200,23269,14896],{"class":235},[200,23271,647],{"class":214},[200,23273,23274],{"class":202,"line":272},[200,23275,528],{"emptyLinePlaceholder":20},[200,23277,23278,23280,23282],{"class":202,"line":283},[200,23279,599],{"class":214},[200,23281,21468],{"class":235},[200,23283,647],{"class":214},[200,23285,23286,23288],{"class":202,"line":300},[200,23287,656],{"class":214},[200,23289,23290],{"class":235},"component\n",[200,23292,23293,23296,23298,23300,23303],{"class":202,"line":310},[200,23294,23295],{"class":379},"    :is",[200,23297,449],{"class":214},[200,23299,610],{"class":214},[200,23301,23302],{"class":245},"overlay.component",[200,23304,616],{"class":214},[200,23306,23307,23310,23312,23314,23317],{"class":202,"line":336},[200,23308,23309],{"class":379},"    v-for",[200,23311,449],{"class":214},[200,23313,610],{"class":214},[200,23315,23316],{"class":245},"overlay in overlays",[200,23318,616],{"class":214},[200,23320,23321,23324,23326,23328,23331],{"class":202,"line":342},[200,23322,23323],{"class":379},"    :key",[200,23325,449],{"class":214},[200,23327,610],{"class":214},[200,23329,23330],{"class":245},"overlay.id",[200,23332,616],{"class":214},[200,23334,23335,23338,23340,23342,23345],{"class":202,"line":365},[200,23336,23337],{"class":379},"    v-bind",[200,23339,449],{"class":214},[200,23341,610],{"class":214},[200,23343,23344],{"class":245},"overlay.props",[200,23346,616],{"class":214},[200,23348,23349,23352,23354,23356,23359],{"class":202,"line":371},[200,23350,23351],{"class":379},"    v-model:open",[200,23353,449],{"class":214},[200,23355,610],{"class":214},[200,23357,23358],{"class":245},"overlay.modelValue",[200,23360,616],{"class":214},[200,23362,23363],{"class":202,"line":398},[200,23364,23365],{"class":214},"  \u002F>\n",[200,23367,23368,23370,23372],{"class":202,"line":405},[200,23369,680],{"class":214},[200,23371,21468],{"class":235},[200,23373,647],{"class":214},[938,23375,23376],{},[35,23377,23378,23380],{},[200,23379,944],{},"\nIl s'agit d'une version simplifiée du code.",[35,23382,23383],{},"Avec cette méthode, les coûts de la gestion d'état sont éliminés, ce qui supprime le besoin de composables partagés ou de boîtes de dialogue globales. Cela améliore considérablement la lisibilité, la maintenabilité et la création d'expériences UI interactives.",[51,23385,23387],{"id":23386},"empilement-des-boîtes-de-dialogue-sans-effort","Empilement des boîtes de dialogue sans effort",[35,23389,23390],{},"Le point culminant de cette approche est l'empilement de boîtes de dialogue sans effort. Il suffit de rendre un tableau de boîtes de dialogue via une boucle pour permettre l'ouverture simultanée de boîtes de dialogue, facilitant un empilement élégant avec des animations fluides.",[35,23392,21454],{},[192,23394,23396],{"className":21457,"code":23395,"filename":22039,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport DialogFeature1 from '.\u002Fcomponents\u002FDialogFeature1.vue'\n\nconst overlay = useOverlay()\nfunction openOverlay() {\n  overlay\n    .create(DialogFeature1, {\n      props: {\n        title: 'Premier Modal',\n        description: 'Ceci est le premier modal'\n      },\n      destroyOnClose: true\n    })\n    .open()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CButton label=\"Ouvrir le Premier Overlay\" @click=\"openOverlay\" \u002F>\n\n    \u003COverlayProvider \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[156,23397,23398,23418,23434,23438,23450,23461,23465,23480,23489,23505,23518,23522,23531,23537,23545,23549,23557,23561,23569,23577,23609,23613,23622,23630],{"__ignoreMap":13},[200,23399,23400,23402,23404,23406,23408,23410,23412,23414,23416],{"class":202,"line":203},[200,23401,599],{"class":214},[200,23403,14896],{"class":235},[200,23405,14594],{"class":379},[200,23407,449],{"class":214},[200,23409,610],{"class":214},[200,23411,1383],{"class":245},[200,23413,610],{"class":214},[200,23415,21825],{"class":379},[200,23417,647],{"class":214},[200,23419,23420,23422,23425,23427,23429,23432],{"class":202,"line":14},[200,23421,972],{"class":206},[200,23423,23424],{"class":210}," DialogFeature1 ",[200,23426,1489],{"class":206},[200,23428,242],{"class":214},[200,23430,23431],{"class":245},".\u002Fcomponents\u002FDialogFeature1.vue",[200,23433,991],{"class":214},[200,23435,23436],{"class":202,"line":255},[200,23437,528],{"emptyLinePlaceholder":20},[200,23439,23440,23442,23444,23446,23448],{"class":202,"line":272},[200,23441,443],{"class":379},[200,23443,23131],{"class":210},[200,23445,449],{"class":214},[200,23447,23136],{"class":223},[200,23449,466],{"class":210},[200,23451,23452,23454,23457,23459],{"class":202,"line":283},[200,23453,14908],{"class":379},[200,23455,23456],{"class":223}," openOverlay",[200,23458,1549],{"class":214},[200,23460,280],{"class":214},[200,23462,23463],{"class":202,"line":300},[200,23464,23160],{"class":210},[200,23466,23467,23469,23471,23473,23476,23478],{"class":202,"line":310},[200,23468,3770],{"class":214},[200,23470,23167],{"class":223},[200,23472,227],{"class":235},[200,23474,23475],{"class":210},"DialogFeature1",[200,23477,549],{"class":214},[200,23479,280],{"class":214},[200,23481,23482,23485,23487],{"class":202,"line":336},[200,23483,23484],{"class":235},"      props",[200,23486,239],{"class":214},[200,23488,280],{"class":214},[200,23490,23491,23494,23496,23498,23501,23503],{"class":202,"line":342},[200,23492,23493],{"class":235},"        title",[200,23495,239],{"class":214},[200,23497,242],{"class":214},[200,23499,23500],{"class":245},"Premier Modal",[200,23502,249],{"class":214},[200,23504,252],{"class":214},[200,23506,23507,23509,23511,23513,23516],{"class":202,"line":365},[200,23508,20017],{"class":235},[200,23510,239],{"class":214},[200,23512,242],{"class":214},[200,23514,23515],{"class":245},"Ceci est le premier modal",[200,23517,991],{"class":214},[200,23519,23520],{"class":202,"line":371},[200,23521,12098],{"class":214},[200,23523,23524,23527,23529],{"class":202,"line":398},[200,23525,23526],{"class":235},"      destroyOnClose",[200,23528,239],{"class":214},[200,23530,22236],{"class":2438},[200,23532,23533,23535],{"class":202,"line":405},[200,23534,2111],{"class":214},[200,23536,416],{"class":235},[200,23538,23539,23541,23543],{"class":202,"line":410},[200,23540,3770],{"class":214},[200,23542,18917],{"class":223},[200,23544,466],{"class":235},[200,23546,23547],{"class":202,"line":860},[200,23548,1129],{"class":214},[200,23550,23551,23553,23555],{"class":202,"line":889},[200,23552,680],{"class":214},[200,23554,14896],{"class":235},[200,23556,647],{"class":214},[200,23558,23559],{"class":202,"line":899},[200,23560,528],{"emptyLinePlaceholder":20},[200,23562,23563,23565,23567],{"class":202,"line":904},[200,23564,599],{"class":214},[200,23566,21468],{"class":235},[200,23568,647],{"class":214},[200,23570,23571,23573,23575],{"class":202,"line":930},[200,23572,656],{"class":214},[200,23574,11400],{"class":235},[200,23576,647],{"class":214},[200,23578,23579,23581,23583,23585,23587,23589,23592,23594,23596,23598,23600,23602,23605,23607],{"class":202,"line":3655},[200,23580,803],{"class":214},[200,23582,21513],{"class":235},[200,23584,21516],{"class":379},[200,23586,449],{"class":214},[200,23588,610],{"class":214},[200,23590,23591],{"class":245},"Ouvrir le Premier Overlay",[200,23593,610],{"class":214},[200,23595,22417],{"class":214},[200,23597,22420],{"class":379},[200,23599,449],{"class":214},[200,23601,610],{"class":214},[200,23603,23604],{"class":210},"openOverlay",[200,23606,610],{"class":214},[200,23608,21527],{"class":214},[200,23610,23611],{"class":202,"line":3660},[200,23612,528],{"emptyLinePlaceholder":20},[200,23614,23615,23617,23620],{"class":202,"line":4035},[200,23616,803],{"class":214},[200,23618,23619],{"class":235},"OverlayProvider",[200,23621,21527],{"class":214},[200,23623,23624,23626,23628],{"class":202,"line":4490},[200,23625,892],{"class":214},[200,23627,11400],{"class":235},[200,23629,647],{"class":214},[200,23631,23632,23634,23636],{"class":202,"line":4496},[200,23633,680],{"class":214},[200,23635,21468],{"class":235},[200,23637,647],{"class":214},[192,23639,23642],{"className":21457,"code":23640,"filename":23641,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport DialogFeature2 from '.\u002FDialogFeature2.vue'\n\nconst props = defineProps\u003C{\n  title: string\n  description: string\n}>()\n\nconst overlay = useOverlay()\nfunction openOverlay() {\n  overlay\n    .create(DialogFeature2, {\n      props: {\n        title: 'Deuxième Modal',\n        description: 'Ceci est le deuxième modal'\n      },\n      destroyOnClose: true\n    })\n    .open()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CModal\n    :title=\"props.title\"\n    :description=\"props.description\"\n  >\n    \u003Ctemplate #body>\n      \u003CButton\n        label=\"Ouvrir le Deuxième Modal\"\n        @click=\"openOverlay\"\n      \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FDialogFeature1.vue",[156,23643,23644,23664,23680,23684,23699,23709,23717,23724,23728,23740,23750,23754,23769,23777,23792,23805,23809,23817,23823,23831,23835,23843,23847,23855,23861,23881,23899,23903,23917,23924,23938,23953,23958,23966,23974],{"__ignoreMap":13},[200,23645,23646,23648,23650,23652,23654,23656,23658,23660,23662],{"class":202,"line":203},[200,23647,599],{"class":214},[200,23649,14896],{"class":235},[200,23651,14594],{"class":379},[200,23653,449],{"class":214},[200,23655,610],{"class":214},[200,23657,1383],{"class":245},[200,23659,610],{"class":214},[200,23661,21825],{"class":379},[200,23663,647],{"class":214},[200,23665,23666,23668,23671,23673,23675,23678],{"class":202,"line":14},[200,23667,972],{"class":206},[200,23669,23670],{"class":210}," DialogFeature2 ",[200,23672,1489],{"class":206},[200,23674,242],{"class":214},[200,23676,23677],{"class":245},".\u002FDialogFeature2.vue",[200,23679,991],{"class":214},[200,23681,23682],{"class":202,"line":255},[200,23683,528],{"emptyLinePlaceholder":20},[200,23685,23686,23688,23691,23693,23696],{"class":202,"line":272},[200,23687,443],{"class":379},[200,23689,23690],{"class":210}," props ",[200,23692,449],{"class":214},[200,23694,23695],{"class":223}," defineProps",[200,23697,23698],{"class":214},"\u003C{\n",[200,23700,23701,23704,23706],{"class":202,"line":283},[200,23702,23703],{"class":235},"  title",[200,23705,239],{"class":214},[200,23707,23708],{"class":1364}," string\n",[200,23710,23711,23713,23715],{"class":202,"line":300},[200,23712,258],{"class":235},[200,23714,239],{"class":214},[200,23716,23708],{"class":1364},[200,23718,23719,23722],{"class":202,"line":310},[200,23720,23721],{"class":214},"}>",[200,23723,466],{"class":210},[200,23725,23726],{"class":202,"line":336},[200,23727,528],{"emptyLinePlaceholder":20},[200,23729,23730,23732,23734,23736,23738],{"class":202,"line":342},[200,23731,443],{"class":379},[200,23733,23131],{"class":210},[200,23735,449],{"class":214},[200,23737,23136],{"class":223},[200,23739,466],{"class":210},[200,23741,23742,23744,23746,23748],{"class":202,"line":365},[200,23743,14908],{"class":379},[200,23745,23456],{"class":223},[200,23747,1549],{"class":214},[200,23749,280],{"class":214},[200,23751,23752],{"class":202,"line":371},[200,23753,23160],{"class":210},[200,23755,23756,23758,23760,23762,23765,23767],{"class":202,"line":398},[200,23757,3770],{"class":214},[200,23759,23167],{"class":223},[200,23761,227],{"class":235},[200,23763,23764],{"class":210},"DialogFeature2",[200,23766,549],{"class":214},[200,23768,280],{"class":214},[200,23770,23771,23773,23775],{"class":202,"line":405},[200,23772,23484],{"class":235},[200,23774,239],{"class":214},[200,23776,280],{"class":214},[200,23778,23779,23781,23783,23785,23788,23790],{"class":202,"line":410},[200,23780,23493],{"class":235},[200,23782,239],{"class":214},[200,23784,242],{"class":214},[200,23786,23787],{"class":245},"Deuxième Modal",[200,23789,249],{"class":214},[200,23791,252],{"class":214},[200,23793,23794,23796,23798,23800,23803],{"class":202,"line":860},[200,23795,20017],{"class":235},[200,23797,239],{"class":214},[200,23799,242],{"class":214},[200,23801,23802],{"class":245},"Ceci est le deuxième modal",[200,23804,991],{"class":214},[200,23806,23807],{"class":202,"line":889},[200,23808,12098],{"class":214},[200,23810,23811,23813,23815],{"class":202,"line":899},[200,23812,23526],{"class":235},[200,23814,239],{"class":214},[200,23816,22236],{"class":2438},[200,23818,23819,23821],{"class":202,"line":904},[200,23820,2111],{"class":214},[200,23822,416],{"class":235},[200,23824,23825,23827,23829],{"class":202,"line":930},[200,23826,3770],{"class":214},[200,23828,18917],{"class":223},[200,23830,466],{"class":235},[200,23832,23833],{"class":202,"line":3655},[200,23834,1129],{"class":214},[200,23836,23837,23839,23841],{"class":202,"line":3660},[200,23838,680],{"class":214},[200,23840,14896],{"class":235},[200,23842,647],{"class":214},[200,23844,23845],{"class":202,"line":4035},[200,23846,528],{"emptyLinePlaceholder":20},[200,23848,23849,23851,23853],{"class":202,"line":4490},[200,23850,599],{"class":214},[200,23852,21468],{"class":235},[200,23854,647],{"class":214},[200,23856,23857,23859],{"class":202,"line":4496},[200,23858,656],{"class":214},[200,23860,21477],{"class":235},[200,23862,23863,23866,23868,23870,23872,23875,23877,23879],{"class":202,"line":4502},[200,23864,23865],{"class":214},"    :",[200,23867,3581],{"class":379},[200,23869,449],{"class":214},[200,23871,610],{"class":214},[200,23873,23874],{"class":210},"props",[200,23876,215],{"class":214},[200,23878,3581],{"class":210},[200,23880,616],{"class":214},[200,23882,23883,23885,23887,23889,23891,23893,23895,23897],{"class":202,"line":4508},[200,23884,23865],{"class":214},[200,23886,3597],{"class":379},[200,23888,449],{"class":214},[200,23890,610],{"class":214},[200,23892,23874],{"class":210},[200,23894,215],{"class":214},[200,23896,3597],{"class":210},[200,23898,616],{"class":214},[200,23900,23901],{"class":202,"line":5469},[200,23902,11550],{"class":214},[200,23904,23905,23907,23909,23912,23915],{"class":202,"line":5474},[200,23906,803],{"class":214},[200,23908,21468],{"class":235},[200,23910,23911],{"class":214}," #",[200,23913,23914],{"class":379},"body",[200,23916,647],{"class":214},[200,23918,23919,23921],{"class":202,"line":13237},[200,23920,22071],{"class":214},[200,23922,23923],{"class":235},"Button\n",[200,23925,23926,23929,23931,23933,23936],{"class":202,"line":13263},[200,23927,23928],{"class":379},"        label",[200,23930,449],{"class":214},[200,23932,610],{"class":214},[200,23934,23935],{"class":245},"Ouvrir le Deuxième Modal",[200,23937,616],{"class":214},[200,23939,23940,23943,23945,23947,23949,23951],{"class":202,"line":13279},[200,23941,23942],{"class":214},"        @",[200,23944,22420],{"class":379},[200,23946,449],{"class":214},[200,23948,610],{"class":214},[200,23950,23604],{"class":210},[200,23952,616],{"class":214},[200,23954,23955],{"class":202,"line":13323},[200,23956,23957],{"class":214},"      \u002F>\n",[200,23959,23960,23962,23964],{"class":202,"line":13329},[200,23961,15078],{"class":214},[200,23963,21468],{"class":235},[200,23965,647],{"class":214},[200,23967,23968,23970,23972],{"class":202,"line":13334},[200,23969,892],{"class":214},[200,23971,21534],{"class":235},[200,23973,647],{"class":214},[200,23975,23976,23978,23980],{"class":202,"line":13360},[200,23977,680],{"class":214},[200,23979,21468],{"class":235},[200,23981,647],{"class":214},[192,23983,23986],{"className":21457,"code":23984,"filename":23985,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst props = defineProps\u003C{\n  title: string\n  description: string\n}>()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CModal\n    :title=\"props.title\"\n    :description=\"props.description\"\n  \u002F>\n\u003C\u002Ftemplate>\n","src\u002Fcomponents\u002FDialogFeature2.vue",[156,23987,23988,24008,24020,24028,24036,24042,24050,24054,24062,24068,24086,24104,24108],{"__ignoreMap":13},[200,23989,23990,23992,23994,23996,23998,24000,24002,24004,24006],{"class":202,"line":203},[200,23991,599],{"class":214},[200,23993,14896],{"class":235},[200,23995,14594],{"class":379},[200,23997,449],{"class":214},[200,23999,610],{"class":214},[200,24001,1383],{"class":245},[200,24003,610],{"class":214},[200,24005,21825],{"class":379},[200,24007,647],{"class":214},[200,24009,24010,24012,24014,24016,24018],{"class":202,"line":14},[200,24011,443],{"class":379},[200,24013,23690],{"class":210},[200,24015,449],{"class":214},[200,24017,23695],{"class":223},[200,24019,23698],{"class":214},[200,24021,24022,24024,24026],{"class":202,"line":255},[200,24023,23703],{"class":235},[200,24025,239],{"class":214},[200,24027,23708],{"class":1364},[200,24029,24030,24032,24034],{"class":202,"line":272},[200,24031,258],{"class":235},[200,24033,239],{"class":214},[200,24035,23708],{"class":1364},[200,24037,24038,24040],{"class":202,"line":283},[200,24039,23721],{"class":214},[200,24041,466],{"class":210},[200,24043,24044,24046,24048],{"class":202,"line":300},[200,24045,680],{"class":214},[200,24047,14896],{"class":235},[200,24049,647],{"class":214},[200,24051,24052],{"class":202,"line":310},[200,24053,528],{"emptyLinePlaceholder":20},[200,24055,24056,24058,24060],{"class":202,"line":336},[200,24057,599],{"class":214},[200,24059,21468],{"class":235},[200,24061,647],{"class":214},[200,24063,24064,24066],{"class":202,"line":342},[200,24065,656],{"class":214},[200,24067,21477],{"class":235},[200,24069,24070,24072,24074,24076,24078,24080,24082,24084],{"class":202,"line":365},[200,24071,23865],{"class":214},[200,24073,3581],{"class":379},[200,24075,449],{"class":214},[200,24077,610],{"class":214},[200,24079,23874],{"class":210},[200,24081,215],{"class":214},[200,24083,3581],{"class":210},[200,24085,616],{"class":214},[200,24087,24088,24090,24092,24094,24096,24098,24100,24102],{"class":202,"line":371},[200,24089,23865],{"class":214},[200,24091,3597],{"class":379},[200,24093,449],{"class":214},[200,24095,610],{"class":214},[200,24097,23874],{"class":210},[200,24099,215],{"class":214},[200,24101,3597],{"class":210},[200,24103,616],{"class":214},[200,24105,24106],{"class":202,"line":398},[200,24107,23365],{"class":214},[200,24109,24110,24112,24114],{"class":202,"line":405},[200,24111,680],{"class":214},[200,24113,21468],{"class":235},[200,24115,647],{"class":214},[35,24117,21630],{},[35,24119,24120,24121],{},"Explorez le code complet sur ",[1599,24122,24124,24125,215],{"repo":24123},"barbapapazes\u002Fvue-dialog-stacking"," et même ",[39,24126,24129],{"href":24127,"rel":24128},"https:\u002F\u002Fvue-dialog-stacking.barbapapazes.dev\u002F",[77],"une démo en direct",[130,24131,132,24132,132,24140],{},[24133,24134],"iframe",{"src":24127,"width":24135,"height":24136,"frameBorder":3948,"scrolling":24137,"allowFullScreen":20,"className":24138},"100%",600,"no",[24139],"bg-white",[143,24141,24142],{},"Démo en direct de l'empilement de boîtes de dialogue",[51,24144,24146],{"id":24145},"conclusion","Conclusion",[35,24148,24149],{},"Après des années de lutte avec les boîtes de dialogue et les modaux et d'essai de plusieurs approches, j'ai enfin trouvé une méthode qui fonctionne pour moi et qui peut fonctionner pour vous.",[35,24151,24152,24153,24158,24159,24166],{},"J'ai d'abord rencontré cette approche avec Angular Material et j'ai espéré quelque chose de similaire dans Vue. ",[39,24154,24157],{"href":24155,"rel":24156},"https:\u002F\u002Fgithub.com\u002Fantfu",[77],"Anthony Fu"," a proposé un début prometteur avec ",[39,24160,24163],{"href":24161,"rel":24162},"https:\u002F\u002Fgithub.com\u002Fantfu\u002Fvue-template-promise",[77],[156,24164,24165],{},"useTemplatePromise",", mais cela est resté complexe uniquement pour l'ouverture de boîtes de dialogue.",[35,24168,24169,24170,24175,24176,1655,24181,24186,24187,24189],{},"Enfin, je remercie ",[39,24171,24174],{"href":24172,"rel":24173},"https:\u002F\u002Fgithub.com\u002Fzernonia",[77],"zernonia"," pour ",[39,24177,24180],{"href":24178,"rel":24179},"https:\u002F\u002Freka-ui.com\u002F",[77],"Reka UI",[39,24182,24185],{"href":24183,"rel":24184},"https:\u002F\u002Fgithub.com\u002Fgenu",[77],"Eugen Istoc"," pour la composable ",[156,24188,23098],{}," dans Nuxt UI, je peux désormais gérer les boîtes de dialogue de manière appropriée dans Vue.",[35,24191,24192],{},"Je suis convaincu que cette approche est un changement majeur pour les expériences UI interactives dans Vue. N'hésitez pas à l'essayer !",[1170,24194,24195],{},"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 .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 .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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":13,"searchDepth":14,"depth":14,"links":24197},[24198,24199,24200,24201,24202,24203],{"id":21403,"depth":14,"text":21404},{"id":21697,"depth":14,"text":21698},{"id":22024,"depth":14,"text":22025},{"id":22908,"depth":14,"text":22909},{"id":23386,"depth":14,"text":23387},{"id":24145,"depth":14,"text":24146},"2025-03-29","Après des années de recherche d'une approche efficace pour gérer les boîtes de dialogue, j'ai enfin découvert une technique fiable et efficace.",{},"\u002Ffr\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs","---\ncontentId: fe3fd9b9-481e-4d38-9cea-969595fc913a\ntitle: \"Les boîtes de dialogue dans l'expérience utilisateur\"\ndescription: \"Après des années de recherche d'une approche efficace pour gérer les boîtes de dialogue, j'ai enfin découvert une technique fiable et efficace.\"\ndate: 2025-03-29\n---\n\nAutant que je me souvienne, ce défi a été le plus persistant depuis que j'ai commencé le **développement web**. Récemment, j'ai découvert une méthode pour construire une bibliothèque de composants, ce qui a été un long processus. Cependant, résoudre les subtilités de la gestion des boîtes de dialogue s'est avéré encore plus vaste, complexe, et crucial en raison de leur présence omniprésente dans chaque application.\n\n\u003CCalloutReadMore id=\"75df6bff-de40-4056-af23-fae2401cd21a\" \u002F>\n\nAujourd'hui, je suis ravi de partager la solution que j'ai trouvée, _je ne l'ai pas développée_, pour gérer efficacement les boîtes de dialogue au sein de vos applications. Cette solution découle de plusieurs années d'expérience, et j'espère qu'elle vous sera aussi bénéfique qu'elle l'a été pour moi.\n\n## Comprendre le problème\n\nAvant de plonger dans le problème, alignons-nous sur ce dont nous parlons. Cet article se concentre sur les **boîtes de dialogue**, **modaux**, et **slideover**. Chacun sert à afficher des informations ou à solliciter une confirmation, différant principalement en termes de présentation et d'utilisation.\n\nEssayez d'ouvrir la boîte de dialogue en cliquant sur le bouton ci-dessous.\n\n\u003Cdiv class=\"flex justify-center\">\n  \u003CModal\n    title=\"Boîte de Dialogue\"\n    description=\"Ceci est une boîte de dialogue\"\n    :ui=\"{ content: 'min-w-[280px]' }\"\n  >\n    \u003CButton label=\"Ouvrir la Boîte de Dialogue\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Fdiv>\n\nDe même, vous pouvez ouvrir un slideover en cliquant sur le bouton.\n\n\u003Cdiv class=\"flex justify-center\">\n  \u003CSlideover\n    title=\"Slideover\"\n    description=\"Ceci est un slideover\"\n    :ui=\"{ content: 'min-w-[280px]' }\"\n  >\n    \u003CButton label=\"Ouvrir le Slideover\" \u002F>\n  \u003C\u002FSlideover>\n\u003C\u002Fdiv>\n\nLe design et les types de dialogues peuvent sembler secondaires ; l'aspect important réside dans le code sous-jacent. Examinons-le :\n\n::: code-group\n\n```vue [src\u002Fcomponents\u002FModal.vue]\n\u003Ctemplate>\n  \u003CModal\n    title=\"Boîte de Dialogue\"\n    description=\"Ceci est une boîte de dialogue\"\n  >\n    \u003CButton label=\"Ouvrir la Boîte de Dialogue\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n```\n\n```vue [src\u002Fcomponents\u002FSlideover.vue]\n\u003Ctemplate>\n  \u003CSlideover\n    title=\"Slideover\"\n    description=\"Ceci est un slideover\"\n  >\n    \u003CButton label=\"Ouvrir le Slideover\" \u002F>\n  \u003C\u002FSlideover>\n\u003C\u002Ftemplate>\n```\n\n:::\n\nIls sont très similaires, reposant sur le même composant en coulisses. La véritable intrigue réside dans la manière dont ce code est utilisé. Ces exemples illustrent que les boîtes de dialogue s'intègrent parfaitement comme tout élément HTML, ce qui est le cœur du problème.\n\nLes boîtes de dialogue sont un type particulier de composant dans une application :\n\n1. Ils peuvent être invoqués de n'importe où dans l'application.\n2. Ils s'ouvrent par divers moyens, tels que :\n    - Clics de bouton\n    - Pressions de touches\n    - Navigations dans les routes\n3. La nature omniprésente rend des applications sans boîtes de dialogue presque impraticables sans sacrifier l'expérience utilisateur :\n    - Création d'éléments\n    - Modification d'éléments\n    - Confirmations d'action\n    - Visualisations d'éléments\n    - Etc.\n\n    Chaque action peut mener à des conséquences immédiates ou attendre une réponse du serveur, ce qui complique les possibilités d'utilisation des boîtes de dialogue.\n\nEn raison de ces facteurs, les boîtes de dialogue nécessitent une attention minutieuse lors du développement et de l'utilisation ; sinon, ils et leur logique peuvent rapidement devenir chaotiques.\n\nDe plus, j'ai toujours eu une préférence personnelle pour permettre l'empilement de boîtes de dialogue avec des animations fluides, comme ceci :\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs\u002Fmodal-stacking.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n  \u003Cfigcaption>Empiler des boîtes de dialogue avec une animation élégante.\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n## Boîte de dialogue en ligne\n\nUne première tentative pour relever ce défi est via des **boîtes de dialogue en ligne**. C'est une approche répandue et qui convient aux cas simples. La stratégie consiste à incorporer un composant modal directement dans le template nécessitant celui-ci, comme vu précédemment.\n\n```vue [src\u002Fcomponents\u002FModal.vue]\n\u003Ctemplate>\n  \u003CModal\n    title=\"Dialogue\"\n    description=\"Ceci est un dialogue\"\n  >\n    \u003CButton label=\"Ouvrir le Dialogue\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n```\n\nBien que l'implémentation soit simple, cette approche présente au moins deux inconvénients significatifs :\n\n1. Les composants de boîte de dialogue deviennent étroitement couplés avec le composant qui les utilise, compliquant la compréhension et le refactoring.\n2. Il devient coûteux, en terme de code, d'ajouter une nouvelle boîte de dialogue, surtout lorsqu'on ouvre les ouvre sans boutons ou en découplant les templates d'une boîte de dialogue. Un exemple d'ouverture d'une boîte dialogue par pression de touche suit :\n\n```vue [src\u002Fcomponents\u002FInlineModal.vue]\n\u003Cscript lang=\"ts\" setup>\nimport { ref } from 'vue'\n\nconst isDialogOpen = ref(false)\n\ndefineShortcuts({\n  meta_k: () => {\n    isDialogOpen.value = !isDialogOpen.value\n  }\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CModal\n      v-model:open=\"isDialogOpen\"\n      title=\"Boîte de Dialogue\"\n      description=\"Ceci est une boîte de dialogue\"\n    \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nCette méthode devient rapidement encombrante et difficile à maintenir au fil du temps. Plusieurs boîtes de dialogue intensifient le code répétitif, nécessitant des ajouts répétés dans chaque composant utilisant la boîte de dialogue.\n\n## Boîtes de dialogue globales\n\nAméliorer la gestion des boîtes de dialogue implique d'en ouvrir programatiquement de manière universelle, découplant leur invocation de toute relation directe avec un composant. Ce modèle facilite le refactoring du code en séparant physiquement les boîtes de dialogue des composants, améliorant la réutilisabilité à travers plusieurs composants sans incorporations redondantes.\n\nUn fichier `App.vue` pourrait ressembler à ceci :\n\n```vue [src\u002FApp.vue]\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CLayout>\n      \u003CRouterView \u002F>\n    \u003C\u002FLayout>\n\n    \u003CModalFeature1 \u002F> \u003C!-- [!code highlight] -->\n    \u003CModalFeature2 \u002F> \u003C!-- [!code highlight] -->\n    \u003CModalFeature3 \u002F> \u003C!-- [!code highlight] -->\n\n    \u003C!-- Et ainsi de suite... -->\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nAtteindre cette configuration exige de partager une variable entre le composant ouvrant la boîte de dialogue et la boîte de dialogue ell-même. Dans Vue, une approche simple, bien que pas idéale, est de créer une composable externalisant son état.\n\n```ts [src\u002Fcomposables\u002FuseDialog.ts]\nimport { ref } from 'vue'\n\nconst isOpen = ref(false) \u002F\u002F [!code highlight]\n\nexport function useDialog() {\n  function open() {\n    isOpen.value = true\n  }\n\n  function close() {\n    isOpen.value = false\n  }\n\n  return {\n    isOpen,\n\n    open,\n    close\n  }\n}\n```\n\nUtilisez ce composable pour le composant ouvrant la boîte de dialogue et la boîte de dialogue elle-même.\n\n```vue [src\u002FApp.vue]\n\u003Cscript lang=\"ts\" setup>\nimport { useDialog } from '@\u002Fcomposables\u002FuseDialog'\n\nconst { open } = useDialog() \u002F\u002F [!code highlight]\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CButton label=\"Ouvrir la Boîte de Dialogue\" @click=\"open\" \u002F>\n\u003C\u002Ftemplate>\n```\n\n```vue [src\u002Fcomponents\u002FModalFeature1.vue]\n\u003Cscript lang=\"ts\" setup>\nimport { useDialog } from '@\u002Fcomposables\u002FuseDialog'\n\nconst { isOpen, close } = useDialog() \u002F\u002F [!code highlight]\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CModal\n    v-model:open=\"isOpen\"\n    title=\"Boîte de Dialogue\"\n    description=\"Ceci est une boîte de dialogue\"\n  >\n    \u003CButton label=\"Fermer\" @click=\"close\" \u002F>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n```\n\nCette approche est certainement supérieure à la méthode en ligne mais présente tout de même des inconvénients :\n\n1. L'inclusion de boîtes de dialogue au niveau de l'application (ou de la route) crée une redondance de code.\n2. La gestion des boîtes de dialogue devient complexe, particulièrement avec de nombreuses boîtes de dialogue ou propriétés, chacune nécessitant un passage à travers la composable.\n\n    ```ts\n    import { ref } from 'vue'\n\n    const isOpen = ref(false)\n    const title = ref('') \u002F\u002F [!code highlight]\n    const description = ref('') \u002F\u002F [!code highlight]\n\n    export function useDialog() {\n      function open(newTitle: string, newDescription: string) {\n        title.value = newTitle\n        description.value = newDescription\n        isOpen.value = true\n      }\n\n      function close() {\n        isOpen.value = false\n      }\n\n      return {\n        isOpen,\n        title,\n        description,\n        open,\n        close\n      }\n    }\n    ```\n\n> [!NOTE]\n> Simplifiez l'écriture des composables avec [`createSharedComposable`](https:\u002F\u002Fvueuse.org\u002Fshared\u002FcreateSharedComposable\u002F) de [`@vueuse\u002Fcore`](https:\u002F\u002Fvueuse.org\u002F) pour créer un composable singleton, partageant automatiquement le même état entre tous les composants qui l'utilisent.\n\n## Dialogues programmatiques\n\nLa méthode la plus récente et profondément efficace que j'ai découverte implique **la gestion programmatique des boîtes de dialogue**. Cette méthode résout les difficultés mentionnées plus tôt en permettant un contrôle complet des boîtes de dialogue par le biais du code plutôt que des templates.\n\nAyant utilisé des boîtes de dialogue programmatiques dans **Angular** avec Angular Material, j'ai apprécié sa capacité à simplifier le code.\n\n```ts [Angular Material Dialog]\nconst dialogRef = dialog.open(UserProfileComponent, {\n  height: '400px',\n  width: '600px',\n})\n\ndialogRef.afterClosed().subscribe((result) => {\n  console.log(`Résultat du dialogue : ${result}`) \u002F\u002F Pizza !\n})\n\ndialogRef.close('Pizza !')\n```\n\nPendant longtemps, j'ai espéré d'une fonctionnalité similaire dans Vue.\n\nCe temps est désormais arrivé, lorsque [Nuxt](https:\u002F\u002Fnuxt.com\u002F) a sorti la version 3 de [Nuxt UI](https:\u002F\u002Fui.nuxt.com\u002F) avec la composable `useOverlay`. Ce composable partagé par l'application permet d'ouvrir des boîtes de dialogue de n'importe quel endroit, imitant la fonctionnalité d'Angular Material.\n\n```vue [src\u002FApp.vue]\n\u003Cscript setup lang=\"ts\">\nconst overlay = useOverlay()\n\nasync function openModal() {\n  overlay\n    .create(MyModal)\n    .open()\n}\n\u003C\u002Fscript>\n```\n\nMagnifique, n'est-ce pas ? 😍\n\nSous le capot, cette méthode utilise la directive [`component`](https:\u002F\u002Fvuejs.org\u002Fguide\u002Fessentials\u002Fcomponent-basics.html#dynamic-components) avec une simple boucle [`v-for`](https:\u002F\u002Fvuejs.org\u002Fapi\u002Fbuilt-in-directives.html#v-for) pour rendre les boîtes de dialogue.\n\n```vue [OverlayProvider.vue]\n\u003Cscript lang=\"ts\" setup>\nconst { overlays, close } = useOverlay()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Ccomponent\n    :is=\"overlay.component\"\n    v-for=\"overlay in overlays\"\n    :key=\"overlay.id\"\n    v-bind=\"overlay.props\"\n    v-model:open=\"overlay.modelValue\"\n  \u002F>\n\u003C\u002Ftemplate>\n```\n\n> [!NOTE]\n> Il s'agit d'une version simplifiée du code.\n\nAvec cette méthode, les coûts de la gestion d'état sont éliminés, ce qui supprime le besoin de composables partagés ou de boîtes de dialogue globales. Cela améliore considérablement la lisibilité, la maintenabilité et la création d'expériences UI interactives.\n\n## Empilement des boîtes de dialogue sans effort\n\nLe point culminant de cette approche est l'empilement de boîtes de dialogue sans effort. Il suffit de rendre un tableau de boîtes de dialogue via une boucle pour permettre l'ouverture simultanée de boîtes de dialogue, facilitant un empilement élégant avec des animations fluides.\n\n::: code-group\n\n```vue [src\u002FApp.vue]\n\u003Cscript lang=\"ts\" setup>\nimport DialogFeature1 from '.\u002Fcomponents\u002FDialogFeature1.vue'\n\nconst overlay = useOverlay()\nfunction openOverlay() {\n  overlay\n    .create(DialogFeature1, {\n      props: {\n        title: 'Premier Modal',\n        description: 'Ceci est le premier modal'\n      },\n      destroyOnClose: true\n    })\n    .open()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CButton label=\"Ouvrir le Premier Overlay\" @click=\"openOverlay\" \u002F>\n\n    \u003COverlayProvider \u002F>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n```vue [src\u002Fcomponents\u002FDialogFeature1.vue]\n\u003Cscript lang=\"ts\" setup>\nimport DialogFeature2 from '.\u002FDialogFeature2.vue'\n\nconst props = defineProps\u003C{\n  title: string\n  description: string\n}>()\n\nconst overlay = useOverlay()\nfunction openOverlay() {\n  overlay\n    .create(DialogFeature2, {\n      props: {\n        title: 'Deuxième Modal',\n        description: 'Ceci est le deuxième modal'\n      },\n      destroyOnClose: true\n    })\n    .open()\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CModal\n    :title=\"props.title\"\n    :description=\"props.description\"\n  >\n    \u003Ctemplate #body>\n      \u003CButton\n        label=\"Ouvrir le Deuxième Modal\"\n        @click=\"openOverlay\"\n      \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FModal>\n\u003C\u002Ftemplate>\n```\n\n```vue [src\u002Fcomponents\u002FDialogFeature2.vue]\n\u003Cscript lang=\"ts\" setup>\nconst props = defineProps\u003C{\n  title: string\n  description: string\n}>()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CModal\n    :title=\"props.title\"\n    :description=\"props.description\"\n  \u002F>\n\u003C\u002Ftemplate>\n```\n\n:::\n\nExplorez le code complet sur \u003CGitHubLink repo=\"barbapapazes\u002Fvue-dialog-stacking\" \u002F> et même [une démo en direct](https:\u002F\u002Fvue-dialog-stacking.barbapapazes.dev\u002F).\n\n\u003Cfigure>\n  \u003Ciframe\n    src=\"https:\u002F\u002Fvue-dialog-stacking.barbapapazes.dev\u002F\"\n    width=\"100%\"\n    height=\"600\"\n    frameborder=\"0\"\n    scrolling=\"no\"\n    allowfullscreen\n    class=\"bg-white\"\n  >\u003C\u002Fiframe>\n  \u003Cfigcaption>Démo en direct de l'empilement de boîtes de dialogue\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\n## Conclusion\n\nAprès des années de lutte avec les boîtes de dialogue et les modaux et d'essai de plusieurs approches, j'ai enfin trouvé une méthode qui fonctionne pour moi et qui peut fonctionner pour vous.\n\nJ'ai d'abord rencontré cette approche avec Angular Material et j'ai espéré quelque chose de similaire dans Vue. [Anthony Fu](https:\u002F\u002Fgithub.com\u002Fantfu) a proposé un début prometteur avec [`useTemplatePromise`](https:\u002F\u002Fgithub.com\u002Fantfu\u002Fvue-template-promise), mais cela est resté complexe uniquement pour l'ouverture de boîtes de dialogue.\n\nEnfin, je remercie [zernonia](https:\u002F\u002Fgithub.com\u002Fzernonia) pour [Reka UI](https:\u002F\u002Freka-ui.com\u002F) et [Eugen Istoc](https:\u002F\u002Fgithub.com\u002Fgenu) pour la composable `useOverlay` dans Nuxt UI, je peux désormais gérer les boîtes de dialogue de manière appropriée dans Vue.\n\nJe suis convaincu que cette approche est un changement majeur pour les expériences UI interactives dans Vue. N'hésitez pas à l'essayer !\n",{"title":21381,"description":24205},{"loc":24207},"fr\u002Fposts\u002Fa-journey-to-craft-interactive-ui-experiences-with-dialogs","YbUj3IsSqTKsSlTsBFoOIF5l5gNuSQEO4HYb2HC-2WE",{"id":24214,"title":24215,"body":24216,"contentId":9265,"date":28920,"description":28921,"extension":18,"meta":28922,"navigation":20,"path":28923,"rawbody":28924,"robots":23,"schemaOrg":23,"seo":28925,"sitemap":28926,"stem":28934,"__hash__":28935},"posts\u002Ffr\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components.md","Fusion ou l'art d'intégrer PHP dans des composants Vue",{"type":7,"value":24217,"toc":28906},[24218,24224,24239,24261,24267,24281,24288,24313,24316,24377,24379,24382,24385,24410,24420,24423,24430,24434,24446,24449,24452,24472,24479,24486,24711,24714,24717,24720,24728,24733,24736,24739,24749,24755,24758,24847,24860,24863,24869,24890,25015,25031,25034,25068,25174,25180,25210,25240,25249,25255,25271,25288,25291,25317,25322,25325,25331,25334,25337,25340,25376,25452,25455,25476,25664,25670,25677,25683,25686,25689,25695,25698,25705,25708,25720,25724,25727,25730,25738,25741,25745,25748,25763,25772,25778,25784,25795,25804,25813,25821,25829,25848,25899,25906,25968,25975,25978,26052,26055,26099,26102,26200,26214,26225,26232,26245,26259,26280,26298,26301,28903],[35,24219,24220,24221,215],{},"Cet article est vraiment ",[6862,24222,24223],{},"spécial",[35,24225,24226,24227,24232,24233,106,24236],{},"Il y a deux semaines, les 3 et 4 février 2025, ",[39,24228,24231],{"href":24229,"rel":24230},"https:\u002F\u002Flaracon.eu\u002F",[77],"Laracon EU"," a eu lieu à Amsterdam. Bien que je n'ai pas pu y assister en personne, j'ai suivi l'événement entier en direct. Les conférences offrent toujours une occasion fantastique d'acquérir de nouvelles connaissances et de s'immerger davantage dans le merveilleux ",[6862,24234,24235],{},"écosystème Laravel",[1216,24237,24238],{},"J'espère assister à Laracon un jour.",[35,24240,24241,24242,6865,24247,24250,24251,1655,24254,24257,24258,24260],{},"Une des présentations qui a retenu mon attention était ",[39,24243,24246],{"href":24244,"rel":24245},"https:\u002F\u002Fyoutu.be\u002FlXjYStxGFIQ?si=ntTW6zP-tAzV0OZO",[77],"\"Introducing [REDACTED]: A New Paradigm for Laravel + Javascript\"",[39,24248,17876],{"href":17874,"rel":24249},[77],". Cela m'a intrigué puisqu'il existe déjà ",[6862,24252,24253],{},"Inertia",[6862,24255,24256],{},"Livewire",", qui sont d'excellents outils pour créer des applications modernes avec ",[6862,24259,19463],{},". Quelles nouvelles avancées pouvaient-elles alors être proposées ?",[35,24262,24263],{},[1707,24264],{"alt":24265,"src":24266},"Miniature de la vidéo \"Introducing [REDACTED]: a new paradigm for Laravel + Javascript\" par Aaron Francis","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fthumbnail.jpg",[35,24268,24269,24270,24277,24278,24280],{},"Aaron a présenté ",[39,24271,24274],{"href":24272,"rel":24273},"https:\u002F\u002Fgithub.com\u002Ffusion-php",[77],[6862,24275,24276],{},"Fusion",", décrit comme une approche novatrice pour intégrer votre frontend moderne avec votre backend Laravel. Le concept permet d'écrire du code PHP directement dans un fichier frontend, et à travers un certain traitement, le code PHP est exécuté côté serveur, avec les résultats envoyés au frontend. Cela rappelle ",[6862,24279,24253],{},", mais poussé beaucoup plus loin.",[938,24282,24283],{},[35,24284,24285,24287],{},[200,24286,944],{},"\nVeuillez visionner la vidéo. C'est fascinant, facile à suivre et cela aidera considérablement à comprendre le reste de cet article.",[35,24289,24290,24291,24296,24297,13741,24300,24302,24303,24308,24309,24312],{},"Lors d'",[39,24292,24295],{"href":24293,"rel":24294},"https:\u002F\u002Fyoutu.be\u002FvqWlJCtoD-k?si=Xnd4MOa5-dcBvPhi",[77],"une interview sur le podcast Navbar",", Aaron a articulé l'objectif de Fusion : combler le fossé entre le frontend et le backend, simplifiant le développement et transformant Laravel avec Inertia pour ressembler à des méta-frameworks actuels comme ",[6862,24298,24299],{},"Next",[6862,24301,2934],{},". Avec son partenaire commercial ",[39,24304,24307],{"href":24305,"rel":24306},"https:\u002F\u002Fx.com\u002Fsteve_tenuto",[77],"Steve",", Aaron a découvert que les conventions Laravel, comme les routes dans ",[156,24310,24311],{},"web.php",", n'étaient pas intuitives pour les développeurs JavaScript qui préfèrent le routage basé sur des fichiers. L'objectif d'Aaron est de rendre Laravel plus accessible aux développeurs JavaScript.",[35,24314,24315],{},"Avec Fusion, un nouveau bloc est introduit dans le SFC de Vue pour écrire du code PHP, en étant une partie intégrante de l'application Laravel.",[192,24317,24319],{"className":21457,"code":24318,"language":12075,"meta":13,"style":13},"\u003Cphp>\n  \u002F\u002F Définir une prop en PHP\n  $name = prop(Auth::user()->name);\n\u003C\u002Fphp>\n\n\u003Ctemplate>\n  \u003C!-- Utilisez-le dans Vue ! -->\n  Bonjour {{ name }} !\n\u003C\u002Ftemplate>\n",[156,24320,24321,24329,24334,24339,24347,24351,24359,24364,24369],{"__ignoreMap":13},[200,24322,24323,24325,24327],{"class":202,"line":203},[200,24324,599],{"class":214},[200,24326,19563],{"class":235},[200,24328,647],{"class":214},[200,24330,24331],{"class":202,"line":14},[200,24332,24333],{"class":210},"  \u002F\u002F Définir une prop en PHP\n",[200,24335,24336],{"class":202,"line":255},[200,24337,24338],{"class":210},"  $name = prop(Auth::user()->name);\n",[200,24340,24341,24343,24345],{"class":202,"line":272},[200,24342,680],{"class":214},[200,24344,19563],{"class":235},[200,24346,647],{"class":214},[200,24348,24349],{"class":202,"line":283},[200,24350,528],{"emptyLinePlaceholder":20},[200,24352,24353,24355,24357],{"class":202,"line":300},[200,24354,599],{"class":214},[200,24356,21468],{"class":235},[200,24358,647],{"class":214},[200,24360,24361],{"class":202,"line":310},[200,24362,24363],{"class":401},"  \u003C!-- Utilisez-le dans Vue ! -->\n",[200,24365,24366],{"class":202,"line":336},[200,24367,24368],{"class":210},"  Bonjour {{ name }} !\n",[200,24370,24371,24373,24375],{"class":202,"line":342},[200,24372,680],{"class":214},[200,24374,21468],{"class":235},[200,24376,647],{"class":214},[1158,24378],{},[35,24380,24381],{},"Cet article n'a pas pour but de déterminer si Fusion devrait être utilisé en production ou si l'intégration de PHP dans un SFC de Vue est conseillée. Ce débat est bien couvert, notamment sur X ou Reddit.",[35,24383,24384],{},"Au lieu de cela, je compte explorer le fonctionnement de Fusion pour deux raisons principales :",[68,24386,24387,24390],{},[71,24388,24389],{},"Il y a beaucoup à apprendre en examinant le code. Très honnêtement, je ne lis pas beaucoup de livres, mais je parcoure le code des projets, volant des astuces et des manières de faire négligés ailleurs.",[71,24391,24392,24393,3092,24395,9502,24397,24400,24401,17891,24404,24409],{},"Fusion incarne un sommet technique de ce qui est réalisable avec ",[6862,24394,19463],{},[6862,24396,17998],{},[6862,24398,24399],{},"Vite",". Mon désir de comprendre son fonctionnement m'a conduit à rédiger cet article. En ce moment, j'étudie également la bibliothèque ",[39,24402,14044],{"href":14042,"rel":24403},[77],[39,24405,24408],{"href":24406,"rel":24407},"https:\u002F\u002Fgithub.com\u002Fjohnsoncodehk",[77],"Johnson Chu"," de la même manière. Comprendre quelque chose en profondeur implique souvent de l'expliquer aux autres, d'où cet article.",[938,24411,24412],{},[35,24413,24414,24416,24417,24419],{},[200,24415,944],{},"\nL'intégration de Vite avec Laravel est exceptionnelle, offrant de nombreux avantages aux développeurs Laravel. Si vous n'avez pas encore exploré ",[6862,24418,24399],{},", vous devriez absolument le faire.",[35,24421,24422],{},"Maintenant, plongeons dans le code.",[938,24424,24425],{},[35,24426,24427,24429],{},[200,24428,944],{},"\nLire la base de code est fascinant ; cela ressemble à lire du PHP écrit en JavaScript.",[51,24431,24433],{"id":24432},"le-code-de-fusion","Le code de Fusion",[35,24435,24436,24437,24440,24441,24445],{},"Une semaine après sa présentation à ",[39,24438,24231],{"href":24229,"rel":24439},[77],", Aaron a rendu la base de code de Fusion publique. Curieux de ses mécanismes, j'ai supposé qu'un plugin Vite pourrait extraire le PHP d'un SFC de Vue, de manière similaire à la façon dont le ",[39,24442,24444],{"href":13738,"rel":24443},[77],"plugin Vue de Vite"," gère les balises script, template et style.",[35,24447,24448],{},"Le support pour HTML, CSS et JavaScript est simple pour Vite puisqu'ils sont gérés nativement par Vite. Cependant, le PHP pose un défi car il n'est pas pris en charge.",[35,24450,24451],{},"Pour mieux comprendre l'organisation de l'article, posons-nous quelques questions. D'abord : comment Fusion gère-t-il le code PHP ?",[130,24453,132,24454,132,24459],{},[134,24455,136,24456,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,24457],{"src":24458,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fhype-video.mp4",[143,24460,24461,24462,1655,24464,3092,24467,24471],{},"\n    Vidéo Hype de Fusion par ",[39,24463,17876],{"href":17874,"target":7156},[39,24465,24466],{"href":24305,"target":7156},"Steve Tenuto",[39,24468,24470],{"href":24469,"target":7156},"https:\u002F\u002Fx.com\u002Faarondfrancis\u002Fstatus\u002F1886768725509976146","disponible sur X",".\n  ",[35,24473,24474,24475,24478],{},"En regardant la vidéo hype, il est évident qu'il y a plus que simplement extraire le code PHP et l'exécuter. Fusion passe les résultats du code PHP en tant que props au composant Vue et prend en charge le RPC sur le backend Laravel depuis le client. De plus, le mot-clé ",[156,24476,24477],{},"sync"," permet la synchronisation entre les états frontend et backend.",[35,24480,24481,24482,24485],{},"Par exemple, une fonction appelée ",[156,24483,24484],{},"favorite"," devient facilement utilisable dans le frontend :",[192,24487,24489],{"className":21457,"code":24488,"language":12075,"meta":13,"style":13},"\u003Cphp>\nuse function \\Fusion\\{prop, expose};\nuse \\App\\Models\\Podcast;\n\n$podcasts = prop(fn () => Podcast::all())->readonly();\n\nexpose(favorite: fn (Podcast $podcast) => response()->json($podcast->toggleFavorite()));\n\u003C\u002Fphp>\n\n\u003Cscript lang=\"ts\" setup>\nimport Podcast from '@Components\u002FPodcast.vue'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CPodcast\n    v-for=\"podcast in podcasts\"\n    :key=\"podcast.id\"\n    v-bind=\"podcast\"\n    @favorite=\"favorite({ podcast }).then(fav => podcast.favorited = fav)\"\n  \u002F>\n\u003C\u002Ftemplate>\n",[156,24490,24491,24499,24504,24509,24513,24518,24522,24527,24535,24539,24559,24575,24583,24587,24595,24602,24621,24640,24652,24699,24703],{"__ignoreMap":13},[200,24492,24493,24495,24497],{"class":202,"line":203},[200,24494,599],{"class":214},[200,24496,19563],{"class":235},[200,24498,647],{"class":214},[200,24500,24501],{"class":202,"line":14},[200,24502,24503],{"class":210},"use function \\Fusion\\{prop, expose};\n",[200,24505,24506],{"class":202,"line":255},[200,24507,24508],{"class":210},"use \\App\\Models\\Podcast;\n",[200,24510,24511],{"class":202,"line":272},[200,24512,528],{"emptyLinePlaceholder":20},[200,24514,24515],{"class":202,"line":283},[200,24516,24517],{"class":210},"$podcasts = prop(fn () => Podcast::all())->readonly();\n",[200,24519,24520],{"class":202,"line":300},[200,24521,528],{"emptyLinePlaceholder":20},[200,24523,24524],{"class":202,"line":310},[200,24525,24526],{"class":210},"expose(favorite: fn (Podcast $podcast) => response()->json($podcast->toggleFavorite()));\n",[200,24528,24529,24531,24533],{"class":202,"line":336},[200,24530,680],{"class":214},[200,24532,19563],{"class":235},[200,24534,647],{"class":214},[200,24536,24537],{"class":202,"line":342},[200,24538,528],{"emptyLinePlaceholder":20},[200,24540,24541,24543,24545,24547,24549,24551,24553,24555,24557],{"class":202,"line":365},[200,24542,599],{"class":214},[200,24544,14896],{"class":235},[200,24546,14594],{"class":379},[200,24548,449],{"class":214},[200,24550,610],{"class":214},[200,24552,1383],{"class":245},[200,24554,610],{"class":214},[200,24556,21825],{"class":379},[200,24558,647],{"class":214},[200,24560,24561,24563,24566,24568,24570,24573],{"class":202,"line":371},[200,24562,972],{"class":206},[200,24564,24565],{"class":210}," Podcast ",[200,24567,1489],{"class":206},[200,24569,242],{"class":214},[200,24571,24572],{"class":245},"@Components\u002FPodcast.vue",[200,24574,991],{"class":214},[200,24576,24577,24579,24581],{"class":202,"line":398},[200,24578,680],{"class":214},[200,24580,14896],{"class":235},[200,24582,647],{"class":214},[200,24584,24585],{"class":202,"line":405},[200,24586,528],{"emptyLinePlaceholder":20},[200,24588,24589,24591,24593],{"class":202,"line":410},[200,24590,599],{"class":214},[200,24592,21468],{"class":235},[200,24594,647],{"class":214},[200,24596,24597,24599],{"class":202,"line":860},[200,24598,656],{"class":214},[200,24600,24601],{"class":235},"Podcast\n",[200,24603,24604,24606,24608,24610,24613,24616,24619],{"class":202,"line":889},[200,24605,23309],{"class":206},[200,24607,449],{"class":214},[200,24609,610],{"class":214},[200,24611,24612],{"class":210},"podcast",[200,24614,24615],{"class":214}," in ",[200,24617,24618],{"class":210},"podcasts",[200,24620,616],{"class":214},[200,24622,24623,24625,24628,24630,24632,24634,24636,24638],{"class":202,"line":899},[200,24624,23865],{"class":214},[200,24626,24627],{"class":379},"key",[200,24629,449],{"class":214},[200,24631,610],{"class":214},[200,24633,24612],{"class":210},[200,24635,215],{"class":214},[200,24637,3295],{"class":210},[200,24639,616],{"class":214},[200,24641,24642,24644,24646,24648,24650],{"class":202,"line":904},[200,24643,23337],{"class":379},[200,24645,449],{"class":214},[200,24647,610],{"class":214},[200,24649,24612],{"class":210},[200,24651,616],{"class":214},[200,24653,24654,24657,24659,24661,24663,24665,24668,24670,24673,24675,24677,24680,24682,24685,24687,24690,24693,24695,24697],{"class":202,"line":930},[200,24655,24656],{"class":214},"    @",[200,24658,24484],{"class":379},[200,24660,449],{"class":214},[200,24662,610],{"class":214},[200,24664,24484],{"class":223},[200,24666,24667],{"class":214},"({ ",[200,24669,24612],{"class":210},[200,24671,24672],{"class":214}," }).",[200,24674,10944],{"class":223},[200,24676,227],{"class":214},[200,24678,24679],{"class":386},"fav",[200,24681,393],{"class":379},[200,24683,24684],{"class":210}," podcast",[200,24686,215],{"class":214},[200,24688,24689],{"class":210},"favorited",[200,24691,24692],{"class":214}," = ",[200,24694,24679],{"class":210},[200,24696,2104],{"class":214},[200,24698,616],{"class":214},[200,24700,24701],{"class":202,"line":3655},[200,24702,23365],{"class":214},[200,24704,24705,24707,24709],{"class":202,"line":3660},[200,24706,680],{"class":214},[200,24708,21468],{"class":235},[200,24710,647],{"class":214},[35,24712,24713],{},"Une autre question se pose : que fait Fusion en coulisses pour permettre cette fonctionnalité ?",[35,24715,24716],{},"Enfin, il est dit que Fusion repousse Inertia au-delà de ses limites actuelles. Donc, une autre question : comment Fusion exploite-t-il Inertia ?",[35,24718,24719],{},"Répondons à ces questions une par une.",[68,24721,24722,24725],{},[71,24723,24724],{},"Comment Fusion gère-t-il le code PHP ?",[71,24726,24727],{},"Quels mécanismes Fusion emploie-t-il en coulisses pour permettre le RPC ?",[68,24729,24730],{"start":272},[71,24731,24732],{},"De quelle manière Fusion utilise-t-il Inertia ?",[35,24734,24735],{},"Prêts pour une exploration ? Commençons.",[51,24737,24724],{"id":24738},"comment-fusion-gère-t-il-le-code-php",[35,24740,24741,24742,106,24746],{},"Tout commence avec ",[39,24743,24399],{"href":24744,"rel":24745},"https:\u002F\u002Fvite.dev\u002F",[77],[1216,24747,24748],{},"Comme d'habitude.",[35,24750,24751,24752,24754],{},"Pour ceux qui ne le connaissent pas, ",[6862,24753,24399],{}," est un outil de build frontend remarquablement rapide qui alimente la prochaine génération d'applications web. Il a introduit une nouvelle philosophie connue sous le nom de philosophie à la demande. Essentiellement, Vite agit comme un proxy entre le navigateur, qui demande des fichiers, et le système de fichiers qui les contient. Lorsque le navigateur demande un fichier, Vite intercepte la requête, récupère le fichier et le renvoie. Cependant, agir comme un proxy permet à Vite des capacités supplémentaires, telles que la transformation de fichiers, l'injection de code, la récupération de fichiers distants et la génération de fichiers virtuels (fichiers non présents sur le système de fichiers). C'est semblable à un pipeline. Sans aucun doute, l'avènement de Vite a été transformateur pour l'écosystème frontend. En ajoutant des plugins à son pipeline, il peut être adapté à n'importe quel besoin.",[35,24756,24757],{},"Avec cela en tête, nous pouvons comprendre comment un fichier SFC subit un traitement par Vite.",[192,24759,24761],{"className":21457,"code":24760,"language":12075,"meta":13,"style":13},"\u003Cscript setup>\n\u002F\u002F Le code JavaScript ira ici\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv \u002F>\n  \u003C!-- Votre code de modèle ira ici -->\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n  \u002F* Votre code CSS ira ici *\u002F\n\u003C\u002Fstyle>\n",[156,24762,24763,24773,24778,24786,24790,24798,24806,24811,24819,24823,24834,24839],{"__ignoreMap":13},[200,24764,24765,24767,24769,24771],{"class":202,"line":203},[200,24766,599],{"class":214},[200,24768,14896],{"class":235},[200,24770,21825],{"class":379},[200,24772,647],{"class":214},[200,24774,24775],{"class":202,"line":14},[200,24776,24777],{"class":401},"\u002F\u002F Le code JavaScript ira ici\n",[200,24779,24780,24782,24784],{"class":202,"line":255},[200,24781,680],{"class":214},[200,24783,14896],{"class":235},[200,24785,647],{"class":214},[200,24787,24788],{"class":202,"line":272},[200,24789,528],{"emptyLinePlaceholder":20},[200,24791,24792,24794,24796],{"class":202,"line":283},[200,24793,599],{"class":214},[200,24795,21468],{"class":235},[200,24797,647],{"class":214},[200,24799,24800,24802,24804],{"class":202,"line":300},[200,24801,656],{"class":214},[200,24803,11400],{"class":235},[200,24805,21527],{"class":214},[200,24807,24808],{"class":202,"line":310},[200,24809,24810],{"class":401},"  \u003C!-- Votre code de modèle ira ici -->\n",[200,24812,24813,24815,24817],{"class":202,"line":336},[200,24814,680],{"class":214},[200,24816,21468],{"class":235},[200,24818,647],{"class":214},[200,24820,24821],{"class":202,"line":342},[200,24822,528],{"emptyLinePlaceholder":20},[200,24824,24825,24827,24829,24832],{"class":202,"line":365},[200,24826,599],{"class":214},[200,24828,1170],{"class":235},[200,24830,24831],{"class":379}," scoped",[200,24833,647],{"class":214},[200,24835,24836],{"class":202,"line":371},[200,24837,24838],{"class":401},"  \u002F* Votre code CSS ira ici *\u002F\n",[200,24840,24841,24843,24845],{"class":202,"line":398},[200,24842,680],{"class":214},[200,24844,1170],{"class":235},[200,24846,647],{"class":214},[35,24848,24849,24850,24852,24853,24856,24857,215],{},"Lorsqu'une requête de fichier ",[156,24851,11866],{}," atteint le navigateur, un plugin Vite nommé ",[39,24854,13740],{"href":13738,"rel":24855},[77]," transforme le fichier en traitant le script et le modèle pour en faire du code JavaScript lisible. Le CSS est extrait et envoyé dans le pipeline CSS avant d'être dispatché au navigateur. L'aspect intéressant est la possibilité de créer un plugin capable d'examiner le pipeline de transformation. Mieux encore, il n'est pas nécessaire de le construire de toutes pièces ; c'est exactement ce que fait le plugin ",[39,24858,13579],{"href":13577,"rel":24859},[77],[35,24861,24862],{},"Par exemple, la transformation d'un SFC de Vue ressemble à ceci :",[35,24864,24865],{},[1707,24866],{"alt":24867,"src":24868},"Transformation d'un SFC de Vue vue à l'aide du plugin vite-plugin-inspect","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvue-sfc-transformation.png",[35,24870,24871,24872,24874,24875,24882,24883,24889],{},"D'accord, merci pour l'explication, mais que se passe-t-il dans Fusion ? Exactement la même chose mais pour le bloc PHP. Lorsqu'une requête contacte le serveur Vite en utilisant un fichier ",[156,24873,11866],{},", un plugin nommé ",[39,24876,24879],{"href":24877,"rel":24878},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L38-L40",[77],[156,24880,24881],{},"fusion-vue"," extrait le bloc PHP. Ce processus commence dans la fonction ",[39,24884,24887],{"href":24885,"rel":24886},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L112",[77],[156,24888,11785],{}," ci-dessous :",[192,24891,24893],{"className":1380,"code":24892,"language":1383,"meta":13,"style":13},"function transform(code, filename) {\n  if (!filename.endsWith('.vue')) {\n    return code\n  }\n\n  return new Transformer({\n    config: fusionConfig,\n    code,\n    filename,\n    isProd\n  }).transform()\n}\n",[156,24894,24895,24914,24941,24948,24952,24956,24969,24981,24988,24994,24999,25011],{"__ignoreMap":13},[200,24896,24897,24899,24901,24903,24905,24907,24910,24912],{"class":202,"line":203},[200,24898,14908],{"class":379},[200,24900,11830],{"class":223},[200,24902,227],{"class":214},[200,24904,156],{"class":386},[200,24906,549],{"class":214},[200,24908,24909],{"class":386}," filename",[200,24911,2104],{"class":214},[200,24913,280],{"class":214},[200,24915,24916,24918,24920,24922,24925,24927,24929,24931,24933,24935,24937,24939],{"class":202,"line":14},[200,24917,20098],{"class":206},[200,24919,1094],{"class":235},[200,24921,11852],{"class":214},[200,24923,24924],{"class":210},"filename",[200,24926,215],{"class":214},[200,24928,11859],{"class":223},[200,24930,227],{"class":235},[200,24932,249],{"class":214},[200,24934,11866],{"class":245},[200,24936,249],{"class":214},[200,24938,6478],{"class":235},[200,24940,230],{"class":214},[200,24942,24943,24945],{"class":202,"line":255},[200,24944,3311],{"class":206},[200,24946,24947],{"class":210}," code\n",[200,24949,24950],{"class":202,"line":272},[200,24951,4505],{"class":214},[200,24953,24954],{"class":202,"line":283},[200,24955,528],{"emptyLinePlaceholder":20},[200,24957,24958,24960,24962,24965,24967],{"class":202,"line":300},[200,24959,3758],{"class":206},[200,24961,13007],{"class":214},[200,24963,24964],{"class":223}," Transformer",[200,24966,227],{"class":235},[200,24968,230],{"class":214},[200,24970,24971,24974,24976,24979],{"class":202,"line":310},[200,24972,24973],{"class":235},"    config",[200,24975,239],{"class":214},[200,24977,24978],{"class":210}," fusionConfig",[200,24980,252],{"class":214},[200,24982,24983,24986],{"class":202,"line":336},[200,24984,24985],{"class":210},"    code",[200,24987,252],{"class":214},[200,24989,24990,24992],{"class":202,"line":342},[200,24991,5119],{"class":210},[200,24993,252],{"class":214},[200,24995,24996],{"class":202,"line":365},[200,24997,24998],{"class":210},"    isProd\n",[200,25000,25001,25003,25005,25007,25009],{"class":202,"line":371},[200,25002,5095],{"class":214},[200,25004,2104],{"class":235},[200,25006,215],{"class":214},[200,25008,11785],{"class":223},[200,25010,466],{"class":235},[200,25012,25013],{"class":202,"line":398},[200,25014,1129],{"class":214},[35,25016,15736,25017,25019,25020,25023,25024,25027,25028,25030],{},[156,25018,11785],{}," forme le cœur du pipeline de Vite. Elle transforme les fichiers avant qu'ils n'atteignent le navigateur. Dans le cas de Fusion, elle passe le fichier (l'intégralité du contenu) à une classe nommée ",[156,25021,25022],{},"Transformer",", qui gère la transformation du fichier. Vous pourriez remarquer la condition ",[156,25025,25026],{},"filename.endsWith('.vue')",", signifiant que la transformation ne s'applique qu'aux fichiers ",[156,25029,11866],{},". Cette approche est un modèle courant parmi les plugins Vite.",[35,25032,25033],{},"La classe Transformer est responsable de :",[68,25035,25036],{},[71,25037,25038,25043,25044,25049,25050,25052,25053,25058,25059,25067],{},[39,25039,25042],{"href":25040,"rel":25041},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L16-L19",[77],"Extraire le bloc PHP"," du SFC de Vue. Il utilise ici un parseur de ",[39,25045,25048],{"href":25046,"rel":25047},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Ftree\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fcompiler-sfc",[77],"@vue\u002Fcompiler-sfc"," pour accomplir cette tâche. Notamment, le plugin ",[156,25051,13740],{}," utilise ce même parseur, illustrant qu'ajouter un bloc personnalisé à un SFC de Vue est simple. J'ai déjà observé cette pratique ; le projet ",[39,25054,25057],{"href":25055,"rel":25056},"https:\u002F\u002Fvue-i18n.intlify.dev\u002Fguide\u002Fintroduction.html",[77],"Vue I18n"," supporte ",[39,25060,25063,25064],{"href":25061,"rel":25062},"https:\u002F\u002Fvue-i18n.intlify.dev\u002Fguide\u002Fadvanced\u002Fsfc.html",[77],"un bloc personnalisé ",[156,25065,25066],{},"i18n"," spécifiant des traductions au sein d'un SFC de Vue.",[192,25069,25073],{"className":25070,"code":25071,"language":25072,"meta":13,"style":13},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { parse } from '@vue\u002Fcompiler-sfc'\n\nconst descriptor = parse(code, { filename }).descriptor\nconst php = descriptor.customBlocks.find(block => block.type === 'php')\n","js",[156,25074,25075,25094,25098,25128],{"__ignoreMap":13},[200,25076,25077,25079,25081,25084,25086,25088,25090,25092],{"class":202,"line":203},[200,25078,972],{"class":206},[200,25080,318],{"class":214},[200,25082,25083],{"class":210}," parse",[200,25085,980],{"class":214},[200,25087,983],{"class":206},[200,25089,242],{"class":214},[200,25091,25048],{"class":245},[200,25093,991],{"class":214},[200,25095,25096],{"class":202,"line":14},[200,25097,528],{"emptyLinePlaceholder":20},[200,25099,25100,25102,25105,25107,25109,25112,25114,25116,25119,25121,25123,25125],{"class":202,"line":255},[200,25101,443],{"class":379},[200,25103,25104],{"class":210}," descriptor ",[200,25106,449],{"class":214},[200,25108,25083],{"class":223},[200,25110,25111],{"class":210},"(code",[200,25113,549],{"class":214},[200,25115,318],{"class":214},[200,25117,25118],{"class":210}," filename ",[200,25120,413],{"class":214},[200,25122,2104],{"class":210},[200,25124,215],{"class":214},[200,25126,25127],{"class":210},"descriptor\n",[200,25129,25130,25132,25135,25137,25140,25142,25145,25147,25149,25151,25154,25156,25159,25161,25164,25166,25168,25170,25172],{"class":202,"line":272},[200,25131,443],{"class":379},[200,25133,25134],{"class":210}," php ",[200,25136,449],{"class":214},[200,25138,25139],{"class":210}," descriptor",[200,25141,215],{"class":214},[200,25143,25144],{"class":210},"customBlocks",[200,25146,215],{"class":214},[200,25148,3281],{"class":223},[200,25150,227],{"class":210},[200,25152,25153],{"class":386},"block",[200,25155,393],{"class":379},[200,25157,25158],{"class":210}," block",[200,25160,215],{"class":214},[200,25162,25163],{"class":210},"type ",[200,25165,6558],{"class":214},[200,25167,242],{"class":214},[200,25169,19563],{"class":245},[200,25171,249],{"class":214},[200,25173,416],{"class":210},[35,25175,25176],{},[1707,25177],{"alt":25178,"src":25179},"Le SFC de Vue transformé dans le pipeline Vite","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvue-sfc-with-php-transformation.png",[68,25181,25182,25190,25202],{"start":14},[71,25183,25184,25189],{},[39,25185,25188],{"href":25186,"rel":25187},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L88-L91",[77],"Supprimer le bloc PHP"," du SFC de Vue.",[71,25191,25192,25197,25198,215],{},[39,25193,25196],{"href":25194,"rel":25195},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L96",[77],"Déterminer si le code PHP mérite d'être persisté",". Un autre aspect critique que nous examinerons dans ",[39,25199,25201],{"href":25200},"#que-fait-fusion-en-coulisses-pour-rendre-cela-possible","la section suivante",[71,25203,25204,25209],{},[39,25205,25208],{"href":25206,"rel":25207},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L107",[77],"Valider et sauvegarder le code PHP",". Cette vérification se fait via un processus externe exécutant une commande Laravel.",[192,25211,25213],{"className":1380,"code":25212,"language":1383,"meta":13,"style":13},"await runPhp(['fusion:conform', this.relativeFilename])\n",[156,25214,25215],{"__ignoreMap":13},[200,25216,25217,25219,25222,25225,25227,25230,25232,25234,25237],{"class":202,"line":203},[200,25218,207],{"class":206},[200,25220,25221],{"class":223}," runPhp",[200,25223,25224],{"class":210},"([",[200,25226,249],{"class":214},[200,25228,25229],{"class":245},"fusion:conform",[200,25231,249],{"class":214},[200,25233,549],{"class":214},[200,25235,25236],{"class":214}," this.",[200,25238,25239],{"class":210},"relativeFilename])\n",[35,25241,25242,25243,25248],{},"Tout code PHP comportant des erreurs déclenche un message d'erreur ",[39,25244,25247],{"href":25245,"rel":25246},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L124",[77],"interprété par Vite",", si précisément qu'il s'affiche dans le navigateur pour indiquer une erreur dans le bloc de code PHP.",[35,25250,25251],{},[1707,25252],{"alt":25253,"src":25254},"L'overlay d'erreur Vite montrant l'erreur dans le code PHP","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvite-error-overlay.png",[35,25256,25257,25258,25263,25264,25267,25268,215],{},"Si le fonctionnement se déroule sans accroc, le ",[39,25259,25262],{"href":25260,"rel":25261},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L68",[77],"code PHP, stocké en tant que fichier extrait",", est sauvegardé dans le dossier ",[156,25265,25266],{},"storage",". Cet aspect est à notable, car nous allons l'explorer davantage dans ",[39,25269,25201],{"href":25270},"#comment-fusion-g%C3%A8re-le-code-php",[68,25272,25273],{"start":283},[71,25274,25275,25276,25281,25282,25287],{},"Création d'un fichier JavaScript ",[39,25277,25280],{"href":25278,"rel":25279},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L112",[77],"basé sur le code PHP"," qui fait le pont ",[39,25283,25286],{"href":25284,"rel":25285},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FShim.php#L78-L113",[77],"entre les besoins du frontend"," et le code du bloc PHP. Cette étape intéressante nécessite une exploration plus approfondie.",[35,25289,25290],{},"Encore une fois, une commande Laravel est invoquée durant cette étape :",[192,25292,25294],{"className":1380,"code":25293,"language":1383,"meta":13,"style":13},"await runPhp(['fusion:shim', this.relativeFilename])\n",[156,25295,25296],{"__ignoreMap":13},[200,25297,25298,25300,25302,25304,25306,25309,25311,25313,25315],{"class":202,"line":203},[200,25299,207],{"class":206},[200,25301,25221],{"class":223},[200,25303,25224],{"class":210},[200,25305,249],{"class":214},[200,25307,25308],{"class":245},"fusion:shim",[200,25310,249],{"class":214},[200,25312,549],{"class":214},[200,25314,25236],{"class":214},[200,25316,25239],{"class":210},[68,25318,25319],{"start":300},[71,25320,25321],{},"Enfin, elle renvoie le code transformé à Vite, permettant au SFC de poursuivre son chemin.",[35,25323,25324],{},"Voici une illustration de la transformation du SFC de Vue :",[35,25326,25327],{},[1707,25328],{"alt":25329,"src":25330},"Schéma montrant toutes les étapes que nous avons vues jusqu'à présent","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Ffusion-schema-vite-transform.png",[35,25332,25333],{},"Dans Fusion, les fichiers SFC de Vue fonctionnent comme un routeur basé sur des fichiers à l'instar du mécanisme de Nuxt. La première requête atteint généralement le serveur PHP, qui répond avec le code initial côté client, lequel vise ensuite le serveur Vite pour des requêtes de ressources supplémentaires. Cependant, si une route SFC de Vue n'a pas été visitée, la fonctionnalité PHP correspondante ne sera pas prête à la gérer, résultant en une erreur 404 du serveur Laravel. En conséquence, le serveur Vite reste intouché, entravant le processus de transformation. Cela pose un obstacle, un énorme.",[35,25335,25336],{},"Pour comprendre pleinement comment ce problème est abordé, nous devons examiner tous les aspects du plugin Vite.",[35,25338,25339],{},"Dans la configuration Vite, nous trouvons deux caractéristiques notables :",[68,25341,25342,25362],{},[71,25343,5778,25344,25349,25350,25355,25356,25361],{},[39,25345,25348],{"href":25346,"rel":25347},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L42-L65",[77],"configuration Vite"," où Vite reconnaît l'emplacement pour trouver le fichier JavaScript généré (le shim), qui reçoit ",[39,25351,25354],{"href":25352,"rel":25353},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L61",[77],"un alias"," attribué dans le résolveur Vite. Cet alias obscurcit l'emplacement du fichier, encourageant les meilleures pratiques comme éviter les chemins codés en dur et rendant l'emplacement du code agnostique aux changements de chemin. Cette configuration liée à Vite provient d'un fichier config PHP pour éliminer les configurations redondantes, donc le ",[39,25357,25360],{"href":25358,"rel":25359},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L45",[77],"plugin Vite Fusion engendre un processus"," exécutant une commande Laravel pour résoudre les nécessités de configuration.",[71,25363,25364,25365,25370,25371,215],{},"Un observateur ",[39,25366,25369],{"href":25367,"rel":25368},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L79",[77],"ajouté pour surveiller les changements dans l'ensemble du projet"," utilisant l'observateur Vite, une ",[39,25372,25375],{"href":25373,"rel":25374},"https:\u002F\u002Fvite.dev\u002Fguide\u002Fapi-javascript.html#vitedevserver:~:text=\u002F**%0A%20%20%20*%20Chokidar%20watcher%20instance.%20If%20%60config.server.watch%60%20is%20set%20to%20%60null%60%2C%0A%20%20%20*%20it%20will%20not%20watch%20any%20files%20and%20calling%20%60add%60%20or%20%60unwatch%60%20will%20have%20no%20effect.%0A%20%20%20*%20https%3A\u002F\u002Fgithub.com\u002Fpaulmillr\u002Fchokidar\u002Ftree\u002F3.6.0%23api%0A%20%20%20*\u002F%0A%20%20watcher%3A%20FSWatcher",[77],"instance Chokidar",[192,25377,25379],{"className":1380,"code":25378,"language":1383,"meta":13,"style":13},"function configureServer(server) {\n  server.watcher.on('all', (event, filename) => {\n    \u002F\u002F Faites ce que vous voulez\n  })\n}\n",[156,25380,25381,25396,25437,25442,25448],{"__ignoreMap":13},[200,25382,25383,25385,25388,25390,25392,25394],{"class":202,"line":203},[200,25384,14908],{"class":379},[200,25386,25387],{"class":223}," configureServer",[200,25389,227],{"class":214},[200,25391,3123],{"class":386},[200,25393,2104],{"class":214},[200,25395,280],{"class":214},[200,25397,25398,25401,25403,25406,25408,25411,25413,25415,25418,25420,25422,25424,25427,25429,25431,25433,25435],{"class":202,"line":14},[200,25399,25400],{"class":210},"  server",[200,25402,215],{"class":214},[200,25404,25405],{"class":210},"watcher",[200,25407,215],{"class":214},[200,25409,25410],{"class":223},"on",[200,25412,227],{"class":235},[200,25414,249],{"class":214},[200,25416,25417],{"class":245},"all",[200,25419,249],{"class":214},[200,25421,549],{"class":214},[200,25423,1094],{"class":214},[200,25425,25426],{"class":386},"event",[200,25428,549],{"class":214},[200,25430,24909],{"class":386},[200,25432,2104],{"class":214},[200,25434,393],{"class":379},[200,25436,280],{"class":214},[200,25438,25439],{"class":202,"line":255},[200,25440,25441],{"class":401},"    \u002F\u002F Faites ce que vous voulez\n",[200,25443,25444,25446],{"class":202,"line":272},[200,25445,5095],{"class":214},[200,25447,416],{"class":235},[200,25449,25450],{"class":202,"line":283},[200,25451,1129],{"class":214},[35,25453,25454],{},"Cet observateur prend en charge deux fonctions :",[68,25456,25457,25465],{},[71,25458,25459,25464],{},[39,25460,25463],{"href":25461,"rel":25462},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L88",[77],"Nettoyer"," la base de données et le dossier de stockage lors de la suppression de fichiers ou de dossiers. Le dossier de stockage contient les fichiers SFC de Vue transformés, servant de routeur basé sur des fichiers. Sans suppression, l'utilisateur peut toujours accéder aux fichiers supprimés.",[71,25466,25467,25472,25473,25475],{},[39,25468,25471],{"href":25469,"rel":25470},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L97-L99",[77],"Déclencher la transformation de fichiers",". Cela fonctionne de manière identique à la fonction ",[156,25474,11785],{}," rencontrée précédemment, nécessitant un chargement manuel de fichiers. Seuls les fichiers SFC de Vue subissent une transformation.",[192,25477,25479],{"className":1380,"code":25478,"language":1383,"meta":13,"style":13},"function configureServer(server) {\n  server.watcher.on('all', async (event, filename) => {\n    if (filename.endsWith('.vue')) {\n      const fileContent = fs.readFileSync(filename, 'utf-8')\n\n      await new Transformer({\n        config: fusionConfig,\n        code: fileContent,\n        filename,\n        isProd\n      }).transform()\n    }\n  })\n}\n",[156,25480,25481,25495,25533,25557,25588,25592,25605,25616,25626,25633,25638,25650,25654,25660],{"__ignoreMap":13},[200,25482,25483,25485,25487,25489,25491,25493],{"class":202,"line":203},[200,25484,14908],{"class":379},[200,25486,25387],{"class":223},[200,25488,227],{"class":214},[200,25490,3123],{"class":386},[200,25492,2104],{"class":214},[200,25494,280],{"class":214},[200,25496,25497,25499,25501,25503,25505,25507,25509,25511,25513,25515,25517,25519,25521,25523,25525,25527,25529,25531],{"class":202,"line":14},[200,25498,25400],{"class":210},[200,25500,215],{"class":214},[200,25502,25405],{"class":210},[200,25504,215],{"class":214},[200,25506,25410],{"class":223},[200,25508,227],{"class":235},[200,25510,249],{"class":214},[200,25512,25417],{"class":245},[200,25514,249],{"class":214},[200,25516,549],{"class":214},[200,25518,380],{"class":379},[200,25520,1094],{"class":214},[200,25522,25426],{"class":386},[200,25524,549],{"class":214},[200,25526,24909],{"class":386},[200,25528,2104],{"class":214},[200,25530,393],{"class":379},[200,25532,280],{"class":214},[200,25534,25535,25537,25539,25541,25543,25545,25547,25549,25551,25553,25555],{"class":202,"line":255},[200,25536,5267],{"class":206},[200,25538,1094],{"class":235},[200,25540,24924],{"class":210},[200,25542,215],{"class":214},[200,25544,11859],{"class":223},[200,25546,227],{"class":235},[200,25548,249],{"class":214},[200,25550,11866],{"class":245},[200,25552,249],{"class":214},[200,25554,6478],{"class":235},[200,25556,230],{"class":214},[200,25558,25559,25561,25564,25566,25569,25571,25574,25576,25578,25580,25582,25584,25586],{"class":202,"line":272},[200,25560,20136],{"class":379},[200,25562,25563],{"class":210}," fileContent",[200,25565,509],{"class":214},[200,25567,25568],{"class":210}," fs",[200,25570,215],{"class":214},[200,25572,25573],{"class":223},"readFileSync",[200,25575,227],{"class":235},[200,25577,24924],{"class":210},[200,25579,549],{"class":214},[200,25581,242],{"class":214},[200,25583,14750],{"class":245},[200,25585,249],{"class":214},[200,25587,416],{"class":235},[200,25589,25590],{"class":202,"line":283},[200,25591,528],{"emptyLinePlaceholder":20},[200,25593,25594,25597,25599,25601,25603],{"class":202,"line":300},[200,25595,25596],{"class":206},"      await",[200,25598,13007],{"class":214},[200,25600,24964],{"class":223},[200,25602,227],{"class":235},[200,25604,230],{"class":214},[200,25606,25607,25610,25612,25614],{"class":202,"line":310},[200,25608,25609],{"class":235},"        config",[200,25611,239],{"class":214},[200,25613,24978],{"class":210},[200,25615,252],{"class":214},[200,25617,25618,25620,25622,25624],{"class":202,"line":336},[200,25619,11899],{"class":235},[200,25621,239],{"class":214},[200,25623,25563],{"class":210},[200,25625,252],{"class":214},[200,25627,25628,25631],{"class":202,"line":342},[200,25629,25630],{"class":210},"        filename",[200,25632,252],{"class":214},[200,25634,25635],{"class":202,"line":365},[200,25636,25637],{"class":210},"        isProd\n",[200,25639,25640,25642,25644,25646,25648],{"class":202,"line":371},[200,25641,2647],{"class":214},[200,25643,2104],{"class":235},[200,25645,215],{"class":214},[200,25647,11785],{"class":223},[200,25649,466],{"class":235},[200,25651,25652],{"class":202,"line":398},[200,25653,4977],{"class":214},[200,25655,25656,25658],{"class":202,"line":405},[200,25657,5095],{"class":214},[200,25659,416],{"class":235},[200,25661,25662],{"class":202,"line":410},[200,25663,1129],{"class":214},[35,25665,25666,25667,25669],{},"Mais pourquoi transformer des fichiers lors de leur création ou modification ? Cette approche semble contredire la philosophie à la demande et la fonction ",[156,25668,11785],{}," définie précédemment. Pourtant, une justification existe.",[35,25671,25672,25673,25676],{},"Considerons qu'une erreur se produise dans le bloc PHP, comme un oubli dans l'importation d'une fonction ou d'une classe, ou en tentant de visiter une route pour la première fois. Lorsque la page est rechargée, le serveur PHP renvoie un message d'erreur, comme Laravel le fait par défaut. Cependant, corriger le bloc PHP du SFC de Vue ne rectifie pas l'erreur du serveur PHP puisque la transformation Vite ne déclenchera pas à nouveau. En conséquence, appuyer à plusieurs reprises sur ",[156,25674,25675],{},"cmd+r"," ne sert à rien.",[35,25678,25679,25680,25682],{},"Pour cette raison, le système de fichiers doit être surveillé pour les changements SFC de Vue, déclenchant une transformation manuelle. Bien que suboptimale, cette méthode est nécessaire. Aaron a conçu une stratégie priorisant les transformations via la méthode ",[156,25681,11785],{}," sur celles déclenchées par l'observateur, avec application d'un throttling sur l'observateur.",[35,25684,25685],{},"C'est également pour résoudre le problème vu précédemment : lorsque le serveur PHP est touché en premier, le fichier PHP généré n'est pas encore créé.",[35,25687,25688],{},"Résumons nos apprentissages jusqu'à présent avec ce schéma :",[35,25690,25691],{},[1707,25692],{"alt":25693,"src":25694},"Un schéma montrant toutes les étapes qui font fonctionner Fusion.","\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Ffusion-schema.png",[35,25696,25697],{},"Bien que nous n'ayons pas complètement couvert chaque composant, ce diagramme aide notre compréhension du reste de l'article.",[938,25699,25700],{},[35,25701,25702,25704],{},[200,25703,944],{},"\nAssurez-vous que le serveur Vite fonctionne correctement pour que tout fonctionne sans accroc.",[35,25706,25707],{},"Cette exploration nous a profondément engagés dans le plugin Vite de Fusion. Passons maintenant à comprendre comment Fusion organise la gestion de son code PHP.",[938,25709,25710],{},[35,25711,25712,25714,25715,25719],{},[200,25713,944],{},"\nVous avez des questions ? ",[39,25716,25718],{"href":25717},"\u002Fama","Demandez-moi tout"," ou laissez un commentaire ci-dessous. J'aime vraiment lire vos retours !",[51,25721,25723],{"id":25722},"que-fait-fusion-en-coulisses-pour-rendre-cela-possible","Que fait Fusion en coulisses pour rendre cela possible ?",[35,25725,25726],{},"Plongeons dans les opérations côté PHP.",[35,25728,25729],{},"Nous avons vu le code PHP extrait, validé et sauvegardé sur le disque. Nous devons maintenant dénouer deux étapes cruciales pour mieux comprendre le fonctionnement de Fusion :",[68,25731,25732,25735],{},[71,25733,25734],{},"Déterminer si le code PHP doit être persistant.",[71,25736,25737],{},"Sauvegarder le code PHP dans le système de fichiers.",[35,25739,25740],{},"Pour faciliter la compréhension, abordons d'abord la deuxième étape. La première étape deviendra ensuite claire.",[1929,25742,25744],{"id":25743},"écriture-du-code-php-sur-le-système-de-fichiers","Écriture du code PHP sur le système de fichiers",[35,25746,25747],{},"Une fois extrait du SFC de Vue, le code PHP doit être persistant sur le système de fichiers. Ce processus est crucial puisque le PHP écrit au sein du SFC de Vue devient une partie intégrante de l'application Laravel, non pas simplement un script PHP indépendant. Ce fichier généré est essentiel pour que l'application Laravel réponde aux requêtes entrantes.",[35,25749,25750,25751,25756,25757,25762],{},"Après l'extraction, le code PHP est placé dans ",[39,25752,25755],{"href":25753,"rel":25754},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L97",[77],"un fichier temporaire",". Ce fichier temporaire maintient le contenu exact du code inhérent au SFC de Vue. Par la suite, Fusion crée une entrée dans une ",[39,25758,25761],{"href":25759,"rel":25760},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FDatabase.js#L3",[77],"base de données SQLite dédiée",". Nous reviendrons sur cette base de données plus tard, en comprenant qu'elle facilite la communication entre les processus PHP et JavaScript.",[35,25764,25765,25766,215],{},"Enfin, le plugin Vite déclenche la ",[39,25767,25769],{"href":25206,"rel":25768},[77],[156,25770,25771],{},"commande fusion:conform",[1929,25773,25775,25776],{"id":25774},"la-commande-fusionconform","La commande ",[156,25777,25229],{},[35,25779,25780,25781,25783],{},"Nous avons retardé la compréhension de cette commande ; il est maintenant temps de clarifier. Dans la commande ",[156,25782,25229],{},", une commande Laravel écrite en PHP, ni Vite ni JavaScript ne s'appliquent, strictement du PHP.",[35,25785,25786,25787,25789,25790,215],{},"Lors de l'invocation, cette commande utilise un argument ",[156,25788,11180],{}," représentant le chemin du fichier SFC de Vue. Agissant comme un identifiant unique, elle permet à Fusion de ",[39,25791,25794],{"href":25792,"rel":25793},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L38",[77],"récupérer l'entrée de la base de données pertinente",[35,25796,25797,25798,25803],{},"Cette entrée contient le chemin de destination pour le fichier PHP final qui sera utilisé par l'application Laravel. Actuellement, ce fichier n'existe pas ; un fichier temporaire conserve seulement le code. Ainsi, l'étape suivante consiste à lire ",[39,25799,25802],{"href":25800,"rel":25801},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L44-L47",[77],"le contenu du fichier temporaire"," avant un traitement ultérieur.",[35,25805,25806,25807,25812],{},"Le processus suivant mobilise une ",[39,25808,25811],{"href":25809,"rel":25810},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L56",[77],"classe conformante",", chargée de :",[68,25814,25815,25818],{},[71,25816,25817],{},"Modifier le code pour l'adapter à l'application Laravel.",[71,25819,25820],{},"S'assurer de la validité du code en le passant par un parseur PHP.",[35,25822,25823,25824,215],{},"La fonction examine la conformité du code PHP en le faisant passer par une séquence de ",[39,25825,25828],{"href":25826,"rel":25827},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConformity\u002FConformer.php#L65C24-L79",[77],"classes de transformation",[35,25830,25831,25832,25834,25835,25838,25839,25842,25843,215],{},"Chaque classe de transformer étend une super-classe ",[156,25833,25022],{},", conçue sur mesure pour étendre ",[156,25836,25837],{},"NodeVisitorAbstract"," et met en œuvre l'interface ",[156,25840,25841],{},"TransformerInterface"," provenant de ",[39,25844,25847],{"href":25845,"rel":25846},"https:\u002F\u002Fgithub.com\u002Fnikic\u002FPHP-Parser",[77],"PHP Parser",[192,25849,25852],{"className":19560,"code":25850,"filename":25851,"language":19563,"meta":13,"style":13},"\u003C?php\n\nnamespace Fusion\\Conformity\\Transformers;\n\nabstract class Transformer extends NodeVisitorAbstract implements TransformerInterface\n{\n    public function __construct(\n        protected ?string $filename = null\n    ) {}\n}\n","Transformer.php",[156,25853,25854,25858,25862,25867,25871,25876,25880,25885,25890,25895],{"__ignoreMap":13},[200,25855,25856],{"class":202,"line":203},[200,25857,19600],{},[200,25859,25860],{"class":202,"line":14},[200,25861,528],{"emptyLinePlaceholder":20},[200,25863,25864],{"class":202,"line":255},[200,25865,25866],{},"namespace Fusion\\Conformity\\Transformers;\n",[200,25868,25869],{"class":202,"line":272},[200,25870,528],{"emptyLinePlaceholder":20},[200,25872,25873],{"class":202,"line":283},[200,25874,25875],{},"abstract class Transformer extends NodeVisitorAbstract implements TransformerInterface\n",[200,25877,25878],{"class":202,"line":300},[200,25879,230],{},[200,25881,25882],{"class":202,"line":310},[200,25883,25884],{},"    public function __construct(\n",[200,25886,25887],{"class":202,"line":336},[200,25888,25889],{},"        protected ?string $filename = null\n",[200,25891,25892],{"class":202,"line":342},[200,25893,25894],{},"    ) {}\n",[200,25896,25897],{"class":202,"line":365},[200,25898,1129],{},[35,25900,25901,25902,25905],{},"Chaque transformateur personnalisé, de plus, implémente la méthode ",[156,25903,25904],{},"enterNode"," pour faire avancer la transformation du code.",[192,25907,25910],{"className":19560,"code":25908,"filename":25909,"language":19563,"meta":13,"style":13},"\u003C?php\n\nnamespace Fusion\\Conformity\\Transformers;\n\nclass PropsTransformer extends Transformer\n{\n    public function enterNode(Node $node)\n    {\n        if ($node instanceof MethodCall) {\n            \u002F\u002F Effectuer une opération\n        }\n    }\n}\n","PropTransformer.php",[156,25911,25912,25916,25920,25924,25928,25933,25937,25942,25946,25951,25956,25960,25964],{"__ignoreMap":13},[200,25913,25914],{"class":202,"line":203},[200,25915,19600],{},[200,25917,25918],{"class":202,"line":14},[200,25919,528],{"emptyLinePlaceholder":20},[200,25921,25922],{"class":202,"line":255},[200,25923,25866],{},[200,25925,25926],{"class":202,"line":272},[200,25927,528],{"emptyLinePlaceholder":20},[200,25929,25930],{"class":202,"line":283},[200,25931,25932],{},"class PropsTransformer extends Transformer\n",[200,25934,25935],{"class":202,"line":300},[200,25936,230],{},[200,25938,25939],{"class":202,"line":310},[200,25940,25941],{},"    public function enterNode(Node $node)\n",[200,25943,25944],{"class":202,"line":336},[200,25945,19661],{},[200,25947,25948],{"class":202,"line":342},[200,25949,25950],{},"        if ($node instanceof MethodCall) {\n",[200,25952,25953],{"class":202,"line":365},[200,25954,25955],{},"            \u002F\u002F Effectuer une opération\n",[200,25957,25958],{"class":202,"line":371},[200,25959,13469],{},[200,25961,25962],{"class":202,"line":398},[200,25963,4977],{},[200,25965,25966],{"class":202,"line":405},[200,25967,1129],{},[938,25969,25970],{},[35,25971,25972,25974],{},[200,25973,944],{},"\nUne exploration détaillée des transformateurs ou des parseurs PHP est en dehors du champ d'application de cet article car ils offrent une fonctionnalité avancée pas immédiatement nécessaire pour comprendre le fonctionnement de Fusion.",[35,25976,25977],{},"Au total, dix transformateurs sont impliqués, chacun ayant une responsabilité spécifique. Concevez-les comme des étapes au sein d'un pipeline, un modèle conventionnel en programmation conçu pour décomposer des tâches complexes en segments plus petits et abordables. Par conséquent, lorsque le code utilisateur subit une transformation et est rendu compatible avec l'application Laravel, la sortie d'un transformateur devient l'entrée du suivant.",[192,25979,25981],{"className":4292,"code":25980,"language":4294,"meta":13,"style":13},"Code PHP du SFC de Vue\n    | |\n[Transformateur procédural]: Transforme le code procédural en une méthode de classe anonyme intitulée runProceduralCode.\n[Transformateur de props]: Change les appels de fonction prop() en appels de méthode de cet objet dans la classe.\n[Transformateur d'importation de fonction]: Supprime les importations de fonction inutiles, spécifiquement celles connues à l'avance.\n[Transformateur de valeur de prop]: Spécifie que les chaînes d'appels de méthode $this->prop() se terminent par ->value().\n[Transformateur d'exposition]: Modifie les appels de fonction expose() en appels de méthode de cet objet, excluant les affectations de variable.\n[Transformateur de montage]: Valide et modifie les appels de fonction mount(), s'assurant qu'il n'y ait qu'un seul argument fonction anonyme.\n[Transformateur de découverte de prop]: Découvre et documente les noms de propriétés des fonctions prop() rencontrées.\n[Transformateur de découverte d'action]: Découvre et construit des méthodes pour les gestionnaires d'action exposés via expose().\n[Transformateur de retour anonyme]: Garantit des retours d'expression directs pour les retours de classe anonyme.\n[Transformateur de classe anonyme]: Convertit les classes anonymes en classes nommées, dans un espace de noms généré, rassemblant les exigences nécessaires.\n    | |\nCode PHP utilisable\n",[156,25982,25983,25988,25993,25998,26003,26008,26013,26018,26023,26028,26033,26038,26043,26047],{"__ignoreMap":13},[200,25984,25985],{"class":202,"line":203},[200,25986,25987],{},"Code PHP du SFC de Vue\n",[200,25989,25990],{"class":202,"line":14},[200,25991,25992],{},"    | |\n",[200,25994,25995],{"class":202,"line":255},[200,25996,25997],{},"[Transformateur procédural]: Transforme le code procédural en une méthode de classe anonyme intitulée runProceduralCode.\n",[200,25999,26000],{"class":202,"line":272},[200,26001,26002],{},"[Transformateur de props]: Change les appels de fonction prop() en appels de méthode de cet objet dans la classe.\n",[200,26004,26005],{"class":202,"line":283},[200,26006,26007],{},"[Transformateur d'importation de fonction]: Supprime les importations de fonction inutiles, spécifiquement celles connues à l'avance.\n",[200,26009,26010],{"class":202,"line":300},[200,26011,26012],{},"[Transformateur de valeur de prop]: Spécifie que les chaînes d'appels de méthode $this->prop() se terminent par ->value().\n",[200,26014,26015],{"class":202,"line":310},[200,26016,26017],{},"[Transformateur d'exposition]: Modifie les appels de fonction expose() en appels de méthode de cet objet, excluant les affectations de variable.\n",[200,26019,26020],{"class":202,"line":336},[200,26021,26022],{},"[Transformateur de montage]: Valide et modifie les appels de fonction mount(), s'assurant qu'il n'y ait qu'un seul argument fonction anonyme.\n",[200,26024,26025],{"class":202,"line":342},[200,26026,26027],{},"[Transformateur de découverte de prop]: Découvre et documente les noms de propriétés des fonctions prop() rencontrées.\n",[200,26029,26030],{"class":202,"line":365},[200,26031,26032],{},"[Transformateur de découverte d'action]: Découvre et construit des méthodes pour les gestionnaires d'action exposés via expose().\n",[200,26034,26035],{"class":202,"line":371},[200,26036,26037],{},"[Transformateur de retour anonyme]: Garantit des retours d'expression directs pour les retours de classe anonyme.\n",[200,26039,26040],{"class":202,"line":398},[200,26041,26042],{},"[Transformateur de classe anonyme]: Convertit les classes anonymes en classes nommées, dans un espace de noms généré, rassemblant les exigences nécessaires.\n",[200,26044,26045],{"class":202,"line":405},[200,26046,25992],{},[200,26048,26049],{"class":202,"line":410},[200,26050,26051],{},"Code PHP utilisable\n",[35,26053,26054],{},"Considérons un exemple concret avant de continuer. Supposons que le code PHP suivant existe au sein du SFC de Vue :",[192,26056,26058],{"className":21457,"code":26057,"language":12075,"meta":13,"style":13},"\u003Cphp>\nuse function \\Fusion\\{prop};\n\n$name = prop('bienvenue');\n\n$name = strtoupper($name);\n\u003C\u002Fphp>\n",[156,26059,26060,26068,26073,26077,26082,26086,26091],{"__ignoreMap":13},[200,26061,26062,26064,26066],{"class":202,"line":203},[200,26063,599],{"class":214},[200,26065,19563],{"class":235},[200,26067,647],{"class":214},[200,26069,26070],{"class":202,"line":14},[200,26071,26072],{"class":210},"use function \\Fusion\\{prop};\n",[200,26074,26075],{"class":202,"line":255},[200,26076,528],{"emptyLinePlaceholder":20},[200,26078,26079],{"class":202,"line":272},[200,26080,26081],{"class":210},"$name = prop('bienvenue');\n",[200,26083,26084],{"class":202,"line":283},[200,26085,528],{"emptyLinePlaceholder":20},[200,26087,26088],{"class":202,"line":300},[200,26089,26090],{"class":210},"$name = strtoupper($name);\n",[200,26092,26093,26095,26097],{"class":202,"line":310},[200,26094,680],{"class":214},[200,26096,19563],{"class":235},[200,26098,647],{"class":214},[35,26100,26101],{},"Après avoir passé par la transformation, cela donne :",[192,26103,26105],{"className":19560,"code":26104,"language":19563,"meta":13,"style":13},"\u003C?php\n\n\u002F**\n * Fichier auto-généré par Fusion.\n * Les modifications ne sont pas conseillées.\n *\u002F\nnamespace Fusion\\Generated\\Pages;\n\nclass IndexGenerated extends \\Fusion\\FusionPage\n{\n    #[\\Fusion\\Attributes\\ServerOnly]\n    public array $discoveredProps = ['name'];\n    use \\Fusion\\Concerns\\IsProceduralPage;\n    public function runProceduralCode()\n    {\n        $name = $this->prop(name: 'name', default: 'bienvenue')->value();\n        $name = strtoupper($name);\n        $this->syncProps(get_defined_vars());\n    }\n}\n",[156,26106,26107,26111,26115,26120,26125,26130,26135,26140,26144,26149,26153,26158,26163,26168,26173,26177,26182,26187,26192,26196],{"__ignoreMap":13},[200,26108,26109],{"class":202,"line":203},[200,26110,19600],{},[200,26112,26113],{"class":202,"line":14},[200,26114,528],{"emptyLinePlaceholder":20},[200,26116,26117],{"class":202,"line":255},[200,26118,26119],{},"\u002F**\n",[200,26121,26122],{"class":202,"line":272},[200,26123,26124],{}," * Fichier auto-généré par Fusion.\n",[200,26126,26127],{"class":202,"line":283},[200,26128,26129],{}," * Les modifications ne sont pas conseillées.\n",[200,26131,26132],{"class":202,"line":300},[200,26133,26134],{}," *\u002F\n",[200,26136,26137],{"class":202,"line":310},[200,26138,26139],{},"namespace Fusion\\Generated\\Pages;\n",[200,26141,26142],{"class":202,"line":336},[200,26143,528],{"emptyLinePlaceholder":20},[200,26145,26146],{"class":202,"line":342},[200,26147,26148],{},"class IndexGenerated extends \\Fusion\\FusionPage\n",[200,26150,26151],{"class":202,"line":365},[200,26152,230],{},[200,26154,26155],{"class":202,"line":371},[200,26156,26157],{},"    #[\\Fusion\\Attributes\\ServerOnly]\n",[200,26159,26160],{"class":202,"line":398},[200,26161,26162],{},"    public array $discoveredProps = ['name'];\n",[200,26164,26165],{"class":202,"line":405},[200,26166,26167],{},"    use \\Fusion\\Concerns\\IsProceduralPage;\n",[200,26169,26170],{"class":202,"line":410},[200,26171,26172],{},"    public function runProceduralCode()\n",[200,26174,26175],{"class":202,"line":860},[200,26176,19661],{},[200,26178,26179],{"class":202,"line":889},[200,26180,26181],{},"        $name = $this->prop(name: 'name', default: 'bienvenue')->value();\n",[200,26183,26184],{"class":202,"line":899},[200,26185,26186],{},"        $name = strtoupper($name);\n",[200,26188,26189],{"class":202,"line":904},[200,26190,26191],{},"        $this->syncProps(get_defined_vars());\n",[200,26193,26194],{"class":202,"line":930},[200,26195,4977],{},[200,26197,26198],{"class":202,"line":3655},[200,26199,1129],{},[35,26201,26202,26203,26206,26207,215],{},"Cette classe ",[156,26204,26205],{},"IndexGenerated"," sera utilisée par l'application Laravel lors du traitement des requêtes à la route '\u002F'. Visualisez-la comme un mini-contrôleur contenant le code utilisateur. Dans les coulisses, une grande partie de la complexité est abstraite dans la classe ",[39,26208,26211],{"href":26209,"rel":26210},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L22",[77],[156,26212,26213],{},"FusionPage",[35,26215,26216,26217,26220,26221,26224],{},"Le tableau ",[156,26218,26219],{},"discoveredProps"," enregistre les noms des propriétés traitées lors des appels de fonction ",[6862,26222,26223],{},"prop()",", assurant la synchronisation entre les propriétés frontend et backend.",[35,26226,26227,26228,26231],{},"La méthode ",[156,26229,26230],{},"runProceduralCode",", invoquée lors du chargement de la page, exécute le code utilisateur aux côtés du code généré par Fusion, garantissant la synchronisation des props entre le frontend et le backend.",[35,26233,26227,26234,26241,26242,26244],{},[39,26235,26238],{"href":26236,"rel":26237},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConcerns\u002FIsProceduralPage.php#L36",[77],[156,26239,26240],{},"syncProps"," maintient la synchronisation des propriétés intérieur-extérieur. Cela récupère les variables définies dans la méthode, et le contenu du tableau discoveredProps, garantissant que la propriété ",[156,26243,23874],{}," générée contient uniquement les données requises par l'utilisateur.",[938,26246,26247],{},[35,26248,26249,26251,26252,26255,26256,215],{},[200,26250,944],{},"\nUtiliser ",[156,26253,26254],{},"get_defined_vars",", une fonction PHP intégrée, retourne un tableau de variables représentatives de la portée actuelle. Dans ce cas, cela constitue ",[156,26257,26258],{},"['name' => 'bienvenue']",[938,26260,26261],{},[35,26262,26263,26265,26268,26269,26274,26275,215],{},[200,26264,944],{},[156,26266,26267],{},"#[ServerOnly]"," classe les propriétés pour une application exclusivement côté serveur. Analogues ",[39,26270,26273],{"href":26271,"rel":26272},"https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fdecorators.html",[77],"décorateurs TypeScript"," fonctionnent de manière similaire sous ",[39,26276,26279],{"href":26277,"rel":26278},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Freflect-metadata",[77],"reflect-metadata",[35,26281,26282,26283,26286,26287,26292,26293,26297],{},"Enfin, Fusion place le contenu transformé dans le chemin de fichier fourni par la base de données, mettant à jour les entrées associées avec le nom législatif de la classe composée du nom d'espace et de la désignation de la classe. La procédure reste enfermée dans un bloc ",[156,26284,26285],{},"try\u002Fcatch",", prête à traiter toute erreur de transformation qui pourrait survenir. Lorsqu'une erreur se produit, ",[39,26288,26291],{"href":26289,"rel":26290},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L131",[77],"un fichier temporaire est supprimé"," tandis qu'",[39,26294,26296],{"href":25245,"rel":26295},[77],"un message d'erreur est dirigé vers Vite",", comme noté plus tôt.",[35,26299,26300],{},"À ce stade, notre système de fichiers apparaît maintenant comme suit :",[26302,26303,26307,26313,26317,26320,26323,26417,26429,26437,26444,26447,26450,26453,26465,26518,26525,26533,26582,26592,26596,26599,26602,26631,26658,26685,26705,26708,26724,26738,26741,26759,26766,27018,27041,27047,27216,27219,27222,27284,27287,27492,27524,27535,27545,27554,27935,27956,27959,27999,28005,28025,28028,28117,28126,28159,28162,28330,28343,28353,28356,28367,28373,28384,28388,28402,28405,28409,28412,28419,28422,28425,28430,28433,28437,28448,28451,28454,28461,28476,28840,28850,28857,28866,28875,28883,28897,28900],"tree",{"name":26304,":items":26305,":default-expanded":26306},"Une application Fusion","[\n {\n   title: 'resources\u002Fjs\u002FPages',\n   children: [\n     { id: '1', title: '[Podcast].vue' },\n     { id: '2', title: 'Index.vue' },\n     { id: '3', title: 'Search.vue' },\n   ],\n },\n {\n   title: 'storages\u002Ffusion',\n   children: [\n     {\n       id: '4', title: 'JavaScript', children: [\n         { id: '7', title: 'Index.js' },\n         { id: '8', title: '[Podcast].js' },\n         { id: '9', title: 'Search.js' },\n       ]\n     },\n     {\n       id: '5', title: 'PHP', children: [\n         { id: '10', title: 'DynamicPodcastGenerated.php' },\n         { id: '11', title: 'IndexGenerated.php' },\n         { id: '12', title: 'SearchGenerated.php' },\n       ]\n     },\n     { id: '6', title: 'fusion.sqlite' },\n   ],\n }\n]","['resources\u002Fjs\u002FPages', 'storages\u002Ffusion']",[35,26308,26309,26310,215],{},"Ensuite, le plugin Vite reprend son opération, comme décrit dans ",[39,26311,25201],{"href":26312},"#gestion-des-demandes-entrantes",[1929,26314,26316],{"id":26315},"la-base-de-données","La base de données",[35,26318,26319],{},"Puisque nous avons mentionné une base de données au cours de cet article, nous avons abstenu de donner des détails jusqu'à présent. Il est temps de comprendre son but !",[35,26321,26322],{},"La base de données aide à établir la communication entre les processus PHP et JavaScript, stockant des informations essentielles à cette fin. D'un coup d'œil, nous pouvons discerner les données stockées et son utilisation.",[26324,26325,26326,26349],"table",{},[26327,26328,26329],"thead",{},[26330,26331,26332,26335,26337,26340,26343,26346],"tr",{},[26333,26334,3295],"th",{},[26333,26336,11180],{},[26333,26338,26339],{},"php_class",[26333,26341,26342],{},"php_path",[26333,26344,26345],{},"shim_path",[26333,26347,26348],{},"php_hash",[26350,26351,26352,26372,26391],"tbody",{},[26330,26353,26354,26357,26360,26363,26366,26369],{},[26355,26356,3203],"td",{},[26355,26358,26359],{},"\u002Fresources\u002Fjs\u002FPages\u002FIndex.vue",[26355,26361,26362],{},"\\Fusion\\Generated\\Pages\\IndexGenerated",[26355,26364,26365],{},"\u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FIndexGenerated.php",[26355,26367,26368],{},"\u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002FIndex.js",[26355,26370,26371],{},"php_1e182535deedd9b60e12c277bef58603",[26330,26373,26374,26376,26379,26382,26385,26388],{},[26355,26375,4797],{},[26355,26377,26378],{},"\u002Fresources\u002Fjs\u002FPages\u002FSearch.vue",[26355,26380,26381],{},"\\Fusion\\Generated\\Pages\\SearchGenerated",[26355,26383,26384],{},"\u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FSearchGenerated.php",[26355,26386,26387],{},"\u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002FSearch.js",[26355,26389,26390],{},"php_468b4badedbe0cb6ce4734fcc921ea5f",[26330,26392,26393,26396,26402,26405,26408,26414],{},[26355,26394,26395],{},"3",[26355,26397,26398,26399,11866],{},"\u002Fresources\u002Fjs\u002FPages\u002F",[200,26400,26401],{},"Podcast",[26355,26403,26404],{},"\\Fusion\\Generated\\Pages\\DynamicPodcastGenerated",[26355,26406,26407],{},"\u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FDynamicPodcastGenerated.php",[26355,26409,26410,26411,26413],{},"\u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002F",[200,26412,26401],{},".js",[26355,26415,26416],{},"php_2ca978b1c880558f54c4a59ce7584e6c",[35,26418,26419,26420,26425,26426,26428],{},"Lors de la transformation des SFC de Vue et de l'extraction du code PHP, un ",[39,26421,26424],{"href":26422,"rel":26423},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L50",[77],"calcul de hash"," se produit, enregistré dans la colonne ",[156,26427,26348],{}," de la base de données.",[35,26430,26431,26432,26436],{},"Le hash sert à vérifier si ",[39,26433,26435],{"href":25194,"rel":26434},[77],"le code PHP a été modifié",". Un hash correspondant signifie que le code PHP n'a pas changé, introduisant une optimisation pour omettre les transformations redondantes.",[938,26438,26439],{},[35,26440,26441,26443],{},[200,26442,944],{},"\nUn hash est une fonction à sens unique générant une chaîne de caractères de taille fixe à partir d'une entrée, généralement composée de séquences numériques et alphanumériques. Un hash identifie de manière unique son entrée, signifiant que des hashes identiques indiquent des entrées identiques.",[35,26445,26446],{},"Ce dépôt de base de données incarne des données supplémentaires comme les chemins de fichiers PHP et JavaScript utilisés principalement pour gérer efficacement les requêtes entrantes.",[35,26448,26449],{},"Une question supplémentaire se pose : comment la base de données SQLite affecte-t-elle l'expérience développeur ?",[35,26451,26452],{},"Tout en utilisant une base de données SQLite, Fusion ne dicte pas le choix de la base de données de l'application Laravel : les deux restent entièrement distincts.",[35,26454,26455,26456,26464],{},"Le fournisseur de services de Fusion attribue ",[39,26457,26460,26461],{"href":26458,"rel":26459},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L103C1-L110C12",[77],"une connexion de base de données appelée ",[156,26462,26463],{},"__fusion"," utilisant SQLite, qui n'interfère pas avec la connexion de la base de données de l'application Laravel. Cela signifie qu'il est possible d'avoir plusieurs connexions de base de données au sein d'une seule application Laravel. Vraiment intéressant à savoir !",[192,26466,26468],{"className":19560,"code":26467,"language":19563,"meta":13,"style":13},"public function boot()\n{\n  ConfigFacade::set('database.connections.__fusion', [\n    'name' => '__fusion',\n    'driver' => 'sqlite',\n    'database' => Fusion::storage('fusion.sqlite'),\n    'foreign_key_constraints' => true,\n    'journal_mode' => 'OFF',\n  ]);\n}\n",[156,26469,26470,26475,26479,26484,26489,26494,26499,26504,26509,26514],{"__ignoreMap":13},[200,26471,26472],{"class":202,"line":203},[200,26473,26474],{},"public function boot()\n",[200,26476,26477],{"class":202,"line":14},[200,26478,230],{},[200,26480,26481],{"class":202,"line":255},[200,26482,26483],{},"  ConfigFacade::set('database.connections.__fusion', [\n",[200,26485,26486],{"class":202,"line":272},[200,26487,26488],{},"    'name' => '__fusion',\n",[200,26490,26491],{"class":202,"line":283},[200,26492,26493],{},"    'driver' => 'sqlite',\n",[200,26495,26496],{"class":202,"line":300},[200,26497,26498],{},"    'database' => Fusion::storage('fusion.sqlite'),\n",[200,26500,26501],{"class":202,"line":310},[200,26502,26503],{},"    'foreign_key_constraints' => true,\n",[200,26505,26506],{"class":202,"line":336},[200,26507,26508],{},"    'journal_mode' => 'OFF',\n",[200,26510,26511],{"class":202,"line":342},[200,26512,26513],{},"  ]);\n",[200,26515,26516],{"class":202,"line":365},[200,26517,1129],{},[938,26519,26520],{},[35,26521,26522,26524],{},[200,26523,944],{},"\nUn fournisseur de services Laravel s'exécute lors du démarrage de l'application pour enregistrer des services ou lier des classes dans le conteneur de services. C'est un lieu capital pour gérer et configurer des services.",[35,26526,19439,26527,26532],{},[39,26528,26531],{"href":26529,"rel":26530},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FModels\u002FComponent.php#L7",[77],"modèle Component"," utilise désormais cette connexion :",[192,26534,26536],{"className":19560,"code":26535,"language":19563,"meta":13,"style":13},"\u003C?php\n\nnamespace Fusion\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Component extends Model\n{\n    protected $connection = '__fusion';\n}\n",[156,26537,26538,26542,26546,26551,26555,26560,26564,26569,26573,26578],{"__ignoreMap":13},[200,26539,26540],{"class":202,"line":203},[200,26541,19600],{},[200,26543,26544],{"class":202,"line":14},[200,26545,528],{"emptyLinePlaceholder":20},[200,26547,26548],{"class":202,"line":255},[200,26549,26550],{},"namespace Fusion\\Models;\n",[200,26552,26553],{"class":202,"line":272},[200,26554,528],{"emptyLinePlaceholder":20},[200,26556,26557],{"class":202,"line":283},[200,26558,26559],{},"use Illuminate\\Database\\Eloquent\\Model;\n",[200,26561,26562],{"class":202,"line":300},[200,26563,528],{"emptyLinePlaceholder":20},[200,26565,26566],{"class":202,"line":310},[200,26567,26568],{},"class Component extends Model\n",[200,26570,26571],{"class":202,"line":336},[200,26572,230],{},[200,26574,26575],{"class":202,"line":342},[200,26576,26577],{},"    protected $connection = '__fusion';\n",[200,26579,26580],{"class":202,"line":365},[200,26581,1129],{},[35,26583,26584,26585,26588,26589,26591],{},"Avec cette configuration, la classe ",[156,26586,26587],{},"Component"," utilise la connexion de base de données ",[156,26590,26463],{},". Par conséquent, la base de données SQLite est uniquement utilisée par Fusion ; elle n'exerce aucun impact visible sur l'application Laravel, permettant à l'application de choisir librement son mode de connexion de base de données. C'est une stratégie simple et intelligemment mise en œuvre.",[1929,26593,26595],{"id":26594},"gestion-des-requêtes-entrantes","Gestion des requêtes entrantes",[35,26597,26598],{},"Plongeons dans peut-être le segment le plus intrigant de l'article : la gestion des requêtes entrantes via Fusion.",[35,26600,26601],{},"En commençant à lire, nous avons largement couvert le développement, incluant le fonctionnement du plugin Vite et la transformation PHP. Notre tâche consiste maintenant à comprendre comment Fusion harmonise ces éléments pour la gestion des requêtes.",[35,26603,26604,26605,26612,26613,26618,26619,26627,26628,215],{},"Fusion adopte une approche de routeur basé sur des fichiers. Les SFC de Vue s'enregistrent automatiquement en tant que routes au sein de l'application Laravel, réalisé en appelant ",[39,26606,26609,26611],{"href":26607,"rel":26608},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusion.php",[77],[156,26610,24276],{}," comme un facade",", soutenu par ",[39,26614,26617],{"href":26615,"rel":26616},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php",[77],"le gestionnaire Fusion",", qui est ensuite ",[39,26620,26623,26624],{"href":26621,"rel":26622},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L30",[77],"enregistré dans le ",[156,26625,26626],{},"FusionServiceProvider"," dans le cadre de la configuration habituelle des routes dans ",[156,26629,26630],{},"routes\u002Fweb.php",[192,26632,26634],{"className":19560,"code":26633,"language":19563,"meta":13,"style":13},"\u003C?php\n\nuse Fusion\\Fusion;\n\nFusion::pages();\n",[156,26635,26636,26640,26644,26649,26653],{"__ignoreMap":13},[200,26637,26638],{"class":202,"line":203},[200,26639,19600],{},[200,26641,26642],{"class":202,"line":14},[200,26643,528],{"emptyLinePlaceholder":20},[200,26645,26646],{"class":202,"line":255},[200,26647,26648],{},"use Fusion\\Fusion;\n",[200,26650,26651],{"class":202,"line":272},[200,26652,528],{"emptyLinePlaceholder":20},[200,26654,26655],{"class":202,"line":283},[200,26656,26657],{},"Fusion::pages();\n",[35,26659,26660,26661,3092,26663,26668,26669,26672,26673,26678,26679,26684],{},"Cela semble être toute l'explication mais continuons pour dévoiler davantage de complexités. Dans la méthode ",[156,26662,5366],{},[39,26664,26667],{"href":26665,"rel":26666},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php#L67",[77],"tous les composants SFC de Vue"," situés dans ",[156,26670,26671],{},"resources\u002Fjs\u002FPages"," sont localisés et ",[39,26674,26677],{"href":26675,"rel":26676},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php#L83",[77],"enregistrés méticuleusement",". Utiliser le ",[39,26680,26683],{"href":26681,"rel":26682},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Frouting",[77],"routeur Laravel"," facilite l'implémentation.",[192,26686,26688],{"className":19560,"code":26687,"language":19563,"meta":13,"style":13},"Route::any($uri, [FusionController::class, 'handle'])\n  ->defaults('__component', $component)\n  ->defaults('__class', Component::where('src', $src)->first()?->php_class);\n",[156,26689,26690,26695,26700],{"__ignoreMap":13},[200,26691,26692],{"class":202,"line":203},[200,26693,26694],{},"Route::any($uri, [FusionController::class, 'handle'])\n",[200,26696,26697],{"class":202,"line":14},[200,26698,26699],{},"  ->defaults('__component', $component)\n",[200,26701,26702],{"class":202,"line":255},[200,26703,26704],{},"  ->defaults('__class', Component::where('src', $src)->first()?->php_class);\n",[35,26706,26707],{},"Notamment, le processus d'acheminement attribue deux valeurs par défaut :",[68,26709,26710,26716],{},[71,26711,26712,26715],{},[156,26713,26714],{},"__component",": Chemin menant au fichier SFC de Vue.",[71,26717,26718,26721,26722,215],{},[156,26719,26720],{},"__class",": Nom de classe PHP pleinement qualifié, cette classe a été générée dans la commande ",[156,26723,25229],{},[35,26725,26726,26727,26729,26730,26733,26734,26737],{},"Par conséquent, lorsqu'une requête accède à la route ",[156,26728,2898],{},", l'opération ",[156,26731,26732],{},"handle"," du ",[156,26735,26736],{},"FusionController"," s'active. À l'intérieur du contrôleur se trouve un système qui gère les différentes possibilités tout en synchronisant les actions, et en interagissant sans accroc avec le frontend.",[35,26739,26740],{},"Après la gestion des requêtes, une réponse appropriée est envoyée, guidée par la nature de la requête comme suit :",[68,26742,26743,26751],{},[71,26744,26745,26746,215],{},"Les requêtes HMR ou d'action reçoivent ",[39,26747,26750],{"href":26748,"rel":26749},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L61-L63",[77],"une réponse JSON",[71,26752,26753,26754,215],{},"Les requêtes standards délèguent les tâches de rendu ",[39,26755,26758],{"href":26756,"rel":26757},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L65-L67",[77],"à Inertia",[35,26760,26761,26762,26765],{},"L'attribut ",[156,26763,26764],{},"data-page"," révèle l'objet Inertia accompagné des composants Fusion associés :",[192,26767,26769],{"className":590,"code":26768,"language":592,"meta":13,"style":13},"\u003Cdiv id=\"app\" data-page=\"{&quot;component&quot;:&quot;Index&quot;,&quot;props&quot;:{&quot;errors&quot;:{},&quot;auth&quot;:{&quot;user&quot;:null},&quot;fusion&quot;:{&quot;meta&quot;:[],&quot;state&quot;:{&quot;name&quot;:&quot;AARON&quot;},&quot;actions&quot;:[{&quot;handler&quot;:&quot;applyServerState&quot;,&quot;priority&quot;:40,&quot;_handler&quot;:&quot;Fusion\\\\Http\\\\Response\\\\Actions\\\\ApplyServerState&quot;}]}},&quot;url&quot;:&quot;\\\u002F&quot;,&quot;version&quot;:&quot;f7e418d032bf1c4cc3cc55d947cab622&quot;,&quot;clearHistory&quot;:false,&quot;encryptHistory&quot;:false}\">\u003C\u002Fdiv>\n",[156,26770,26771],{"__ignoreMap":13},[200,26772,26773,26775,26777,26779,26781,26783,26786,26788,26791,26793,26795,26797,26800,26802,26804,26806,26808,26811,26813,26815,26817,26819,26821,26824,26826,26829,26831,26834,26836,26839,26841,26843,26845,26848,26850,26853,26855,26858,26860,26862,26864,26866,26868,26871,26873,26876,26878,26880,26882,26884,26886,26888,26890,26893,26895,26898,26900,26903,26905,26908,26910,26913,26915,26917,26919,26922,26924,26926,26928,26931,26933,26936,26938,26941,26943,26945,26947,26950,26952,26955,26957,26959,26961,26963,26965,26968,26970,26972,26974,26977,26979,26981,26983,26986,26988,26990,26992,26995,26997,27000,27002,27005,27007,27010,27012,27014,27016],{"class":202,"line":203},[200,26774,599],{"class":214},[200,26776,11400],{"class":235},[200,26778,3239],{"class":379},[200,26780,449],{"class":214},[200,26782,610],{"class":214},[200,26784,26785],{"class":245},"app",[200,26787,610],{"class":214},[200,26789,26790],{"class":379}," data-page",[200,26792,449],{"class":214},[200,26794,610],{"class":214},[200,26796,2767],{"class":245},[200,26798,26799],{"class":210},"&quot;",[200,26801,23207],{"class":245},[200,26803,26799],{"class":210},[200,26805,239],{"class":245},[200,26807,26799],{"class":210},[200,26809,26810],{"class":245},"Index",[200,26812,26799],{"class":210},[200,26814,549],{"class":245},[200,26816,26799],{"class":210},[200,26818,23874],{"class":245},[200,26820,26799],{"class":210},[200,26822,26823],{"class":245},":{",[200,26825,26799],{"class":210},[200,26827,26828],{"class":245},"errors",[200,26830,26799],{"class":210},[200,26832,26833],{"class":245},":{},",[200,26835,26799],{"class":210},[200,26837,26838],{"class":245},"auth",[200,26840,26799],{"class":210},[200,26842,26823],{"class":245},[200,26844,26799],{"class":210},[200,26846,26847],{"class":245},"user",[200,26849,26799],{"class":210},[200,26851,26852],{"class":245},":null},",[200,26854,26799],{"class":210},[200,26856,26857],{"class":245},"fusion",[200,26859,26799],{"class":210},[200,26861,26823],{"class":245},[200,26863,26799],{"class":210},[200,26865,2030],{"class":245},[200,26867,26799],{"class":210},[200,26869,26870],{"class":245},":[],",[200,26872,26799],{"class":210},[200,26874,26875],{"class":245},"state",[200,26877,26799],{"class":210},[200,26879,26823],{"class":245},[200,26881,26799],{"class":210},[200,26883,20112],{"class":245},[200,26885,26799],{"class":210},[200,26887,239],{"class":245},[200,26889,26799],{"class":210},[200,26891,26892],{"class":245},"AARON",[200,26894,26799],{"class":210},[200,26896,26897],{"class":245},"},",[200,26899,26799],{"class":210},[200,26901,26902],{"class":245},"actions",[200,26904,26799],{"class":210},[200,26906,26907],{"class":245},":[{",[200,26909,26799],{"class":210},[200,26911,26912],{"class":245},"handler",[200,26914,26799],{"class":210},[200,26916,239],{"class":245},[200,26918,26799],{"class":210},[200,26920,26921],{"class":245},"applyServerState",[200,26923,26799],{"class":210},[200,26925,549],{"class":245},[200,26927,26799],{"class":210},[200,26929,26930],{"class":245},"priority",[200,26932,26799],{"class":210},[200,26934,26935],{"class":245},":40,",[200,26937,26799],{"class":210},[200,26939,26940],{"class":245},"_handler",[200,26942,26799],{"class":210},[200,26944,239],{"class":245},[200,26946,26799],{"class":210},[200,26948,26949],{"class":245},"Fusion\\\\Http\\\\Response\\\\Actions\\\\ApplyServerState",[200,26951,26799],{"class":210},[200,26953,26954],{"class":245},"}]}},",[200,26956,26799],{"class":210},[200,26958,2035],{"class":245},[200,26960,26799],{"class":210},[200,26962,239],{"class":245},[200,26964,26799],{"class":210},[200,26966,26967],{"class":245},"\\\u002F",[200,26969,26799],{"class":210},[200,26971,549],{"class":245},[200,26973,26799],{"class":210},[200,26975,26976],{"class":245},"version",[200,26978,26799],{"class":210},[200,26980,239],{"class":245},[200,26982,26799],{"class":210},[200,26984,26985],{"class":245},"f7e418d032bf1c4cc3cc55d947cab622",[200,26987,26799],{"class":210},[200,26989,549],{"class":245},[200,26991,26799],{"class":210},[200,26993,26994],{"class":245},"clearHistory",[200,26996,26799],{"class":210},[200,26998,26999],{"class":245},":false,",[200,27001,26799],{"class":210},[200,27003,27004],{"class":245},"encryptHistory",[200,27006,26799],{"class":210},[200,27008,27009],{"class":245},":false}",[200,27011,610],{"class":214},[200,27013,10206],{"class":214},[200,27015,11400],{"class":235},[200,27017,647],{"class":214},[35,27019,419,27020,27022,27023,27026,27027,1655,27029,27031,27032,27035,27036,215],{},[156,27021,26857],{}," est d'un intérêt particulier. Il est composé de données initialisées dans le bloc PHP du SFC de Vue via la fonction ",[6862,27024,27025],{},"prop"," ; comprenant ",[6862,27028,26875],{},[6862,27030,26902],{}," injectées au client en conséquence. La classe ",[156,27033,27034],{},"PendingResponse",", responsable de la gestion, catégorise spécifiquement ces opérations au sein de Fusion ",[39,27037,27040],{"href":27038,"rel":27039},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FHttp\u002FResponse\u002FPendingResponse.php#L31-L43",[77],"comme suit",[35,27042,27043,27044,27046],{},"Spécifiquement, l'action ",[156,27045,26921],{}," utilise :",[192,27048,27050],{"className":25070,"code":27049,"language":25072,"meta":13,"style":13},"import { ref, unref } from 'vue'\n\nexport default function applyServerState({ fusion, state }, next) {\n  Object.keys(fusion.state).forEach((key) => {\n    state[key] = ref(unref(fusion.state[key]))\n  })\n\n  return next({ fusion, state })\n}\n",[156,27051,27052,27075,27079,27110,27147,27182,27188,27192,27212],{"__ignoreMap":13},[200,27053,27054,27056,27058,27060,27062,27065,27067,27069,27071,27073],{"class":202,"line":203},[200,27055,972],{"class":206},[200,27057,318],{"class":214},[200,27059,18698],{"class":210},[200,27061,549],{"class":214},[200,27063,27064],{"class":210}," unref",[200,27066,980],{"class":214},[200,27068,983],{"class":206},[200,27070,242],{"class":214},[200,27072,12075],{"class":245},[200,27074,991],{"class":214},[200,27076,27077],{"class":202,"line":14},[200,27078,528],{"emptyLinePlaceholder":20},[200,27080,27081,27083,27085,27087,27090,27092,27095,27097,27100,27103,27106,27108],{"class":202,"line":255},[200,27082,1390],{"class":206},[200,27084,1393],{"class":206},[200,27086,5002],{"class":379},[200,27088,27089],{"class":223}," applyServerState",[200,27091,3966],{"class":214},[200,27093,27094],{"class":386}," fusion",[200,27096,549],{"class":214},[200,27098,27099],{"class":386}," state",[200,27101,27102],{"class":214}," },",[200,27104,27105],{"class":386}," next",[200,27107,2104],{"class":214},[200,27109,280],{"class":214},[200,27111,27112,27115,27117,27120,27122,27124,27126,27128,27130,27132,27135,27137,27139,27141,27143,27145],{"class":202,"line":272},[200,27113,27114],{"class":210},"  Object",[200,27116,215],{"class":214},[200,27118,27119],{"class":223},"keys",[200,27121,227],{"class":235},[200,27123,26857],{"class":210},[200,27125,215],{"class":214},[200,27127,26875],{"class":210},[200,27129,2104],{"class":235},[200,27131,215],{"class":214},[200,27133,27134],{"class":223},"forEach",[200,27136,227],{"class":235},[200,27138,227],{"class":214},[200,27140,24627],{"class":386},[200,27142,2104],{"class":214},[200,27144,393],{"class":379},[200,27146,280],{"class":214},[200,27148,27149,27152,27154,27156,27158,27160,27162,27164,27167,27169,27171,27173,27175,27177,27179],{"class":202,"line":283},[200,27150,27151],{"class":210},"    state",[200,27153,9872],{"class":235},[200,27155,24627],{"class":210},[200,27157,18849],{"class":235},[200,27159,449],{"class":214},[200,27161,18698],{"class":223},[200,27163,227],{"class":235},[200,27165,27166],{"class":223},"unref",[200,27168,227],{"class":235},[200,27170,26857],{"class":210},[200,27172,215],{"class":214},[200,27174,26875],{"class":210},[200,27176,9872],{"class":235},[200,27178,24627],{"class":210},[200,27180,27181],{"class":235},"]))\n",[200,27183,27184,27186],{"class":202,"line":300},[200,27185,5095],{"class":214},[200,27187,416],{"class":235},[200,27189,27190],{"class":202,"line":310},[200,27191,528],{"emptyLinePlaceholder":20},[200,27193,27194,27196,27198,27200,27202,27204,27206,27208,27210],{"class":202,"line":336},[200,27195,3758],{"class":206},[200,27197,27105],{"class":223},[200,27199,227],{"class":235},[200,27201,2767],{"class":214},[200,27203,27094],{"class":210},[200,27205,549],{"class":214},[200,27207,27099],{"class":210},[200,27209,980],{"class":214},[200,27211,416],{"class":235},[200,27213,27214],{"class":202,"line":342},[200,27215,1129],{"class":214},[35,27217,27218],{},"Les développeurs bénéficient d'exploiter automatiquement le traitement des actions par Fusion sans appels manuels.",[35,27220,27221],{},"Je n'ai pas encore tout révélé. En plus d'extraire du bloc PHP du SFC de Vue, Fusion injecte de la logique JavaScript dans le SFC de Vue, précédant la transformation. Ce qui a commencé comme un composant initial :",[192,27223,27225],{"className":21457,"code":27224,"language":12075,"meta":13,"style":13},"\u003Cphp>\nuse function \\Fusion\\{prop};\n\n$name = prop('Aaron');\n$name = strtoupper($name);\n\u003C\u002Fphp>\n\n\u003Ctemplate>\n  Bonjour {{ name }} !\n\u003C\u002Ftemplate>\n",[156,27226,27227,27235,27239,27243,27248,27252,27260,27264,27272,27276],{"__ignoreMap":13},[200,27228,27229,27231,27233],{"class":202,"line":203},[200,27230,599],{"class":214},[200,27232,19563],{"class":235},[200,27234,647],{"class":214},[200,27236,27237],{"class":202,"line":14},[200,27238,26072],{"class":210},[200,27240,27241],{"class":202,"line":255},[200,27242,528],{"emptyLinePlaceholder":20},[200,27244,27245],{"class":202,"line":272},[200,27246,27247],{"class":210},"$name = prop('Aaron');\n",[200,27249,27250],{"class":202,"line":283},[200,27251,26090],{"class":210},[200,27253,27254,27256,27258],{"class":202,"line":300},[200,27255,680],{"class":214},[200,27257,19563],{"class":235},[200,27259,647],{"class":214},[200,27261,27262],{"class":202,"line":310},[200,27263,528],{"emptyLinePlaceholder":20},[200,27265,27266,27268,27270],{"class":202,"line":336},[200,27267,599],{"class":214},[200,27269,21468],{"class":235},[200,27271,647],{"class":214},[200,27273,27274],{"class":202,"line":342},[200,27275,24368],{"class":210},[200,27277,27278,27280,27282],{"class":202,"line":365},[200,27279,680],{"class":214},[200,27281,21468],{"class":235},[200,27283,647],{"class":214},[35,27285,27286],{},"Se transforme durant la transformation du plugin Fusion (plus tard gérée par Vite) comme suit :",[192,27288,27290],{"className":21457,"code":27289,"language":12075,"meta":13,"style":13},"\u003Cscript setup>\nimport { useFusion } from '$fusion\u002FPages\u002FIndex.js'\nimport useHotFusion from '@fusion\u002Fvue\u002Fhmr'\n\n\u002F\u002F __props est une variable magique fournie au script setup par Vue.\nconst __fusionData = useFusion(['name', 'fusion'], __props.fusion)\n\nconst { name, fusion } = __fusionData\n\nuseHotFusion(__fusionData, { id: 'hot_07f7c82af56ef80f306d905892430461', hot: import.meta?.hot })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  Bonjour {{ name }} !\n\u003C\u002Ftemplate>\n",[156,27291,27292,27302,27322,27338,27342,27347,27386,27390,27410,27414,27460,27468,27472,27480,27484],{"__ignoreMap":13},[200,27293,27294,27296,27298,27300],{"class":202,"line":203},[200,27295,599],{"class":214},[200,27297,14896],{"class":235},[200,27299,21825],{"class":379},[200,27301,647],{"class":214},[200,27303,27304,27306,27308,27311,27313,27315,27317,27320],{"class":202,"line":14},[200,27305,972],{"class":206},[200,27307,318],{"class":214},[200,27309,27310],{"class":210}," useFusion",[200,27312,980],{"class":214},[200,27314,983],{"class":206},[200,27316,242],{"class":214},[200,27318,27319],{"class":245},"$fusion\u002FPages\u002FIndex.js",[200,27321,991],{"class":214},[200,27323,27324,27326,27329,27331,27333,27336],{"class":202,"line":255},[200,27325,972],{"class":206},[200,27327,27328],{"class":210}," useHotFusion ",[200,27330,1489],{"class":206},[200,27332,242],{"class":214},[200,27334,27335],{"class":245},"@fusion\u002Fvue\u002Fhmr",[200,27337,991],{"class":214},[200,27339,27340],{"class":202,"line":272},[200,27341,528],{"emptyLinePlaceholder":20},[200,27343,27344],{"class":202,"line":283},[200,27345,27346],{"class":401},"\u002F\u002F __props est une variable magique fournie au script setup par Vue.\n",[200,27348,27349,27351,27354,27356,27358,27360,27362,27364,27366,27368,27370,27372,27374,27376,27378,27381,27383],{"class":202,"line":300},[200,27350,443],{"class":379},[200,27352,27353],{"class":210}," __fusionData ",[200,27355,449],{"class":214},[200,27357,27310],{"class":223},[200,27359,25224],{"class":210},[200,27361,249],{"class":214},[200,27363,20112],{"class":245},[200,27365,249],{"class":214},[200,27367,549],{"class":214},[200,27369,242],{"class":214},[200,27371,26857],{"class":245},[200,27373,249],{"class":214},[200,27375,360],{"class":210},[200,27377,549],{"class":214},[200,27379,27380],{"class":210}," __props",[200,27382,215],{"class":214},[200,27384,27385],{"class":210},"fusion)\n",[200,27387,27388],{"class":202,"line":310},[200,27389,528],{"emptyLinePlaceholder":20},[200,27391,27392,27394,27396,27398,27400,27403,27405,27407],{"class":202,"line":336},[200,27393,443],{"class":379},[200,27395,318],{"class":214},[200,27397,700],{"class":210},[200,27399,549],{"class":214},[200,27401,27402],{"class":210}," fusion ",[200,27404,413],{"class":214},[200,27406,509],{"class":214},[200,27408,27409],{"class":210}," __fusionData\n",[200,27411,27412],{"class":202,"line":342},[200,27413,528],{"emptyLinePlaceholder":20},[200,27415,27416,27419,27422,27424,27426,27428,27430,27432,27435,27437,27439,27442,27444,27447,27449,27451,27453,27456,27458],{"class":202,"line":365},[200,27417,27418],{"class":223},"useHotFusion",[200,27420,27421],{"class":210},"(__fusionData",[200,27423,549],{"class":214},[200,27425,318],{"class":214},[200,27427,3239],{"class":235},[200,27429,239],{"class":214},[200,27431,242],{"class":214},[200,27433,27434],{"class":245},"hot_07f7c82af56ef80f306d905892430461",[200,27436,249],{"class":214},[200,27438,549],{"class":214},[200,27440,27441],{"class":235}," hot",[200,27443,239],{"class":214},[200,27445,27446],{"class":206}," import",[200,27448,215],{"class":214},[200,27450,2030],{"class":210},[200,27452,6536],{"class":214},[200,27454,27455],{"class":210},"hot ",[200,27457,413],{"class":214},[200,27459,416],{"class":210},[200,27461,27462,27464,27466],{"class":202,"line":371},[200,27463,680],{"class":214},[200,27465,14896],{"class":235},[200,27467,647],{"class":214},[200,27469,27470],{"class":202,"line":398},[200,27471,528],{"emptyLinePlaceholder":20},[200,27473,27474,27476,27478],{"class":202,"line":405},[200,27475,599],{"class":214},[200,27477,21468],{"class":235},[200,27479,647],{"class":214},[200,27481,27482],{"class":202,"line":410},[200,27483,24368],{"class":210},[200,27485,27486,27488,27490],{"class":202,"line":860},[200,27487,680],{"class":214},[200,27489,21468],{"class":235},[200,27491,647],{"class":214},[35,27493,27494,27495,27502,27503,27506,27507,27509,27510,27512,27513,27516,27517,27520,27521,27523],{},"Notez le bloc ",[39,27496,27499],{"href":27497,"rel":27498},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Finjectors\u002FcreateScriptSetup.js",[77],[156,27500,27501],{},"script setup"," injecté synthétiquement, ne nécessitant aucun input manuel. Il présente un composable ",[156,27504,27505],{},"useFusion",", acceptant deux arguments, le premier détaillant les clés de props nécessitant extraction (affinées par la reconnaissance des variables PHP via les appels à ",[6862,27508,27025],{},") et un argument ",[156,27511,23874],{}," signifiant les props. En essence, cela ressemble à l'utilisation de ",[156,27514,27515],{},"const props = defineProps()",". Fusion déclenche la variable ",[156,27518,27519],{},"__props",", fournie par Vue, permettant aux développeurs d'écrire leur bloc ",[156,27522,27501],{}," sans entraves. Cela permet de conserver la syntaxe normale des SFC de Vue non entravée par les processus Fusion.",[35,27525,27526,27527,1655,27529,27531,27532,27534],{},"Essentiellement, les valeurs composables ",[156,27528,20112],{},[156,27530,26857],{}," sont extraites via ",[156,27533,27501],{}," avec une injection de modèle sans interruption.",[35,27536,27537,27538,27540,27541,27544],{},"D'où vient ",[156,27539,27505],{}," ? Le préfixe ",[156,27542,27543],{},"$fusion\u002FPages"," révèle la vue d'ensemble.",[35,27546,27547,27548,27550,27551,27553],{},"Concernant ",[156,27549,27543],{},", nous avons déjà vu sa fonctionnalité en action via l'alias généré par le plugin Vite. Cet alias abstrait l'emplacement réel du fichier JavaScript, provenant d'une précédente entreprise formulant le ",[156,27552,25308],{},". Son contenu se lit :",[192,27555,27557],{"className":25070,"code":27556,"language":25072,"meta":13,"style":13},"import ActionFactory from '@fusion\u002Fvue\u002FactionFactory'\nimport Pipeline from '@fusion\u002Fvue\u002Fpipeline'\n\nexport const state = ['name']\nexport const actions = []\nexport const fusionActions = ['fusionSync']\n\nlet cachedState\n\nexport function useFusion(keys = [], props = {}, useCachedState = false) {\n  const state = (useCachedState && cachedState) ? cachedState : new Pipeline(props).createState()\n\n  if (!useCachedState) {\n    cachedState = state\n  }\n\n  const all = {\n    ...state,\n    ...new ActionFactory([...actions, ...fusionActions], state),\n  }\n\n  const shouldExport = {}\n  for (const key of keys) {\n    if (key in all) {\n      shouldExport[key] = all[key]\n    }\n  }\n\n  return shouldExport\n}\n",[156,27558,27559,27575,27591,27595,27615,27628,27650,27654,27661,27665,27703,27748,27752,27766,27776,27780,27784,27795,27804,27836,27840,27844,27855,27874,27891,27912,27916,27920,27924,27931],{"__ignoreMap":13},[200,27560,27561,27563,27566,27568,27570,27573],{"class":202,"line":203},[200,27562,972],{"class":206},[200,27564,27565],{"class":210}," ActionFactory ",[200,27567,1489],{"class":206},[200,27569,242],{"class":214},[200,27571,27572],{"class":245},"@fusion\u002Fvue\u002FactionFactory",[200,27574,991],{"class":214},[200,27576,27577,27579,27582,27584,27586,27589],{"class":202,"line":14},[200,27578,972],{"class":206},[200,27580,27581],{"class":210}," Pipeline ",[200,27583,1489],{"class":206},[200,27585,242],{"class":214},[200,27587,27588],{"class":245},"@fusion\u002Fvue\u002Fpipeline",[200,27590,991],{"class":214},[200,27592,27593],{"class":202,"line":255},[200,27594,528],{"emptyLinePlaceholder":20},[200,27596,27597,27599,27601,27603,27605,27607,27609,27611,27613],{"class":202,"line":272},[200,27598,1390],{"class":206},[200,27600,14305],{"class":379},[200,27602,18960],{"class":210},[200,27604,449],{"class":214},[200,27606,350],{"class":210},[200,27608,249],{"class":214},[200,27610,20112],{"class":245},[200,27612,249],{"class":214},[200,27614,4511],{"class":210},[200,27616,27617,27619,27621,27624,27626],{"class":202,"line":283},[200,27618,1390],{"class":206},[200,27620,14305],{"class":379},[200,27622,27623],{"class":210}," actions ",[200,27625,449],{"class":214},[200,27627,6433],{"class":210},[200,27629,27630,27632,27634,27637,27639,27641,27643,27646,27648],{"class":202,"line":300},[200,27631,1390],{"class":206},[200,27633,14305],{"class":379},[200,27635,27636],{"class":210}," fusionActions ",[200,27638,449],{"class":214},[200,27640,350],{"class":210},[200,27642,249],{"class":214},[200,27644,27645],{"class":245},"fusionSync",[200,27647,249],{"class":214},[200,27649,4511],{"class":210},[200,27651,27652],{"class":202,"line":310},[200,27653,528],{"emptyLinePlaceholder":20},[200,27655,27656,27658],{"class":202,"line":336},[200,27657,12337],{"class":379},[200,27659,27660],{"class":210}," cachedState\n",[200,27662,27663],{"class":202,"line":342},[200,27664,528],{"emptyLinePlaceholder":20},[200,27666,27667,27669,27671,27673,27675,27677,27679,27682,27684,27687,27689,27692,27695,27697,27699,27701],{"class":202,"line":365},[200,27668,1390],{"class":206},[200,27670,5002],{"class":379},[200,27672,27310],{"class":223},[200,27674,227],{"class":214},[200,27676,27119],{"class":386},[200,27678,509],{"class":214},[200,27680,27681],{"class":210}," []",[200,27683,549],{"class":214},[200,27685,27686],{"class":386}," props",[200,27688,509],{"class":214},[200,27690,27691],{"class":214}," {},",[200,27693,27694],{"class":386}," useCachedState",[200,27696,509],{"class":214},[200,27698,16169],{"class":2438},[200,27700,2104],{"class":214},[200,27702,280],{"class":214},[200,27704,27705,27707,27709,27711,27713,27716,27718,27721,27723,27726,27728,27730,27732,27735,27737,27739,27741,27743,27746],{"class":202,"line":371},[200,27706,3738],{"class":379},[200,27708,27099],{"class":210},[200,27710,509],{"class":214},[200,27712,1094],{"class":235},[200,27714,27715],{"class":210},"useCachedState",[200,27717,14932],{"class":214},[200,27719,27720],{"class":210}," cachedState",[200,27722,5282],{"class":235},[200,27724,27725],{"class":214},"?",[200,27727,27720],{"class":210},[200,27729,2186],{"class":214},[200,27731,13007],{"class":214},[200,27733,27734],{"class":223}," Pipeline",[200,27736,227],{"class":235},[200,27738,23874],{"class":210},[200,27740,2104],{"class":235},[200,27742,215],{"class":214},[200,27744,27745],{"class":223},"createState",[200,27747,466],{"class":235},[200,27749,27750],{"class":202,"line":398},[200,27751,528],{"emptyLinePlaceholder":20},[200,27753,27754,27756,27758,27760,27762,27764],{"class":202,"line":405},[200,27755,20098],{"class":206},[200,27757,1094],{"class":235},[200,27759,11852],{"class":214},[200,27761,27715],{"class":210},[200,27763,5282],{"class":235},[200,27765,230],{"class":214},[200,27767,27768,27771,27773],{"class":202,"line":410},[200,27769,27770],{"class":210},"    cachedState",[200,27772,509],{"class":214},[200,27774,27775],{"class":210}," state\n",[200,27777,27778],{"class":202,"line":860},[200,27779,4505],{"class":214},[200,27781,27782],{"class":202,"line":889},[200,27783,528],{"emptyLinePlaceholder":20},[200,27785,27786,27788,27791,27793],{"class":202,"line":899},[200,27787,3738],{"class":379},[200,27789,27790],{"class":210}," all",[200,27792,509],{"class":214},[200,27794,280],{"class":214},[200,27796,27797,27800,27802],{"class":202,"line":904},[200,27798,27799],{"class":214},"    ...",[200,27801,26875],{"class":210},[200,27803,252],{"class":214},[200,27805,27806,27809,27812,27814,27816,27818,27820,27823,27826,27828,27830,27832,27834],{"class":202,"line":930},[200,27807,27808],{"class":214},"    ...new",[200,27810,27811],{"class":223}," ActionFactory",[200,27813,25224],{"class":235},[200,27815,573],{"class":214},[200,27817,26902],{"class":210},[200,27819,549],{"class":214},[200,27821,27822],{"class":214}," ...",[200,27824,27825],{"class":210},"fusionActions",[200,27827,360],{"class":235},[200,27829,549],{"class":214},[200,27831,27099],{"class":210},[200,27833,2104],{"class":235},[200,27835,252],{"class":214},[200,27837,27838],{"class":202,"line":3655},[200,27839,4505],{"class":214},[200,27841,27842],{"class":202,"line":3660},[200,27843,528],{"emptyLinePlaceholder":20},[200,27845,27846,27848,27851,27853],{"class":202,"line":4035},[200,27847,3738],{"class":379},[200,27849,27850],{"class":210}," shouldExport",[200,27852,509],{"class":214},[200,27854,19914],{"class":214},[200,27856,27857,27859,27861,27863,27865,27867,27870,27872],{"class":202,"line":4490},[200,27858,6438],{"class":206},[200,27860,1094],{"class":235},[200,27862,443],{"class":379},[200,27864,18835],{"class":210},[200,27866,6457],{"class":214},[200,27868,27869],{"class":210}," keys",[200,27871,5282],{"class":235},[200,27873,230],{"class":214},[200,27875,27876,27878,27880,27882,27885,27887,27889],{"class":202,"line":4496},[200,27877,5267],{"class":206},[200,27879,1094],{"class":235},[200,27881,24627],{"class":210},[200,27883,27884],{"class":214}," in",[200,27886,27790],{"class":210},[200,27888,5282],{"class":235},[200,27890,230],{"class":214},[200,27892,27893,27896,27898,27900,27902,27904,27906,27908,27910],{"class":202,"line":4502},[200,27894,27895],{"class":210},"      shouldExport",[200,27897,9872],{"class":235},[200,27899,24627],{"class":210},[200,27901,18849],{"class":235},[200,27903,449],{"class":214},[200,27905,27790],{"class":210},[200,27907,9872],{"class":235},[200,27909,24627],{"class":210},[200,27911,4511],{"class":235},[200,27913,27914],{"class":202,"line":4508},[200,27915,4977],{"class":214},[200,27917,27918],{"class":202,"line":5469},[200,27919,4505],{"class":214},[200,27921,27922],{"class":202,"line":5474},[200,27923,528],{"emptyLinePlaceholder":20},[200,27925,27926,27928],{"class":202,"line":13237},[200,27927,3758],{"class":206},[200,27929,27930],{"class":210}," shouldExport\n",[200,27932,27933],{"class":202,"line":13263},[200,27934,1129],{"class":214},[35,27936,27937,27938,1655,27940,27942,27943,1655,27945,27948,27949,27952,27953,27955],{},"Les variables ",[156,27939,26875],{},[156,27941,26857],{}," résident dans ce fichier, reflétant les déclarations du SFC de Vue dans les fonctions PHP ",[6862,27944,27025],{},[6862,27946,27947],{},"expose",". Accompagné d'un ",[156,27950,27951],{},"ActionFactory"," responsable de l'application des actions imposées par le serveur à l'état, y compris ",[156,27954,26921],{},", la synchronisation automatique et transparente se produit.",[35,27957,27958],{},"En tout, Fusion propose plusieurs actions préétablies, telles que :",[1593,27960,27961,27970,27980,27989],{},[71,27962,27963,27969],{},[39,27964,27967],{"href":27965,"rel":27966},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FapplyServerState.js",[77],[156,27968,26921],{},": Synchronise les données d'état entre le backend et le client, mettant à jour l'état pour correspondre aux attentes du serveur en temps réel.",[71,27971,27972,27979],{},[39,27973,27976],{"href":27974,"rel":27975},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FsyncQueryString.js",[77],[156,27977,27978],{},"syncQueryString",": Synchronise les paramètres de chaîne de requête et l'état.",[71,27981,27982,27988],{},[39,27983,27986],{"href":27984,"rel":27985},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002Flog.js",[77],[156,27987,1117],{},": Permet l'enregistrement à des fins de débogage.",[71,27990,27991,27998],{},[39,27992,27995],{"href":27993,"rel":27994},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FlogStack.js",[77],[156,27996,27997],{},"logStack",": Enregistre la progression de l'action pour le débogage.",[35,28000,28001,28002,28004],{},"La discussion devient encore plus captivante en voyant la fonction ",[156,28003,27947],{},". Cette méthode exposent une fonctionnalité PHP côté serveur au client, créant des comportements de type RPC permettant à des requêtes côté client de déclencher des opérations côté serveur.",[938,28006,28007],{},[35,28008,28009,28011,28012,28014,28015,1655,28020,215],{},[200,28010,944],{},"\nLe concept ",[156,28013,27947],{}," est apparenté à des approches comme ",[39,28016,28019],{"href":28017,"rel":28018},"https:\u002F\u002Ftrpc.io\u002F",[77],"tRPC",[39,28021,28024],{"href":28022,"rel":28023},"https:\u002F\u002Freact.dev\u002Freference\u002Frsc\u002Fserver-functions",[77],"React Server Functions",[35,28026,28027],{},"Un exemple illustratif :",[192,28029,28031],{"className":21457,"code":28030,"language":12075,"meta":13,"style":13},"\u003Cphp>\nexpose(favorite: function(Podcast $podcast) {\n    return response()->json($podcast->toggleFavorite());\n});\n\u003C\u002Fphp>\n\n\u003Ctemplate>\n  \u003Cbutton @click=\"favorite({ podcast }).then(fav => podcast.favorited = fav)\">\n    Favori\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[156,28032,28033,28041,28046,28051,28056,28064,28068,28076,28096,28101,28109],{"__ignoreMap":13},[200,28034,28035,28037,28039],{"class":202,"line":203},[200,28036,599],{"class":214},[200,28038,19563],{"class":235},[200,28040,647],{"class":214},[200,28042,28043],{"class":202,"line":14},[200,28044,28045],{"class":210},"expose(favorite: function(Podcast $podcast) {\n",[200,28047,28048],{"class":202,"line":255},[200,28049,28050],{"class":210},"    return response()->json($podcast->toggleFavorite());\n",[200,28052,28053],{"class":202,"line":272},[200,28054,28055],{"class":210},"});\n",[200,28057,28058,28060,28062],{"class":202,"line":283},[200,28059,680],{"class":214},[200,28061,19563],{"class":235},[200,28063,647],{"class":214},[200,28065,28066],{"class":202,"line":300},[200,28067,528],{"emptyLinePlaceholder":20},[200,28069,28070,28072,28074],{"class":202,"line":310},[200,28071,599],{"class":214},[200,28073,21468],{"class":235},[200,28075,647],{"class":214},[200,28077,28078,28080,28082,28085,28087,28089,28092,28094],{"class":202,"line":336},[200,28079,656],{"class":214},[200,28081,909],{"class":235},[200,28083,28084],{"class":379}," @click",[200,28086,449],{"class":214},[200,28088,610],{"class":214},[200,28090,28091],{"class":245},"favorite({ podcast }).then(fav => podcast.favorited = fav)",[200,28093,610],{"class":214},[200,28095,647],{"class":214},[200,28097,28098],{"class":202,"line":342},[200,28099,28100],{"class":210},"    Favori\n",[200,28102,28103,28105,28107],{"class":202,"line":365},[200,28104,892],{"class":214},[200,28106,909],{"class":235},[200,28108,647],{"class":214},[200,28110,28111,28113,28115],{"class":202,"line":371},[200,28112,680],{"class":214},[200,28114,21468],{"class":235},[200,28116,647],{"class":214},[35,28118,28119,28120,28122,28123,28125],{},"L'exposition d'une méthode comme ",[156,28121,24484],{}," facilite son utilisation au sein du modèle. Le composable ",[156,28124,27505],{}," décrit précédemment gère la fonctionnalité, néanmoins, Fusion garantit un traitement approfondi permettant l'interaction de bouton côté client pour déclencher des opérations soutenues par le serveur.",[68,28127,28128,28143,28151],{},[71,28129,28130,28131,28136,28137,1655,28140,215],{},"Le client ",[39,28132,28135],{"href":28133,"rel":28134},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FActionFactory.js#L72-L77",[77],"envoie une requête au serveur"," pourvue des en-têtes ",[156,28138,28139],{},"x-fusion-action-request",[156,28141,28142],{},"x-fusion-action-handler",[71,28144,28145,28146,215],{},"Le backend traite ces requêtes, aidé par les métadonnées fournies, répondant au client en conséquence, semblable à ",[39,28147,28150],{"href":28148,"rel":28149},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FHttp\u002FControllers\u002FFusionController.php#L50",[77],"comment Inertia gère son protocole avec les en-têtes",[71,28152,28153,28154,215],{},"L'état côté client correspond aux retours du serveur, ",[39,28155,28158],{"href":28156,"rel":28157},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FActionFactory.js#L87",[77],"après traitement",[35,28160,28161],{},"Cliquer sur le bouton favori dans l'exemple ci-dessus génère cette requête ciblée vers le serveur :",[192,28163,28167],{"className":28164,"code":28165,"language":28166,"meta":13,"style":13},"language-http shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","POST \u002Fsearch HTTP\u002F1.1\nAccept: application\u002Fjson, text\u002Fplain, *\u002F*\nX-Fusion-Action-Handler: favorite\nX-Fusion-Action-Request: true\n\n{\n  \"fusion\": {\n    \"args\": {\n      \"podcast\": {\n        \"id\": 1,\n        \"title\": \"Art Of Product\",\n        \"author\": \"Derrick Reimer & Ben Orenstein\",\n        \"image\": \"\u002Fart\u002Fart-of-product.jpg\",\n        \"favorited\": false,\n        \"created_at\": \"2025-02-15T18:01:36.000000Z\",\n        \"updated_at\": \"2025-02-19T20:49:13.000000Z\"\n      }\n    },\n    \"state\": {\n      \"search\": \"\",\n      \"podcasts\": [\n        {\n          \"id\": 1,\n          \"title\": \"Art Of Product\",\n          \"author\": \"Derrick Reimer & Ben Orenstein\",\n          \"image\": \"\u002Fart\u002Fart-of-product.jpg\",\n          \"favorited\": false,\n          \"created_at\": \"2025-02-15T18:01:36.000000Z\",\n          \"updated_at\": \"2025-02-19T20:49:13.000000Z\"\n        }\n      ]\n    }\n  }\n}\n","http",[156,28168,28169,28174,28179,28184,28189,28193,28197,28202,28207,28212,28217,28222,28227,28232,28237,28242,28247,28251,28255,28260,28265,28270,28274,28279,28284,28289,28294,28299,28304,28309,28313,28318,28322,28326],{"__ignoreMap":13},[200,28170,28171],{"class":202,"line":203},[200,28172,28173],{},"POST \u002Fsearch HTTP\u002F1.1\n",[200,28175,28176],{"class":202,"line":14},[200,28177,28178],{},"Accept: application\u002Fjson, text\u002Fplain, *\u002F*\n",[200,28180,28181],{"class":202,"line":255},[200,28182,28183],{},"X-Fusion-Action-Handler: favorite\n",[200,28185,28186],{"class":202,"line":272},[200,28187,28188],{},"X-Fusion-Action-Request: true\n",[200,28190,28191],{"class":202,"line":283},[200,28192,528],{"emptyLinePlaceholder":20},[200,28194,28195],{"class":202,"line":300},[200,28196,230],{},[200,28198,28199],{"class":202,"line":310},[200,28200,28201],{},"  \"fusion\": {\n",[200,28203,28204],{"class":202,"line":336},[200,28205,28206],{},"    \"args\": {\n",[200,28208,28209],{"class":202,"line":342},[200,28210,28211],{},"      \"podcast\": {\n",[200,28213,28214],{"class":202,"line":365},[200,28215,28216],{},"        \"id\": 1,\n",[200,28218,28219],{"class":202,"line":371},[200,28220,28221],{},"        \"title\": \"Art Of Product\",\n",[200,28223,28224],{"class":202,"line":398},[200,28225,28226],{},"        \"author\": \"Derrick Reimer & Ben Orenstein\",\n",[200,28228,28229],{"class":202,"line":405},[200,28230,28231],{},"        \"image\": \"\u002Fart\u002Fart-of-product.jpg\",\n",[200,28233,28234],{"class":202,"line":410},[200,28235,28236],{},"        \"favorited\": false,\n",[200,28238,28239],{"class":202,"line":860},[200,28240,28241],{},"        \"created_at\": \"2025-02-15T18:01:36.000000Z\",\n",[200,28243,28244],{"class":202,"line":889},[200,28245,28246],{},"        \"updated_at\": \"2025-02-19T20:49:13.000000Z\"\n",[200,28248,28249],{"class":202,"line":899},[200,28250,4493],{},[200,28252,28253],{"class":202,"line":904},[200,28254,339],{},[200,28256,28257],{"class":202,"line":930},[200,28258,28259],{},"    \"state\": {\n",[200,28261,28262],{"class":202,"line":3655},[200,28263,28264],{},"      \"search\": \"\",\n",[200,28266,28267],{"class":202,"line":3660},[200,28268,28269],{},"      \"podcasts\": [\n",[200,28271,28272],{"class":202,"line":4035},[200,28273,4924],{},[200,28275,28276],{"class":202,"line":4490},[200,28277,28278],{},"          \"id\": 1,\n",[200,28280,28281],{"class":202,"line":4496},[200,28282,28283],{},"          \"title\": \"Art Of Product\",\n",[200,28285,28286],{"class":202,"line":4502},[200,28287,28288],{},"          \"author\": \"Derrick Reimer & Ben Orenstein\",\n",[200,28290,28291],{"class":202,"line":4508},[200,28292,28293],{},"          \"image\": \"\u002Fart\u002Fart-of-product.jpg\",\n",[200,28295,28296],{"class":202,"line":5469},[200,28297,28298],{},"          \"favorited\": false,\n",[200,28300,28301],{"class":202,"line":5474},[200,28302,28303],{},"          \"created_at\": \"2025-02-15T18:01:36.000000Z\",\n",[200,28305,28306],{"class":202,"line":13237},[200,28307,28308],{},"          \"updated_at\": \"2025-02-19T20:49:13.000000Z\"\n",[200,28310,28311],{"class":202,"line":13263},[200,28312,13469],{},[200,28314,28315],{"class":202,"line":13279},[200,28316,28317],{},"      ]\n",[200,28319,28320],{"class":202,"line":13323},[200,28321,4977],{},[200,28323,28324],{"class":202,"line":13329},[200,28325,4505],{},[200,28327,28328],{"class":202,"line":13334},[200,28329,1129],{},[35,28331,28332,28333,1655,28335,28337,28338,215],{},"Pendant ce temps, le serveur traite ce type de requête en interprétant les en-têtes ",[156,28334,28139],{},[156,28336,28142],{},". Dans son fournisseur de services, Fusion ",[39,28339,28342],{"href":28340,"rel":28341},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L62-L65",[77],"enregistre des macros de requête pour une identification et un traitement faciles des types de requête",[35,28344,28345,28346,28349,28350,28352],{},"À l'intérieur du corps de la requête, ",[156,28347,28348],{},"args"," détient les arguments de la méthode (podcast), tandis que ",[156,28351,26875],{}," comprend l'état actuel de l'application.",[35,28354,28355],{},"En réponse, le serveur envoie la suivante :",[192,28357,28361],{"className":28358,"code":28359,"language":28360,"meta":13,"style":13},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","true\n","json",[156,28362,28363],{"__ignoreMap":13},[200,28364,28365],{"class":202,"line":203},[200,28366,28359],{"class":214},[35,28368,28369,28370,28372],{},"Ce qui est la réponse de la méthode ",[156,28371,24484],{},". Le client met ensuite à jour l'état en conséquence.",[35,28374,28375,28376,28378,28379,28381,28382,15917],{},"Fusion offre une méthode ",[156,28377,24477],{}," favorisant la synchronisation des états entre le frontend et le backend. Par exemple, un champ de recherche pourrait filtrer des éléments basés sur des calculs du backend, réalisé via ",[156,28380,24477],{},". Essentiellement, sync imite une action catégorisée par le gestionnaire ",[156,28383,27645],{},[51,28385,28387],{"id":28386},"comment-fusion-utilise-t-il-inertia","Comment Fusion utilise-t-il Inertia ?",[35,28389,28390,28391,28395,28396,28401],{},"En vérité, Fusion simplifie ingénieusement et s'appuie sur l'intégration transparente d'Inertia pour traiter efficacement les SFC de Vue sans réinventer son framework frontend. La méthode ",[39,28392,28394],{"href":26756,"rel":28393},[77],"Inertia::render"," illustre le rendu basé sur Inertia tandis que ",[39,28397,28400],{"href":28398,"rel":28399},"https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fapps\u002Fvue\u002Fresources\u002Fjs\u002Fapp.js#L11",[77],"createInertiaApp"," établit l'environnement de l'application Vue de manière efficace, constituant l'ampleur totale de l'empreinte d'Inertia dans le projet Fusion.",[35,28403,28404],{},"Construire sur Inertia se révèle judicieux, permettant à Aaron de se concentrer sur l'innovation autour de l'objectif de Fusion sans être distrait par les complexités d'intégration de Vue au sein de Laravel. L'utilité d'Inertia annule toute redondance. De plus, transcender le parcours intermédiaire fait évoluer Fusion pour englober d'autres frameworks comme React de manière fluide.",[51,28406,28408],{"id":28407},"dernières-pensées","Dernières pensées",[35,28410,28411],{},"Fusion a un potentiel, notamment pour les développeurs en transition de Next ou Nuxt vers une capacité full-stack, accueillant avec enthousiasme Laravel avec Inertia. En préservant des concepts chers aux développeurs de ces frameworks, tels que le routage basé sur des fichiers et la communication pilotée par le RPC, tout en les enrichissant avec la puissance de Laravel, Fusion devient un choix convaincant. Vous devriez absolument l'essayer !",[35,28413,28414,28415,28418],{},"Au sein de la feuille de route de Fusion, Aaron envisage la compatibilité avec React, réalisable en s'appuyant sur les épaules d'Inertia. Toutefois, je suis impatient de voir la perspective d'Aaron sur la gestion de l'exécution du code serveur dans JSX, peut-être à l'aide d'une directive ",[156,28416,28417],{},"'use php'"," ? 🤔",[35,28420,28421],{},"Ce projet illustre efficacement la force de la relation profonde entre Vite et Laravel. Accéder sans effort à l'utilité complète de Vite demeure une capacité révolutionnaire. Cela réaffirme également la capacité modulaire du SFC dans Vue, Fusion le confirme assurément. Il n'y a pratiquement aucune limite à ce que vous pouvez réaliser en utilisant Vite et des composants SFC de Vue !",[35,28423,28424],{},"Enfin, la communication de la base de données SQLite inter-processus de Fusion renforce un modèle élégant et intelligent !",[35,28426,28427,28428,215],{},"L'article a été passionnant à rédiger alors que j'explorais la base de code de Fusion. J'espère qu'il a révélé de nouvelles perspectives pour vous, et si vous êtes enclin à explorer les subtilités d'une autre bibliothèque, n'hésitez pas à commenter ou à utiliser la page ",[39,28429,25718],{"href":25717},[35,28431,28432],{},"Mais j'ai un supplément pour vous ! 👇",[51,28434,28436],{"id":28435},"une-dernière-chose","Une dernière chose",[35,28438,28439,28440,28443,28444,28447],{},"Tout en rédigeant cet article, les exemples de code PHP en bloc fréquents manquaient de surlignement syntaxique approprié. J'ai réfléchi et décidé de m'attaquer à cette tâche. Puisque ",[6862,28441,28442],{},"Shiki"," est utilisé pour la mise en surbrillance des codes sur ce site et qu'il utilise ",[6862,28445,28446],{},"TextMate grammars",", je sais que je pourrais ajouter le support pour le bloc PHP dans VSCode.",[35,28449,28450],{},"Mais comment ?",[35,28452,28453],{},"En fouillant, j'ai réalisé que l'intégration de langages dans des contextes à fichier unique n'est pas nouvelle. Les SFC de Vue sont notables, tout comme Markdown, Angular, et même HTML impliquant différents langages. Par la suite, je me suis concentré sur la grammaire TextMate des SFC de Vue.",[938,28455,28456],{},[35,28457,28458,28460],{},[200,28459,944],{},"\nUne grammaire TextMate définit les mises en surbrillance de syntaxe du langage de codage via des modèles et définit des règles. Fonctionnant derrière VSCode et Shiki, c'est le moteur du surligneur de syntaxe.",[35,28462,28463,28464,28469,28470,28475],{},"Examiner le ",[39,28465,28468],{"href":28466,"rel":28467},"https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002F",[77],"dépôt textmate-grammars-themes de Shikijs"," qui s'occupe de chaque langue et thème soutenu par Shiki a mis au jour la ",[39,28471,28474],{"href":28472,"rel":28473},"https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Ftm-grammars\u002Fgrammars\u002Fvue.json",[77],"grammaire Vue",". Bien que long, des concepts éclairants émergent lors de la lecture, tels que les intégrations HTML, CSS, et JavaScript au sein des blocs respectifs:",[192,28477,28479],{"className":28358,"code":28478,"language":28360,"meta":13,"style":13},"{\n  \"begin\": \"(script)\\\\b\",\n  \"beginCaptures\": {\n    \"1\": {\n      \"name\": \"entity.name.tag.$1.html.vue\"\n    }\n  },\n  \"end\": \"(\u003C\u002F)(\\\\1)\\\\s*(?=>)\",\n  \"endCaptures\": {\n    \"1\": {\n      \"name\": \"punctuation.definition.tag.begin.html.vue\"\n    },\n    \"2\": {\n      \"name\": \"entity.name.tag.$2.html.vue\"\n    }\n  },\n  \"patterns\": [\n    {\n      \"include\": \"#tag-stuff\"\n    },\n    {\n      \"begin\": \"(?\u003C=>)\",\n      \"end\": \"(?=\u003C\\\\\u002Fscript\\\\b)\",\n      \"name\": \"source.js\",\n      \"patterns\": [\n        {\n          \"include\": \"source.js\"\n        }\n      ]\n    }\n  ]\n}\n",[156,28480,28481,28485,28512,28525,28538,28556,28560,28564,28594,28607,28619,28636,28640,28652,28669,28673,28677,28690,28694,28712,28716,28720,28739,28768,28787,28799,28803,28820,28824,28828,28832,28836],{"__ignoreMap":13},[200,28482,28483],{"class":202,"line":203},[200,28484,230],{"class":214},[200,28486,28487,28490,28493,28495,28497,28500,28503,28506,28508,28510],{"class":202,"line":14},[200,28488,28489],{"class":214},"  \"",[200,28491,28492],{"class":379},"begin",[200,28494,610],{"class":214},[200,28496,239],{"class":214},[200,28498,28499],{"class":214}," \"",[200,28501,28502],{"class":245},"(script)",[200,28504,28505],{"class":210},"\\\\",[200,28507,3899],{"class":245},[200,28509,610],{"class":214},[200,28511,252],{"class":214},[200,28513,28514,28516,28519,28521,28523],{"class":202,"line":255},[200,28515,28489],{"class":214},[200,28517,28518],{"class":379},"beginCaptures",[200,28520,610],{"class":214},[200,28522,239],{"class":214},[200,28524,280],{"class":214},[200,28526,28527,28530,28532,28534,28536],{"class":202,"line":272},[200,28528,28529],{"class":214},"    \"",[200,28531,3203],{"class":1364},[200,28533,610],{"class":214},[200,28535,239],{"class":214},[200,28537,280],{"class":214},[200,28539,28540,28543,28545,28547,28549,28551,28554],{"class":202,"line":283},[200,28541,28542],{"class":214},"      \"",[200,28544,20112],{"class":2060},[200,28546,610],{"class":214},[200,28548,239],{"class":214},[200,28550,28499],{"class":214},[200,28552,28553],{"class":245},"entity.name.tag.$1.html.vue",[200,28555,616],{"class":214},[200,28557,28558],{"class":202,"line":300},[200,28559,4977],{"class":214},[200,28561,28562],{"class":202,"line":310},[200,28563,368],{"class":214},[200,28565,28566,28568,28571,28573,28575,28577,28580,28582,28585,28587,28590,28592],{"class":202,"line":336},[200,28567,28489],{"class":214},[200,28569,28570],{"class":379},"end",[200,28572,610],{"class":214},[200,28574,239],{"class":214},[200,28576,28499],{"class":214},[200,28578,28579],{"class":245},"(\u003C\u002F)(",[200,28581,28505],{"class":210},[200,28583,28584],{"class":245},"1)",[200,28586,28505],{"class":210},[200,28588,28589],{"class":245},"s*(?=>)",[200,28591,610],{"class":214},[200,28593,252],{"class":214},[200,28595,28596,28598,28601,28603,28605],{"class":202,"line":342},[200,28597,28489],{"class":214},[200,28599,28600],{"class":379},"endCaptures",[200,28602,610],{"class":214},[200,28604,239],{"class":214},[200,28606,280],{"class":214},[200,28608,28609,28611,28613,28615,28617],{"class":202,"line":365},[200,28610,28529],{"class":214},[200,28612,3203],{"class":1364},[200,28614,610],{"class":214},[200,28616,239],{"class":214},[200,28618,280],{"class":214},[200,28620,28621,28623,28625,28627,28629,28631,28634],{"class":202,"line":371},[200,28622,28542],{"class":214},[200,28624,20112],{"class":2060},[200,28626,610],{"class":214},[200,28628,239],{"class":214},[200,28630,28499],{"class":214},[200,28632,28633],{"class":245},"punctuation.definition.tag.begin.html.vue",[200,28635,616],{"class":214},[200,28637,28638],{"class":202,"line":398},[200,28639,339],{"class":214},[200,28641,28642,28644,28646,28648,28650],{"class":202,"line":405},[200,28643,28529],{"class":214},[200,28645,4797],{"class":1364},[200,28647,610],{"class":214},[200,28649,239],{"class":214},[200,28651,280],{"class":214},[200,28653,28654,28656,28658,28660,28662,28664,28667],{"class":202,"line":410},[200,28655,28542],{"class":214},[200,28657,20112],{"class":2060},[200,28659,610],{"class":214},[200,28661,239],{"class":214},[200,28663,28499],{"class":214},[200,28665,28666],{"class":245},"entity.name.tag.$2.html.vue",[200,28668,616],{"class":214},[200,28670,28671],{"class":202,"line":860},[200,28672,4977],{"class":214},[200,28674,28675],{"class":202,"line":889},[200,28676,368],{"class":214},[200,28678,28679,28681,28684,28686,28688],{"class":202,"line":899},[200,28680,28489],{"class":214},[200,28682,28683],{"class":379},"patterns",[200,28685,610],{"class":214},[200,28687,239],{"class":214},[200,28689,1410],{"class":214},[200,28691,28692],{"class":202,"line":904},[200,28693,19661],{"class":214},[200,28695,28696,28698,28701,28703,28705,28707,28710],{"class":202,"line":930},[200,28697,28542],{"class":214},[200,28699,28700],{"class":1364},"include",[200,28702,610],{"class":214},[200,28704,239],{"class":214},[200,28706,28499],{"class":214},[200,28708,28709],{"class":245},"#tag-stuff",[200,28711,616],{"class":214},[200,28713,28714],{"class":202,"line":3655},[200,28715,339],{"class":214},[200,28717,28718],{"class":202,"line":3660},[200,28719,19661],{"class":214},[200,28721,28722,28724,28726,28728,28730,28732,28735,28737],{"class":202,"line":4035},[200,28723,28542],{"class":214},[200,28725,28492],{"class":1364},[200,28727,610],{"class":214},[200,28729,239],{"class":214},[200,28731,28499],{"class":214},[200,28733,28734],{"class":245},"(?\u003C=>)",[200,28736,610],{"class":214},[200,28738,252],{"class":214},[200,28740,28741,28743,28745,28747,28749,28751,28754,28756,28759,28761,28764,28766],{"class":202,"line":4490},[200,28742,28542],{"class":214},[200,28744,28570],{"class":1364},[200,28746,610],{"class":214},[200,28748,239],{"class":214},[200,28750,28499],{"class":214},[200,28752,28753],{"class":245},"(?=\u003C",[200,28755,28505],{"class":210},[200,28757,28758],{"class":245},"\u002Fscript",[200,28760,28505],{"class":210},[200,28762,28763],{"class":245},"b)",[200,28765,610],{"class":214},[200,28767,252],{"class":214},[200,28769,28770,28772,28774,28776,28778,28780,28783,28785],{"class":202,"line":4496},[200,28771,28542],{"class":214},[200,28773,20112],{"class":1364},[200,28775,610],{"class":214},[200,28777,239],{"class":214},[200,28779,28499],{"class":214},[200,28781,28782],{"class":245},"source.js",[200,28784,610],{"class":214},[200,28786,252],{"class":214},[200,28788,28789,28791,28793,28795,28797],{"class":202,"line":4502},[200,28790,28542],{"class":214},[200,28792,28683],{"class":1364},[200,28794,610],{"class":214},[200,28796,239],{"class":214},[200,28798,1410],{"class":214},[200,28800,28801],{"class":202,"line":4508},[200,28802,4924],{"class":214},[200,28804,28805,28808,28810,28812,28814,28816,28818],{"class":202,"line":5469},[200,28806,28807],{"class":214},"          \"",[200,28809,28700],{"class":2060},[200,28811,610],{"class":214},[200,28813,239],{"class":214},[200,28815,28499],{"class":214},[200,28817,28782],{"class":245},[200,28819,616],{"class":214},[200,28821,28822],{"class":202,"line":5474},[200,28823,13469],{"class":214},[200,28825,28826],{"class":202,"line":13237},[200,28827,28317],{"class":214},[200,28829,28830],{"class":202,"line":13263},[200,28831,4977],{"class":214},[200,28833,28834],{"class":202,"line":13279},[200,28835,13683],{"class":214},[200,28837,28838],{"class":202,"line":13323},[200,28839,1129],{"class":214},[35,28841,28842,28843,28846,28847,28849],{},"Cela gère la mise en surbrillance de JavaScript au sein des blocs ",[156,28844,28845],{},"\u003Cscript>"," en utilisant l'inclusion de la grammaire JavaScript à travers ",[156,28848,28700],{},". Le même modèle s'applique probablement à d'autres langages, y compris PHP.",[35,28851,28852,28853,28856],{},"C'est exactement ce que je veux atteindre, mais avec ",[156,28854,28855],{},"\u003Cphp>"," et la grammaire PHP. Bien sûr, il existe déjà une grammaire PHP, donc je dois juste l'inclure de la même manière. J'essaie, et ça fonctionne. Youpi ! 🥳",[35,28858,28859,28860,28865],{},"Mais attendez, je ne peux pas soumettre un PR à ",[39,28861,28864],{"href":28862,"rel":28863},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools",[77],"Vue Language Tools"," (Shiki récupère la grammaire de ce dépôt) pour ajouter le support de la grammaire PHP. Cela n'a aucun sens, et il n'y a aucune raison de leur part de l'accepter. Donc, je dois trouver une autre solution.",[35,28867,28868,28869,28874],{},"Après avoir continué ma recherche, j'ai trouvé la grammaire ",[39,28870,28873],{"href":28871,"rel":28872},"https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002Fblob\u002Fmain\u002Fpackages\u002Ftm-grammars\u002Fgrammars\u002Fangular-inline-style.json",[77],"angular-inline-style",". Celle-ci est utilisée pour mettre en évidence les styles (CSS, SCSS) à l'intérieur d'un composant Angular (TypeScript). Pour ce faire, la grammaire utilise le mot clé injectTo, qui agit comme une extension d'une grammaire existante, dans ce cas, source.ts.ng. C'est parfait ! Que se passerait-il si je créais une nouvelle grammaire qui étend la grammaire Vue en injectant la grammaire PHP ? Rétrospectivement, cela semblait plus facile que cela ne l'a été en réalité.",[35,28876,28877,28878,215],{},"Après quelques heures d'essais et d'erreurs, j'ai finalement réussi à créer une grammaire qui fonctionne dans Shiki. L'un des éléments les plus compliqués a été de comprendre ce qui n'allait pas lorsque les choses ne fonctionnaient pas. Avec ce genre de configuration, il n'y a pas de messages d'erreur, pas d'avertissements, rien. Vous devez juste deviner ce qui ne va pas. Vous pouvez trouver la solution fonctionnelle dans le code source de ",[39,28879,28882],{"href":28880,"rel":28881},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fcode.soubiran.dev",[77],"mon générateur de code en image",[35,28884,28885,28886,28891,28892,215],{},"Enfin, j'ai créé une extension VSCode que tout le monde peut utiliser pour ajouter le support des blocs PHP dans les SFC de Vue. Vous pouvez la trouver sur le ",[39,28887,28890],{"href":28888,"rel":28889},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ffusion-vscode",[77],"marketplace"," avec la grammaire fonctionnelle : ",[39,28893,28896],{"href":28894,"rel":28895},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ffusion-vscode\u002Fblob\u002Fmain\u002Fsyntaxes\u002Fvue.php.tmLanguage.json",[77],"source.vue.php",[35,28898,28899],{},"Si Fusion devient populaire, je pourrais éventuellement soumettre un PR pour ajouter le language à Shiki, rendant plus facile à tous la mise en surbrillance des blocs PHP.",[35,28901,28902],{},"Bon code ! 👨‍💻",[1170,28904,28905],{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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}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 .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 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 .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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":13,"searchDepth":14,"depth":14,"links":28907},[28908,28909,28910,28917,28918,28919],{"id":24432,"depth":14,"text":24433},{"id":24738,"depth":14,"text":24724},{"id":25722,"depth":14,"text":25723,"children":28911},[28912,28913,28915,28916],{"id":25743,"depth":255,"text":25744},{"id":25774,"depth":255,"text":28914},"La commande fusion:conform",{"id":26315,"depth":255,"text":26316},{"id":26594,"depth":255,"text":26595},{"id":28386,"depth":14,"text":28387},{"id":28407,"depth":14,"text":28408},{"id":28435,"depth":14,"text":28436},"2025-02-18","Relier le frontend et le backend est un défi que tente de résoudre Fusion. En chemin, il apporte beaucoup de choses à apprendre.",{},"\u002Ffr\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components","---\ncontentId: c3e7499a-3b98-4008-b25e-756b529d8dcc\ntitle: Fusion ou l'art d'intégrer PHP dans des composants Vue\ndescription: Relier le frontend et le backend est un défi que tente de résoudre Fusion. En chemin, il apporte beaucoup de choses à apprendre.\ndate: 2025-02-18\n---\n\nCet article est vraiment **spécial**.\n\nIl y a deux semaines, les 3 et 4 février 2025, [Laracon EU](https:\u002F\u002Flaracon.eu\u002F) a eu lieu à Amsterdam. Bien que je n'ai pas pu y assister en personne, j'ai suivi l'événement entier en direct. Les conférences offrent toujours une occasion fantastique d'acquérir de nouvelles connaissances et de s'immerger davantage dans le merveilleux **écosystème Laravel**. _J'espère assister à Laracon un jour._\n\nUne des présentations qui a retenu mon attention était [\"Introducing \\[REDACTED\\]: A New Paradigm for Laravel + Javascript\"](https:\u002F\u002Fyoutu.be\u002FlXjYStxGFIQ?si=ntTW6zP-tAzV0OZO) par [Aaron Francis](https:\u002F\u002Fx.com\u002Faarondfrancis). Cela m'a intrigué puisqu'il existe déjà **Inertia** et **Livewire**, qui sont d'excellents outils pour créer des applications modernes avec **Laravel**. Quelles nouvelles avancées pouvaient-elles alors être proposées ?\n\n![Miniature de la vidéo \"Introducing \\[REDACTED\\]: a new paradigm for Laravel + Javascript\" par Aaron Francis](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fthumbnail.jpg)\n\nAaron a présenté [**Fusion**](https:\u002F\u002Fgithub.com\u002Ffusion-php), décrit comme une approche novatrice pour intégrer votre frontend moderne avec votre backend Laravel. Le concept permet d'écrire du code PHP directement dans un fichier frontend, et à travers un certain traitement, le code PHP est exécuté côté serveur, avec les résultats envoyés au frontend. Cela rappelle **Inertia**, mais poussé beaucoup plus loin.\n\n> [!NOTE]\n> Veuillez visionner la vidéo. C'est fascinant, facile à suivre et cela aidera considérablement à comprendre le reste de cet article.\n\nLors d'[une interview sur le podcast Navbar](https:\u002F\u002Fyoutu.be\u002FvqWlJCtoD-k?si=Xnd4MOa5-dcBvPhi), Aaron a articulé l'objectif de Fusion : combler le fossé entre le frontend et le backend, simplifiant le développement et transformant Laravel avec Inertia pour ressembler à des méta-frameworks actuels comme **Next** ou **Nuxt**. Avec son partenaire commercial [Steve](https:\u002F\u002Fx.com\u002Fsteve_tenuto), Aaron a découvert que les conventions Laravel, comme les routes dans `web.php`, n'étaient pas intuitives pour les développeurs JavaScript qui préfèrent le routage basé sur des fichiers. L'objectif d'Aaron est de rendre Laravel plus accessible aux développeurs JavaScript.\n\nAvec Fusion, un nouveau bloc est introduit dans le SFC de Vue pour écrire du code PHP, en étant une partie intégrante de l'application Laravel.\n\n```vue\n\u003Cphp>\n  \u002F\u002F Définir une prop en PHP\n  $name = prop(Auth::user()->name);\n\u003C\u002Fphp>\n\n\u003Ctemplate>\n  \u003C!-- Utilisez-le dans Vue ! -->\n  Bonjour {{ name }} !\n\u003C\u002Ftemplate>\n```\n\n---\n\nCet article n'a pas pour but de déterminer si Fusion devrait être utilisé en production ou si l'intégration de PHP dans un SFC de Vue est conseillée. Ce débat est bien couvert, notamment sur X ou Reddit.\n\nAu lieu de cela, je compte explorer le fonctionnement de Fusion pour deux raisons principales :\n\n1. Il y a beaucoup à apprendre en examinant le code. Très honnêtement, je ne lis pas beaucoup de livres, mais je parcoure le code des projets, volant des astuces et des manières de faire négligés ailleurs.\n2. Fusion incarne un sommet technique de ce qui est réalisable avec **Laravel**, **Vue**, et **Vite**. Mon désir de comprendre son fonctionnement m'a conduit à rédiger cet article. En ce moment, j'étudie également la bibliothèque [Alien Signals](https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals) de [Johnson Chu](https:\u002F\u002Fgithub.com\u002Fjohnsoncodehk) de la même manière. Comprendre quelque chose en profondeur implique souvent de l'expliquer aux autres, d'où cet article.\n\n> [!NOTE]\n> L'intégration de Vite avec Laravel est exceptionnelle, offrant de nombreux avantages aux développeurs Laravel. Si vous n'avez pas encore exploré **Vite**, vous devriez absolument le faire.\n\nMaintenant, plongeons dans le code.\n\n> [!NOTE]\n> Lire la base de code est fascinant ; cela ressemble à lire du PHP écrit en JavaScript.\n\n## Le code de Fusion\n\nUne semaine après sa présentation à [Laracon EU](https:\u002F\u002Flaracon.eu\u002F), Aaron a rendu la base de code de Fusion publique. Curieux de ses mécanismes, j'ai supposé qu'un plugin Vite pourrait extraire le PHP d'un SFC de Vue, de manière similaire à la façon dont le [plugin Vue de Vite](https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue) gère les balises script, template et style.\n\nLe support pour HTML, CSS et JavaScript est simple pour Vite puisqu'ils sont gérés nativement par Vite. Cependant, le PHP pose un défi car il n'est pas pris en charge.\n\nPour mieux comprendre l'organisation de l'article, posons-nous quelques questions. D'abord : comment Fusion gère-t-il le code PHP ?\n\n\u003Cfigure>\n  \u003Cvideo autoplay loop muted playsinline>\n    \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fhype-video.mp4\" type=\"video\u002Fmp4\">\n  \u003C\u002Fvideo>\n\n  \u003Cfigcaption>\n    Vidéo Hype de Fusion par \u003Ca href=\"https:\u002F\u002Fx.com\u002Faarondfrancis\" target=\"_blank\">Aaron Francis\u003C\u002Fa> et \u003Ca href=\"https:\u002F\u002Fx.com\u002Fsteve_tenuto\" target=\"_blank\">Steve Tenuto\u003C\u002Fa>, \u003Ca href=\"https:\u002F\u002Fx.com\u002Faarondfrancis\u002Fstatus\u002F1886768725509976146\" target=\"_blank\">disponible sur X\u003C\u002Fa>.\n  \u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\nEn regardant la vidéo hype, il est évident qu'il y a plus que simplement extraire le code PHP et l'exécuter. Fusion passe les résultats du code PHP en tant que props au composant Vue et prend en charge le RPC sur le backend Laravel depuis le client. De plus, le mot-clé `sync` permet la synchronisation entre les états frontend et backend.\n\nPar exemple, une fonction appelée `favorite` devient facilement utilisable dans le frontend :\n\n```vue\n\u003Cphp>\nuse function \\Fusion\\{prop, expose};\nuse \\App\\Models\\Podcast;\n\n$podcasts = prop(fn () => Podcast::all())->readonly();\n\nexpose(favorite: fn (Podcast $podcast) => response()->json($podcast->toggleFavorite()));\n\u003C\u002Fphp>\n\n\u003Cscript lang=\"ts\" setup>\nimport Podcast from '@Components\u002FPodcast.vue'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CPodcast\n    v-for=\"podcast in podcasts\"\n    :key=\"podcast.id\"\n    v-bind=\"podcast\"\n    @favorite=\"favorite({ podcast }).then(fav => podcast.favorited = fav)\"\n  \u002F>\n\u003C\u002Ftemplate>\n```\n\nUne autre question se pose : que fait Fusion en coulisses pour permettre cette fonctionnalité ?\n\n\u003C!-- Lors de sa présentation, Aaron a mentionné que Fusion supporte le Hot Module Reloading (HMR). C'est un gros avantage, et je prends ces travaux à cœur : \"si l'expérience n'est pas agréable, personne ne l'utilisera\". Je suis totalement d'accord avec cela. Mais, la troisième question : comment le HMR fonctionne-t-il dans Fusion ? -->\n\nEnfin, il est dit que Fusion repousse Inertia au-delà de ses limites actuelles. Donc, une autre question : comment Fusion exploite-t-il Inertia ?\n\nRépondons à ces questions une par une.\n\n1. Comment Fusion gère-t-il le code PHP ?\n2. Quels mécanismes Fusion emploie-t-il en coulisses pour permettre le RPC ?\n\u003C!-- 3. Comment le HMR fonctionne-t-il dans Fusion ? -->\n4. De quelle manière Fusion utilise-t-il Inertia ?\n\nPrêts pour une exploration ? Commençons.\n\n## Comment Fusion gère-t-il le code PHP ?\n\nTout commence avec [Vite](https:\u002F\u002Fvite.dev\u002F). _Comme d'habitude._\n\nPour ceux qui ne le connaissent pas, **Vite** est un outil de build frontend remarquablement rapide qui alimente la prochaine génération d'applications web. Il a introduit une nouvelle philosophie connue sous le nom de philosophie à la demande. Essentiellement, Vite agit comme un proxy entre le navigateur, qui demande des fichiers, et le système de fichiers qui les contient. Lorsque le navigateur demande un fichier, Vite intercepte la requête, récupère le fichier et le renvoie. Cependant, agir comme un proxy permet à Vite des capacités supplémentaires, telles que la transformation de fichiers, l'injection de code, la récupération de fichiers distants et la génération de fichiers virtuels (fichiers non présents sur le système de fichiers). C'est semblable à un pipeline. Sans aucun doute, l'avènement de Vite a été transformateur pour l'écosystème frontend. En ajoutant des plugins à son pipeline, il peut être adapté à n'importe quel besoin.\n\nAvec cela en tête, nous pouvons comprendre comment un fichier SFC subit un traitement par Vite.\n\n```vue\n\u003Cscript setup>\n\u002F\u002F Le code JavaScript ira ici\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv \u002F>\n  \u003C!-- Votre code de modèle ira ici -->\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n  \u002F* Votre code CSS ira ici *\u002F\n\u003C\u002Fstyle>\n```\n\nLorsqu'une requête de fichier `.vue` atteint le navigateur, un plugin Vite nommé [vite-plugin-vue](https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue) transforme le fichier en traitant le script et le modèle pour en faire du code JavaScript lisible. Le CSS est extrait et envoyé dans le pipeline CSS avant d'être dispatché au navigateur. L'aspect intéressant est la possibilité de créer un plugin capable d'examiner le pipeline de transformation. Mieux encore, il n'est pas nécessaire de le construire de toutes pièces ; c'est exactement ce que fait le plugin [vite-plugin-inspect](https:\u002F\u002Fgithub.com\u002Fantfu-collective\u002Fvite-plugin-inspect).\n\nPar exemple, la transformation d'un SFC de Vue ressemble à ceci :\n\n![Transformation d'un SFC de Vue vue à l'aide du plugin vite-plugin-inspect](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvue-sfc-transformation.png)\n\nD'accord, merci pour l'explication, mais que se passe-t-il dans Fusion ? Exactement la même chose mais pour le bloc PHP. Lorsqu'une requête contacte le serveur Vite en utilisant un fichier `.vue`, un plugin nommé [`fusion-vue`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L38-L40) extrait le bloc PHP. Ce processus commence dans la fonction [`transform`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L112) ci-dessous :\n\n```ts\nfunction transform(code, filename) {\n  if (!filename.endsWith('.vue')) {\n    return code\n  }\n\n  return new Transformer({\n    config: fusionConfig,\n    code,\n    filename,\n    isProd\n  }).transform()\n}\n```\n\nLa fonction `transform` forme le cœur du pipeline de Vite. Elle transforme les fichiers avant qu'ils n'atteignent le navigateur. Dans le cas de Fusion, elle passe le fichier (l'intégralité du contenu) à une classe nommée `Transformer`, qui gère la transformation du fichier. Vous pourriez remarquer la condition `filename.endsWith('.vue')`, signifiant que la transformation ne s'applique qu'aux fichiers `.vue`. Cette approche est un modèle courant parmi les plugins Vite.\n\nLa classe Transformer est responsable de :\n\n1. [Extraire le bloc PHP](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L16-L19) du SFC de Vue. Il utilise ici un parseur de [@vue\u002Fcompiler-sfc](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Ftree\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fcompiler-sfc) pour accomplir cette tâche. Notamment, le plugin `vite-plugin-vue` utilise ce même parseur, illustrant qu'ajouter un bloc personnalisé à un SFC de Vue est simple. J'ai déjà observé cette pratique ; le projet [Vue I18n](https:\u002F\u002Fvue-i18n.intlify.dev\u002Fguide\u002Fintroduction.html) supporte [un bloc personnalisé `i18n`](https:\u002F\u002Fvue-i18n.intlify.dev\u002Fguide\u002Fadvanced\u002Fsfc.html) spécifiant des traductions au sein d'un SFC de Vue.\n\n```js\nimport { parse } from '@vue\u002Fcompiler-sfc'\n\nconst descriptor = parse(code, { filename }).descriptor\nconst php = descriptor.customBlocks.find(block => block.type === 'php')\n```\n\n![Le SFC de Vue transformé dans le pipeline Vite](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvue-sfc-with-php-transformation.png)\n\n2. [Supprimer le bloc PHP](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L88-L91) du SFC de Vue.\n3. [Déterminer si le code PHP mérite d'être persisté](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L96). Un autre aspect critique que nous examinerons dans [la section suivante](#que-fait-fusion-en-coulisses-pour-rendre-cela-possible).\n4. [Valider et sauvegarder le code PHP](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L107). Cette vérification se fait via un processus externe exécutant une commande Laravel.\n\n```ts\nawait runPhp(['fusion:conform', this.relativeFilename])\n```\n\nTout code PHP comportant des erreurs déclenche un message d'erreur [interprété par Vite](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L124), si précisément qu'il s'affiche dans le navigateur pour indiquer une erreur dans le bloc de code PHP.\n\n![L'overlay d'erreur Vite montrant l'erreur dans le code PHP](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Fvite-error-overlay.png)\n\nSi le fonctionnement se déroule sans accroc, le [code PHP, stocké en tant que fichier extrait](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L68), est sauvegardé dans le dossier `storage`. Cet aspect est à notable, car nous allons l'explorer davantage dans [la section suivante](#comment-fusion-gère-le-code-php).\n\n5. Création d'un fichier JavaScript [basé sur le code PHP](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L112) qui fait le pont [entre les besoins du frontend](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FShim.php#L78-L113) et le code du bloc PHP. Cette étape intéressante nécessite une exploration plus approfondie.\n\nEncore une fois, une commande Laravel est invoquée durant cette étape :\n\n```ts\nawait runPhp(['fusion:shim', this.relativeFilename])\n```\n\n6. Enfin, elle renvoie le code transformé à Vite, permettant au SFC de poursuivre son chemin.\n\nVoici une illustration de la transformation du SFC de Vue :\n\n![Schéma montrant toutes les étapes que nous avons vues jusqu'à présent](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Ffusion-schema-vite-transform.png)\n\nDans Fusion, les fichiers SFC de Vue fonctionnent comme un routeur basé sur des fichiers à l'instar du mécanisme de Nuxt. La première requête atteint généralement le serveur PHP, qui répond avec le code initial côté client, lequel vise ensuite le serveur Vite pour des requêtes de ressources supplémentaires. Cependant, si une route SFC de Vue n'a pas été visitée, la fonctionnalité PHP correspondante ne sera pas prête à la gérer, résultant en une erreur 404 du serveur Laravel. En conséquence, le serveur Vite reste intouché, entravant le processus de transformation. Cela pose un obstacle, un énorme.\n\nPour comprendre pleinement comment ce problème est abordé, nous devons examiner tous les aspects du plugin Vite.\n\nDans la configuration Vite, nous trouvons deux caractéristiques notables :\n\n1. La [configuration Vite](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L42-L65) où Vite reconnaît l'emplacement pour trouver le fichier JavaScript généré (le shim), qui reçoit [un alias](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L61) attribué dans le résolveur Vite. Cet alias obscurcit l'emplacement du fichier, encourageant les meilleures pratiques comme éviter les chemins codés en dur et rendant l'emplacement du code agnostique aux changements de chemin. Cette configuration liée à Vite provient d'un fichier config PHP pour éliminer les configurations redondantes, donc le [plugin Vite Fusion engendre un processus](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L45) exécutant une commande Laravel pour résoudre les nécessités de configuration.\n2. Un observateur [ajouté pour surveiller les changements dans l'ensemble du projet](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L79) utilisant l'observateur Vite, une [instance Chokidar](https:\u002F\u002Fvite.dev\u002Fguide\u002Fapi-javascript.html#vitedevserver:~:text=\u002F**%0A%20%20%20*%20Chokidar%20watcher%20instance.%20If%20%60config.server.watch%60%20is%20set%20to%20%60null%60%2C%0A%20%20%20*%20it%20will%20not%20watch%20any%20files%20and%20calling%20%60add%60%20or%20%60unwatch%60%20will%20have%20no%20effect.%0A%20%20%20*%20https%3A\u002F\u002Fgithub.com\u002Fpaulmillr\u002Fchokidar\u002Ftree\u002F3.6.0%23api%0A%20%20%20*\u002F%0A%20%20watcher%3A%20FSWatcher).\n\n```ts\nfunction configureServer(server) {\n  server.watcher.on('all', (event, filename) => {\n    \u002F\u002F Faites ce que vous voulez\n  })\n}\n```\n\nCet observateur prend en charge deux fonctions :\n\n1. [Nettoyer](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L88) la base de données et le dossier de stockage lors de la suppression de fichiers ou de dossiers. Le dossier de stockage contient les fichiers SFC de Vue transformés, servant de routeur basé sur des fichiers. Sans suppression, l'utilisateur peut toujours accéder aux fichiers supprimés.\n2. [Déclencher la transformation de fichiers](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Fvite.js#L97-L99). Cela fonctionne de manière identique à la fonction `transform` rencontrée précédemment, nécessitant un chargement manuel de fichiers. Seuls les fichiers SFC de Vue subissent une transformation.\n\n```ts\nfunction configureServer(server) {\n  server.watcher.on('all', async (event, filename) => {\n    if (filename.endsWith('.vue')) {\n      const fileContent = fs.readFileSync(filename, 'utf-8')\n\n      await new Transformer({\n        config: fusionConfig,\n        code: fileContent,\n        filename,\n        isProd\n      }).transform()\n    }\n  })\n}\n```\n\nMais pourquoi transformer des fichiers lors de leur création ou modification ? Cette approche semble contredire la philosophie à la demande et la fonction `transform` définie précédemment. Pourtant, une justification existe.\n\nConsiderons qu'une erreur se produise dans le bloc PHP, comme un oubli dans l'importation d'une fonction ou d'une classe, ou en tentant de visiter une route pour la première fois. Lorsque la page est rechargée, le serveur PHP renvoie un message d'erreur, comme Laravel le fait par défaut. Cependant, corriger le bloc PHP du SFC de Vue ne rectifie pas l'erreur du serveur PHP puisque la transformation Vite ne déclenchera pas à nouveau. En conséquence, appuyer à plusieurs reprises sur `cmd+r` ne sert à rien.\n\nPour cette raison, le système de fichiers doit être surveillé pour les changements SFC de Vue, déclenchant une transformation manuelle. Bien que suboptimale, cette méthode est nécessaire. Aaron a conçu une stratégie priorisant les transformations via la méthode `transform` sur celles déclenchées par l'observateur, avec application d'un throttling sur l'observateur.\n\nC'est également pour résoudre le problème vu précédemment : lorsque le serveur PHP est touché en premier, le fichier PHP généré n'est pas encore créé.\n\nRésumons nos apprentissages jusqu'à présent avec ce schéma :\n\n![Un schéma montrant toutes les étapes qui font fonctionner Fusion.](\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components\u002Ffusion-schema.png)\n\nBien que nous n'ayons pas complètement couvert chaque composant, ce diagramme aide notre compréhension du reste de l'article.\n\n> [!NOTE]\n> Assurez-vous que le serveur Vite fonctionne correctement pour que tout fonctionne sans accroc.\n\nCette exploration nous a profondément engagés dans le plugin Vite de Fusion. Passons maintenant à comprendre comment Fusion organise la gestion de son code PHP.\n\n> [!NOTE]\n> Vous avez des questions ? [Demandez-moi tout](\u002Fama) ou laissez un commentaire ci-dessous. J'aime vraiment lire vos retours !\n\n## Que fait Fusion en coulisses pour rendre cela possible ?\n\nPlongeons dans les opérations côté PHP.\n\nNous avons vu le code PHP extrait, validé et sauvegardé sur le disque. Nous devons maintenant dénouer deux étapes cruciales pour mieux comprendre le fonctionnement de Fusion :\n\n1. Déterminer si le code PHP doit être persistant.\n2. Sauvegarder le code PHP dans le système de fichiers.\n\nPour faciliter la compréhension, abordons d'abord la deuxième étape. La première étape deviendra ensuite claire.\n\n### Écriture du code PHP sur le système de fichiers\n\nUne fois extrait du SFC de Vue, le code PHP doit être persistant sur le système de fichiers. Ce processus est crucial puisque le PHP écrit au sein du SFC de Vue devient une partie intégrante de l'application Laravel, non pas simplement un script PHP indépendant. Ce fichier généré est essentiel pour que l'application Laravel réponde aux requêtes entrantes.\n\nAprès l'extraction, le code PHP est placé dans [un fichier temporaire](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L97). Ce fichier temporaire maintient le contenu exact du code inhérent au SFC de Vue. Par la suite, Fusion crée une entrée dans une [base de données SQLite dédiée](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FDatabase.js#L3). Nous reviendrons sur cette base de données plus tard, en comprenant qu'elle facilite la communication entre les processus PHP et JavaScript.\n\nEnfin, le plugin Vite déclenche la [`commande fusion:conform`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L107).\n\n### La commande `fusion:conform`\n\nNous avons retardé la compréhension de cette commande ; il est maintenant temps de clarifier. Dans la commande `fusion:conform`, une commande Laravel écrite en PHP, ni Vite ni JavaScript ne s'appliquent, strictement du PHP.\n\nLors de l'invocation, cette commande utilise un argument `src` représentant le chemin du fichier SFC de Vue. Agissant comme un identifiant unique, elle permet à Fusion de [récupérer l'entrée de la base de données pertinente](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L38).\n\nCette entrée contient le chemin de destination pour le fichier PHP final qui sera utilisé par l'application Laravel. Actuellement, ce fichier n'existe pas ; un fichier temporaire conserve seulement le code. Ainsi, l'étape suivante consiste à lire [le contenu du fichier temporaire](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L44-L47) avant un traitement ultérieur.\n\nLe processus suivant mobilise une [classe conformante](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L56), chargée de :\n\n1. Modifier le code pour l'adapter à l'application Laravel.\n2. S'assurer de la validité du code en le passant par un parseur PHP.\n\nLa fonction examine la conformité du code PHP en le faisant passer par une séquence de [classes de transformation](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConformity\u002FConformer.php#L65C24-L79).\n\nChaque classe de transformer étend une super-classe `Transformer`, conçue sur mesure pour étendre `NodeVisitorAbstract` et met en œuvre l'interface `TransformerInterface` provenant de [PHP Parser](https:\u002F\u002Fgithub.com\u002Fnikic\u002FPHP-Parser).\n\n```php [Transformer.php]\n\u003C?php\n\nnamespace Fusion\\Conformity\\Transformers;\n\nabstract class Transformer extends NodeVisitorAbstract implements TransformerInterface\n{\n    public function __construct(\n        protected ?string $filename = null\n    ) {}\n}\n```\n\nChaque transformateur personnalisé, de plus, implémente la méthode `enterNode` pour faire avancer la transformation du code.\n\n```php [PropTransformer.php]\n\u003C?php\n\nnamespace Fusion\\Conformity\\Transformers;\n\nclass PropsTransformer extends Transformer\n{\n    public function enterNode(Node $node)\n    {\n        if ($node instanceof MethodCall) {\n            \u002F\u002F Effectuer une opération\n        }\n    }\n}\n```\n\n> [!NOTE]\n> Une exploration détaillée des transformateurs ou des parseurs PHP est en dehors du champ d'application de cet article car ils offrent une fonctionnalité avancée pas immédiatement nécessaire pour comprendre le fonctionnement de Fusion.\n\nAu total, dix transformateurs sont impliqués, chacun ayant une responsabilité spécifique. Concevez-les comme des étapes au sein d'un pipeline, un modèle conventionnel en programmation conçu pour décomposer des tâches complexes en segments plus petits et abordables. Par conséquent, lorsque le code utilisateur subit une transformation et est rendu compatible avec l'application Laravel, la sortie d'un transformateur devient l'entrée du suivant.\n\n```txt\nCode PHP du SFC de Vue\n    | |\n[Transformateur procédural]: Transforme le code procédural en une méthode de classe anonyme intitulée runProceduralCode.\n[Transformateur de props]: Change les appels de fonction prop() en appels de méthode de cet objet dans la classe.\n[Transformateur d'importation de fonction]: Supprime les importations de fonction inutiles, spécifiquement celles connues à l'avance.\n[Transformateur de valeur de prop]: Spécifie que les chaînes d'appels de méthode $this->prop() se terminent par ->value().\n[Transformateur d'exposition]: Modifie les appels de fonction expose() en appels de méthode de cet objet, excluant les affectations de variable.\n[Transformateur de montage]: Valide et modifie les appels de fonction mount(), s'assurant qu'il n'y ait qu'un seul argument fonction anonyme.\n[Transformateur de découverte de prop]: Découvre et documente les noms de propriétés des fonctions prop() rencontrées.\n[Transformateur de découverte d'action]: Découvre et construit des méthodes pour les gestionnaires d'action exposés via expose().\n[Transformateur de retour anonyme]: Garantit des retours d'expression directs pour les retours de classe anonyme.\n[Transformateur de classe anonyme]: Convertit les classes anonymes en classes nommées, dans un espace de noms généré, rassemblant les exigences nécessaires.\n    | |\nCode PHP utilisable\n```\n\nConsidérons un exemple concret avant de continuer. Supposons que le code PHP suivant existe au sein du SFC de Vue :\n\n```vue\n\u003Cphp>\nuse function \\Fusion\\{prop};\n\n$name = prop('bienvenue');\n\n$name = strtoupper($name);\n\u003C\u002Fphp>\n```\n\nAprès avoir passé par la transformation, cela donne :\n\n```php\n\u003C?php\n\n\u002F**\n * Fichier auto-généré par Fusion.\n * Les modifications ne sont pas conseillées.\n *\u002F\nnamespace Fusion\\Generated\\Pages;\n\nclass IndexGenerated extends \\Fusion\\FusionPage\n{\n    #[\\Fusion\\Attributes\\ServerOnly]\n    public array $discoveredProps = ['name'];\n    use \\Fusion\\Concerns\\IsProceduralPage;\n    public function runProceduralCode()\n    {\n        $name = $this->prop(name: 'name', default: 'bienvenue')->value();\n        $name = strtoupper($name);\n        $this->syncProps(get_defined_vars());\n    }\n}\n```\n\nCette classe `IndexGenerated` sera utilisée par l'application Laravel lors du traitement des requêtes à la route '\u002F'. Visualisez-la comme un mini-contrôleur contenant le code utilisateur. Dans les coulisses, une grande partie de la complexité est abstraite dans la classe [`FusionPage`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L22).\n\nLe tableau `discoveredProps` enregistre les noms des propriétés traitées lors des appels de fonction **prop()**, assurant la synchronisation entre les propriétés frontend et backend.\n\nLa méthode `runProceduralCode`, invoquée lors du chargement de la page, exécute le code utilisateur aux côtés du code généré par Fusion, garantissant la synchronisation des props entre le frontend et le backend.\n\nLa méthode [`syncProps`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConcerns\u002FIsProceduralPage.php#L36) maintient la synchronisation des propriétés intérieur-extérieur. Cela récupère les variables définies dans la méthode, et le contenu du tableau discoveredProps, garantissant que la propriété `props` générée contient uniquement les données requises par l'utilisateur.\n\n> [!NOTE]\n> Utiliser `get_defined_vars`, une fonction PHP intégrée, retourne un tableau de variables représentatives de la portée actuelle. Dans ce cas, cela constitue `['name' => 'bienvenue']`.\n\n> [!NOTE]\n> `#[ServerOnly]` classe les propriétés pour une application exclusivement côté serveur. Analogues [décorateurs TypeScript](https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fdecorators.html) fonctionnent de manière similaire sous [reflect-metadata](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Freflect-metadata).\n\nEnfin, Fusion place le contenu transformé dans le chemin de fichier fourni par la base de données, mettant à jour les entrées associées avec le nom législatif de la classe composée du nom d'espace et de la désignation de la classe. La procédure reste enfermée dans un bloc `try\u002Fcatch`, prête à traiter toute erreur de transformation qui pourrait survenir. Lorsqu'une erreur se produit, [un fichier temporaire est supprimé](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L131) tandis qu'[un message d'erreur est dirigé vers Vite](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FConsole\u002FCommands\u002FConform.php#L124), comme noté plus tôt.\n\nÀ ce stade, notre système de fichiers apparaît maintenant comme suit :\n\n\u003CTree\n  name=\"Une application Fusion\"\n  :items=\"[\n    {\n      title: 'resources\u002Fjs\u002FPages',\n      children: [\n        { id: '1', title: '[Podcast].vue' },\n        { id: '2', title: 'Index.vue' },\n        { id: '3', title: 'Search.vue' },\n      ],\n    },\n    {\n      title: 'storages\u002Ffusion',\n      children: [\n        {\n          id: '4', title: 'JavaScript', children: [\n            { id: '7', title: 'Index.js' },\n            { id: '8', title: '[Podcast].js' },\n            { id: '9', title: 'Search.js' },\n          ]\n        },\n        {\n          id: '5', title: 'PHP', children: [\n            { id: '10', title: 'DynamicPodcastGenerated.php' },\n            { id: '11', title: 'IndexGenerated.php' },\n            { id: '12', title: 'SearchGenerated.php' },\n          ]\n        },\n        { id: '6', title: 'fusion.sqlite' },\n      ],\n    }\n  ]\"\n  :default-expanded=\"['resources\u002Fjs\u002FPages', 'storages\u002Ffusion']\"\n\u002F>\n\nEnsuite, le plugin Vite reprend son opération, comme décrit dans [la section suivante](#gestion-des-demandes-entrantes).\n\n### La base de données\n\nPuisque nous avons mentionné une base de données au cours de cet article, nous avons abstenu de donner des détails jusqu'à présent. Il est temps de comprendre son but !\n\nLa base de données aide à établir la communication entre les processus PHP et JavaScript, stockant des informations essentielles à cette fin. D'un coup d'œil, nous pouvons discerner les données stockées et son utilisation.\n\n| id | src                            | php_class                                      | php_path                                            | shim_path                                   | php_hash                            |\n|----|--------------------------------|------------------------------------------------|-----------------------------------------------------|---------------------------------------------|-------------------------------------|\n| 1  | \u002Fresources\u002Fjs\u002FPages\u002FIndex.vue  | \\Fusion\\Generated\\Pages\\IndexGenerated         | \u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FIndexGenerated.php        | \u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002FIndex.js   | php_1e182535deedd9b60e12c277bef58603|\n| 2  | \u002Fresources\u002Fjs\u002FPages\u002FSearch.vue | \\Fusion\\Generated\\Pages\\SearchGenerated        | \u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FSearchGenerated.php       | \u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002FSearch.js  | php_468b4badedbe0cb6ce4734fcc921ea5f|\n| 3  | \u002Fresources\u002Fjs\u002FPages\u002F[Podcast].vue | \\Fusion\\Generated\\Pages\\DynamicPodcastGenerated | \u002Fstorage\u002Ffusion\u002FPHP\u002FPages\u002FDynamicPodcastGenerated.php | \u002Fstorage\u002Ffusion\u002FJavaScript\u002FPages\u002F[Podcast].js | php_2ca978b1c880558f54c4a59ce7584e6c |\n\nLors de la transformation des SFC de Vue et de l'extraction du code PHP, un [calcul de hash](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L50) se produit, enregistré dans la colonne `php_hash` de la base de données.\n\nLe hash sert à vérifier si [le code PHP a été modifié](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FTransformer.js#L96). Un hash correspondant signifie que le code PHP n'a pas changé, introduisant une optimisation pour omettre les transformations redondantes.\n\n> [!NOTE]\n> Un hash est une fonction à sens unique générant une chaîne de caractères de taille fixe à partir d'une entrée, généralement composée de séquences numériques et alphanumériques. Un hash identifie de manière unique son entrée, signifiant que des hashes identiques indiquent des entrées identiques.\n\nCe dépôt de base de données incarne des données supplémentaires comme les chemins de fichiers PHP et JavaScript utilisés principalement pour gérer efficacement les requêtes entrantes.\n\nUne question supplémentaire se pose : comment la base de données SQLite affecte-t-elle l'expérience développeur ?\n\nTout en utilisant une base de données SQLite, Fusion ne dicte pas le choix de la base de données de l'application Laravel : les deux restent entièrement distincts.\n\nLe fournisseur de services de Fusion attribue [une connexion de base de données appelée `__fusion`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L103C1-L110C12) utilisant SQLite, qui n'interfère pas avec la connexion de la base de données de l'application Laravel. Cela signifie qu'il est possible d'avoir plusieurs connexions de base de données au sein d'une seule application Laravel. Vraiment intéressant à savoir !\n\n```php\npublic function boot()\n{\n  ConfigFacade::set('database.connections.__fusion', [\n    'name' => '__fusion',\n    'driver' => 'sqlite',\n    'database' => Fusion::storage('fusion.sqlite'),\n    'foreign_key_constraints' => true,\n    'journal_mode' => 'OFF',\n  ]);\n}\n```\n\n> [!NOTE]\n> Un fournisseur de services Laravel s'exécute lors du démarrage de l'application pour enregistrer des services ou lier des classes dans le conteneur de services. C'est un lieu capital pour gérer et configurer des services.\n\nLe [modèle Component](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FModels\u002FComponent.php#L7) utilise désormais cette connexion :\n\n```php\n\u003C?php\n\nnamespace Fusion\\Models;\n\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Component extends Model\n{\n    protected $connection = '__fusion';\n}\n```\n\nAvec cette configuration, la classe `Component` utilise la connexion de base de données `__fusion`. Par conséquent, la base de données SQLite est uniquement utilisée par Fusion ; elle n'exerce aucun impact visible sur l'application Laravel, permettant à l'application de choisir librement son mode de connexion de base de données. C'est une stratégie simple et intelligemment mise en œuvre.\n\n### Gestion des requêtes entrantes\n\nPlongeons dans peut-être le segment le plus intrigant de l'article : la gestion des requêtes entrantes via Fusion.\n\nEn commençant à lire, nous avons largement couvert le développement, incluant le fonctionnement du plugin Vite et la transformation PHP. Notre tâche consiste maintenant à comprendre comment Fusion harmonise ces éléments pour la gestion des requêtes.\n\nFusion adopte une approche de routeur basé sur des fichiers. Les SFC de Vue s'enregistrent automatiquement en tant que routes au sein de l'application Laravel, réalisé en appelant [`Fusion` comme un facade](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusion.php), soutenu par [le gestionnaire Fusion](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php), qui est ensuite [enregistré dans le `FusionServiceProvider`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L30) dans le cadre de la configuration habituelle des routes dans `routes\u002Fweb.php`.\n\n```php\n\u003C?php\n\nuse Fusion\\Fusion;\n\nFusion::pages();\n```\n\nCela semble être toute l'explication mais continuons pour dévoiler davantage de complexités. Dans la méthode `pages`, [tous les composants SFC de Vue](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php#L67) situés dans `resources\u002Fjs\u002FPages` sont localisés et [enregistrés méticuleusement](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionManager.php#L83). Utiliser le [routeur Laravel](https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Frouting) facilite l'implémentation.\n\n```php\nRoute::any($uri, [FusionController::class, 'handle'])\n  ->defaults('__component', $component)\n  ->defaults('__class', Component::where('src', $src)->first()?->php_class);\n```\n\nNotamment, le processus d'acheminement attribue deux valeurs par défaut :\n\n1. `__component`: Chemin menant au fichier SFC de Vue.\n2. `__class`: Nom de classe PHP pleinement qualifié, cette classe a été générée dans la commande `fusion:conform`.\n\nPar conséquent, lorsqu'une requête accède à la route `\u002F`, l'opération `handle` du `FusionController` s'active. À l'intérieur du contrôleur se trouve un système qui gère les différentes possibilités tout en synchronisant les actions, et en interagissant sans accroc avec le frontend.\n\nAprès la gestion des requêtes, une réponse appropriée est envoyée, guidée par la nature de la requête comme suit :\n\n1. Les requêtes HMR ou d'action reçoivent [une réponse JSON](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L61-L63).\n2. Les requêtes standards délèguent les tâches de rendu [à Inertia](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L65-L67).\n\nL'attribut `data-page` révèle l'objet Inertia accompagné des composants Fusion associés :\n\n```html\n\u003Cdiv id=\"app\" data-page=\"{&quot;component&quot;:&quot;Index&quot;,&quot;props&quot;:{&quot;errors&quot;:{},&quot;auth&quot;:{&quot;user&quot;:null},&quot;fusion&quot;:{&quot;meta&quot;:[],&quot;state&quot;:{&quot;name&quot;:&quot;AARON&quot;},&quot;actions&quot;:[{&quot;handler&quot;:&quot;applyServerState&quot;,&quot;priority&quot;:40,&quot;_handler&quot;:&quot;Fusion\\\\Http\\\\Response\\\\Actions\\\\ApplyServerState&quot;}]}},&quot;url&quot;:&quot;\\\u002F&quot;,&quot;version&quot;:&quot;f7e418d032bf1c4cc3cc55d947cab622&quot;,&quot;clearHistory&quot;:false,&quot;encryptHistory&quot;:false}\">\u003C\u002Fdiv>\n```\n\nL'objet `fusion` est d'un intérêt particulier. Il est composé de données initialisées dans le bloc PHP du SFC de Vue via la fonction **prop** ; comprenant **state** et **actions** injectées au client en conséquence. La classe `PendingResponse`, responsable de la gestion, catégorise spécifiquement ces opérations au sein de Fusion [comme suit](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FHttp\u002FResponse\u002FPendingResponse.php#L31-L43).\n\nSpécifiquement, l'action `applyServerState` utilise :\n\n```js\nimport { ref, unref } from 'vue'\n\nexport default function applyServerState({ fusion, state }, next) {\n  Object.keys(fusion.state).forEach((key) => {\n    state[key] = ref(unref(fusion.state[key]))\n  })\n\n  return next({ fusion, state })\n}\n```\n\nLes développeurs bénéficient d'exploiter automatiquement le traitement des actions par Fusion sans appels manuels.\n\nJe n'ai pas encore tout révélé. En plus d'extraire du bloc PHP du SFC de Vue, Fusion injecte de la logique JavaScript dans le SFC de Vue, précédant la transformation. Ce qui a commencé comme un composant initial :\n\n```vue\n\u003Cphp>\nuse function \\Fusion\\{prop};\n\n$name = prop('Aaron');\n$name = strtoupper($name);\n\u003C\u002Fphp>\n\n\u003Ctemplate>\n  Bonjour {{ name }} !\n\u003C\u002Ftemplate>\n```\n\nSe transforme durant la transformation du plugin Fusion (plus tard gérée par Vite) comme suit :\n\n```vue\n\u003Cscript setup>\nimport { useFusion } from '$fusion\u002FPages\u002FIndex.js'\nimport useHotFusion from '@fusion\u002Fvue\u002Fhmr'\n\n\u002F\u002F __props est une variable magique fournie au script setup par Vue.\nconst __fusionData = useFusion(['name', 'fusion'], __props.fusion)\n\nconst { name, fusion } = __fusionData\n\nuseHotFusion(__fusionData, { id: 'hot_07f7c82af56ef80f306d905892430461', hot: import.meta?.hot })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  Bonjour {{ name }} !\n\u003C\u002Ftemplate>\n```\n\nNotez le bloc [`script setup`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Finjectors\u002FcreateScriptSetup.js) injecté synthétiquement, ne nécessitant aucun input manuel. Il présente un composable `useFusion`, acceptant deux arguments, le premier détaillant les clés de props nécessitant extraction (affinées par la reconnaissance des variables PHP via les appels à **prop**) et un argument `props` signifiant les props. En essence, cela ressemble à l'utilisation de `const props = defineProps()`. Fusion déclenche la variable `__props`, fournie par Vue, permettant aux développeurs d'écrire leur bloc `script setup` sans entraves. Cela permet de conserver la syntaxe normale des SFC de Vue non entravée par les processus Fusion.\n\nEssentiellement, les valeurs composables `name` et `fusion` sont extraites via `script setup` avec une injection de modèle sans interruption.\n\nD'où vient `useFusion` ? Le préfixe `$fusion\u002FPages` révèle la vue d'ensemble.\n\nConcernant `$fusion\u002FPages`, nous avons déjà vu sa fonctionnalité en action via l'alias généré par le plugin Vite. Cet alias abstrait l'emplacement réel du fichier JavaScript, provenant d'une précédente entreprise formulant le `fusion:shim`. Son contenu se lit :\n\n```js\nimport ActionFactory from '@fusion\u002Fvue\u002FactionFactory'\nimport Pipeline from '@fusion\u002Fvue\u002Fpipeline'\n\nexport const state = ['name']\nexport const actions = []\nexport const fusionActions = ['fusionSync']\n\nlet cachedState\n\nexport function useFusion(keys = [], props = {}, useCachedState = false) {\n  const state = (useCachedState && cachedState) ? cachedState : new Pipeline(props).createState()\n\n  if (!useCachedState) {\n    cachedState = state\n  }\n\n  const all = {\n    ...state,\n    ...new ActionFactory([...actions, ...fusionActions], state),\n  }\n\n  const shouldExport = {}\n  for (const key of keys) {\n    if (key in all) {\n      shouldExport[key] = all[key]\n    }\n  }\n\n  return shouldExport\n}\n```\n\nLes variables `state` et `fusion` résident dans ce fichier, reflétant les déclarations du SFC de Vue dans les fonctions PHP **prop** et **expose**. Accompagné d'un `ActionFactory` responsable de l'application des actions imposées par le serveur à l'état, y compris `applyServerState`, la synchronisation automatique et transparente se produit.\n\nEn tout, Fusion propose plusieurs actions préétablies, telles que :\n\n- [`applyServerState`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FapplyServerState.js): Synchronise les données d'état entre le backend et le client, mettant à jour l'état pour correspondre aux attentes du serveur en temps réel.\n- [`syncQueryString`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FsyncQueryString.js): Synchronise les paramètres de chaîne de requête et l'état.\n- [`log`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002Flog.js): Permet l'enregistrement à des fins de débogage.\n- [`logStack`](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002Factions\u002FlogStack.js): Enregistre la progression de l'action pour le débogage.\n\nLa discussion devient encore plus captivante en voyant la fonction `expose`. Cette méthode exposent une fonctionnalité PHP côté serveur au client, créant des comportements de type RPC permettant à des requêtes côté client de déclencher des opérations côté serveur.\n\n> [!NOTE]\n> Le concept `expose` est apparenté à des approches comme [tRPC](https:\u002F\u002Ftrpc.io\u002F) et [React Server Functions](https:\u002F\u002Freact.dev\u002Freference\u002Frsc\u002Fserver-functions).\n\nUn exemple illustratif :\n\n```vue\n\u003Cphp>\nexpose(favorite: function(Podcast $podcast) {\n    return response()->json($podcast->toggleFavorite());\n});\n\u003C\u002Fphp>\n\n\u003Ctemplate>\n  \u003Cbutton @click=\"favorite({ podcast }).then(fav => podcast.favorited = fav)\">\n    Favori\n  \u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n```\n\nL'exposition d'une méthode comme `favorite` facilite son utilisation au sein du modèle. Le composable `useFusion` décrit précédemment gère la fonctionnalité, néanmoins, Fusion garantit un traitement approfondi permettant l'interaction de bouton côté client pour déclencher des opérations soutenues par le serveur.\n\n1. Le client [envoie une requête au serveur](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FActionFactory.js#L72-L77) pourvue des en-têtes `x-fusion-action-request` et `x-fusion-action-handler`.\n2. Le backend traite ces requêtes, aidé par les métadonnées fournies, répondant au client en conséquence, semblable à [comment Inertia gère son protocole avec les en-têtes](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FHttp\u002FControllers\u002FFusionController.php#L50).\n3. L'état côté client correspond aux retours du serveur, [après traitement](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Fvue\u002FActionFactory.js#L87).\n\nCliquer sur le bouton favori dans l'exemple ci-dessus génère cette requête ciblée vers le serveur :\n\n```http\nPOST \u002Fsearch HTTP\u002F1.1\nAccept: application\u002Fjson, text\u002Fplain, *\u002F*\nX-Fusion-Action-Handler: favorite\nX-Fusion-Action-Request: true\n\n{\n  \"fusion\": {\n    \"args\": {\n      \"podcast\": {\n        \"id\": 1,\n        \"title\": \"Art Of Product\",\n        \"author\": \"Derrick Reimer & Ben Orenstein\",\n        \"image\": \"\u002Fart\u002Fart-of-product.jpg\",\n        \"favorited\": false,\n        \"created_at\": \"2025-02-15T18:01:36.000000Z\",\n        \"updated_at\": \"2025-02-19T20:49:13.000000Z\"\n      }\n    },\n    \"state\": {\n      \"search\": \"\",\n      \"podcasts\": [\n        {\n          \"id\": 1,\n          \"title\": \"Art Of Product\",\n          \"author\": \"Derrick Reimer & Ben Orenstein\",\n          \"image\": \"\u002Fart\u002Fart-of-product.jpg\",\n          \"favorited\": false,\n          \"created_at\": \"2025-02-15T18:01:36.000000Z\",\n          \"updated_at\": \"2025-02-19T20:49:13.000000Z\"\n        }\n      ]\n    }\n  }\n}\n```\n\nPendant ce temps, le serveur traite ce type de requête en interprétant les en-têtes `x-fusion-action-request` et `x-fusion-action-handler`. Dans son fournisseur de services, Fusion [enregistre des macros de requête pour une identification et un traitement faciles des types de requête](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FProviders\u002FFusionServiceProvider.php#L62-L65).\n\nÀ l'intérieur du corps de la requête, `args` détient les arguments de la méthode (podcast), tandis que `state` comprend l'état actuel de l'application.\n\nEn réponse, le serveur envoie la suivante :\n\n```json\ntrue\n```\n\nCe qui est la réponse de la méthode `favorite`. Le client met ensuite à jour l'état en conséquence.\n\nFusion offre une méthode `sync` favorisant la synchronisation des états entre le frontend et le backend. Par exemple, un champ de recherche pourrait filtrer des éléments basés sur des calculs du backend, réalisé via `sync`. Essentiellement, sync imite une action catégorisée par le gestionnaire `fusionSync` !\n\n\u003C!--## Comment le HMR fonctionne-t-il dans Fusion ?\n\nPour garantir la meilleure expérience développeur, Fusion tire parti du Hot Module Replacement (HMR) de Vite. Par conséquent, apporter des modifications à votre SFC de Vue transgresse les recharges de page complètes.\n\n**En attente de l'avis d'Aaron**\n\n-->\n\n## Comment Fusion utilise-t-il Inertia ?\n\nEn vérité, Fusion simplifie ingénieusement et s'appuie sur l'intégration transparente d'Inertia pour traiter efficacement les SFC de Vue sans réinventer son framework frontend. La méthode [Inertia::render](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fsrc\u002FFusionPage.php#L65-L67) illustre le rendu basé sur Inertia tandis que [createInertiaApp](https:\u002F\u002Fgithub.com\u002Ffusion-php\u002Ffusion\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fapps\u002Fvue\u002Fresources\u002Fjs\u002Fapp.js#L11) établit l'environnement de l'application Vue de manière efficace, constituant l'ampleur totale de l'empreinte d'Inertia dans le projet Fusion.\n\nConstruire sur Inertia se révèle judicieux, permettant à Aaron de se concentrer sur l'innovation autour de l'objectif de Fusion sans être distrait par les complexités d'intégration de Vue au sein de Laravel. L'utilité d'Inertia annule toute redondance. De plus, transcender le parcours intermédiaire fait évoluer Fusion pour englober d'autres frameworks comme React de manière fluide.\n\n## Dernières pensées\n\nFusion a un potentiel, notamment pour les développeurs en transition de Next ou Nuxt vers une capacité full-stack, accueillant avec enthousiasme Laravel avec Inertia. En préservant des concepts chers aux développeurs de ces frameworks, tels que le routage basé sur des fichiers et la communication pilotée par le RPC, tout en les enrichissant avec la puissance de Laravel, Fusion devient un choix convaincant. Vous devriez absolument l'essayer !\n\nAu sein de la feuille de route de Fusion, Aaron envisage la compatibilité avec React, réalisable en s'appuyant sur les épaules d'Inertia. Toutefois, je suis impatient de voir la perspective d'Aaron sur la gestion de l'exécution du code serveur dans JSX, peut-être à l'aide d'une directive `'use php'` ? 🤔\n\nCe projet illustre efficacement la force de la relation profonde entre Vite et Laravel. Accéder sans effort à l'utilité complète de Vite demeure une capacité révolutionnaire. Cela réaffirme également la capacité modulaire du SFC dans Vue, Fusion le confirme assurément. Il n'y a pratiquement aucune limite à ce que vous pouvez réaliser en utilisant Vite et des composants SFC de Vue !\n\nEnfin, la communication de la base de données SQLite inter-processus de Fusion renforce un modèle élégant et intelligent !\n\nL'article a été passionnant à rédiger alors que j'explorais la base de code de Fusion. J'espère qu'il a révélé de nouvelles perspectives pour vous, et si vous êtes enclin à explorer les subtilités d'une autre bibliothèque, n'hésitez pas à commenter ou à utiliser la page [Demandez-moi tout](\u002Fama).\n\nMais j'ai un supplément pour vous ! 👇\n\n## Une dernière chose\n\nTout en rédigeant cet article, les exemples de code PHP en bloc fréquents manquaient de surlignement syntaxique approprié. J'ai réfléchi et décidé de m'attaquer à cette tâche. Puisque **Shiki** est utilisé pour la mise en surbrillance des codes sur ce site et qu'il utilise **TextMate grammars**, je sais que je pourrais ajouter le support pour le bloc PHP dans VSCode.\n\nMais comment ?\n\nEn fouillant, j'ai réalisé que l'intégration de langages dans des contextes à fichier unique n'est pas nouvelle. Les SFC de Vue sont notables, tout comme Markdown, Angular, et même HTML impliquant différents langages. Par la suite, je me suis concentré sur la grammaire TextMate des SFC de Vue.\n\n> [!NOTE]\n> Une grammaire TextMate définit les mises en surbrillance de syntaxe du langage de codage via des modèles et définit des règles. Fonctionnant derrière VSCode et Shiki, c'est le moteur du surligneur de syntaxe.\n\nExaminer le [dépôt textmate-grammars-themes de Shikijs](https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002F) qui s'occupe de chaque langue et thème soutenu par Shiki a mis au jour la [grammaire Vue](https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002Fblob\u002F1355f60ed3897846cf33e31b8d2c06563a563db7\u002Fpackages\u002Ftm-grammars\u002Fgrammars\u002Fvue.json). Bien que long, des concepts éclairants émergent lors de la lecture, tels que les intégrations HTML, CSS, et JavaScript au sein des blocs respectifs:\n\n```json\n{\n  \"begin\": \"(script)\\\\b\",\n  \"beginCaptures\": {\n    \"1\": {\n      \"name\": \"entity.name.tag.$1.html.vue\"\n    }\n  },\n  \"end\": \"(\u003C\u002F)(\\\\1)\\\\s*(?=>)\",\n  \"endCaptures\": {\n    \"1\": {\n      \"name\": \"punctuation.definition.tag.begin.html.vue\"\n    },\n    \"2\": {\n      \"name\": \"entity.name.tag.$2.html.vue\"\n    }\n  },\n  \"patterns\": [\n    {\n      \"include\": \"#tag-stuff\"\n    },\n    {\n      \"begin\": \"(?\u003C=>)\",\n      \"end\": \"(?=\u003C\\\\\u002Fscript\\\\b)\",\n      \"name\": \"source.js\",\n      \"patterns\": [\n        {\n          \"include\": \"source.js\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nCela gère la mise en surbrillance de JavaScript au sein des blocs `\u003Cscript>` en utilisant l'inclusion de la grammaire JavaScript à travers `include`. Le même modèle s'applique probablement à d'autres langages, y compris PHP.\n\nC'est exactement ce que je veux atteindre, mais avec `\u003Cphp>` et la grammaire PHP. Bien sûr, il existe déjà une grammaire PHP, donc je dois juste l'inclure de la même manière. J'essaie, et ça fonctionne. Youpi ! 🥳\n\nMais attendez, je ne peux pas soumettre un PR à [Vue Language Tools](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Flanguage-tools) (Shiki récupère la grammaire de ce dépôt) pour ajouter le support de la grammaire PHP. Cela n'a aucun sens, et il n'y a aucune raison de leur part de l'accepter. Donc, je dois trouver une autre solution.\n\nAprès avoir continué ma recherche, j'ai trouvé la grammaire [angular-inline-style](https:\u002F\u002Fgithub.com\u002Fshikijs\u002Ftextmate-grammars-themes\u002Fblob\u002Fmain\u002Fpackages\u002Ftm-grammars\u002Fgrammars\u002Fangular-inline-style.json). Celle-ci est utilisée pour mettre en évidence les styles (CSS, SCSS) à l'intérieur d'un composant Angular (TypeScript). Pour ce faire, la grammaire utilise le mot clé injectTo, qui agit comme une extension d'une grammaire existante, dans ce cas, source.ts.ng. C'est parfait ! Que se passerait-il si je créais une nouvelle grammaire qui étend la grammaire Vue en injectant la grammaire PHP ? Rétrospectivement, cela semblait plus facile que cela ne l'a été en réalité.\n\nAprès quelques heures d'essais et d'erreurs, j'ai finalement réussi à créer une grammaire qui fonctionne dans Shiki. L'un des éléments les plus compliqués a été de comprendre ce qui n'allait pas lorsque les choses ne fonctionnaient pas. Avec ce genre de configuration, il n'y a pas de messages d'erreur, pas d'avertissements, rien. Vous devez juste deviner ce qui ne va pas. Vous pouvez trouver la solution fonctionnelle dans le code source de [mon générateur de code en image](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fcode.soubiran.dev).\n\nEnfin, j'ai créé une extension VSCode que tout le monde peut utiliser pour ajouter le support des blocs PHP dans les SFC de Vue. Vous pouvez la trouver sur le [marketplace](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ffusion-vscode) avec la grammaire fonctionnelle : [source.vue.php](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Ffusion-vscode\u002Fblob\u002Fmain\u002Fsyntaxes\u002Fvue.php.tmLanguage.json).\n\nSi Fusion devient populaire, je pourrais éventuellement soumettre un PR pour ajouter le language à Shiki, rendant plus facile à tous la mise en surbrillance des blocs PHP.\n\nBon code ! 👨‍💻\n",{"title":24215,"description":28921},{"loc":28923,"images":28927},[28928,28929,28930,28931,28932,28933],{"loc":24266},{"loc":24868},{"loc":25179},{"loc":25254},{"loc":25330},{"loc":25694},"fr\u002Fposts\u002Ffusion-or-the-art-of-writing-php-into-a-vue-sfc-components","CnUHo-0ODhdypqqiEZ79-UgFHf_8z4Lbl9LaoLwYGuk",{"id":28937,"title":28938,"body":28939,"contentId":9260,"date":30447,"description":30448,"extension":18,"meta":30449,"navigation":20,"path":30450,"rawbody":30451,"robots":23,"schemaOrg":23,"seo":30452,"sitemap":30453,"stem":30459,"__hash__":30460},"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":28940,"toc":30440},[28941,28946,28952,28965,28969,28977,28983,28992,29000,29003,29009,29012,29020,29058,29066,29072,29075,29079,29091,29097,29100,29103,29106,29120,29123,29250,29253,29386,29397,29400,29413,29415,29418,29421,29429,29439,29447,29450,29493,29496,29540,29543,29558,29561,29648,29651,29654,29657,29827,29830,29843,29854,29858,29861,29864,29873,29880,29971,29983,29986,29995,30001,30004,30013,30016,30019,30025,30262,30275,30278,30281,30284,30287,30300,30319,30324,30398,30405,30408,30414,30417,30420,30426,30428,30431,30437],[35,28942,28943],{},[1216,28944,28945],{},"Ce titre peut sembler dramatique, mais croyez-moi, cela vaut la peine d'être lu. Plongez-y !",[35,28947,28948,28949,28951],{},"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 ",[6862,28950,19463],{},", et ce ne sera pas le dernier !",[35,28953,28954,28955,1655,28957,28959,28960,215],{},"Aujourd'hui, je souhaite partager une expérience que j'ai vécue avec ",[6862,28956,19463],{},[6862,28958,24399],{}," en raison de la rigueur du ",[39,28961,28964],{"href":28962,"rel":28963},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\u002FUsing_web_workers",[77],"Web Worker API",[51,28966,28968],{"id":28967},"lintégration-de-laravel-et-vite","L'intégration de Laravel et Vite",[35,28970,28971,28972,28974,28975,215],{},"Avant de plonger dans le problème, il est crucial de comprendre comment ",[6862,28973,24399],{}," fonctionne dans un projet ",[6862,28976,19463],{},[35,28978,28979,28980,28982],{},"D'abord, qu'est-ce que ",[6862,28981,24399],{}," ? 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.",[35,28984,28985,28986,28988,28989],{},"Et ",[6862,28987,19463],{}," ? 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é. ",[1216,28990,28991],{},"Et il excelle à ce jeu !",[35,28993,28994,28995,1655,28997,28999],{},"Lorsque vous adoptez ",[6862,28996,19463],{},[6862,28998,24399],{},", vous obtenez une pile redoutable pour le développement d'applications web.",[35,29001,29002],{},"Ça sonne bien, n'est-ce pas ? Mais comment cela fonctionne-t-il en pratique ? Permettez-moi d'illustrer.",[35,29004,29005],{},[1707,29006],{"alt":29007,"src":29008},"Intégration de Laravel et Vite","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flaravel-vite-request-response.png",[35,29010,29011],{},"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.",[68,29013,29014,29017],{},[71,29015,29016],{},"Un utilisateur demande une page au serveur Laravel.",[71,29018,29019],{},"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 :",[192,29021,29023],{"className":590,"code":29022,"language":592,"meta":13,"style":13},"\u003Cscript type=\"module\" src=\"http:\u002F\u002Flocalhost:5173\u002Findex.ts\">\u003C\u002Fscript>\n",[156,29024,29025],{"__ignoreMap":13},[200,29026,29027,29029,29031,29033,29035,29037,29039,29041,29043,29045,29047,29050,29052,29054,29056],{"class":202,"line":203},[200,29028,599],{"class":214},[200,29030,14896],{"class":235},[200,29032,321],{"class":379},[200,29034,449],{"class":214},[200,29036,610],{"class":214},[200,29038,15097],{"class":245},[200,29040,610],{"class":214},[200,29042,10120],{"class":379},[200,29044,449],{"class":214},[200,29046,610],{"class":214},[200,29048,29049],{"class":245},"http:\u002F\u002Flocalhost:5173\u002Findex.ts",[200,29051,610],{"class":214},[200,29053,10206],{"class":214},[200,29055,14896],{"class":235},[200,29057,647],{"class":214},[68,29059,29060,29063],{"start":255},[71,29061,29062],{},"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.",[71,29064,29065],{},"Le serveur Vite livre le script au navigateur de l'utilisateur.",[35,29067,29068,29069],{},"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). ",[1216,29070,29071],{},"De nombreux développeurs pourraient ne pas apprécier pleinement à quel point cette intégration fonctionne sans couture par rapport à d'autres outils.",[35,29073,29074],{},"Cependant, parfois, les choses se compliquent. Et c'est là que notre histoire commence.",[51,29076,29078],{"id":29077},"le-contexte","Le Contexte",[35,29080,29081,29082,29087,29088],{},"Pour un projet, j'ai dû utiliser le ",[39,29083,29086],{"href":29084,"rel":29085},"https:\u002F\u002Fmicrosoft.github.io\u002Fmonaco-editor\u002F",[77],"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 ! ",[1216,29089,29090],{},"Et cela s'est avéré beaucoup plus complexe que prévu.",[35,29092,29093],{},[1707,29094],{"alt":29095,"src":29096},"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",[35,29098,29099],{},"Pour parvenir à cela, l'éditeur Monaco nécessite de démarrer quelques Web Workers. Pourquoi et qu'est-ce que les Web Workers ?",[35,29101,29102],{},"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.",[35,29104,29105],{},"Pour gérer les tâches longues sans affecter les performances, nous avons deux options :",[1593,29107,29108,29114],{},[71,29109,29110,29113],{},[6862,29111,29112],{},"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.",[71,29115,29116,29119],{},[6862,29117,29118],{},"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.",[35,29121,29122],{},"Voici un exemple simple d'un script de Web Worker :",[192,29124,29127],{"className":1380,"code":29125,"filename":29126,"language":1383,"meta":13,"style":13},"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",[156,29128,29129,29152,29171,29215,29234,29246],{"__ignoreMap":13},[200,29130,29131,29134,29136,29139,29141,29143,29145,29148,29150],{"class":202,"line":203},[200,29132,29133],{"class":210},"globThis",[200,29135,215],{"class":214},[200,29137,29138],{"class":223},"onmessage",[200,29140,509],{"class":214},[200,29142,5002],{"class":379},[200,29144,1094],{"class":214},[200,29146,29147],{"class":386},"e",[200,29149,2104],{"class":214},[200,29151,280],{"class":214},[200,29153,29154,29156,29158,29160,29162,29164,29167,29169],{"class":202,"line":14},[200,29155,1112],{"class":210},[200,29157,215],{"class":214},[200,29159,1117],{"class":223},[200,29161,227],{"class":235},[200,29163,249],{"class":214},[200,29165,29166],{"class":245},"Message reçu du script principal",[200,29168,249],{"class":214},[200,29170,416],{"class":235},[200,29172,29173,29175,29178,29180,29182,29185,29187,29189,29191,29194,29196,29198,29201,29204,29206,29208,29210,29212],{"class":202,"line":255},[200,29174,3738],{"class":379},[200,29176,29177],{"class":210}," workerResult",[200,29179,509],{"class":214},[200,29181,11154],{"class":214},[200,29183,29184],{"class":245},"Résultat : ",[200,29186,4868],{"class":214},[200,29188,29147],{"class":210},[200,29190,215],{"class":214},[200,29192,29193],{"class":210},"data[",[200,29195,3948],{"class":2060},[200,29197,18849],{"class":210},[200,29199,29200],{"class":214},"*",[200,29202,29203],{"class":210}," e",[200,29205,215],{"class":214},[200,29207,29193],{"class":210},[200,29209,3203],{"class":2060},[200,29211,360],{"class":210},[200,29213,29214],{"class":214},"}`\n",[200,29216,29217,29219,29221,29223,29225,29227,29230,29232],{"class":202,"line":272},[200,29218,1112],{"class":210},[200,29220,215],{"class":214},[200,29222,1117],{"class":223},[200,29224,227],{"class":235},[200,29226,249],{"class":214},[200,29228,29229],{"class":245},"Envoi du message de retour au script principal",[200,29231,249],{"class":214},[200,29233,416],{"class":235},[200,29235,29236,29239,29241,29244],{"class":202,"line":283},[200,29237,29238],{"class":223},"  postMessage",[200,29240,227],{"class":235},[200,29242,29243],{"class":210},"workerResult",[200,29245,416],{"class":235},[200,29247,29248],{"class":202,"line":300},[200,29249,1129],{"class":214},[35,29251,29252],{},"Avec son script principal :",[192,29254,29257],{"className":1380,"code":29255,"filename":29256,"language":1383,"meta":13,"style":13},"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",[156,29258,29259,29283,29287,29318,29337,29355,29361,29365],{"__ignoreMap":13},[200,29260,29261,29263,29266,29268,29270,29273,29275,29277,29279,29281],{"class":202,"line":203},[200,29262,443],{"class":379},[200,29264,29265],{"class":210}," worker ",[200,29267,449],{"class":214},[200,29269,13007],{"class":214},[200,29271,29272],{"class":223}," Worker",[200,29274,227],{"class":210},[200,29276,249],{"class":214},[200,29278,29126],{"class":245},[200,29280,249],{"class":214},[200,29282,416],{"class":210},[200,29284,29285],{"class":202,"line":14},[200,29286,528],{"emptyLinePlaceholder":20},[200,29288,29289,29292,29294,29297,29299,29301,29304,29306,29308,29310,29312,29314,29316],{"class":202,"line":255},[200,29290,29291],{"class":210},"worker",[200,29293,215],{"class":214},[200,29295,29296],{"class":223},"addEventListener",[200,29298,227],{"class":210},[200,29300,249],{"class":214},[200,29302,29303],{"class":245},"message",[200,29305,249],{"class":214},[200,29307,549],{"class":214},[200,29309,1094],{"class":214},[200,29311,29147],{"class":386},[200,29313,2104],{"class":214},[200,29315,393],{"class":379},[200,29317,280],{"class":214},[200,29319,29320,29322,29324,29326,29328,29330,29333,29335],{"class":202,"line":272},[200,29321,1112],{"class":210},[200,29323,215],{"class":214},[200,29325,1117],{"class":223},[200,29327,227],{"class":235},[200,29329,249],{"class":214},[200,29331,29332],{"class":245},"Message reçu du worker",[200,29334,249],{"class":214},[200,29336,416],{"class":235},[200,29338,29339,29341,29343,29345,29347,29349,29351,29353],{"class":202,"line":283},[200,29340,1112],{"class":210},[200,29342,215],{"class":214},[200,29344,1117],{"class":223},[200,29346,227],{"class":235},[200,29348,29147],{"class":210},[200,29350,215],{"class":214},[200,29352,12736],{"class":210},[200,29354,416],{"class":235},[200,29356,29357,29359],{"class":202,"line":300},[200,29358,413],{"class":214},[200,29360,416],{"class":210},[200,29362,29363],{"class":202,"line":310},[200,29364,528],{"emptyLinePlaceholder":20},[200,29366,29367,29369,29371,29374,29376,29378,29380,29383],{"class":202,"line":336},[200,29368,29291],{"class":210},[200,29370,215],{"class":214},[200,29372,29373],{"class":223},"postMessage",[200,29375,25224],{"class":210},[200,29377,4797],{"class":2060},[200,29379,549],{"class":214},[200,29381,29382],{"class":2060}," 3",[200,29384,29385],{"class":210},"])\n",[35,29387,29388,29389,29391,29392,29394,29395,215],{},"Dans cet exemple, le script principal crée un nouveau worker et lui envoie un message en utilisant ",[156,29390,29373],{},". Le worker reçoit et répond avec ",[156,29393,29373],{},". En raison de la nature événementielle de JavaScript, le script principal écoute le message du worker en utilisant ",[156,29396,29296],{},[35,29398,29399],{},"Dans notre cas, l'éditeur Monaco exploite les Web Workers pour certaines fonctionnalités, y compris la validation du schéma JSON.",[938,29401,29402],{},[35,29403,29404,29406,29407,29412],{},[200,29405,944],{},"\nL'utilisation d'un Web Worker permet à l'éditeur Monaco d'exécuter un serveur ",[39,29408,29411],{"href":29409,"rel":29410},"https:\u002F\u002Fmicrosoft.github.io\u002Flanguage-server-protocol\u002F",[77],"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.",[51,29414,18647],{"id":18646},[35,29416,29417],{},"Mais voici le hic :",[35,29419,29420],{},"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.",[938,29422,29423],{},[35,29424,29425,29428],{},[200,29426,29427],{},"!WARNING","\nC'est une mesure de sécurité pour prévenir les attaques d'origine croisée, impossible à contourner.",[35,29430,29431,29432,1655,29435,29438],{},"Une origine est définie par le schéma, l'hôte et le port d'une URL. Cela signifie que ",[156,29433,29434],{},"https:\u002F\u002Fexample.com",[156,29436,29437],{},"https:\u002F\u002Fexample.com:1024"," sont considérés comme des origines différentes.",[35,29440,29441,29442,29444,29445,215],{},"Imaginez que nous sommes sur la page ",[156,29443,29434],{}," et que vous souhaitez créer un Web Worker. L'URL du script passée au Web Worker doit être accessible depuis ",[156,29446,29434],{},[35,29448,29449],{},"Cela signifie que cela fonctionne :",[192,29451,29453],{"className":590,"code":29452,"language":592,"meta":13,"style":13},"\u003Cscript>\n  const worker = new Worker('worker.js')\n\u003C\u002Fscript>\n",[156,29454,29455,29463,29485],{"__ignoreMap":13},[200,29456,29457,29459,29461],{"class":202,"line":203},[200,29458,599],{"class":214},[200,29460,14896],{"class":235},[200,29462,647],{"class":214},[200,29464,29465,29467,29469,29471,29473,29475,29477,29479,29481,29483],{"class":202,"line":14},[200,29466,3738],{"class":379},[200,29468,29265],{"class":210},[200,29470,449],{"class":214},[200,29472,13007],{"class":214},[200,29474,29272],{"class":223},[200,29476,227],{"class":210},[200,29478,249],{"class":214},[200,29480,29126],{"class":245},[200,29482,249],{"class":214},[200,29484,416],{"class":210},[200,29486,29487,29489,29491],{"class":202,"line":255},[200,29488,680],{"class":214},[200,29490,14896],{"class":235},[200,29492,647],{"class":214},[35,29494,29495],{},"Mais cela ne fonctionne pas :",[192,29497,29499],{"className":590,"code":29498,"language":592,"meta":13,"style":13},"\u003Cscript>\n  const worker = new Worker('https:\u002F\u002Fanother-origin.example.com\u002Fworker.js')\n\u003C\u002Fscript>\n",[156,29500,29501,29509,29532],{"__ignoreMap":13},[200,29502,29503,29505,29507],{"class":202,"line":203},[200,29504,599],{"class":214},[200,29506,14896],{"class":235},[200,29508,647],{"class":214},[200,29510,29511,29513,29515,29517,29519,29521,29523,29525,29528,29530],{"class":202,"line":14},[200,29512,3738],{"class":379},[200,29514,29265],{"class":210},[200,29516,449],{"class":214},[200,29518,13007],{"class":214},[200,29520,29272],{"class":223},[200,29522,227],{"class":210},[200,29524,249],{"class":214},[200,29526,29527],{"class":245},"https:\u002F\u002Fanother-origin.example.com\u002Fworker.js",[200,29529,249],{"class":214},[200,29531,416],{"class":210},[200,29533,29534,29536,29538],{"class":202,"line":255},[200,29535,680],{"class":214},[200,29537,14896],{"class":235},[200,29539,647],{"class":214},[35,29541,29542],{},"Voyez-vous maintenant le problème ?",[35,29544,29545,29546,29549,29550,106,29553,1655,29555,29557],{},"Notre serveur Laravel fonctionne sur ",[156,29547,29548],{},"http:\u002F\u002Flocalhost:8000",", tandis que Vite est sur ",[156,29551,29552],{},"http:\u002F\u002Flocalhost:5173",[156,29554,29548],{},[156,29556,29552],{}," 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.",[35,29559,29560],{},"Dans le navigateur, nous pouvons facilement repérer cette erreur avec le message d'avertissement suivant :",[192,29562,29564],{"className":1354,"code":29563,"language":1357,"meta":13,"style":13},"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",[156,29565,29566],{"__ignoreMap":13},[200,29567,29568,29571,29574,29577,29580,29583,29585,29587,29590,29592,29594,29597,29600,29603,29605,29608,29610,29613,29616,29619,29622,29625,29628,29631,29633,29636,29638,29641,29643,29645],{"class":202,"line":203},[200,29569,29570],{"class":1364},"app.js:21",[200,29572,29573],{"class":245}," Échec",[200,29575,29576],{"class":245}," de",[200,29578,29579],{"class":245}," la",[200,29581,29582],{"class":245}," construction",[200,29584,29576],{"class":245},[200,29586,242],{"class":214},[200,29588,29589],{"class":245},"Worker",[200,29591,249],{"class":214},[200,29593,2186],{"class":245},[200,29595,29596],{"class":245}," le",[200,29598,29599],{"class":245}," script",[200,29601,29602],{"class":245}," à",[200,29604,242],{"class":214},[200,29606,29607],{"class":245},"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",[200,29609,249],{"class":214},[200,29611,29612],{"class":245}," ne",[200,29614,29615],{"class":245}," peut",[200,29617,29618],{"class":245}," pas",[200,29620,29621],{"class":245}," être",[200,29623,29624],{"class":245}," accédé",[200,29626,29627],{"class":245}," depuis",[200,29629,29630],{"class":245}," l",[200,29632,249],{"class":214},[200,29634,29635],{"class":245},"origine ",[200,29637,249],{"class":214},[200,29639,29640],{"class":245},"http:\u002F\u002Flocalhost",[200,29642,2186],{"class":245},[200,29644,249],{"class":214},[200,29646,29647],{"class":245},".\n",[35,29649,29650],{},"Cette erreur est un frein. L'éditeur Monaco ne peut pas fonctionner correctement sans le script Web Worker. Cherchons une solution.",[35,29652,29653],{},"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.",[35,29655,29656],{},"Le code problématique ressemble à cela :",[192,29658,29660],{"className":1380,"code":29659,"language":1383,"meta":13,"style":13},"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",[156,29661,29662,29683,29699,29715,29719,29733,29751,29764,29777,29788,29795,29806,29810,29814,29818,29822],{"__ignoreMap":13},[200,29663,29664,29666,29669,29671,29674,29676,29678,29681],{"class":202,"line":203},[200,29665,972],{"class":206},[200,29667,29668],{"class":214}," *",[200,29670,14678],{"class":206},[200,29672,29673],{"class":210}," monaco ",[200,29675,1489],{"class":206},[200,29677,242],{"class":214},[200,29679,29680],{"class":245},"monaco-editor",[200,29682,991],{"class":214},[200,29684,29685,29687,29690,29692,29694,29697],{"class":202,"line":14},[200,29686,972],{"class":206},[200,29688,29689],{"class":210}," EditorWorker ",[200,29691,1489],{"class":206},[200,29693,242],{"class":214},[200,29695,29696],{"class":245},"monaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker",[200,29698,991],{"class":214},[200,29700,29701,29703,29706,29708,29710,29713],{"class":202,"line":255},[200,29702,972],{"class":206},[200,29704,29705],{"class":210}," JsonWorker ",[200,29707,1489],{"class":206},[200,29709,242],{"class":214},[200,29711,29712],{"class":245},"monaco-editor\u002Fesm\u002Fvs\u002Flanguage\u002Fjson\u002Fjson.worker?worker",[200,29714,991],{"class":214},[200,29716,29717],{"class":202,"line":272},[200,29718,528],{"emptyLinePlaceholder":20},[200,29720,29721,29724,29726,29729,29731],{"class":202,"line":283},[200,29722,29723],{"class":210},"globalThis",[200,29725,215],{"class":214},[200,29727,29728],{"class":210},"MonacoEnvironment ",[200,29730,449],{"class":214},[200,29732,280],{"class":214},[200,29734,29735,29738,29740,29743,29745,29747,29749],{"class":202,"line":300},[200,29736,29737],{"class":235},"  getWorker",[200,29739,227],{"class":214},[200,29741,29742],{"class":386},"workerId",[200,29744,549],{"class":214},[200,29746,21516],{"class":386},[200,29748,2104],{"class":214},[200,29750,280],{"class":214},[200,29752,29753,29756,29758,29760,29762],{"class":202,"line":310},[200,29754,29755],{"class":206},"    switch",[200,29757,1094],{"class":235},[200,29759,659],{"class":210},[200,29761,5282],{"class":235},[200,29763,230],{"class":214},[200,29765,29766,29769,29771,29773,29775],{"class":202,"line":336},[200,29767,29768],{"class":206},"      case",[200,29770,242],{"class":214},[200,29772,28360],{"class":245},[200,29774,249],{"class":214},[200,29776,19494],{"class":214},[200,29778,29779,29781,29783,29786],{"class":202,"line":342},[200,29780,13413],{"class":206},[200,29782,13007],{"class":214},[200,29784,29785],{"class":223}," JsonWorker",[200,29787,466],{"class":235},[200,29789,29790,29793],{"class":202,"line":365},[200,29791,29792],{"class":206},"      default",[200,29794,19494],{"class":214},[200,29796,29797,29799,29801,29804],{"class":202,"line":371},[200,29798,13413],{"class":206},[200,29800,13007],{"class":214},[200,29802,29803],{"class":223}," EditorWorker",[200,29805,466],{"class":235},[200,29807,29808],{"class":202,"line":398},[200,29809,4977],{"class":214},[200,29811,29812],{"class":202,"line":405},[200,29813,368],{"class":214},[200,29815,29816],{"class":202,"line":410},[200,29817,1129],{"class":214},[200,29819,29820],{"class":202,"line":860},[200,29821,528],{"emptyLinePlaceholder":20},[200,29823,29824],{"class":202,"line":889},[200,29825,29826],{"class":401},"\u002F\u002F Initialisation de l'éditeur Monaco et fonctionnalité de diagnostic\n",[35,29828,29829],{},"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.",[938,29831,29832],{},[35,29833,29834,29836,29837,29842],{},[200,29835,944],{},"\nDans des frameworks comme ",[39,29838,29841],{"href":29839,"rel":29840},"https:\u002F\u002Fadonisjs.com\u002F",[77],"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.",[938,29844,29845],{},[35,29846,29847,29849,29850,29853],{},[200,29848,944],{},"\nCe problème n'existe pas si les actifs sont construits et chargés depuis ",[156,29851,29852],{},"\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.",[51,29855,29857],{"id":29856},"la-solution","La Solution",[35,29859,29860],{},"Nous avons maintenant une compréhension claire du problème et de l'origine de celui-ci. Plongeons dans la solution.",[35,29862,29863],{},"Tout d'abord, examinons l'URL du script worker :",[192,29865,29867],{"className":4292,"code":29866,"language":4294,"meta":13,"style":13},"monaco-editor\u002Fesm\u002Fvs\u002Feditor\u002Feditor.worker?worker\n",[156,29868,29869],{"__ignoreMap":13},[200,29870,29871],{"class":202,"line":203},[200,29872,29866],{},[35,29874,29875,29876,29879],{},"C'est un script worker - pas destiné à un chargement direct dans le navigateur. Remarquez le paramètre de requête ",[156,29877,29878],{},"?worker",", une indication de Vite pour encapsuler le script pour un usage facile. Nous pouvons inspecter la réponse pour mieux comprendre.",[192,29881,29883],{"className":25070,"code":29882,"language":25072,"meta":13,"style":13},"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",[156,29884,29885,29905,29915,29926,29930,29945,29958,29962,29967],{"__ignoreMap":13},[200,29886,29887,29889,29891,29893,29896,29898,29901,29903],{"class":202,"line":203},[200,29888,1390],{"class":206},[200,29890,1393],{"class":206},[200,29892,5002],{"class":379},[200,29894,29895],{"class":223}," WorkerWrapper",[200,29897,227],{"class":214},[200,29899,29900],{"class":386},"options",[200,29902,2104],{"class":214},[200,29904,280],{"class":214},[200,29906,29907,29909,29911,29913],{"class":202,"line":14},[200,29908,3758],{"class":206},[200,29910,13007],{"class":214},[200,29912,29272],{"class":223},[200,29914,3130],{"class":235},[200,29916,29917,29919,29922,29924],{"class":202,"line":255},[200,29918,1415],{"class":214},[200,29920,29921],{"class":245},"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",[200,29923,249],{"class":214},[200,29925,252],{"class":214},[200,29927,29928],{"class":202,"line":272},[200,29929,19661],{"class":214},[200,29931,29932,29935,29937,29939,29941,29943],{"class":202,"line":283},[200,29933,29934],{"class":235},"      type",[200,29936,239],{"class":214},[200,29938,242],{"class":214},[200,29940,15097],{"class":245},[200,29942,249],{"class":214},[200,29944,252],{"class":214},[200,29946,29947,29949,29951,29953,29955],{"class":202,"line":300},[200,29948,2068],{"class":235},[200,29950,239],{"class":214},[200,29952,9842],{"class":210},[200,29954,6536],{"class":214},[200,29956,29957],{"class":210},"name\n",[200,29959,29960],{"class":202,"line":310},[200,29961,4977],{"class":214},[200,29963,29964],{"class":202,"line":336},[200,29965,29966],{"class":235},"  )\n",[200,29968,29969],{"class":202,"line":342},[200,29970,1129],{"class":214},[35,29972,29973,29974,29976,29977,29979,29980,29982],{},"Grâce au paramètre de requête ",[156,29975,29878],{},", Vite encapsule automatiquement le script dans une fonction qui retourne une nouvelle instance de ",[156,29978,29589],{},". 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 ",[156,29981,29589],{}," avec l'URL et les options correctes.",[35,29984,29985],{},"Le worker utilise le script suivant :",[192,29987,29989],{"className":4292,"code":29988,"language":4294,"meta":13,"style":13},"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",[156,29990,29991],{"__ignoreMap":13},[200,29992,29993],{"class":202,"line":203},[200,29994,29988],{},[35,29996,29997,29998,30000],{},"L'origine ",[156,29999,29552],{}," appartient au serveur Vite et c'est la racine du problème.",[35,30002,30003],{},"Pour réussir, l'URL du script doit correspondre à l'origine de notre serveur Laravel :",[192,30005,30007],{"className":4292,"code":30006,"language":4294,"meta":13,"style":13},"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",[156,30008,30009],{"__ignoreMap":13},[200,30010,30011],{"class":202,"line":203},[200,30012,30006],{},[35,30014,30015],{},"Mais comment modifier l'URL dynamiquement pour qu'elle corresponde à l'origine du serveur Laravel ?",[35,30017,30018],{},"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.",[35,30020,30021,30022,30024],{},"Dans notre ",[156,30023,1477],{},", nous pouvons définir un plugin pour réécrire l'URL du script worker :",[192,30026,30028],{"className":1380,"code":30027,"language":1383,"meta":13,"style":13},"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",[156,30029,30030,30048,30052,30064,30072,30082,30088,30103,30120,30150,30155,30160,30164,30188,30201,30213,30224,30229,30233,30237,30241,30250,30256],{"__ignoreMap":13},[200,30031,30032,30034,30036,30038,30040,30042,30044,30046],{"class":202,"line":203},[200,30033,972],{"class":206},[200,30035,318],{"class":214},[200,30037,1505],{"class":210},[200,30039,980],{"class":214},[200,30041,983],{"class":206},[200,30043,242],{"class":214},[200,30045,1514],{"class":245},[200,30047,991],{"class":214},[200,30049,30050],{"class":202,"line":14},[200,30051,528],{"emptyLinePlaceholder":20},[200,30053,30054,30056,30058,30060,30062],{"class":202,"line":255},[200,30055,1390],{"class":206},[200,30057,1393],{"class":206},[200,30059,1505],{"class":223},[200,30061,227],{"class":210},[200,30063,230],{"class":214},[200,30065,30066,30068,30070],{"class":202,"line":272},[200,30067,1537],{"class":235},[200,30069,239],{"class":214},[200,30071,1410],{"class":210},[200,30073,30074,30076,30078,30080],{"class":202,"line":283},[200,30075,11719],{"class":210},[200,30077,1549],{"class":214},[200,30079,393],{"class":379},[200,30081,280],{"class":214},[200,30083,30084,30086],{"class":202,"line":300},[200,30085,11730],{"class":206},[200,30087,280],{"class":214},[200,30089,30090,30092,30094,30096,30099,30101],{"class":202,"line":310},[200,30091,11737],{"class":235},[200,30093,239],{"class":214},[200,30095,242],{"class":214},[200,30097,30098],{"class":245},"monaco-editor:rewrite-worker",[200,30100,249],{"class":214},[200,30102,252],{"class":214},[200,30104,30105,30108,30110,30112,30114,30116,30118],{"class":202,"line":336},[200,30106,30107],{"class":235},"        transform",[200,30109,227],{"class":214},[200,30111,156],{"class":386},[200,30113,549],{"class":214},[200,30115,3239],{"class":386},[200,30117,2104],{"class":214},[200,30119,280],{"class":214},[200,30121,30122,30125,30127,30129,30132,30134,30137,30139,30141,30144,30146,30148],{"class":202,"line":342},[200,30123,30124],{"class":206},"          if",[200,30126,1094],{"class":235},[200,30128,4741],{"class":214},[200,30130,30131],{"class":210},"environment",[200,30133,215],{"class":214},[200,30135,30136],{"class":210},"mode",[200,30138,13398],{"class":214},[200,30140,242],{"class":214},[200,30142,30143],{"class":245},"dev",[200,30145,249],{"class":214},[200,30147,5282],{"class":235},[200,30149,230],{"class":214},[200,30151,30152],{"class":202,"line":365},[200,30153,30154],{"class":206},"            return\n",[200,30156,30157],{"class":202,"line":371},[200,30158,30159],{"class":214},"          }\n",[200,30161,30162],{"class":202,"line":398},[200,30163,528],{"emptyLinePlaceholder":20},[200,30165,30166,30168,30170,30172,30174,30176,30178,30180,30182,30184,30186],{"class":202,"line":405},[200,30167,30124],{"class":206},[200,30169,1094],{"class":235},[200,30171,3295],{"class":210},[200,30173,215],{"class":214},[200,30175,3856],{"class":223},[200,30177,227],{"class":235},[200,30179,249],{"class":214},[200,30181,29291],{"class":245},[200,30183,249],{"class":214},[200,30185,6478],{"class":235},[200,30187,230],{"class":214},[200,30189,30190,30193,30195,30197,30199],{"class":202,"line":410},[200,30191,30192],{"class":206},"            return",[200,30194,4179],{"class":210},[200,30196,215],{"class":214},[200,30198,6148],{"class":223},[200,30200,3130],{"class":235},[200,30202,30203,30206,30209,30211],{"class":202,"line":860},[200,30204,30205],{"class":214},"              '",[200,30207,30208],{"class":245},"__laravel_vite_placeholder__.test",[200,30210,249],{"class":214},[200,30212,252],{"class":214},[200,30214,30215,30217,30220,30222],{"class":202,"line":889},[200,30216,30205],{"class":214},[200,30218,30219],{"class":245},"localhost:8000",[200,30221,249],{"class":214},[200,30223,252],{"class":214},[200,30225,30226],{"class":202,"line":899},[200,30227,30228],{"class":235},"            )\n",[200,30230,30231],{"class":202,"line":904},[200,30232,30159],{"class":214},[200,30234,30235],{"class":202,"line":930},[200,30236,4965],{"class":214},[200,30238,30239],{"class":202,"line":3655},[200,30240,4493],{"class":214},[200,30242,30243,30245,30248],{"class":202,"line":3660},[200,30244,2111],{"class":214},[200,30246,30247],{"class":210},")()",[200,30249,252],{"class":214},[200,30251,30252,30254],{"class":202,"line":4035},[200,30253,1427],{"class":210},[200,30255,252],{"class":214},[200,30257,30258,30260],{"class":202,"line":4490},[200,30259,413],{"class":214},[200,30261,416],{"class":210},[938,30263,30264],{},[35,30265,30266,30268,30269,30271,30272,30274],{},[200,30267,944],{},"\nIci, nous remplaçons ",[156,30270,30208],{}," au lieu de ",[156,30273,29552],{},", car cette URL est généralement gérée par le plugin Laravel Vite.",[35,30276,30277],{},"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.",[35,30279,30280],{},"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.",[35,30282,30283],{},"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.",[35,30285,30286],{},"Cela semble viable, faisable et suffisamment élégant. Implémentons-le.",[35,30288,30289,30290,2873,30293,30296,30297,30299],{},"Nous pouvons créer une route Laravel, disponible uniquement en développement, pour proxy les demandes vers le serveur Vite. Créons un fichier ",[156,30291,30292],{},"proxy.dev.php",[156,30294,30295],{},"routes"," de Laravel et incluons-le dans ",[156,30298,26630],{}," uniquement durant le développement.",[192,30301,30303],{"className":19560,"code":30302,"language":19563,"meta":13,"style":13},"if (!app()->isProduction()) {\n    require __DIR__ . '\u002Fproxy.dev.php';\n}\n",[156,30304,30305,30310,30315],{"__ignoreMap":13},[200,30306,30307],{"class":202,"line":203},[200,30308,30309],{},"if (!app()->isProduction()) {\n",[200,30311,30312],{"class":202,"line":14},[200,30313,30314],{},"    require __DIR__ . '\u002Fproxy.dev.php';\n",[200,30316,30317],{"class":202,"line":255},[200,30318,1129],{},[35,30320,30321,30322,2186],{},"Ensuite, définissons notre route proxy dans ",[156,30323,30292],{},[192,30325,30327],{"className":19560,"code":30326,"language":19563,"meta":13,"style":13},"\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",[156,30328,30329,30333,30337,30341,30345,30350,30355,30360,30365,30370,30375,30380,30388,30393],{"__ignoreMap":13},[200,30330,30331],{"class":202,"line":203},[200,30332,19600],{},[200,30334,30335],{"class":202,"line":14},[200,30336,528],{"emptyLinePlaceholder":20},[200,30338,30339],{"class":202,"line":255},[200,30340,19575],{},[200,30342,30343],{"class":202,"line":272},[200,30344,528],{"emptyLinePlaceholder":20},[200,30346,30347],{"class":202,"line":283},[200,30348,30349],{},"Route::get('\u002Fnode_modules\u002F{any}', function ($any) {\n",[200,30351,30352],{"class":202,"line":300},[200,30353,30354],{},"    $url = \"http:\u002F\u002Flocalhost:5173\u002Fnode_modules\u002F{$any}\";\n",[200,30356,30357],{"class":202,"line":310},[200,30358,30359],{},"    return response()->stream(function () use ($url) {\n",[200,30361,30362],{"class":202,"line":336},[200,30363,30364],{},"        \u002F\u002F Directly stream the remote content without loading it fully into memory\n",[200,30366,30367],{"class":202,"line":342},[200,30368,30369],{},"        readfile($url);\n",[200,30371,30372],{"class":202,"line":365},[200,30373,30374],{},"    }, 200, [\n",[200,30376,30377],{"class":202,"line":371},[200,30378,30379],{},"      'Content-Type' => 'text\u002Fjavascript'\n",[200,30381,30382,30385],{"class":202,"line":398},[200,30383,30384],{},"      'Cache-Control' => 'public, max-age=86400'",[200,30386,30387],{}," \u002F\u002F Cache for 1 day\n",[200,30389,30390],{"class":202,"line":405},[200,30391,30392],{},"    ]);\n",[200,30394,30395],{"class":202,"line":410},[200,30396,30397],{},"})->where('any', '(.*)');\n",[938,30399,30400],{},[35,30401,30402,30404],{},[200,30403,944],{},"\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.",[35,30406,30407],{},"Et voilà ! Le script worker se charge, et l'éditeur Monaco fonctionne parfaitement.",[35,30409,30410],{},[1707,30411],{"alt":30412,"src":30413},"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",[35,30415,30416],{},"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.",[35,30418,30419],{},"Voici un aperçu de la solution :",[35,30421,30422],{},[1707,30423],{"alt":30424,"src":30425},"Architecture de la solution","\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin\u002Flarave-proxying-to-vite.png",[51,30427,24146],{"id":24145},[35,30429,30430],{},"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 !",[35,30432,30433,30434,30436],{},"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 ",[6862,30435,24399],{}," puissent sembler intimidants, les comprendre supprime toutes les limites vous permettant de résoudre des problèmes complexes.",[1170,30438,30439],{},"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":13,"searchDepth":14,"depth":14,"links":30441},[30442,30443,30444,30445,30446],{"id":28967,"depth":14,"text":28968},{"id":29077,"depth":14,"text":29078},{"id":18646,"depth":14,"text":18647},{"id":29856,"depth":14,"text":29857},{"id":24145,"depth":14,"text":24146},"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.",{},"\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",{"title":28938,"description":30448},{"loc":30450,"images":30454},[30455,30456,30457,30458],{"loc":29008},{"loc":29096},{"loc":30413},{"loc":30425},"fr\u002Fposts\u002Flaravel-and-vite-a-love-story-ruined-with-cross-origin","n2DUfpuXwLM9qHcN-X1xUdh2kw0i4npVTuO94dDhDQ4",{"id":30462,"title":30463,"body":30464,"contentId":8795,"date":30938,"description":30939,"extension":18,"meta":30940,"navigation":20,"path":30941,"rawbody":30942,"robots":23,"schemaOrg":23,"seo":30943,"sitemap":30944,"stem":30953,"__hash__":30954},"posts\u002Ffr\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next.md","2024 : Réflexions sur une Année de Changement et la Suite",{"type":7,"value":30465,"toc":30927},[30466,30469,30481,30484,30489,30492,30496,30511,30522,30528,30531,30535,30556,30565,30572,30575,30581,30590,30593,30605,30612,30619,30621,30627,30640,30646,30657,30663,30679,30685,30692,30703,30709,30716,30722,30726,30736,30747,30751,30767,30773,30776,30780,30783,30795,30805,30815,30831,30840,30846,30849,30853,30856,30876,30890,30896,30909,30912,30915,30918,30921],[35,30467,30468],{},"Salut tout le monde ! 👋",[35,30470,30471,30472,30475,30476,30480],{},"Cet article sera un peu différent des articles techniques habituels. Je veux prendre un moment pour réfléchir sur ",[6862,30473,30474],{},"2024"," et partager quelques pensées sur ce qui vient ensuite. C'est définitivement un article personnel, mais pour moi, c'est aussi un moyen de suivre mon parcours et de voir comment j'évolue au fil du temps. L'année dernière, j'ai écrit ",[39,30477,30479],{"href":8798,"rel":30478},[77],"un article similaire",", et j'aime le relire de temps en temps pour voir à quel point les choses ont évolué.",[35,30482,30483],{},"J'ai terminé l'article de l'année dernière avec ces mots :",[938,30485,30486],{},[35,30487,30488],{},"Pour 2024, je continue ma lancée, et de nouvelles choses sont en préparation à partir de février avec mon départ pour la Suisse et peut-être un Devoxx 2024 !",[35,30490,30491],{},"Voyons comment cela s'est passé.",[51,30493,30495],{"id":30494},"suisse","Suisse",[35,30497,30498,30499,30501,30502,30504,30505,30507,30508,30510],{},"Pour ma dernière année d'études d'ingénieur, j'ai eu l'opportunité de faire un stage chez ",[6862,30500,21156],{}," à Genève, ",[6862,30503,30495],{},". Pendant six mois, j'ai travaillé sur la bibliothèque de composants interne avec ",[6862,30506,22922],{},". Malgré l'incroyable équipe et l'entreprise, je dois admettre que travailler avec ",[6862,30509,22922],{}," n'a pas toujours été agréable.",[35,30512,30513,30514,14181,30517,13741,30519,30521],{},"Au cours des trois dernières années, Angular a introduit de nombreuses nouvelles fonctionnalités, améliorations et méthodologies. Bien que celles-ci soient excellentes, il n'y a pas beaucoup d'explications sur la façon de les appliquer dans des projets réels. Pendant ce temps, la bibliothèque de composants sur laquelle je travaillais a été construite de toutes pièces pour migrer de Material vers un système personnalisé. C'est une tâche significative et quelque peu frustrante quand on voit de nombreuses bibliothèques ",[1216,30515,30516],{},"headless",[6862,30518,18003],{},[6862,30520,17998],{},". Néanmoins, j'ai beaucoup appris et je suis reconnaissant pour cette expérience. Savoir comment construire un composant est essentiel pour tout projet frontend, quel que soit le projet.",[35,30523,30524],{},[1707,30525],{"alt":30526,"src":30527},"Moi devant Genève.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fgeneva.jpeg",[35,30529,30530],{},"Pendant ce stage, j'ai eu beaucoup de temps libre pour travailler sur mes projets personnels.",[1929,30532,30534],{"id":30533},"nouveau-blog","Nouveau Blog",[35,30536,30537,30538,30541,30542,30547,30548,30551,30552,30555],{},"Au début de l'année, j'ai écrit quelques articles pour le site ",[6862,30539,30540],{},"UnJS",". J'étais ravi de les partager avec la communauté, mais malheureusement, je n'ai pas eu la chance de les publier. À l'époque, mon blog était sur ",[39,30543,30546],{"href":30544,"rel":30545},"https:\u002F\u002Festeban-soubiran.site",[77],"esteban-soubiran.site"," et uniquement disponible en ",[6862,30549,30550],{},"français",". Je voulais écrire en ",[6862,30553,30554],{},"anglais"," depuis longtemps, et j'ai reçu de nombreux retours à ce sujet après mes articles sur les paquets UnJS.",[35,30557,30558,30559,30561,30562],{},"J'ai donc décidé de créer un nouveau blog avec un nouveau nom de domaine, en ",[6862,30560,30554],{},", et avec un nouveau design axé sur la simplicité pour faciliter l'écriture. J'ai détaillé le processus dans cet article ",[3045,30563,215],{"id":30564},"a06fbfe1-6283-4eed-b3ec-e1d454613b9a",[35,30566,30567,30568],{},"J'ai publié mon premier article, ",[3045,30569,30571],{"id":30570},"aa8454be-94ba-4d46-8be0-8cf703c0bfed",", le 19 mars, mais au départ, mon blog se composait seulement d'un seul composant pour vous permettre de lire l'article. Il n'y avait pas de page d'accueil, pas de menu, rien.",[1929,30573,18581],{"id":30574},"twitch",[35,30576,30577,30578,30580],{},"J'ai également commencé à streamer sur ",[6862,30579,18581],{},". Cela me trottait dans la tête depuis longtemps, mais avoir un environnement propice était un obstacle. Ma chambre pendant mon stage était parfaite : calme avec une bonne connexion internet.",[35,30582,30583,30584,30586,30587],{},"J'ai commencé quelques streams, et ensemble, nous avons construit une application ",[6862,30585,2934],{}," full-stack avec NuxtHub, sur laquelle j'ai écrit tout un article, ",[3045,30588,215],{"id":30589},"2349bbcd-1535-4f33-8da7-299f46af18b9",[35,30591,30592],{},"Nous avons beaucoup appris ensemble, en particulier sur le développement full-stack avec Nuxt. J'ai partagé tout cela dans deux articles :",[1593,30594,30595,30600],{},[71,30596,30597],{},[3045,30598],{"id":30599},"8c077ba8-63ca-4784-b693-0e1fa8623bf9",[71,30601,30602],{},[3045,30603],{"id":30604},"05da35d9-c21f-4b45-ba86-e88d61339502",[35,30606,30607,30608,215],{},"Cela a conduit à la création d'un paquet appelé ",[39,30609,9234],{"href":30610,"rel":30611},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fnuxt-authorization",[77],[35,30613,30614,30615,30618],{},"C'était définitivement une excellente expérience que j'ai dû mettre en pause parce que je suis passé sur un ",[6862,30616,30617],{},"MacBook Air",", qui n'avait pas une puissance graphique suffisante pour streamer. Mais en 2025, je prévois d'acheter un deuxième ordinateur pour reprendre le streaming.",[51,30620,8958],{"id":8957},[35,30622,30623,30624,30626],{},"En 2023, j'ai parlé pour la première fois à ",[6862,30625,7154],{},". En 2024, j'ai eu l'opportunité de prendre la parole six fois.",[35,30628,30629,30630,30633,30634,30636,30637],{},"En mars, ",[6862,30631,30632],{},"StrasbourgJS"," m'a invité à parler de l'écosystème ",[6862,30635,30540],{},". C'était vraiment super de partager ce savoir et de rencontrer enfin certains membres de la communauté comme Romain Lanz, Michel et Thomas. Ce fut une excellente préparation pour ma prochaine intervention à Devoxx France. J'ai écrit un article à ce sujet, ",[3045,30638,215],{"id":30639},"ba562d1f-534c-4cef-9004-2fefc09829a6",[35,30641,30642],{},[1707,30643],{"alt":30644,"src":30645},"Équipe et Intervenants de StrasbourgJS.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fstrasbourgjs.jpeg",[35,30647,30648,30649,30651,30652,30654,30655],{},"Pour la deuxième fois, j'ai eu le privilège de parler à ",[6862,30650,7154],{},". Cette fois, j'ai abordé les paquets ",[6862,30653,30540],{}," lors d'un quickie, un format très exigeant car il faut être très concis et clair. J'étais satisfait du résultat et j'ai reçu beaucoup de retours positifs. J'ai écrit un article à ce sujet, ",[3045,30656,215],{"id":20859},[35,30658,30659],{},[1707,30660],{"alt":30661,"src":30662},"Moi à Devoxx France.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fdevoxx-france.png",[35,30664,30665,30666,30669,30670,30672,30673,30675,30676],{},"En septembre, j'ai parlé à la ",[6862,30667,30668],{},"Prague Vue Conference",". C'était ma première conférence en anglais, donc je me suis bien préparé et entraîné pour être prêt. J'ai discuté des capacités full-stack de ",[6862,30671,2934],{}," avec NuxtHub. J'ai également rencontré des figures notables de la communauté ",[6862,30674,17998],{},", comme Daniel Roe et Alexander Lichter. J'ai documenté l'expérience dans un article, ",[3045,30677,215],{"id":30678},"74e76e2e-0d34-46a3-826f-efcab800ec8a",[35,30680,30681],{},[1707,30682],{"alt":30683,"src":30684},"Équipe et Intervenants de PragVue.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fpragvue.jpg",[35,30686,30687,30688,30691],{},"En novembre, j'ai parlé à ",[6862,30689,30690],{},"NuxtNations",", une fois de plus sur les capacités full-stack de Nuxt. C'était une conférence en ligne, mais j'étais certainement un peu anxieux avant ma présentation.",[35,30693,30694,30695,30698,30699,30702],{},"Puis, j'ai poursuivi avec une intervention lors du meetup ",[6862,30696,30697],{},"TakiMeet",". Il est organisé par une entreprise à Paris, où j'ai parlé de l'expérience utilisateur et de l'",[6862,30700,30701],{},"UI"," optimiste. Entre les deux conférences, je n'avais qu'une semaine pour me préparer, terminant mes diapositives seulement quelques heures avant la présentation. Je ne recommande pas même si j'étais satisfait du résultat. Un grand merci à Camille pour l'invitation et à toute l'équipe pour l'organisation.",[35,30704,30705],{},[1707,30706],{"alt":30707,"src":30708},"Le public à TakiMeet pendant ma présentation.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Ftakimeet.jpg",[35,30710,30711,30712,30715],{},"Enfin, j'ai parlé au meetup ",[6862,30713,30714],{},"VueJS Paris"," sur les capacités full-stack de Nuxt. C'était similaire à mes précédentes interventions à Prague et à NuxtNations, mais en français. Les gens étaient très intéressés et curieux à propos de cette technologie émergente. Merci à Elise et Eduardo pour l'invitation et l'organisation.",[35,30717,30718],{},[1707,30719],{"alt":30720,"src":30721},"Le public à VueJS Paris pendant ma présentation.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fvuejs-paris.jpg",[51,30723,30725],{"id":30724},"fin-de-mes-études-et-mon-nouveau-poste","Fin de Mes Études et Mon Nouveau Poste",[35,30727,30728,30729,30732,30733],{},"J'ai terminé mes études d'ingénieur en septembre avec un stage en Suisse, et j'ai obtenu un diplôme en ",[6862,30730,30731],{},"sécurité informatique",". Six années d'études, deux stages, une année de césure et de nombreux projets, associations et expériences. Ce fut un incroyable parcours où j'ai rencontré beaucoup de personnes formidables. J'ai écrit un article à ce sujet, ",[3045,30734,215],{"id":30735},"b083ef4c-dd4d-4ace-ac09-76b5231c35bc",[35,30737,30738,30739,30742,30743,30746],{},"Aujourd'hui, je suis ravi d'avoir terminé ce chapitre de ma vie et de commencer un nouveau. Je suis maintenant ingénieur logiciel à temps plein chez ",[6862,30740,30741],{},"MaiaSpace",", une entreprise française qui conçoit, fabrique, exploite et commercialise des solutions compétitives et durables pour le ",[6862,30744,30745],{},"lancement et la mobilité spatiale",". Je n'aurais jamais imaginé travailler dans l'industrie spatiale, surtout en tant que développeur web. C'est une opportunité unique que je ne pouvais pas laisser passer, mais aussi un défi important avec beaucoup à apprendre. J'ai hâte du lancement en 2026 (même si j'ai encore le temps, car un autre récapitulatif sera écrit entre-temps). 🧑‍🚀",[51,30748,30750],{"id":30749},"apprentissage-toujours-apprendre","Apprentissage, Toujours Apprendre",[35,30752,30753,30754,30756,30757,3092,30760,1655,30763,30766],{},"Cela fait maintenant six ans que j'apprends le ",[6862,30755,21389],{},". Au départ, j'ai beaucoup appris sur ",[6862,30758,30759],{},"HTML",[6862,30761,30762],{},"CSS",[6862,30764,30765],{},"JavaScript",", mais ces dernières années, j'ai quelque peu stagné et je n'étais pas totalement satisfait de cela. Je voulais construire une application full-stack par moi-même et comprendre tous ses composants.",[35,30768,30769,30770,30772],{},"Donc, en juin, j'ai commencé à apprendre ",[6862,30771,19463],{}," grâce à l'excellente plateforme Laracasts. Ça a changé ma vie. J'ai tellement appris en peu de temps. Je ne me suis jamais autant amusé à coder et à créer des applications. Cette décision m'a aidé à obtenir mon poste chez MaiaSpace.",[35,30774,30775],{},"C'est peut-être le plus grand changement de ma vie en 2024 et probablement ma meilleure décision.",[51,30777,30779],{"id":30778},"mon-blog","Mon Blog",[35,30781,30782],{},"Je l'ai mentionné, mais j'ai créé un nouveau blog technique en mars 2024 avec une idée en tête : écrire des articles que j'aurais adoré lire en commençant mon parcours de développement web. Je voulais que mes articles soient clairs, techniques et complets. J'en ai assez de tous ces articles accrocheurs sur Medium ou Dev Community qui manquent de structure et où le contenu est aussi basique que la documentation.",[35,30784,30785,30786,3092,30788,3092,30790,9502,30792,30794],{},"Avec cette ligne directrice, j'ai écrit plusieurs articles sur ",[6862,30787,2934],{},[6862,30789,17998],{},[6862,30791,30540],{},[6862,30793,19463],{},". Pour moi, écrire est un moyen de solidifier mes connaissances et de m'assurer que je comprends vraiment le sujet. J'aime aussi partager mes connaissances avec la communauté et aider les gens à apprendre des choses nouvelles et utiles.",[35,30796,30797,30798,30800,30801],{},"En octobre, j'ai décidé d'écrire sur ",[6862,30799,2929],{}," et sa personnalisabilité. Le concept d'un article de blog ne me comblait plus, et je voulais créer une série d'articles. J'ai introduit le concept de séries sur mon blog avec la série ",[3045,30802,30804],{"id":30803},"f2681a44-f9bf-4ff7-840a-37f7729eea41",". Ces séries sont parfaites pour approfondir un sujet et expliquer des concepts complexes de manière simple et actionnable.",[35,30806,30807,30808],{},"En décembre, j'ai publié une deuxième série appelée ",[3045,30809,30811,30812,30814],{"id":30810},"1fafed86-05bb-4de4-b7d0-bb0bd6abc9d2",". Depuis ma première rencontre avec l'écosystème ",[6862,30813,17998],{}," il y a six ans, j'ai toujours voulu trouver quelque chose qui explique en profondeur comment construire une bibliothèque de composants de manière professionnelle, avec tous les outils et techniques utilisés dans l'industrie. Maintenant, c'est fait, et j'en suis très fier !",[35,30816,30817,30818,30821,30822],{},"Tout au long du parcours, j'ai continuellement amélioré mon blog. J'ai atteint un point où j'étais très satisfait, mais il manquait encore quelque chose. Je voulais permettre aux gens d'interagir avec mon blog, de poser des questions, de partager leurs pensées et de donner des retours. J'ai donc décidé de créer une ",[6862,30819,30820],{},"API"," avec Laravel pour faciliter cela. J'ai écrit plusieurs articles à ce sujet, ",[3045,30823,3092,30825],{"id":30824},"9d802f9e-2f40-426f-b636-533b05b8c1a3",[3045,30826,9502,30828],{"id":30827},"c95f7904-8ade-4f11-84a8-99688d26c036",[3045,30829,215],{"id":30830},"17af0246-4d45-45f9-ad9a-57d10c9e883d",[35,30832,30833,30834,30839],{},"Ce n'était pas prévu, mais un thème sombre a également été ajouté grâce à ",[39,30835,30838],{"href":30836,"rel":30837},"https:\u002F\u002Fhrynevych.dev\u002F",[77],"Roman",". Il m'a envoyé un message sur X et a proposé d'ajouter un thème sombre à mon blog. J'ai rapidement accepté, et je dois dire que je le trouve absolument superbe.",[35,30841,30842,30843,30845],{},"Enfin, j'ai lancé mon programme de parrainage sur ",[6862,30844,11636],{}," et à travers mon blog. Ce premier pas est un moyen de me soutenir, moi et mon travail. Je suis très reconnaissant envers ceux qui me soutiennent déjà, et j'ai hâte de voir comment cela évoluera à l'avenir. De grands plans pour ce programme sont en préparation, et je suis impatient de les partager avec vous.",[35,30847,30848],{},"Mon objectif pour 2024 était de publier un article par semaine. J'ai publié 40 articles depuis mars, donc c'est un succès. 🥳",[51,30850,30852],{"id":30851},"ce-qui-vient-ensuite","Ce Qui Vient Ensuite",[35,30854,30855],{},"2024 a été une année de changement, d'apprentissage et de croissance. Je suis fier de mes réalisations et des progrès que j'ai réalisés. Mais je n'ai pas fini. Il y a encore tant à apprendre, à explorer et à créer.",[35,30857,30858,30859,30861,30862,30865,30866,14181,30869,1655,30872,30875],{},"En 2025, je souhaite continuer mon apprentissage de ",[6862,30860,19463],{}," et approfondir encore plus le sujet. Je suis impatient d'explorer des sujets plus avancés et de construire des applications plus complexes. Avec les connaissances que j'ai acquises, je suis confiant de pouvoir relever de nouveaux défis et créer des projets. Construire des applications est un excellent début, mais je veux également en apprendre davantage sur ",[6862,30863,30864],{},"l'infrastructure et le déploiement",". Mon objectif est de déployer mes applications de manière professionnelle en gérant mon ",[6862,30867,30868],{},"VPS",[6862,30870,30871],{},"Ansible",[6862,30873,30874],{},"Terraform",". Je veux surveiller mes applications, résoudre les problèmes en cas de défaillance et les faire évoluer si nécessaire. Cela prendra du temps, mais c'est correct. J'ai l'année qui vient pour apprendre et grandir là-dedans.",[35,30877,30878,30879,30881,30882,30885,30886,30889],{},"Je continuerai à explorer l'écosystème ",[6862,30880,17998],{},". Je suis impatient d'approfondir le ",[6862,30883,30884],{},"3D"," avec Three.js et d'explorer comment cela peut être intégré dans mes projets. J'achète le cours ",[39,30887,8877],{"href":8875,"rel":30888},[77],", et j'ai hâte de plonger dedans. J'aimerais pouvoir partager ce parcours avec vous sur Twitch. C'est l'un des points pour le deuxième semestre de 2025, pouvoir streamer à nouveau.",[35,30891,30892,30893,30895],{},"J'ai beaucoup d'idées pour de nouveaux projets, mais j'aimerais vraiment lancer ",[6862,30894,7894],{},", mon premier SaaS, qui rend vos discours plus engageants avec des interactions en temps réel. Cela m'importe beaucoup, et j'ai hâte de partager plus à ce sujet à l'avenir, peut-être lors de sessions de développement en direct sur Twitch.",[35,30897,30898,30899,30902,30903,1655,30905,30908],{},"Je vais continuer à parler lors de conférences et de meetups. En décembre, j'ai soumis 5 propositions pour ",[6862,30900,30901],{},"Vue Amsterdam",", et j'espère sincèrement être sélectionné. J'ai soumis quelques interventions à des meetups à Paris, et en janvier, je soumettrai quelques interventions à ",[6862,30904,7154],{},[6862,30906,30907],{},"MiXit",". J'espère vraiment que cette année sera aussi riche que 2024. J'adore partager mes connaissances avec la communauté et établir des connexions avec d'autres développeurs. N'hésitez pas à me contacter si vous organisez un événement et souhaitez que je prenne la parole.",[35,30910,30911],{},"Enfin, je vais continuer à écrire des articles et à créer des séries sur mon blog. Je suis impatient d'explorer de nouveaux sujets et de partager des idées avec vous. J'ai des changements excitants prévus pour mon blog afin de le rendre plus interactif et engageant. J'ai hâte de les partager avec vous.",[35,30913,30914],{},"2024 a été une année de changement, de croissance et d'apprentissage. Je suis fier de ce que j'ai accompli et impatient de découvrir ce qui m'attend. Merci de faire partie de cette aventure avec moi.",[35,30916,30917],{},"2025 est une année ambitieuse, mais je n'ai jamais été aussi motivé et confiant qu'aujourd'hui. Ce sera une belle année, et j'ai hâte de la partager avec vous.",[35,30919,30920],{},"Prenez soin de vous et bonne année ! 🫶",[35,30922,30923],{},[1707,30924],{"alt":30925,"src":30926},"Moi en train de skier déguisé pour un mariage.","\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fskiing.jpg",{"title":13,"searchDepth":14,"depth":14,"links":30928},[30929,30933,30934,30935,30936,30937],{"id":30494,"depth":14,"text":30495,"children":30930},[30931,30932],{"id":30533,"depth":255,"text":30534},{"id":30574,"depth":255,"text":18581},{"id":8957,"depth":14,"text":8958},{"id":30724,"depth":14,"text":30725},{"id":30749,"depth":14,"text":30750},{"id":30778,"depth":14,"text":30779},{"id":30851,"depth":14,"text":30852},"2024-12-31","Réflexion sur le parcours transformateur de 2024 et préparation aux défis et explorations futurs. 2025 sera une année d'apprentissage et de croissance.",{},"\u002Ffr\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next","---\ncontentId: 2c1dd2f2-e4eb-483c-aa2e-b8bfb9ffd49c\ntitle: \"2024 : Réflexions sur une Année de Changement et la Suite\"\ndescription: \"Réflexion sur le parcours transformateur de 2024 et préparation aux défis et explorations futurs. 2025 sera une année d'apprentissage et de croissance.\"\ndate: 2024-12-31\n---\n\nSalut tout le monde ! 👋\n\nCet article sera un peu différent des articles techniques habituels. Je veux prendre un moment pour réfléchir sur **2024** et partager quelques pensées sur ce qui vient ensuite. C'est définitivement un article personnel, mais pour moi, c'est aussi un moyen de suivre mon parcours et de voir comment j'évolue au fil du temps. L'année dernière, j'ai écrit [un article similaire](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Frecap-2023), et j'aime le relire de temps en temps pour voir à quel point les choses ont évolué.\n\nJ'ai terminé l'article de l'année dernière avec ces mots :\n\n> Pour 2024, je continue ma lancée, et de nouvelles choses sont en préparation à partir de février avec mon départ pour la Suisse et peut-être un Devoxx 2024 !\n\nVoyons comment cela s'est passé.\n\n## Suisse\n\nPour ma dernière année d'études d'ingénieur, j'ai eu l'opportunité de faire un stage chez **Infomaniak** à Genève, **Suisse**. Pendant six mois, j'ai travaillé sur la bibliothèque de composants interne avec **Angular**. Malgré l'incroyable équipe et l'entreprise, je dois admettre que travailler avec **Angular** n'a pas toujours été agréable.\n\nAu cours des trois dernières années, Angular a introduit de nombreuses nouvelles fonctionnalités, améliorations et méthodologies. Bien que celles-ci soient excellentes, il n'y a pas beaucoup d'explications sur la façon de les appliquer dans des projets réels. Pendant ce temps, la bibliothèque de composants sur laquelle je travaillais a été construite de toutes pièces pour migrer de Material vers un système personnalisé. C'est une tâche significative et quelque peu frustrante quand on voit de nombreuses bibliothèques _headless_ avec **React** ou **Vue**. Néanmoins, j'ai beaucoup appris et je suis reconnaissant pour cette expérience. Savoir comment construire un composant est essentiel pour tout projet frontend, quel que soit le projet.\n\n![Moi devant Genève.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fgeneva.jpeg)\n\nPendant ce stage, j'ai eu beaucoup de temps libre pour travailler sur mes projets personnels.\n\n### Nouveau Blog\n\nAu début de l'année, j'ai écrit quelques articles pour le site **UnJS**. J'étais ravi de les partager avec la communauté, mais malheureusement, je n'ai pas eu la chance de les publier. À l'époque, mon blog était sur [esteban-soubiran.site](https:\u002F\u002Festeban-soubiran.site) et uniquement disponible en **français**. Je voulais écrire en **anglais** depuis longtemps, et j'ai reçu de nombreux retours à ce sujet après mes articles sur les paquets UnJS.\n\nJ'ai donc décidé de créer un nouveau blog avec un nouveau nom de domaine, en **anglais**, et avec un nouveau design axé sur la simplicité pour faciliter l'écriture. J'ai détaillé le processus dans cet article \u003CLinkToArticle id=\"a06fbfe1-6283-4eed-b3ec-e1d454613b9a\" \u002F>.\n\nJ'ai publié mon premier article, \u003CLinkToArticle id=\"aa8454be-94ba-4d46-8be0-8cf703c0bfed\" \u002F>, le 19 mars, mais au départ, mon blog se composait seulement d'un seul composant pour vous permettre de lire l'article. Il n'y avait pas de page d'accueil, pas de menu, rien.\n\n### Twitch\n\nJ'ai également commencé à streamer sur **Twitch**. Cela me trottait dans la tête depuis longtemps, mais avoir un environnement propice était un obstacle. Ma chambre pendant mon stage était parfaite : calme avec une bonne connexion internet.\n\nJ'ai commencé quelques streams, et ensemble, nous avons construit une application **Nuxt** full-stack avec NuxtHub, sur laquelle j'ai écrit tout un article, \u003CLinkToArticle id=\"2349bbcd-1535-4f33-8da7-299f46af18b9\" \u002F>.\n\nNous avons beaucoup appris ensemble, en particulier sur le développement full-stack avec Nuxt. J'ai partagé tout cela dans deux articles :\n\n- \u003CLinkToArticle id=\"8c077ba8-63ca-4784-b693-0e1fa8623bf9\" \u002F>\n- \u003CLinkToArticle id=\"05da35d9-c21f-4b45-ba86-e88d61339502\" \u002F>\n\nCela a conduit à la création d'un paquet appelé [Nuxt Authorization](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fnuxt-authorization).\n\nC'était définitivement une excellente expérience que j'ai dû mettre en pause parce que je suis passé sur un **MacBook Air**, qui n'avait pas une puissance graphique suffisante pour streamer. Mais en 2025, je prévois d'acheter un deuxième ordinateur pour reprendre le streaming.\n\n## Meetups et Conférences\n\nEn 2023, j'ai parlé pour la première fois à **Devoxx France**. En 2024, j'ai eu l'opportunité de prendre la parole six fois.\n\nEn mars, **StrasbourgJS** m'a invité à parler de l'écosystème **UnJS**. C'était vraiment super de partager ce savoir et de rencontrer enfin certains membres de la communauté comme Romain Lanz, Michel et Thomas. Ce fut une excellente préparation pour ma prochaine intervention à Devoxx France. J'ai écrit un article à ce sujet, \u003CLinkToArticle id=\"ba562d1f-534c-4cef-9004-2fefc09829a6\" \u002F>.\n\n![Équipe et Intervenants de StrasbourgJS.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fstrasbourgjs.jpeg)\n\nPour la deuxième fois, j'ai eu le privilège de parler à **Devoxx France**. Cette fois, j'ai abordé les paquets **UnJS** lors d'un quickie, un format très exigeant car il faut être très concis et clair. J'étais satisfait du résultat et j'ai reçu beaucoup de retours positifs. J'ai écrit un article à ce sujet, \u003CLinkToArticle id=\"f3e97a6a-ce47-42b8-8493-06eb37f8e964\" \u002F>.\n\n![Moi à Devoxx France.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fdevoxx-france.png)\n\nEn septembre, j'ai parlé à la **Prague Vue Conference**. C'était ma première conférence en anglais, donc je me suis bien préparé et entraîné pour être prêt. J'ai discuté des capacités full-stack de **Nuxt** avec NuxtHub. J'ai également rencontré des figures notables de la communauté **Vue**, comme Daniel Roe et Alexander Lichter. J'ai documenté l'expérience dans un article, \u003CLinkToArticle id=\"74e76e2e-0d34-46a3-826f-efcab800ec8a\" \u002F>.\n\n![Équipe et Intervenants de PragVue.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fpragvue.jpg)\n\nEn novembre, j'ai parlé à **NuxtNations**, une fois de plus sur les capacités full-stack de Nuxt. C'était une conférence en ligne, mais j'étais certainement un peu anxieux avant ma présentation.\n\nPuis, j'ai poursuivi avec une intervention lors du meetup **TakiMeet**. Il est organisé par une entreprise à Paris, où j'ai parlé de l'expérience utilisateur et de l'**UI** optimiste. Entre les deux conférences, je n'avais qu'une semaine pour me préparer, terminant mes diapositives seulement quelques heures avant la présentation. Je ne recommande pas même si j'étais satisfait du résultat. Un grand merci à Camille pour l'invitation et à toute l'équipe pour l'organisation.\n\n![Le public à TakiMeet pendant ma présentation.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Ftakimeet.jpg)\n\nEnfin, j'ai parlé au meetup **VueJS Paris** sur les capacités full-stack de Nuxt. C'était similaire à mes précédentes interventions à Prague et à NuxtNations, mais en français. Les gens étaient très intéressés et curieux à propos de cette technologie émergente. Merci à Elise et Eduardo pour l'invitation et l'organisation.\n\n![Le public à VueJS Paris pendant ma présentation.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fvuejs-paris.jpg)\n\n## Fin de Mes Études et Mon Nouveau Poste\n\nJ'ai terminé mes études d'ingénieur en septembre avec un stage en Suisse, et j'ai obtenu un diplôme en **sécurité informatique**. Six années d'études, deux stages, une année de césure et de nombreux projets, associations et expériences. Ce fut un incroyable parcours où j'ai rencontré beaucoup de personnes formidables. J'ai écrit un article à ce sujet, \u003CLinkToArticle id=\"b083ef4c-dd4d-4ace-ac09-76b5231c35bc\" \u002F>.\n\nAujourd'hui, je suis ravi d'avoir terminé ce chapitre de ma vie et de commencer un nouveau. Je suis maintenant ingénieur logiciel à temps plein chez **MaiaSpace**, une entreprise française qui conçoit, fabrique, exploite et commercialise des solutions compétitives et durables pour le **lancement et la mobilité spatiale**. Je n'aurais jamais imaginé travailler dans l'industrie spatiale, surtout en tant que développeur web. C'est une opportunité unique que je ne pouvais pas laisser passer, mais aussi un défi important avec beaucoup à apprendre. J'ai hâte du lancement en 2026 (même si j'ai encore le temps, car un autre récapitulatif sera écrit entre-temps). 🧑‍🚀\n\n## Apprentissage, Toujours Apprendre\n\nCela fait maintenant six ans que j'apprends le **développement web**. Au départ, j'ai beaucoup appris sur **HTML**, **CSS** et **JavaScript**, mais ces dernières années, j'ai quelque peu stagné et je n'étais pas totalement satisfait de cela. Je voulais construire une application full-stack par moi-même et comprendre tous ses composants.\n\nDonc, en juin, j'ai commencé à apprendre **Laravel** grâce à l'excellente plateforme Laracasts. Ça a changé ma vie. J'ai tellement appris en peu de temps. Je ne me suis jamais autant amusé à coder et à créer des applications. Cette décision m'a aidé à obtenir mon poste chez MaiaSpace.\n\nC'est peut-être le plus grand changement de ma vie en 2024 et probablement ma meilleure décision.\n\n## Mon Blog\n\nJe l'ai mentionné, mais j'ai créé un nouveau blog technique en mars 2024 avec une idée en tête : écrire des articles que j'aurais adoré lire en commençant mon parcours de développement web. Je voulais que mes articles soient clairs, techniques et complets. J'en ai assez de tous ces articles accrocheurs sur Medium ou Dev Community qui manquent de structure et où le contenu est aussi basique que la documentation.\n\nAvec cette ligne directrice, j'ai écrit plusieurs articles sur **Nuxt**, **Vue**, **UnJS**, et **Laravel**. Pour moi, écrire est un moyen de solidifier mes connaissances et de m'assurer que je comprends vraiment le sujet. J'aime aussi partager mes connaissances avec la communauté et aider les gens à apprendre des choses nouvelles et utiles.\n\nEn octobre, j'ai décidé d'écrire sur **VitePress** et sa personnalisabilité. Le concept d'un article de blog ne me comblait plus, et je voulais créer une série d'articles. J'ai introduit le concept de séries sur mon blog avec la série \u003CLinkToArticle id=\"f2681a44-f9bf-4ff7-840a-37f7729eea41\" \u002F>. Ces séries sont parfaites pour approfondir un sujet et expliquer des concepts complexes de manière simple et actionnable.\n\nEn décembre, j'ai publié une deuxième série appelée \u003CLinkToArticle id=\"1fafed86-05bb-4de4-b7d0-bb0bd6abc9d2\" \u002F>. Depuis ma première rencontre avec l'écosystème **Vue** il y a six ans, j'ai toujours voulu trouver quelque chose qui explique en profondeur comment construire une bibliothèque de composants de manière professionnelle, avec tous les outils et techniques utilisés dans l'industrie. Maintenant, c'est fait, et j'en suis très fier !\n\nTout au long du parcours, j'ai continuellement amélioré mon blog. J'ai atteint un point où j'étais très satisfait, mais il manquait encore quelque chose. Je voulais permettre aux gens d'interagir avec mon blog, de poser des questions, de partager leurs pensées et de donner des retours. J'ai donc décidé de créer une **API** avec Laravel pour faciliter cela. J'ai écrit plusieurs articles à ce sujet, \u003CLinkToArticle id=\"9d802f9e-2f40-426f-b636-533b05b8c1a3\" \u002F>, \u003CLinkToArticle id=\"c95f7904-8ade-4f11-84a8-99688d26c036\" \u002F>, et \u003CLinkToArticle id=\"17af0246-4d45-45f9-ad9a-57d10c9e883d\" \u002F>.\n\nCe n'était pas prévu, mais un thème sombre a également été ajouté grâce à [Roman](https:\u002F\u002Fhrynevych.dev\u002F). Il m'a envoyé un message sur X et a proposé d'ajouter un thème sombre à mon blog. J'ai rapidement accepté, et je dois dire que je le trouve absolument superbe.\n\nEnfin, j'ai lancé mon programme de parrainage sur **GitHub** et à travers mon blog. Ce premier pas est un moyen de me soutenir, moi et mon travail. Je suis très reconnaissant envers ceux qui me soutiennent déjà, et j'ai hâte de voir comment cela évoluera à l'avenir. De grands plans pour ce programme sont en préparation, et je suis impatient de les partager avec vous.\n\nMon objectif pour 2024 était de publier un article par semaine. J'ai publié 40 articles depuis mars, donc c'est un succès. 🥳\n\n## Ce Qui Vient Ensuite\n\n2024 a été une année de changement, d'apprentissage et de croissance. Je suis fier de mes réalisations et des progrès que j'ai réalisés. Mais je n'ai pas fini. Il y a encore tant à apprendre, à explorer et à créer.\n\nEn 2025, je souhaite continuer mon apprentissage de **Laravel** et approfondir encore plus le sujet. Je suis impatient d'explorer des sujets plus avancés et de construire des applications plus complexes. Avec les connaissances que j'ai acquises, je suis confiant de pouvoir relever de nouveaux défis et créer des projets. Construire des applications est un excellent début, mais je veux également en apprendre davantage sur **l'infrastructure et le déploiement**. Mon objectif est de déployer mes applications de manière professionnelle en gérant mon **VPS** avec **Ansible** et **Terraform**. Je veux surveiller mes applications, résoudre les problèmes en cas de défaillance et les faire évoluer si nécessaire. Cela prendra du temps, mais c'est correct. J'ai l'année qui vient pour apprendre et grandir là-dedans.\n\nJe continuerai à explorer l'écosystème **Vue**. Je suis impatient d'approfondir le **3D** avec Three.js et d'explorer comment cela peut être intégré dans mes projets. J'achète le cours [Three.js Journey](https:\u002F\u002Fthreejs-journey.com\u002F), et j'ai hâte de plonger dedans. J'aimerais pouvoir partager ce parcours avec vous sur Twitch. C'est l'un des points pour le deuxième semestre de 2025, pouvoir streamer à nouveau.\n\nJ'ai beaucoup d'idées pour de nouveaux projets, mais j'aimerais vraiment lancer **Inalia**, mon premier SaaS, qui rend vos discours plus engageants avec des interactions en temps réel. Cela m'importe beaucoup, et j'ai hâte de partager plus à ce sujet à l'avenir, peut-être lors de sessions de développement en direct sur Twitch.\n\nJe vais continuer à parler lors de conférences et de meetups. En décembre, j'ai soumis 5 propositions pour **Vue Amsterdam**, et j'espère sincèrement être sélectionné. J'ai soumis quelques interventions à des meetups à Paris, et en janvier, je soumettrai quelques interventions à **Devoxx France** et **MiXit**. J'espère vraiment que cette année sera aussi riche que 2024. J'adore partager mes connaissances avec la communauté et établir des connexions avec d'autres développeurs. N'hésitez pas à me contacter si vous organisez un événement et souhaitez que je prenne la parole.\n\nEnfin, je vais continuer à écrire des articles et à créer des séries sur mon blog. Je suis impatient d'explorer de nouveaux sujets et de partager des idées avec vous. J'ai des changements excitants prévus pour mon blog afin de le rendre plus interactif et engageant. J'ai hâte de les partager avec vous.\n\n2024 a été une année de changement, de croissance et d'apprentissage. Je suis fier de ce que j'ai accompli et impatient de découvrir ce qui m'attend. Merci de faire partie de cette aventure avec moi.\n\n2025 est une année ambitieuse, mais je n'ai jamais été aussi motivé et confiant qu'aujourd'hui. Ce sera une belle année, et j'ai hâte de la partager avec vous.\n\nPrenez soin de vous et bonne année ! 🫶\n\n![Moi en train de skier déguisé pour un mariage.](\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next\u002Fskiing.jpg)\n",{"title":30463,"description":30939},{"loc":30941,"images":30945},[30946,30947,30948,30949,30950,30951,30952],{"loc":30527},{"loc":30645},{"loc":30662},{"loc":30684},{"loc":30708},{"loc":30721},{"loc":30926},"fr\u002Fposts\u002F2024-reflections-on-a-year-of-change-and-what-comes-next","7vtj1tjY6w5DJ5Kse6nIMTIr9gy5A0JBTwNO1MrV_GE",{"id":30956,"title":30957,"body":30958,"contentId":33224,"date":33225,"description":33226,"extension":18,"meta":33227,"navigation":20,"path":33228,"rawbody":33229,"robots":23,"schemaOrg":23,"seo":33230,"sitemap":33231,"stem":33232,"__hash__":33233},"posts\u002Ffr\u002Fposts\u002Fthe-perfect-guide-to-setting-up-a-new-nuxt-project.md","Le guide parfait pour configurer un nouveau projet Nuxt",{"type":7,"value":30959,"toc":33215},[30960,30969,30976,30980,30986,31013,31029,31041,31051,31055,31058,31073,31093,31096,31102,31149,31156,31165,31188,31197,33212],[35,30961,30962,30963,30965,30966,30968],{},"Chaque fois que je crée un nouveau projet ",[6862,30964,2934],{},", je suis des étapes spécifiques pour garantir une configuration optimale. Je travaille avec ",[6862,30967,2934],{}," depuis 2019 et j'ai développé des habitudes spécifiques lors du démarrage de nouveaux projets.",[35,30970,30971,30972,30975],{},"J'ai pu explorer de nombreux modules et examiner des projets aussi bien d'experts que de débutants. Cela m'a permis de bien comprendre ce qui fonctionne dans l'",[6862,30973,30974],{},"écosystème Nuxt",". Configurer un nouveau projet peut sembler décourageant, surtout pour les débutants, c'est pourquoi j'ai décidé de partager ce guide pour vous aider à configurer votre projet Nuxt aussi efficacement que possible.",[51,30977,30979],{"id":30978},"création-dun-nouveau-projet-nuxt","Création d'un nouveau projet Nuxt",[35,30981,30982,30983,30985],{},"Commençons par créer un nouveau projet ",[6862,30984,2934],{},". C'est l'étape la plus simple, nécessitant juste une commande :",[192,30987,30989],{"className":2166,"code":30988,"language":2168,"meta":13,"style":13},"npx nuxi@latest init \u003Cnom-du-projet>\n",[156,30990,30991],{"__ignoreMap":13},[200,30992,30993,30996,30999,31002,31005,31008,31011],{"class":202,"line":203},[200,30994,30995],{"class":1364},"npx",[200,30997,30998],{"class":245}," nuxi@latest",[200,31000,31001],{"class":245}," init",[200,31003,31004],{"class":214}," \u003C",[200,31006,31007],{"class":245},"nom-du-proje",[200,31009,31010],{"class":210},"t",[200,31012,647],{"class":214},[938,31014,31015],{},[35,31016,31017,31020,31021,31024,31025,215],{},[200,31018,31019],{},"!TIP","\nVous pouvez également créer un projet prêt pour la v4 avec ",[156,31022,31023],{},"npx nuxi init nuxt-app -t v4-compat",", ce qui vous évite la nécessité de ",[39,31026,31028],{"href":31027},"#preparer-pour-nuxt-4","Préparer pour Nuxt 4",[938,31030,31031],{},[35,31032,31033,31035,31036,31038,31039,215],{},[200,31034,944],{},"\nJe choisis toujours ",[6862,31037,1365],{}," comme gestionnaire de paquets. Vous pouvez sélectionner celui que vous préférez, mais je recommande d'essayer ",[6862,31040,1365],{},[35,31042,31043,31044,31047,31048,215],{},"L'exécution de cette commande va créer un nouveau projet Nuxt dans le répertoire ",[156,31045,31046],{},"\u003Cnom-du-projet>",". L'application est minimaliste : aucune configuration, aucun module, juste Nuxt et un fichier ",[156,31049,31050],{},"app.vue",[51,31052,31054],{"id":31053},"mettre-à-jour-les-dépendances","Mettre à jour les dépendances",[35,31056,31057],{},"Avant de continuer, il est essentiel de mettre à jour les dépendances vers leurs dernières versions. Cela vous permet de tirer parti des nouvelles fonctionnalités et correctifs tout en évitant des dépendances obsolètes qui pourraient causer des problèmes par la suite. Exécutez la commande suivante pour les mettre à jour :",[192,31059,31061],{"className":2166,"code":31060,"language":2168,"meta":13,"style":13},"npx taze -w\n",[156,31062,31063],{"__ignoreMap":13},[200,31064,31065,31067,31070],{"class":202,"line":203},[200,31066,30995],{"class":1364},[200,31068,31069],{"class":245}," taze",[200,31071,31072],{"class":245}," -w\n",[35,31074,31075,31076,31079,31080,31084,31085,31088,31089,31092],{},"Le paquet ",[6862,31077,31078],{},"taze",", développé par ",[39,31081,24157],{"href":31082,"rel":31083},"https:\u002F\u002Fantfu.me",[77],", met à jour toutes les dépendances vers leurs dernières versions. C'est similaire à ",[156,31086,31087],{},"npm-check-updates"," mais plus puissant grâce à son respect version avec ",[6862,31090,31091],{},"semver"," tout en étant plus rapide.",[51,31094,31028],{"id":31095},"préparer-pour-nuxt-4",[35,31097,31098,31099,2186],{},"Actuellement, la dernière version stable de Nuxt est la version 3. Cependant, la version 4 est en développement et promet de nombreuses fonctionnalités et améliorations. Bien qu'elle ne soit pas officiellement publiée, vous pouvez préparer vos projets pour cette future mise à jour en ajoutant la configuration suivante dans votre ",[156,31100,31101],{},"nuxt.config.js",[192,31103,31105],{"className":1380,"code":31104,"language":1383,"meta":13,"style":13},"export default defineNuxtConfig({\n  future: {\n    compatibilityVersion: 4,\n  }\n})\n",[156,31106,31107,31119,31128,31139,31143],{"__ignoreMap":13},[200,31108,31109,31111,31113,31115,31117],{"class":202,"line":203},[200,31110,1390],{"class":206},[200,31112,1393],{"class":206},[200,31114,1396],{"class":223},[200,31116,227],{"class":210},[200,31118,230],{"class":214},[200,31120,31121,31124,31126],{"class":202,"line":14},[200,31122,31123],{"class":235},"  future",[200,31125,239],{"class":214},[200,31127,280],{"class":214},[200,31129,31130,31133,31135,31137],{"class":202,"line":255},[200,31131,31132],{"class":235},"    compatibilityVersion",[200,31134,239],{"class":214},[200,31136,11033],{"class":2060},[200,31138,252],{"class":214},[200,31140,31141],{"class":202,"line":272},[200,31142,4505],{"class":214},[200,31144,31145,31147],{"class":202,"line":283},[200,31146,413],{"class":214},[200,31148,416],{"class":210},[35,31150,31151,31152,31155],{},"Cette configuration vous permet de commencer à développer vos projets comme si vous utilisiez déjà ",[6862,31153,31154],{},"Nuxt 4",". Cette prévoyance vous permet non seulement de bénéficier des fonctionnalités à venir, mais aussi de minimiser le travail requis pour la mise à niveau lorsque Nuxt 4 sera publié.",[35,31157,31158,31159,31161,31162,31164],{},"Pour tirer le meilleur parti de Nuxt 4, créez un nouveau dossier ",[156,31160,26785],{}," et déplacez le fichier ",[156,31163,31050],{}," à l'intérieur :",[192,31166,31168],{"className":2166,"code":31167,"language":2168,"meta":13,"style":13},"mkdir app && mv app.vue app\n",[156,31169,31170],{"__ignoreMap":13},[200,31171,31172,31175,31177,31179,31182,31185],{"class":202,"line":203},[200,31173,31174],{"class":1364},"mkdir",[200,31176,14334],{"class":245},[200,31178,14932],{"class":214},[200,31180,31181],{"class":1364}," mv",[200,31183,31184],{"class":245}," app.vue",[200,31186,31187],{"class":245}," app\n",[938,31189,31190],{},[35,31191,31192,31194,31195,215],{},[200,31193,31019],{},"\nVous pouvez également créer un projet prêt pour la v4 en utilisant la commande ",[156,31196,31023],{},[7918,31198,31201,31205,31208,31241,31256,31314,31321,31347,31353,31421,31432,31445,31449,31455,31459,31471],{"text":31199,"href":31200},"Mise à niveau vers Nuxt 4","https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fgetting-started\u002Fupgrade#opting-in-to-nuxt-4",[51,31202,31204],{"id":31203},"linting-et-formatage","Linting et formatage",[35,31206,31207],{},"Nuxt offre une excellente expérience développeur pour le linting et le formatage. Installez à la fois le linter et le formateur avec la commande suivante :",[192,31209,31211],{"className":2166,"code":31210,"language":2168,"meta":13,"style":13},"npx nuxi module add eslint && npx @antfu\u002Fni -D typescript\n",[156,31212,31213],{"__ignoreMap":13},[200,31214,31215,31217,31220,31223,31225,31228,31230,31233,31236,31238],{"class":202,"line":203},[200,31216,30995],{"class":1364},[200,31218,31219],{"class":245}," nuxi",[200,31221,31222],{"class":245}," module",[200,31224,2177],{"class":245},[200,31226,31227],{"class":245}," eslint",[200,31229,14932],{"class":214},[200,31231,31232],{"class":1364}," npx",[200,31234,31235],{"class":245}," @antfu\u002Fni",[200,31237,1371],{"class":245},[200,31239,31240],{"class":245}," typescript\n",[35,31242,31243,31244,31247,31248,31253,31254,2186],{},"Cette commande installe le module ",[156,31245,31246],{},"@nuxt\u002Feslint",", qui propose une ",[39,31249,31252],{"href":31250,"rel":31251},"https:\u002F\u002Feslint.nuxt.com",[77],"configuration ESLint sans configuration pour les projets Nuxt",". Activez le formatage, et il corrigera automatiquement les problèmes lors de l'enregistrement d'un fichier. Je le configure toujours dans mon ",[156,31255,1382],{},[192,31257,31259],{"className":9755,"code":31258,"language":1383,"meta":13,"style":13},"export default defineNuxtConfig({\n  eslint: {\n    config: {\n      stylistic: true \u002F\u002F [!code highlight]\n    }\n  }\n})\n",[156,31260,31261,31273,31282,31290,31300,31304,31308],{"__ignoreMap":13},[200,31262,31263,31265,31267,31269,31271],{"class":202,"line":203},[200,31264,1390],{"class":206},[200,31266,1393],{"class":206},[200,31268,1396],{"class":223},[200,31270,227],{"class":210},[200,31272,230],{"class":214},[200,31274,31275,31278,31280],{"class":202,"line":14},[200,31276,31277],{"class":235},"  eslint",[200,31279,239],{"class":214},[200,31281,280],{"class":214},[200,31283,31284,31286,31288],{"class":202,"line":255},[200,31285,24973],{"class":235},[200,31287,239],{"class":214},[200,31289,280],{"class":214},[200,31291,31293,31296,31298],{"class":31292,"line":272},[202,9885],[200,31294,31295],{"class":235},"      stylistic",[200,31297,239],{"class":214},[200,31299,2439],{"class":2438},[200,31301,31302],{"class":202,"line":283},[200,31303,4977],{"class":214},[200,31305,31306],{"class":202,"line":300},[200,31307,4505],{"class":214},[200,31309,31310,31312],{"class":202,"line":310},[200,31311,413],{"class":214},[200,31313,416],{"class":210},[35,31315,31316,31317,31320],{},"De plus, créez un fichier ",[156,31318,31319],{},".vscode\u002Fsettings.json"," pour désactiver l'extension prettier, évitant les conflits avec le formateur ESLint :",[192,31322,31324],{"className":28358,"code":31323,"language":28360,"meta":13,"style":13},"{\n  \"prettier.enable\": false\n}\n",[156,31325,31326,31330,31343],{"__ignoreMap":13},[200,31327,31328],{"class":202,"line":203},[200,31329,230],{"class":214},[200,31331,31332,31334,31337,31339,31341],{"class":202,"line":14},[200,31333,28489],{"class":214},[200,31335,31336],{"class":379},"prettier.enable",[200,31338,610],{"class":214},[200,31340,239],{"class":214},[200,31342,18902],{"class":214},[200,31344,31345],{"class":202,"line":255},[200,31346,1129],{"class":214},[35,31348,31349,31350,2186],{},"Ajoutez deux scripts à votre fichier ",[156,31351,31352],{},"package.json",[192,31354,31356],{"className":28358,"code":31355,"language":28360,"meta":13,"style":13},"{\n  \"scripts\": {\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint --fix .\"\n  }\n}\n",[156,31357,31358,31362,31375,31395,31413,31417],{"__ignoreMap":13},[200,31359,31360],{"class":202,"line":203},[200,31361,230],{"class":214},[200,31363,31364,31366,31369,31371,31373],{"class":202,"line":14},[200,31365,28489],{"class":214},[200,31367,31368],{"class":379},"scripts",[200,31370,610],{"class":214},[200,31372,239],{"class":214},[200,31374,280],{"class":214},[200,31376,31377,31379,31382,31384,31386,31388,31391,31393],{"class":202,"line":255},[200,31378,28529],{"class":214},[200,31380,31381],{"class":1364},"lint",[200,31383,610],{"class":214},[200,31385,239],{"class":214},[200,31387,28499],{"class":214},[200,31389,31390],{"class":245},"eslint .",[200,31392,610],{"class":214},[200,31394,252],{"class":214},[200,31396,31397,31399,31402,31404,31406,31408,31411],{"class":202,"line":272},[200,31398,28529],{"class":214},[200,31400,31401],{"class":1364},"lint:fix",[200,31403,610],{"class":214},[200,31405,239],{"class":214},[200,31407,28499],{"class":214},[200,31409,31410],{"class":245},"eslint --fix .",[200,31412,616],{"class":214},[200,31414,31415],{"class":202,"line":283},[200,31416,4505],{"class":214},[200,31418,31419],{"class":202,"line":300},[200,31420,1129],{"class":214},[35,31422,31423,31424,31427,31428,31431],{},"Maintenant, vous pouvez exécuter ",[156,31425,31426],{},"pnpm run lint"," pour vérifier les erreurs et ",[156,31429,31430],{},"pnpm run lint:fix"," pour les corriger.",[35,31433,31434,31435,31438,31439,1655,31442,31444],{},"Ces configurations prédéfinies facilitent la mise en place de ",[6862,31436,31437],{},"ESLint"," avec un support pour ",[6862,31440,31441],{},"TypeScript",[6862,31443,17998],{},", garantissant un code cohérent et propre.",[51,31446,31448],{"id":31447},"ui-et-composants","UI et composants",[35,31450,31451,31452,31454],{},"Avec une configuration de projet propre, les dernières dépendances, la préparation pour ",[6862,31453,31154],{},", et un linting et formatage robustes, il est temps de se concentrer sur l'UI et les composants. Cette étape varie en fonction des objectifs du projet et des exigences de conception.",[1929,31456,31458],{"id":31457},"nuxt-ui-et-nuxt-ui-pro","Nuxt UI et Nuxt UI Pro",[35,31460,31461,31462,31465,31466,31470],{},"Si le projet met l'accent sur la logique métier plutôt que sur l'UI, j'utilise les bibliothèques de composants ",[39,31463,9203],{"href":14207,"rel":31464},[77]," et Nuxt UI Pro. Elles sont bien conçues et faciles à utiliser. Lire le ",[39,31467,31469],{"href":9201,"rel":31468},[77],"code source de Nuxt UI"," est instructif, et si vous voulez plongez davantage dans les composants, j'ai écris un article à ce sujet.",[7918,31472,31473,31480,31483,31500,31505,31545,31551,31584,31589,31630,31641,31722,31733,31780,31787,31791,31802,31805,31822,31828,31845,31850,31942,31951,32027,32030,32034,32042,32045,32048,32059,32077,32080,32091,32107,32113,32231,32236,32263,32269,32273,32285,32303,32306,32310,32316,32326,32557,32565,32789,32793,32802,32808,32847,32850,32854,32857,32861,32870,32930,32934,32947,32984,32995,33005,33019,33064,33081,33087,33094,33103,33125,33132,33137],{"id":30810},[938,31474,31475],{},[35,31476,31477,31479],{},[200,31478,944],{},"\nAu moment de la rédaction, Nuxt UI 3 n'est pas officiellement publié, mais il peut être utilisé dans les projets. Si vous lisez cela plus tard, laissez-moi un commentaire, et je mettrai à jour ce guide.",[35,31481,31482],{},"Tout d'abord, installez Nuxt UI et Tailwind CSS :",[192,31484,31486],{"className":2166,"code":31485,"language":2168,"meta":13,"style":13},"pnpm add @nuxt\u002Fui@next tailwindcss@next\n",[156,31487,31488],{"__ignoreMap":13},[200,31489,31490,31492,31494,31497],{"class":202,"line":203},[200,31491,1365],{"class":1364},[200,31493,2177],{"class":245},[200,31495,31496],{"class":245}," @nuxt\u002Fui@next",[200,31498,31499],{"class":245}," tailwindcss@next\n",[35,31501,31502,31503,2186],{},"Ajoutez le module à votre fichier ",[156,31504,1382],{},[192,31506,31508],{"className":1380,"code":31507,"language":1383,"meta":13,"style":13},"export default defineNuxtConfig({\n  modules: ['@nuxt\u002Fui']\n})\n",[156,31509,31510,31522,31539],{"__ignoreMap":13},[200,31511,31512,31514,31516,31518,31520],{"class":202,"line":203},[200,31513,1390],{"class":206},[200,31515,1393],{"class":206},[200,31517,1396],{"class":223},[200,31519,227],{"class":210},[200,31521,230],{"class":214},[200,31523,31524,31526,31528,31530,31532,31535,31537],{"class":202,"line":14},[200,31525,1405],{"class":235},[200,31527,239],{"class":214},[200,31529,350],{"class":210},[200,31531,249],{"class":214},[200,31533,31534],{"class":245},"@nuxt\u002Fui",[200,31536,249],{"class":214},[200,31538,4511],{"class":210},[200,31540,31541,31543],{"class":202,"line":255},[200,31542,413],{"class":214},[200,31544,416],{"class":210},[35,31546,31547,31548,2186],{},"Importez le CSS nécessaire dans votre fichier ",[156,31549,31550],{},"app\u002Fassets\u002Fcss\u002Fmain.css",[192,31552,31556],{"className":31553,"code":31554,"language":31555,"meta":13,"style":13},"language-css shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","@import \"tailwindcss\";\n@import \"@nuxt\u002Fui\";\n","css",[156,31557,31558,31572],{"__ignoreMap":13},[200,31559,31560,31563,31565,31568,31570],{"class":202,"line":203},[200,31561,31562],{"class":206},"@import",[200,31564,28499],{"class":214},[200,31566,31567],{"class":245},"tailwindcss",[200,31569,610],{"class":214},[200,31571,5944],{"class":214},[200,31573,31574,31576,31578,31580,31582],{"class":202,"line":14},[200,31575,31562],{"class":206},[200,31577,28499],{"class":214},[200,31579,31534],{"class":245},[200,31581,610],{"class":214},[200,31583,5944],{"class":214},[35,31585,31586,31587,2186],{},"Assurez-vous que ce fichier CSS est inclus dans le fichier ",[156,31588,1382],{},[192,31590,31592],{"className":1380,"code":31591,"language":1383,"meta":13,"style":13},"export default defineNuxtConfig({\n  css: ['~\u002Fassets\u002Fcss\u002Fmain.css']\n})\n",[156,31593,31594,31606,31624],{"__ignoreMap":13},[200,31595,31596,31598,31600,31602,31604],{"class":202,"line":203},[200,31597,1390],{"class":206},[200,31599,1393],{"class":206},[200,31601,1396],{"class":223},[200,31603,227],{"class":210},[200,31605,230],{"class":214},[200,31607,31608,31611,31613,31615,31617,31620,31622],{"class":202,"line":14},[200,31609,31610],{"class":235},"  css",[200,31612,239],{"class":214},[200,31614,350],{"class":210},[200,31616,249],{"class":214},[200,31618,31619],{"class":245},"~\u002Fassets\u002Fcss\u002Fmain.css",[200,31621,249],{"class":214},[200,31623,4511],{"class":210},[200,31625,31626,31628],{"class":202,"line":255},[200,31627,413],{"class":214},[200,31629,416],{"class":210},[35,31631,31632,31633,31635,31636,2186],{},"Mettez à jour votre fichier ",[156,31634,31319],{}," pour améliorer l'expérience avec l'extension ",[39,31637,31640],{"href":31638,"rel":31639},"https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=bradlc.vscode-tailwindcss",[77],"Tailwind CSS IntelliSense",[192,31642,31644],{"className":28358,"code":31643,"language":28360,"meta":13,"style":13},"{\n  \"files.associations\": {\n    \"*.css\": \"tailwindcss\"\n  },\n  \"editor.quickSuggestions\": {\n    \"strings\": \"on\"\n  }\n}\n",[156,31645,31646,31650,31663,31680,31684,31697,31714,31718],{"__ignoreMap":13},[200,31647,31648],{"class":202,"line":203},[200,31649,230],{"class":214},[200,31651,31652,31654,31657,31659,31661],{"class":202,"line":14},[200,31653,28489],{"class":214},[200,31655,31656],{"class":379},"files.associations",[200,31658,610],{"class":214},[200,31660,239],{"class":214},[200,31662,280],{"class":214},[200,31664,31665,31667,31670,31672,31674,31676,31678],{"class":202,"line":255},[200,31666,28529],{"class":214},[200,31668,31669],{"class":1364},"*.css",[200,31671,610],{"class":214},[200,31673,239],{"class":214},[200,31675,28499],{"class":214},[200,31677,31567],{"class":245},[200,31679,616],{"class":214},[200,31681,31682],{"class":202,"line":272},[200,31683,368],{"class":214},[200,31685,31686,31688,31691,31693,31695],{"class":202,"line":283},[200,31687,28489],{"class":214},[200,31689,31690],{"class":379},"editor.quickSuggestions",[200,31692,610],{"class":214},[200,31694,239],{"class":214},[200,31696,280],{"class":214},[200,31698,31699,31701,31704,31706,31708,31710,31712],{"class":202,"line":300},[200,31700,28529],{"class":214},[200,31702,31703],{"class":1364},"strings",[200,31705,610],{"class":214},[200,31707,239],{"class":214},[200,31709,28499],{"class":214},[200,31711,25410],{"class":245},[200,31713,616],{"class":214},[200,31715,31716],{"class":202,"line":310},[200,31717,4505],{"class":214},[200,31719,31720],{"class":202,"line":336},[200,31721,1129],{"class":214},[35,31723,31724,31725,31728,31729,31732],{},"Enfin, modifiez votre fichier ",[156,31726,31727],{},"app\u002Fapp.vue"," pour inclure le composant Nuxt UI ",[156,31730,31731],{},"\u003CUApp>",", injectant automatiquement des composants globaux :",[192,31734,31736],{"className":21457,"code":31735,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CUApp>\n    \u003CNuxtPage \u002F>\n  \u003C\u002FUApp>\n\u003C\u002Ftemplate>\n",[156,31737,31738,31746,31755,31764,31772],{"__ignoreMap":13},[200,31739,31740,31742,31744],{"class":202,"line":203},[200,31741,599],{"class":214},[200,31743,21468],{"class":235},[200,31745,647],{"class":214},[200,31747,31748,31750,31753],{"class":202,"line":14},[200,31749,656],{"class":214},[200,31751,31752],{"class":235},"UApp",[200,31754,647],{"class":214},[200,31756,31757,31759,31762],{"class":202,"line":255},[200,31758,803],{"class":214},[200,31760,31761],{"class":235},"NuxtPage",[200,31763,21527],{"class":214},[200,31765,31766,31768,31770],{"class":202,"line":272},[200,31767,892],{"class":214},[200,31769,31752],{"class":235},[200,31771,647],{"class":214},[200,31773,31774,31776,31778],{"class":202,"line":283},[200,31775,680],{"class":214},[200,31777,21468],{"class":235},[200,31779,647],{"class":214},[938,31781,31782],{},[35,31783,31784,31786],{},[200,31785,944],{},"\nNuxt UI Pro est une extension payante offrant des composants de mise en page pour un développement d'application rapide. C'est un investissement valable qui fait gagner du temps et des efforts tout en soutenant l'équipe Nuxt et le développement open-source. 💚",[1929,31788,31790],{"id":31789},"tailwind-css","Tailwind CSS",[35,31792,31793,31794,31798,31799,31801],{},"Pour les projets nécessitant des conceptions spécifiques ou lorsque je collabore avec des designers, je me tourne vers ",[39,31795,31790],{"href":31796,"rel":31797},"https:\u002F\u002Ftailwindcss.com",[77],". Il s'agit d'un cadre CSS utilitaire qui rationalise le processus de conception à tel point qu'il est inimaginable de travailler sans. La version à venir, Tailwind CSS 4, inclut un plugin ",[6862,31800,24399],{}," indispensable, éliminant le besoin de modules supplémentaires et simplifiant la configuration.",[35,31803,31804],{},"Installez la dernière version de Tailwind CSS :",[192,31806,31808],{"className":2166,"code":31807,"language":2168,"meta":13,"style":13},"pnpm add tailwindcss@next @tailwindcss\u002Fvite@next\n",[156,31809,31810],{"__ignoreMap":13},[200,31811,31812,31814,31816,31819],{"class":202,"line":203},[200,31813,1365],{"class":1364},[200,31815,2177],{"class":245},[200,31817,31818],{"class":245}," tailwindcss@next",[200,31820,31821],{"class":245}," @tailwindcss\u002Fvite@next\n",[35,31823,31824,31825,31827],{},"Créez un fichier ",[156,31826,31550],{}," et importez Tailwind CSS :",[192,31829,31831],{"className":31553,"code":31830,"language":31555,"meta":13,"style":13},"@import \"tailwindcss\";\n",[156,31832,31833],{"__ignoreMap":13},[200,31834,31835,31837,31839,31841,31843],{"class":202,"line":203},[200,31836,31562],{"class":206},[200,31838,28499],{"class":214},[200,31840,31567],{"class":245},[200,31842,610],{"class":214},[200,31844,5944],{"class":214},[35,31846,31632,31847,31849],{},[156,31848,1382],{}," pour utiliser le plugin Vite :",[192,31851,31853],{"className":1380,"code":31852,"language":1383,"meta":13,"style":13},"import Tailwind from '@tailwindcss\u002Fvite'\n\nexport default defineNuxtConfig({\n  css: ['~\u002Fassets\u002Fcss\u002Fmain.css'],\n  vite: {\n    plugins: [\n      Tailwind()\n    ]\n  }\n})\n",[156,31854,31855,31871,31875,31887,31905,31913,31921,31928,31932,31936],{"__ignoreMap":13},[200,31856,31857,31859,31862,31864,31866,31869],{"class":202,"line":203},[200,31858,972],{"class":206},[200,31860,31861],{"class":210}," Tailwind ",[200,31863,1489],{"class":206},[200,31865,242],{"class":214},[200,31867,31868],{"class":245},"@tailwindcss\u002Fvite",[200,31870,991],{"class":214},[200,31872,31873],{"class":202,"line":14},[200,31874,528],{"emptyLinePlaceholder":20},[200,31876,31877,31879,31881,31883,31885],{"class":202,"line":255},[200,31878,1390],{"class":206},[200,31880,1393],{"class":206},[200,31882,1396],{"class":223},[200,31884,227],{"class":210},[200,31886,230],{"class":214},[200,31888,31889,31891,31893,31895,31897,31899,31901,31903],{"class":202,"line":272},[200,31890,31610],{"class":235},[200,31892,239],{"class":214},[200,31894,350],{"class":210},[200,31896,249],{"class":214},[200,31898,31619],{"class":245},[200,31900,249],{"class":214},[200,31902,360],{"class":210},[200,31904,252],{"class":214},[200,31906,31907,31909,31911],{"class":202,"line":283},[200,31908,2605],{"class":235},[200,31910,239],{"class":214},[200,31912,280],{"class":214},[200,31914,31915,31917,31919],{"class":202,"line":300},[200,31916,2614],{"class":235},[200,31918,239],{"class":214},[200,31920,1410],{"class":210},[200,31922,31923,31926],{"class":202,"line":310},[200,31924,31925],{"class":223},"      Tailwind",[200,31927,466],{"class":210},[200,31929,31930],{"class":202,"line":336},[200,31931,4499],{"class":210},[200,31933,31934],{"class":202,"line":342},[200,31935,4505],{"class":214},[200,31937,31938,31940],{"class":202,"line":365},[200,31939,413],{"class":214},[200,31941,416],{"class":210},[35,31943,31944,31945,31947,31948,2186],{},"Améliorez votre expérience développeur en mettant à jour votre fichier ",[156,31946,31319],{}," pour l'extension ",[39,31949,31640],{"href":31638,"rel":31950},[77],[192,31952,31953],{"className":28358,"code":31643,"language":28360,"meta":13,"style":13},[156,31954,31955,31959,31971,31987,31991,32003,32019,32023],{"__ignoreMap":13},[200,31956,31957],{"class":202,"line":203},[200,31958,230],{"class":214},[200,31960,31961,31963,31965,31967,31969],{"class":202,"line":14},[200,31962,28489],{"class":214},[200,31964,31656],{"class":379},[200,31966,610],{"class":214},[200,31968,239],{"class":214},[200,31970,280],{"class":214},[200,31972,31973,31975,31977,31979,31981,31983,31985],{"class":202,"line":255},[200,31974,28529],{"class":214},[200,31976,31669],{"class":1364},[200,31978,610],{"class":214},[200,31980,239],{"class":214},[200,31982,28499],{"class":214},[200,31984,31567],{"class":245},[200,31986,616],{"class":214},[200,31988,31989],{"class":202,"line":272},[200,31990,368],{"class":214},[200,31992,31993,31995,31997,31999,32001],{"class":202,"line":283},[200,31994,28489],{"class":214},[200,31996,31690],{"class":379},[200,31998,610],{"class":214},[200,32000,239],{"class":214},[200,32002,280],{"class":214},[200,32004,32005,32007,32009,32011,32013,32015,32017],{"class":202,"line":300},[200,32006,28529],{"class":214},[200,32008,31703],{"class":1364},[200,32010,610],{"class":214},[200,32012,239],{"class":214},[200,32014,28499],{"class":214},[200,32016,25410],{"class":245},[200,32018,616],{"class":214},[200,32020,32021],{"class":202,"line":310},[200,32022,4505],{"class":214},[200,32024,32025],{"class":202,"line":336},[200,32026,1129],{"class":214},[35,32028,32029],{},"La configuration est incroyablement simple !",[51,32031,32033],{"id":32032},"icônes","Icônes",[35,32035,32036,32037,32041],{},"Incorporer des icônes dans votre projet est essentiel, bien que cela puisse s'avérer trompeusement complexe. L'article d'",[39,32038,24157],{"href":32039,"rel":32040},"https:\u002F\u002Fnuxt.com\u002Fblog\u002Fnuxt-icon-v1-0",[77]," explique comment il a trouvé la solution d'icônes parfaite.",[35,32043,32044],{},"Si vous utilisez Nuxt UI, les icônes sont déjà inclus. Vous pouvez donc ignorer cette section.",[35,32046,32047],{},"Pour les utilisateurs de Tailwind CSS, deux options sont disponibles :",[68,32049,32050],{},[71,32051,32052,32053,32058],{},"Utiliser le module ",[39,32054,32057],{"href":32055,"rel":32056},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ficon",[77],"Nuxt Icons"," fournit accès à plus de 200 000 icônes de divers ensembles d'icônes :",[192,32060,32062],{"className":2166,"code":32061,"language":2168,"meta":13,"style":13},"npx nuxi module add icon\n",[156,32063,32064],{"__ignoreMap":13},[200,32065,32066,32068,32070,32072,32074],{"class":202,"line":203},[200,32067,30995],{"class":1364},[200,32069,31219],{"class":245},[200,32071,31222],{"class":245},[200,32073,2177],{"class":245},[200,32075,32076],{"class":245}," icon\n",[35,32078,32079],{},"C'est la solution la plus simple et avec les dernières améliorations de la v1, c'est un choix puissant. Je recommande de commencer par cette option.",[68,32081,32082],{"start":14},[71,32083,32084,32085,32090],{},"Utiliser le plugin Tailwind CSS, ",[39,32086,32089],{"href":32087,"rel":32088},"https:\u002F\u002Fgithub.com\u002Fegoist\u002Ftailwindcss-icons",[77],"Tailwind CSS Icons",", offre des icônes similaires via une approche différente :",[192,32092,32094],{"className":2166,"code":32093,"language":2168,"meta":13,"style":13},"npx @antfu\u002Fni -D @egoist\u002Ftailwindcss-icons\n",[156,32095,32096],{"__ignoreMap":13},[200,32097,32098,32100,32102,32104],{"class":202,"line":203},[200,32099,30995],{"class":1364},[200,32101,31235],{"class":245},[200,32103,1371],{"class":245},[200,32105,32106],{"class":245}," @egoist\u002Ftailwindcss-icons\n",[35,32108,31824,32109,32112],{},[156,32110,32111],{},"tailwind.config.js"," et incluez le plugin :",[192,32114,32116],{"className":25070,"code":32115,"language":25072,"meta":13,"style":13},"import { getIconCollections, iconsPlugin } from '@egoist\u002Ftailwindcss-icons'\n\n\u002F** @type {import('tailwindcss').Config} *\u002F\nmodule.exports = {\n  plugins: [\n    iconsPlugin({\n      collections: getIconCollections([\u002F** ... *\u002F]),\n    }),\n  ],\n}\n",[156,32117,32118,32143,32147,32168,32177,32185,32194,32213,32221,32227],{"__ignoreMap":13},[200,32119,32120,32122,32124,32127,32129,32132,32134,32136,32138,32141],{"class":202,"line":203},[200,32121,972],{"class":206},[200,32123,318],{"class":214},[200,32125,32126],{"class":210}," getIconCollections",[200,32128,549],{"class":214},[200,32130,32131],{"class":210}," iconsPlugin",[200,32133,980],{"class":214},[200,32135,983],{"class":206},[200,32137,242],{"class":214},[200,32139,32140],{"class":245},"@egoist\u002Ftailwindcss-icons",[200,32142,991],{"class":214},[200,32144,32145],{"class":202,"line":14},[200,32146,528],{"emptyLinePlaceholder":20},[200,32148,32149,32152,32155,32158,32160,32164,32166],{"class":202,"line":255},[200,32150,32151],{"class":401},"\u002F** ",[200,32153,32154],{"class":206},"@",[200,32156,3786],{"class":32157},"s6hCs",[200,32159,318],{"class":206},[200,32161,32163],{"class":32162},"sFweD","import('tailwindcss').Config",[200,32165,413],{"class":206},[200,32167,26134],{"class":401},[200,32169,32170,32173,32175],{"class":202,"line":272},[200,32171,32172],{"class":214},"module.exports",[200,32174,509],{"class":214},[200,32176,280],{"class":214},[200,32178,32179,32181,32183],{"class":202,"line":283},[200,32180,1537],{"class":235},[200,32182,239],{"class":214},[200,32184,1410],{"class":210},[200,32186,32187,32190,32192],{"class":202,"line":300},[200,32188,32189],{"class":223},"    iconsPlugin",[200,32191,227],{"class":210},[200,32193,230],{"class":214},[200,32195,32196,32199,32201,32203,32205,32208,32211],{"class":202,"line":310},[200,32197,32198],{"class":235},"      collections",[200,32200,239],{"class":214},[200,32202,32126],{"class":223},[200,32204,25224],{"class":210},[200,32206,32207],{"class":401},"\u002F** ... *\u002F",[200,32209,32210],{"class":210},"])",[200,32212,252],{"class":214},[200,32214,32215,32217,32219],{"class":202,"line":336},[200,32216,2111],{"class":214},[200,32218,2104],{"class":210},[200,32220,252],{"class":214},[200,32222,32223,32225],{"class":202,"line":342},[200,32224,1427],{"class":210},[200,32226,252],{"class":214},[200,32228,32229],{"class":202,"line":365},[200,32230,1129],{"class":214},[35,32232,32233,32234,2186],{},"Importez la configuration dans votre fichier ",[156,32235,31550],{},[192,32237,32239],{"className":31553,"code":32238,"language":31555,"meta":13,"style":13},"@import \"tailwindcss\";\n@config \"..\u002F..\u002F..\u002Ftailwind.config.js\";\n",[156,32240,32241,32253],{"__ignoreMap":13},[200,32242,32243,32245,32247,32249,32251],{"class":202,"line":203},[200,32244,31562],{"class":206},[200,32246,28499],{"class":214},[200,32248,31567],{"class":245},[200,32250,610],{"class":214},[200,32252,5944],{"class":214},[200,32254,32255,32258,32261],{"class":202,"line":14},[200,32256,32257],{"class":206},"@config",[200,32259,32260],{"class":210}," \"..\u002F..\u002F..\u002Ftailwind.config.js\"",[200,32262,5944],{"class":214},[35,32264,32265,32266,215],{},"Personnellement, je préfère la deuxième solution en raison de sa configuration uniforme à travers les projets, y compris ceux n'utilisant pas Nuxt, et au moment où j'ai fait ce choix, le module Nuxt Icons souffrait de nombreux inconvénients. Cependant, ce n'est plus le cas et je vous recommande fortement de commencer par ",[39,32267,32057],{"href":32055,"rel":32268},[77],[51,32270,32272],{"id":32271},"polices","Polices",[35,32274,32275,32276,32278,32279,32284],{},"Les polices définissent la personnalité de votre projet et influencent la perception utilisateur. ",[6862,32277,2934],{}," fournit une solution : ",[39,32280,32283],{"href":32281,"rel":32282},"https:\u002F\u002Ffonts.nuxt.com\u002F",[77],"Nuxt Fonts",". Ce module zéro configuration charge automatiquement les polices requises en scannant les fichiers CSS. L'installation est simple :",[192,32286,32288],{"className":2166,"code":32287,"language":2168,"meta":13,"style":13},"npx nuxi module add fonts\n",[156,32289,32290],{"__ignoreMap":13},[200,32291,32292,32294,32296,32298,32300],{"class":202,"line":203},[200,32293,30995],{"class":1364},[200,32295,31219],{"class":245},[200,32297,31222],{"class":245},[200,32299,2177],{"class":245},[200,32301,32302],{"class":245}," fonts\n",[35,32304,32305],{},"Ce module fait partie de mes projets sauf si j'utilise Nuxt UI, où il est inclus. Mais je dois dire que j'ai rarement besoin de l'installer puisque Nuxt UI suffit dans la plupart des scénarios.",[51,32307,32309],{"id":32308},"ci","CI",[35,32311,32312,32313,32315],{},"Pour mes projets personnels, j'utilise ",[6862,32314,11636],{}," et je configure toujours un CI pour faire respecter le linting et corriger automatiquement les erreurs. Cette approche assure un code propre et cohérent. Bien que les linters et formatters soient excellents, ils sont inutiles s'ils ne sont pas utilisés.",[35,32317,32318,32319,32322,32323,2186],{},"J'utilise ",[6862,32320,32321],{},"GitHub Actions",", détaillées dans un fichier ",[156,32324,32325],{},".github\u002Fworkflows\u002Fci.yml",[192,32327,32331],{"className":32328,"code":32329,"language":32330,"meta":13,"style":13},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions\u002Fcheckout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4\n      - run: corepack enable\n      - uses: actions\u002Fsetup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4\n        with:\n          node-version: 22\n          cache: pnpm\n\n      - name: Installer les dépendances\n        run: pnpm install\n\n      - name: Linter les fichiers\n        run: pnpm run lint\n","yaml",[156,32332,32333,32342,32346,32352,32359,32366,32374,32381,32387,32393,32397,32404,32411,32420,32430,32434,32441,32456,32468,32481,32488,32498,32508,32512,32523,32533,32537,32548],{"__ignoreMap":13},[200,32334,32335,32337,32339],{"class":202,"line":203},[200,32336,20112],{"class":235},[200,32338,239],{"class":214},[200,32340,32341],{"class":245}," CI\n",[200,32343,32344],{"class":202,"line":14},[200,32345,528],{"emptyLinePlaceholder":20},[200,32347,32348,32350],{"class":202,"line":255},[200,32349,25410],{"class":2438},[200,32351,19494],{"class":214},[200,32353,32354,32357],{"class":202,"line":272},[200,32355,32356],{"class":235},"  push",[200,32358,19494],{"class":214},[200,32360,32361,32364],{"class":202,"line":283},[200,32362,32363],{"class":235},"    branches",[200,32365,19494],{"class":214},[200,32367,32368,32371],{"class":202,"line":300},[200,32369,32370],{"class":214},"      -",[200,32372,32373],{"class":245}," main\n",[200,32375,32376,32379],{"class":202,"line":310},[200,32377,32378],{"class":235},"  pull_request",[200,32380,19494],{"class":214},[200,32382,32383,32385],{"class":202,"line":336},[200,32384,32363],{"class":235},[200,32386,19494],{"class":214},[200,32388,32389,32391],{"class":202,"line":342},[200,32390,32370],{"class":214},[200,32392,32373],{"class":245},[200,32394,32395],{"class":202,"line":365},[200,32396,528],{"emptyLinePlaceholder":20},[200,32398,32399,32402],{"class":202,"line":371},[200,32400,32401],{"class":235},"jobs",[200,32403,19494],{"class":214},[200,32405,32406,32409],{"class":202,"line":398},[200,32407,32408],{"class":235},"  lint",[200,32410,19494],{"class":214},[200,32412,32413,32415,32417],{"class":202,"line":405},[200,32414,11812],{"class":235},[200,32416,239],{"class":214},[200,32418,32419],{"class":245}," Lint\n",[200,32421,32422,32425,32427],{"class":202,"line":410},[200,32423,32424],{"class":235},"    runs-on",[200,32426,239],{"class":214},[200,32428,32429],{"class":245}," ubuntu-latest\n",[200,32431,32432],{"class":202,"line":860},[200,32433,528],{"emptyLinePlaceholder":20},[200,32435,32436,32439],{"class":202,"line":889},[200,32437,32438],{"class":235},"    steps",[200,32440,19494],{"class":214},[200,32442,32443,32445,32448,32450,32453],{"class":202,"line":899},[200,32444,32370],{"class":214},[200,32446,32447],{"class":235}," uses",[200,32449,239],{"class":214},[200,32451,32452],{"class":245}," actions\u002Fcheckout@b4ffde65f46336ab88eb53be808477a3936bae11",[200,32454,32455],{"class":401}," # v4\n",[200,32457,32458,32460,32463,32465],{"class":202,"line":904},[200,32459,32370],{"class":214},[200,32461,32462],{"class":235}," run",[200,32464,239],{"class":214},[200,32466,32467],{"class":245}," corepack enable\n",[200,32469,32470,32472,32474,32476,32479],{"class":202,"line":930},[200,32471,32370],{"class":214},[200,32473,32447],{"class":235},[200,32475,239],{"class":214},[200,32477,32478],{"class":245}," actions\u002Fsetup-node@60edb5dd545a775178f52524783378180af0d1f8",[200,32480,32455],{"class":401},[200,32482,32483,32486],{"class":202,"line":3655},[200,32484,32485],{"class":235},"        with",[200,32487,19494],{"class":214},[200,32489,32490,32493,32495],{"class":202,"line":3660},[200,32491,32492],{"class":235},"          node-version",[200,32494,239],{"class":214},[200,32496,32497],{"class":2060}," 22\n",[200,32499,32500,32503,32505],{"class":202,"line":4035},[200,32501,32502],{"class":235},"          cache",[200,32504,239],{"class":214},[200,32506,32507],{"class":245}," pnpm\n",[200,32509,32510],{"class":202,"line":4490},[200,32511,528],{"emptyLinePlaceholder":20},[200,32513,32514,32516,32518,32520],{"class":202,"line":4496},[200,32515,32370],{"class":214},[200,32517,700],{"class":235},[200,32519,239],{"class":214},[200,32521,32522],{"class":245}," Installer les dépendances\n",[200,32524,32525,32528,32530],{"class":202,"line":4502},[200,32526,32527],{"class":235},"        run",[200,32529,239],{"class":214},[200,32531,32532],{"class":245}," pnpm install\n",[200,32534,32535],{"class":202,"line":4508},[200,32536,528],{"emptyLinePlaceholder":20},[200,32538,32539,32541,32543,32545],{"class":202,"line":5469},[200,32540,32370],{"class":214},[200,32542,700],{"class":235},[200,32544,239],{"class":214},[200,32546,32547],{"class":245}," Linter les fichiers\n",[200,32549,32550,32552,32554],{"class":202,"line":5474},[200,32551,32527],{"class":235},[200,32553,239],{"class":214},[200,32555,32556],{"class":245}," pnpm run lint\n",[35,32558,32559,32560,2186],{},"Pour automatiser le formatage du code et les commits, j'utilise ",[39,32561,32564],{"href":32562,"rel":32563},"https:\u002F\u002Fautofix.ci\u002F",[77],"autofix.ci",[192,32566,32568],{"className":32328,"code":32567,"language":32330,"meta":13,"style":13},"name: autofix.ci # needed to securely identify the workflow\n\non:\n  pull_request:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n\njobs:\n  autofix:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4\n      - run: corepack enable\n      - uses: actions\u002Fsetup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4\n        with:\n          node-version: 20.5\n          cache: pnpm\n\n      - name: Installer les dépendances\n        run: pnpm install\n\n      - name: Linter et corriger\n        run: pnpm run lint:fix\n\n      - uses: autofix-ci\u002Faction@ff86a557419858bb967097bfc916833f5647fa8c\n",[156,32569,32570,32582,32586,32592,32598,32604,32617,32621,32628,32638,32642,32648,32655,32663,32669,32682,32692,32705,32711,32720,32728,32732,32742,32750,32754,32765,32774,32778],{"__ignoreMap":13},[200,32571,32572,32574,32576,32579],{"class":202,"line":203},[200,32573,20112],{"class":235},[200,32575,239],{"class":214},[200,32577,32578],{"class":245}," autofix.ci",[200,32580,32581],{"class":401}," # needed to securely identify the workflow\n",[200,32583,32584],{"class":202,"line":14},[200,32585,528],{"emptyLinePlaceholder":20},[200,32587,32588,32590],{"class":202,"line":255},[200,32589,25410],{"class":2438},[200,32591,19494],{"class":214},[200,32593,32594,32596],{"class":202,"line":272},[200,32595,32378],{"class":235},[200,32597,19494],{"class":214},[200,32599,32600,32602],{"class":202,"line":283},[200,32601,32356],{"class":235},[200,32603,19494],{"class":214},[200,32605,32606,32608,32610,32612,32615],{"class":202,"line":300},[200,32607,32363],{"class":235},[200,32609,239],{"class":214},[200,32611,350],{"class":214},[200,32613,32614],{"class":245},"main",[200,32616,4511],{"class":214},[200,32618,32619],{"class":202,"line":310},[200,32620,528],{"emptyLinePlaceholder":20},[200,32622,32623,32626],{"class":202,"line":336},[200,32624,32625],{"class":235},"permissions",[200,32627,19494],{"class":214},[200,32629,32630,32633,32635],{"class":202,"line":342},[200,32631,32632],{"class":235},"  contents",[200,32634,239],{"class":214},[200,32636,32637],{"class":245}," read\n",[200,32639,32640],{"class":202,"line":365},[200,32641,528],{"emptyLinePlaceholder":20},[200,32643,32644,32646],{"class":202,"line":371},[200,32645,32401],{"class":235},[200,32647,19494],{"class":214},[200,32649,32650,32653],{"class":202,"line":398},[200,32651,32652],{"class":235},"  autofix",[200,32654,19494],{"class":214},[200,32656,32657,32659,32661],{"class":202,"line":405},[200,32658,32424],{"class":235},[200,32660,239],{"class":214},[200,32662,32429],{"class":245},[200,32664,32665,32667],{"class":202,"line":410},[200,32666,32438],{"class":235},[200,32668,19494],{"class":214},[200,32670,32671,32673,32675,32677,32680],{"class":202,"line":860},[200,32672,32370],{"class":214},[200,32674,32447],{"class":235},[200,32676,239],{"class":214},[200,32678,32679],{"class":245}," actions\u002Fcheckout@11bd71901bbe5b1630ceea73d27597364c9af683",[200,32681,32455],{"class":401},[200,32683,32684,32686,32688,32690],{"class":202,"line":889},[200,32685,32370],{"class":214},[200,32687,32462],{"class":235},[200,32689,239],{"class":214},[200,32691,32467],{"class":245},[200,32693,32694,32696,32698,32700,32703],{"class":202,"line":899},[200,32695,32370],{"class":214},[200,32697,32447],{"class":235},[200,32699,239],{"class":214},[200,32701,32702],{"class":245}," actions\u002Fsetup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af",[200,32704,32455],{"class":401},[200,32706,32707,32709],{"class":202,"line":904},[200,32708,32485],{"class":235},[200,32710,19494],{"class":214},[200,32712,32713,32715,32717],{"class":202,"line":930},[200,32714,32492],{"class":235},[200,32716,239],{"class":214},[200,32718,32719],{"class":2060}," 20.5\n",[200,32721,32722,32724,32726],{"class":202,"line":3655},[200,32723,32502],{"class":235},[200,32725,239],{"class":214},[200,32727,32507],{"class":245},[200,32729,32730],{"class":202,"line":3660},[200,32731,528],{"emptyLinePlaceholder":20},[200,32733,32734,32736,32738,32740],{"class":202,"line":4035},[200,32735,32370],{"class":214},[200,32737,700],{"class":235},[200,32739,239],{"class":214},[200,32741,32522],{"class":245},[200,32743,32744,32746,32748],{"class":202,"line":4490},[200,32745,32527],{"class":235},[200,32747,239],{"class":214},[200,32749,32532],{"class":245},[200,32751,32752],{"class":202,"line":4496},[200,32753,528],{"emptyLinePlaceholder":20},[200,32755,32756,32758,32760,32762],{"class":202,"line":4502},[200,32757,32370],{"class":214},[200,32759,700],{"class":235},[200,32761,239],{"class":214},[200,32763,32764],{"class":245}," Linter et corriger\n",[200,32766,32767,32769,32771],{"class":202,"line":4508},[200,32768,32527],{"class":235},[200,32770,239],{"class":214},[200,32772,32773],{"class":245}," pnpm run lint:fix\n",[200,32775,32776],{"class":202,"line":5469},[200,32777,528],{"emptyLinePlaceholder":20},[200,32779,32780,32782,32784,32786],{"class":202,"line":5474},[200,32781,32370],{"class":214},[200,32783,32447],{"class":235},[200,32785,239],{"class":214},[200,32787,32788],{"class":245}," autofix-ci\u002Faction@ff86a557419858bb967097bfc916833f5647fa8c\n",[51,32790,32792],{"id":32791},"garder-les-dépendances-à-jour","Garder les dépendances à jour",[35,32794,32795,32796,32801],{},"Maintenir des dépendances à jour est crucial. Avec de nombreux projets, cela peut prendre beaucoup de temps. J'utilise ",[39,32797,32800],{"href":32798,"rel":32799},"https:\u002F\u002Fdocs.renovatebot.com\u002F",[77],"Renovate"," dans chaque projet pour automatiser les mises à jour des dépendances :",[35,32803,31824,32804,32807],{},[156,32805,32806],{},"renovate.json"," avec :",[192,32809,32811],{"className":28358,"code":32810,"language":28360,"meta":13,"style":13},"{\n  \"extends\": [\n    \"github>nuxt\u002Frenovate-config-nuxt\"\n  ]\n}\n",[156,32812,32813,32817,32830,32839,32843],{"__ignoreMap":13},[200,32814,32815],{"class":202,"line":203},[200,32816,230],{"class":214},[200,32818,32819,32821,32824,32826,32828],{"class":202,"line":14},[200,32820,28489],{"class":214},[200,32822,32823],{"class":379},"extends",[200,32825,610],{"class":214},[200,32827,239],{"class":214},[200,32829,1410],{"class":214},[200,32831,32832,32834,32837],{"class":202,"line":255},[200,32833,28529],{"class":214},[200,32835,32836],{"class":245},"github>nuxt\u002Frenovate-config-nuxt",[200,32838,616],{"class":214},[200,32840,32841],{"class":202,"line":272},[200,32842,13683],{"class":214},[200,32844,32845],{"class":202,"line":283},[200,32846,1129],{"class":214},[35,32848,32849],{},"Cet outil est indispensable, et je trouve difficile de travailler sur des projets ne disposant pas de Renovate (comme certains projets professionnels).",[51,32851,32853],{"id":32852},"astuces-conseils-et-considérations-supplémentaires","Astuces, conseils et considérations supplémentaires",[35,32855,32856],{},"Voici quelques astuces et conseils supplémentaires que je trouve utiles à toujours avoir en tête, même s'ils ne sont pas directement liés à la configuration :",[1929,32858,32860],{"id":32859},"toujours-placer-les-scripts-en-premier","Toujours placer les scripts en premier",[35,32862,32863,32864,32866,32867,32869],{},"Organisez les fichiers ",[156,32865,12075],{}," en plaçant la section ",[156,32868,14896],{}," en premier. Cela offre de la clarté en présentant la logique de composant avant le template :",[192,32871,32873],{"className":21457,"code":32872,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\">\n\u002F\u002F ...\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u002F\u002F ...\n\u003C\u002Ftemplate>\n",[156,32874,32875,32893,32898,32906,32910,32918,32922],{"__ignoreMap":13},[200,32876,32877,32879,32881,32883,32885,32887,32889,32891],{"class":202,"line":203},[200,32878,599],{"class":214},[200,32880,14896],{"class":235},[200,32882,14594],{"class":379},[200,32884,449],{"class":214},[200,32886,610],{"class":214},[200,32888,1383],{"class":245},[200,32890,610],{"class":214},[200,32892,647],{"class":214},[200,32894,32895],{"class":202,"line":14},[200,32896,32897],{"class":401},"\u002F\u002F ...\n",[200,32899,32900,32902,32904],{"class":202,"line":255},[200,32901,680],{"class":214},[200,32903,14896],{"class":235},[200,32905,647],{"class":214},[200,32907,32908],{"class":202,"line":272},[200,32909,528],{"emptyLinePlaceholder":20},[200,32911,32912,32914,32916],{"class":202,"line":283},[200,32913,599],{"class":214},[200,32915,21468],{"class":235},[200,32917,647],{"class":214},[200,32919,32920],{"class":202,"line":300},[200,32921,10648],{"class":210},[200,32923,32924,32926,32928],{"class":202,"line":310},[200,32925,680],{"class":214},[200,32927,21468],{"class":235},[200,32929,647],{"class":214},[1929,32931,32933],{"id":32932},"adoptez-typescript","Adoptez TypeScript",[35,32935,32936,32937,32939,32940,32942,32943,32946],{},"Je plaide pour l'utilisation de ",[6862,32938,31441],{}," dans tous les fichiers ",[156,32941,12075],{}," et les scripts utilitaires ou composables. Ajouter ",[156,32944,32945],{},"lang=\"ts\""," même sans code TypeScript améliore l'expérience développeur :",[192,32948,32950],{"className":21457,"code":32949,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\n\u002F\u002F ...\n\u003C\u002Fscript>\n",[156,32951,32952,32972,32976],{"__ignoreMap":13},[200,32953,32954,32956,32958,32960,32962,32964,32966,32968,32970],{"class":202,"line":203},[200,32955,599],{"class":214},[200,32957,14896],{"class":235},[200,32959,14594],{"class":379},[200,32961,449],{"class":214},[200,32963,610],{"class":214},[200,32965,1383],{"class":245},[200,32967,610],{"class":214},[200,32969,21825],{"class":379},[200,32971,647],{"class":214},[200,32973,32974],{"class":202,"line":14},[200,32975,32897],{"class":401},[200,32977,32978,32980,32982],{"class":202,"line":255},[200,32979,680],{"class":214},[200,32981,14896],{"class":235},[200,32983,647],{"class":214},[938,32985,32986],{},[35,32987,32988,32990,32991,32994],{},[200,32989,944],{},"\nOptez toujours pour la syntaxe ",[156,32992,32993],{},"setup"," dans les composants.",[1929,32996,32998,32999,33002,33003],{"id":32997},"évitez-dutiliser-processenv-dans-nuxtconfigts","Évitez d'utiliser ",[156,33000,33001],{},"process.env"," dans ",[156,33004,1382],{},[35,33006,33007,33008,33002,33010,33012,33013,33016,33017,2186],{},"Évitez d'accéder directement à ",[156,33009,33001],{},[156,33011,1382],{},". Par exemple, définir des valeurs par défaut pour ",[156,33014,33015],{},"runtimeConfig"," ne doit pas utiliser ",[156,33018,33001],{},[192,33020,33022],{"className":1380,"code":33021,"language":1383,"meta":13,"style":13},"export default defineNuxtConfig({\n  runtimeConfig: {\n    apiUrl: ''\n  }\n})\n",[156,33023,33024,33036,33045,33054,33058],{"__ignoreMap":13},[200,33025,33026,33028,33030,33032,33034],{"class":202,"line":203},[200,33027,1390],{"class":206},[200,33029,1393],{"class":206},[200,33031,1396],{"class":223},[200,33033,227],{"class":210},[200,33035,230],{"class":214},[200,33037,33038,33041,33043],{"class":202,"line":14},[200,33039,33040],{"class":235},"  runtimeConfig",[200,33042,239],{"class":214},[200,33044,280],{"class":214},[200,33046,33047,33050,33052],{"class":202,"line":255},[200,33048,33049],{"class":235},"    apiUrl",[200,33051,239],{"class":214},[200,33053,18888],{"class":214},[200,33055,33056],{"class":202,"line":272},[200,33057,4505],{"class":214},[200,33059,33060,33062],{"class":202,"line":283},[200,33061,413],{"class":214},[200,33063,416],{"class":210},[938,33065,33066],{},[35,33067,33068,33070,33071,33074,33075,33077,33078,215],{},[200,33069,31019],{},"\nSurvolez la clé ",[156,33072,33073],{},"apiUrl"," dans votre IDE pour des indices sur les variables d'environnement. Dans ce cas ",[6862,33076,2934],{}," vous informe que la variable d'environnement doit avoir le nom ",[156,33079,33080],{},"NUXT_API_URL",[35,33082,31824,33083,33086],{},[156,33084,33085],{},".env"," à la racine du projet pour définir les variables d'environnement :",[192,33088,33092],{"className":33089,"code":33091,"language":697},[33090],"language-text","NUXT_API_URL=https:\u002F\u002Fapi.example.com\n",[156,33093,33091],{"__ignoreMap":13},[35,33095,33096,33097,33099,33100,2186],{},"Excluez le fichier ",[156,33098,33085],{}," à l'aide de ",[156,33101,33102],{},".gitignore",[192,33104,33106],{"className":2166,"code":33105,"language":2168,"meta":13,"style":13},"echo \".env\" >> .gitignore\n",[156,33107,33108],{"__ignoreMap":13},[200,33109,33110,33113,33115,33117,33119,33122],{"class":202,"line":203},[200,33111,33112],{"class":223},"echo",[200,33114,28499],{"class":214},[200,33116,33085],{"class":245},[200,33118,610],{"class":214},[200,33120,33121],{"class":214}," >>",[200,33123,33124],{"class":245}," .gitignore\n",[35,33126,33127,33128,33131],{},"Fournissez un fichier ",[156,33129,33130],{},".env.example"," pour guider les autres développeurs :",[192,33133,33135],{"className":33134,"code":33091,"language":697},[33090],[156,33136,33091],{"__ignoreMap":13},[7918,33138,33141,33145,33151,33161,33176,33179,33192,33196,33202,33209],{"text":33139,"href":33140},"Runtime Config: Common Mistakes","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=_FYV5WfiWvs",[1929,33142,33144],{"id":33143},"auditor-avant-dinstaller-des-modules","Auditor avant d'installer des modules",[35,33146,33147,33148,33150],{},"Réalisez un audit avant d'ajouter de nouveaux modules pour évaluer leur nécessité et fonctionnalité. Parfois, il est possible de procéder sans eux ou de choisir une alternative supérieure. Évitez de baser vos décisions sur des étoiles ou des téléchargements. Parcourez plutôt la documentation, le code source, les problèmes et les PR pour une évaluation complète du projet. L'écosystème ",[6862,33149,2934],{}," évolue rapidement, et certains modules peuvent devenir obsolètes ou incompatibles avec les nouvelles versions de Nuxt.",[1929,33152,33154,33155,1655,33158],{"id":33153},"choisir-entre-usefetch-et-fetch","Choisir entre ",[156,33156,33157],{},"useFetch",[156,33159,33160],{},"$fetch",[35,33162,32998,33163,33166,33167,33169,33170,215],{},[156,33164,33165],{},"fetch"," directement ; optez pour ",[156,33168,33160],{},", une instance de ",[39,33171,33174],{"href":33172,"rel":33173},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fofetch",[77],[6862,33175,19794],{},[35,33177,33178],{},"Pour savoir lequel utiliser, voici un guide rapide :",[68,33180,33181,33187],{},[71,33182,33183,33184,33186],{},"Utilisez ",[156,33185,33160],{}," pour les opérations côté client, telles que les soumissions de données de formulaire.",[71,33188,33183,33189,33191],{},[156,33190,33157],{}," lorsque le rendu côté serveur est envisageable, comme lors de la récupération de données d'API à afficher sur des pages.",[51,33193,33195],{"id":33194},"la-configuration-parfaite-est-à-vous","La configuration parfaite est à vous",[35,33197,33198,33199,33201],{},"Ce guide incarne tout ce que je fais pour initier un nouveau projet ",[6862,33200,2934],{},". Cette configuration complète prend environ 10 minutes, ce qui donne un projet propre, puissant et prêt pour l'avenir, permettant de se concentrer sur la logique métier sans distractions.",[938,33203,33204],{},[35,33205,33206,33208],{},[200,33207,29427],{},"\nÉvitez de créer un template à partir de ce guide. L'écosystème est dynamique, et de nouveaux modules émergent rapidement. Chaque projet peut nécessiter des ajustements nuancés. Ce guide est un point de départ, une liste de vérification pour la configuration initiale. Cependant, adaptez toujours en fonction des besoins spécifiques du projet. Maintenir un modèle nécessite des efforts et n'est généralement pas rentable.",[35,33210,33211],{},"J'espère que ce guide améliorera votre configuration de projet Nuxt. Si vous avez des questions ou souhaitez partager votre configuration, veuillez laisser un commentaire ci-dessous.",[1170,33213,33214],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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}html pre.shiki code .s6hCs, html code.shiki .s6hCs{--shiki-light:#9C3EDA;--shiki-light-font-style:italic;--shiki-default:#C792EA;--shiki-default-font-style:italic;--shiki-dark:#C792EA;--shiki-dark-font-style:italic}html pre.shiki code .sFweD, html code.shiki .sFweD{--shiki-light:#E2931D;--shiki-light-font-style:italic;--shiki-default:#FFCB6B;--shiki-default-font-style:italic;--shiki-dark:#FFCB6B;--shiki-dark-font-style:italic}",{"title":13,"searchDepth":14,"depth":14,"links":33216},[33217,33218,33219,33220,33221],{"id":30978,"depth":14,"text":30979},{"id":31053,"depth":14,"text":31054},{"id":31095,"depth":14,"text":31028},{"id":31203,"depth":14,"text":31204},{"id":31447,"depth":14,"text":31448,"children":33222},[33223],{"id":31457,"depth":255,"text":31458},"1bc3959e-9b88-4fd7-9898-cb60cb3420ff","2024-12-21","Un guide complet pour configurer efficacement un projet Nuxt, garantissant un projet propre, performant et prêt pour l'avenir.",{},"\u002Ffr\u002Fposts\u002Fthe-perfect-guide-to-setting-up-a-new-nuxt-project","---\ncontentId: 1bc3959e-9b88-4fd7-9898-cb60cb3420ff\ntitle: \"Le guide parfait pour configurer un nouveau projet Nuxt\"\ndescription: \"Un guide complet pour configurer efficacement un projet Nuxt, garantissant un projet propre, performant et prêt pour l'avenir.\"\ndate: 2024-12-21\n---\n\nChaque fois que je crée un nouveau projet **Nuxt**, je suis des étapes spécifiques pour garantir une configuration optimale. Je travaille avec **Nuxt** depuis 2019 et j'ai développé des habitudes spécifiques lors du démarrage de nouveaux projets.\n\nJ'ai pu explorer de nombreux modules et examiner des projets aussi bien d'experts que de débutants. Cela m'a permis de bien comprendre ce qui fonctionne dans l'**écosystème Nuxt**. Configurer un nouveau projet peut sembler décourageant, surtout pour les débutants, c'est pourquoi j'ai décidé de partager ce guide pour vous aider à configurer votre projet Nuxt aussi efficacement que possible.\n\n## Création d'un nouveau projet Nuxt\n\nCommençons par créer un nouveau projet **Nuxt**. C'est l'étape la plus simple, nécessitant juste une commande :\n\n```bash\nnpx nuxi@latest init \u003Cnom-du-projet>\n```\n\n> [!TIP]\n> Vous pouvez également créer un projet prêt pour la v4 avec `npx nuxi init nuxt-app -t v4-compat`, ce qui vous évite la nécessité de [Préparer pour Nuxt 4](#preparer-pour-nuxt-4).\n\n> [!NOTE]\n> Je choisis toujours **pnpm** comme gestionnaire de paquets. Vous pouvez sélectionner celui que vous préférez, mais je recommande d'essayer **pnpm**.\n\nL'exécution de cette commande va créer un nouveau projet Nuxt dans le répertoire `\u003Cnom-du-projet>`. L'application est minimaliste : aucune configuration, aucun module, juste Nuxt et un fichier `app.vue`.\n\n## Mettre à jour les dépendances\n\nAvant de continuer, il est essentiel de mettre à jour les dépendances vers leurs dernières versions. Cela vous permet de tirer parti des nouvelles fonctionnalités et correctifs tout en évitant des dépendances obsolètes qui pourraient causer des problèmes par la suite. Exécutez la commande suivante pour les mettre à jour :\n\n```bash\nnpx taze -w\n```\n\nLe paquet **taze**, développé par [Anthony Fu](https:\u002F\u002Fantfu.me), met à jour toutes les dépendances vers leurs dernières versions. C'est similaire à `npm-check-updates` mais plus puissant grâce à son respect version avec **semver** tout en étant plus rapide.\n\n## Préparer pour Nuxt 4\n\nActuellement, la dernière version stable de Nuxt est la version 3. Cependant, la version 4 est en développement et promet de nombreuses fonctionnalités et améliorations. Bien qu'elle ne soit pas officiellement publiée, vous pouvez préparer vos projets pour cette future mise à jour en ajoutant la configuration suivante dans votre `nuxt.config.js` :\n\n```ts\nexport default defineNuxtConfig({\n  future: {\n    compatibilityVersion: 4,\n  }\n})\n```\n\nCette configuration vous permet de commencer à développer vos projets comme si vous utilisiez déjà **Nuxt 4**. Cette prévoyance vous permet non seulement de bénéficier des fonctionnalités à venir, mais aussi de minimiser le travail requis pour la mise à niveau lorsque Nuxt 4 sera publié.\n\nPour tirer le meilleur parti de Nuxt 4, créez un nouveau dossier `app` et déplacez le fichier `app.vue` à l'intérieur :\n\n```bash\nmkdir app && mv app.vue app\n```\n\n> [!TIP]\n> Vous pouvez également créer un projet prêt pour la v4 en utilisant la commande `npx nuxi init nuxt-app -t v4-compat`.\n\n\u003CCalloutReadMore text=\"Mise à niveau vers Nuxt 4\" href=\"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fgetting-started\u002Fupgrade#opting-in-to-nuxt-4\" \u002F>\n\n## Linting et formatage\n\nNuxt offre une excellente expérience développeur pour le linting et le formatage. Installez à la fois le linter et le formateur avec la commande suivante :\n\n```bash\nnpx nuxi module add eslint && npx @antfu\u002Fni -D typescript\n```\n\nCette commande installe le module `@nuxt\u002Feslint`, qui propose une [configuration ESLint sans configuration pour les projets Nuxt](https:\u002F\u002Feslint.nuxt.com). Activez le formatage, et il corrigera automatiquement les problèmes lors de l'enregistrement d'un fichier. Je le configure toujours dans mon `nuxt.config.ts` :\n\n```ts\nexport default defineNuxtConfig({\n  eslint: {\n    config: {\n      stylistic: true \u002F\u002F [!code highlight]\n    }\n  }\n})\n```\n\nDe plus, créez un fichier `.vscode\u002Fsettings.json` pour désactiver l'extension prettier, évitant les conflits avec le formateur ESLint :\n\n```json\n{\n  \"prettier.enable\": false\n}\n```\n\nAjoutez deux scripts à votre fichier `package.json` :\n\n```json\n{\n  \"scripts\": {\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint --fix .\"\n  }\n}\n```\n\nMaintenant, vous pouvez exécuter `pnpm run lint` pour vérifier les erreurs et `pnpm run lint:fix` pour les corriger.\n\nCes configurations prédéfinies facilitent la mise en place de **ESLint** avec un support pour **TypeScript** et **Vue**, garantissant un code cohérent et propre.\n\n## UI et composants\n\nAvec une configuration de projet propre, les dernières dépendances, la préparation pour **Nuxt 4**, et un linting et formatage robustes, il est temps de se concentrer sur l'UI et les composants. Cette étape varie en fonction des objectifs du projet et des exigences de conception.\n\n### Nuxt UI et Nuxt UI Pro\n\nSi le projet met l'accent sur la logique métier plutôt que sur l'UI, j'utilise les bibliothèques de composants [Nuxt UI](https:\u002F\u002Fui.nuxt.com) et Nuxt UI Pro. Elles sont bien conçues et faciles à utiliser. Lire le [code source de Nuxt UI](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui) est instructif, et si vous voulez plongez davantage dans les composants, j'ai écris un article à ce sujet.\n\n\u003CCalloutReadMore id=\"1fafed86-05bb-4de4-b7d0-bb0bd6abc9d2\" \u002F>\n\n> [!NOTE]\n> Au moment de la rédaction, Nuxt UI 3 n'est pas officiellement publié, mais il peut être utilisé dans les projets. Si vous lisez cela plus tard, laissez-moi un commentaire, et je mettrai à jour ce guide.\n\nTout d'abord, installez Nuxt UI et Tailwind CSS :\n\n```bash\npnpm add @nuxt\u002Fui@next tailwindcss@next\n```\n\nAjoutez le module à votre fichier `nuxt.config.ts` :\n\n```ts\nexport default defineNuxtConfig({\n  modules: ['@nuxt\u002Fui']\n})\n```\n\nImportez le CSS nécessaire dans votre fichier `app\u002Fassets\u002Fcss\u002Fmain.css` :\n\n```css\n@import \"tailwindcss\";\n@import \"@nuxt\u002Fui\";\n```\n\nAssurez-vous que ce fichier CSS est inclus dans le fichier `nuxt.config.ts` :\n\n```ts\nexport default defineNuxtConfig({\n  css: ['~\u002Fassets\u002Fcss\u002Fmain.css']\n})\n```\n\nMettez à jour votre fichier `.vscode\u002Fsettings.json` pour améliorer l'expérience avec l'extension [Tailwind CSS IntelliSense](https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=bradlc.vscode-tailwindcss) :\n\n```json\n{\n  \"files.associations\": {\n    \"*.css\": \"tailwindcss\"\n  },\n  \"editor.quickSuggestions\": {\n    \"strings\": \"on\"\n  }\n}\n```\n\nEnfin, modifiez votre fichier `app\u002Fapp.vue` pour inclure le composant Nuxt UI `\u003CUApp>`, injectant automatiquement des composants globaux :\n\n```vue\n\u003Ctemplate>\n  \u003CUApp>\n    \u003CNuxtPage \u002F>\n  \u003C\u002FUApp>\n\u003C\u002Ftemplate>\n```\n\n> [!NOTE]\n> Nuxt UI Pro est une extension payante offrant des composants de mise en page pour un développement d'application rapide. C'est un investissement valable qui fait gagner du temps et des efforts tout en soutenant l'équipe Nuxt et le développement open-source. 💚\n\n### Tailwind CSS\n\nPour les projets nécessitant des conceptions spécifiques ou lorsque je collabore avec des designers, je me tourne vers [Tailwind CSS](https:\u002F\u002Ftailwindcss.com). Il s'agit d'un cadre CSS utilitaire qui rationalise le processus de conception à tel point qu'il est inimaginable de travailler sans. La version à venir, Tailwind CSS 4, inclut un plugin **Vite** indispensable, éliminant le besoin de modules supplémentaires et simplifiant la configuration.\n\nInstallez la dernière version de Tailwind CSS :\n\n```bash\npnpm add tailwindcss@next @tailwindcss\u002Fvite@next\n```\n\nCréez un fichier `app\u002Fassets\u002Fcss\u002Fmain.css` et importez Tailwind CSS :\n\n```css\n@import \"tailwindcss\";\n```\n\nMettez à jour votre fichier `nuxt.config.ts` pour utiliser le plugin Vite :\n\n```ts\nimport Tailwind from '@tailwindcss\u002Fvite'\n\nexport default defineNuxtConfig({\n  css: ['~\u002Fassets\u002Fcss\u002Fmain.css'],\n  vite: {\n    plugins: [\n      Tailwind()\n    ]\n  }\n})\n```\n\nAméliorez votre expérience développeur en mettant à jour votre fichier `.vscode\u002Fsettings.json` pour l'extension [Tailwind CSS IntelliSense](https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=bradlc.vscode-tailwindcss) :\n\n```json\n{\n  \"files.associations\": {\n    \"*.css\": \"tailwindcss\"\n  },\n  \"editor.quickSuggestions\": {\n    \"strings\": \"on\"\n  }\n}\n```\n\nLa configuration est incroyablement simple !\n\n## Icônes\n\nIncorporer des icônes dans votre projet est essentiel, bien que cela puisse s'avérer trompeusement complexe. L'article d'[Anthony Fu](https:\u002F\u002Fnuxt.com\u002Fblog\u002Fnuxt-icon-v1-0) explique comment il a trouvé la solution d'icônes parfaite.\n\nSi vous utilisez Nuxt UI, les icônes sont déjà inclus. Vous pouvez donc ignorer cette section.\n\nPour les utilisateurs de Tailwind CSS, deux options sont disponibles :\n\n1. Utiliser le module [Nuxt Icons](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ficon) fournit accès à plus de 200 000 icônes de divers ensembles d'icônes :\n\n```bash\nnpx nuxi module add icon\n```\n\nC'est la solution la plus simple et avec les dernières améliorations de la v1, c'est un choix puissant. Je recommande de commencer par cette option.\n\n2. Utiliser le plugin Tailwind CSS, [Tailwind CSS Icons](https:\u002F\u002Fgithub.com\u002Fegoist\u002Ftailwindcss-icons), offre des icônes similaires via une approche différente :\n\n```bash\nnpx @antfu\u002Fni -D @egoist\u002Ftailwindcss-icons\n```\n\nCréez un fichier `tailwind.config.js` et incluez le plugin :\n\n```js\nimport { getIconCollections, iconsPlugin } from '@egoist\u002Ftailwindcss-icons'\n\n\u002F** @type {import('tailwindcss').Config} *\u002F\nmodule.exports = {\n  plugins: [\n    iconsPlugin({\n      collections: getIconCollections([\u002F** ... *\u002F]),\n    }),\n  ],\n}\n```\n\nImportez la configuration dans votre fichier `app\u002Fassets\u002Fcss\u002Fmain.css` :\n\n```css\n@import \"tailwindcss\";\n@config \"..\u002F..\u002F..\u002Ftailwind.config.js\";\n```\n\nPersonnellement, je préfère la deuxième solution en raison de sa configuration uniforme à travers les projets, y compris ceux n'utilisant pas Nuxt, et au moment où j'ai fait ce choix, le module Nuxt Icons souffrait de nombreux inconvénients. Cependant, ce n'est plus le cas et je vous recommande fortement de commencer par [Nuxt Icons](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ficon).\n\n## Polices\n\nLes polices définissent la personnalité de votre projet et influencent la perception utilisateur. **Nuxt** fournit une solution : [Nuxt Fonts](https:\u002F\u002Ffonts.nuxt.com\u002F). Ce module zéro configuration charge automatiquement les polices requises en scannant les fichiers CSS. L'installation est simple :\n\n```bash\nnpx nuxi module add fonts\n```\n\nCe module fait partie de mes projets sauf si j'utilise Nuxt UI, où il est inclus. Mais je dois dire que j'ai rarement besoin de l'installer puisque Nuxt UI suffit dans la plupart des scénarios.\n\n## CI\n\nPour mes projets personnels, j'utilise **GitHub** et je configure toujours un CI pour faire respecter le linting et corriger automatiquement les erreurs. Cette approche assure un code propre et cohérent. Bien que les linters et formatters soient excellents, ils sont inutiles s'ils ne sont pas utilisés.\n\nJ'utilise **GitHub Actions**, détaillées dans un fichier `.github\u002Fworkflows\u002Fci.yml` :\n\n```yaml\nname: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions\u002Fcheckout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4\n      - run: corepack enable\n      - uses: actions\u002Fsetup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4\n        with:\n          node-version: 22\n          cache: pnpm\n\n      - name: Installer les dépendances\n        run: pnpm install\n\n      - name: Linter les fichiers\n        run: pnpm run lint\n```\n\nPour automatiser le formatage du code et les commits, j'utilise [autofix.ci](https:\u002F\u002Fautofix.ci\u002F) :\n\n```yaml\nname: autofix.ci # needed to securely identify the workflow\n\non:\n  pull_request:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n\njobs:\n  autofix:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4\n      - run: corepack enable\n      - uses: actions\u002Fsetup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4\n        with:\n          node-version: 20.5\n          cache: pnpm\n\n      - name: Installer les dépendances\n        run: pnpm install\n\n      - name: Linter et corriger\n        run: pnpm run lint:fix\n\n      - uses: autofix-ci\u002Faction@ff86a557419858bb967097bfc916833f5647fa8c\n```\n\n## Garder les dépendances à jour\n\nMaintenir des dépendances à jour est crucial. Avec de nombreux projets, cela peut prendre beaucoup de temps. J'utilise [Renovate](https:\u002F\u002Fdocs.renovatebot.com\u002F) dans chaque projet pour automatiser les mises à jour des dépendances :\n\nCréez un fichier `renovate.json` avec :\n\n```json\n{\n  \"extends\": [\n    \"github>nuxt\u002Frenovate-config-nuxt\"\n  ]\n}\n```\n\nCet outil est indispensable, et je trouve difficile de travailler sur des projets ne disposant pas de Renovate (comme certains projets professionnels).\n\n## Astuces, conseils et considérations supplémentaires\n\nVoici quelques astuces et conseils supplémentaires que je trouve utiles à toujours avoir en tête, même s'ils ne sont pas directement liés à la configuration :\n\n### Toujours placer les scripts en premier\n\nOrganisez les fichiers `vue` en plaçant la section `script` en premier. Cela offre de la clarté en présentant la logique de composant avant le template :\n\n```vue\n\u003Cscript lang=\"ts\">\n\u002F\u002F ...\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u002F\u002F ...\n\u003C\u002Ftemplate>\n```\n\n### Adoptez TypeScript\n\nJe plaide pour l'utilisation de **TypeScript** dans tous les fichiers `vue` et les scripts utilitaires ou composables. Ajouter `lang=\"ts\"` même sans code TypeScript améliore l'expérience développeur :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\n\u002F\u002F ...\n\u003C\u002Fscript>\n```\n\n> [!NOTE]\n> Optez toujours pour la syntaxe `setup` dans les composants.\n\n### Évitez d'utiliser `process.env` dans `nuxt.config.ts`\n\nÉvitez d'accéder directement à `process.env` dans `nuxt.config.ts`. Par exemple, définir des valeurs par défaut pour `runtimeConfig` ne doit pas utiliser `process.env` :\n\n```ts\nexport default defineNuxtConfig({\n  runtimeConfig: {\n    apiUrl: ''\n  }\n})\n```\n\n> [!TIP]\n> Survolez la clé `apiUrl` dans votre IDE pour des indices sur les variables d'environnement. Dans ce cas **Nuxt** vous informe que la variable d'environnement doit avoir le nom `NUXT_API_URL`.\n\nCréez un fichier `.env` à la racine du projet pour définir les variables d'environnement :\n\n```\nNUXT_API_URL=https:\u002F\u002Fapi.example.com\n```\n\nExcluez le fichier `.env` à l'aide de `.gitignore` :\n\n```bash\necho \".env\" >> .gitignore\n```\n\nFournissez un fichier `.env.example` pour guider les autres développeurs :\n\n```\nNUXT_API_URL=https:\u002F\u002Fapi.example.com\n```\n\n\u003CCalloutReadMore text=\"Runtime Config: Common Mistakes\" href=\"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=_FYV5WfiWvs\" \u002F>\n\n### Auditor avant d'installer des modules\n\nRéalisez un audit avant d'ajouter de nouveaux modules pour évaluer leur nécessité et fonctionnalité. Parfois, il est possible de procéder sans eux ou de choisir une alternative supérieure. Évitez de baser vos décisions sur des étoiles ou des téléchargements. Parcourez plutôt la documentation, le code source, les problèmes et les PR pour une évaluation complète du projet. L'écosystème **Nuxt** évolue rapidement, et certains modules peuvent devenir obsolètes ou incompatibles avec les nouvelles versions de Nuxt.\n\n### Choisir entre `useFetch` et `$fetch`\n\nÉvitez d'utiliser `fetch` directement ; optez pour `$fetch`, une instance de [**ofetch**](https:\u002F\u002Fgithub.com\u002Funjs\u002Fofetch).\n\nPour savoir lequel utiliser, voici un guide rapide :\n\n1. Utilisez `$fetch` pour les opérations côté client, telles que les soumissions de données de formulaire.\n2. Utilisez `useFetch` lorsque le rendu côté serveur est envisageable, comme lors de la récupération de données d'API à afficher sur des pages.\n\n## La configuration parfaite est à vous\n\nCe guide incarne tout ce que je fais pour initier un nouveau projet **Nuxt**. Cette configuration complète prend environ 10 minutes, ce qui donne un projet propre, puissant et prêt pour l'avenir, permettant de se concentrer sur la logique métier sans distractions.\n\n> [!WARNING]\n> Évitez de créer un template à partir de ce guide. L'écosystème est dynamique, et de nouveaux modules émergent rapidement. Chaque projet peut nécessiter des ajustements nuancés. Ce guide est un point de départ, une liste de vérification pour la configuration initiale. Cependant, adaptez toujours en fonction des besoins spécifiques du projet. Maintenir un modèle nécessite des efforts et n'est généralement pas rentable.\n\nJ'espère que ce guide améliorera votre configuration de projet Nuxt. Si vous avez des questions ou souhaitez partager votre configuration, veuillez laisser un commentaire ci-dessous.\n",{"title":30957,"description":33226},{"loc":33228},"fr\u002Fposts\u002Fthe-perfect-guide-to-setting-up-a-new-nuxt-project","LOHxzl2alqZWXJkbwjdsnADFUFvBZPncts2DR1rx_BM",{"id":33235,"title":33236,"body":33237,"contentId":33630,"date":33631,"description":33632,"extension":18,"meta":33633,"navigation":20,"path":33634,"rawbody":33635,"robots":23,"schemaOrg":23,"seo":33636,"sitemap":33637,"stem":33638,"__hash__":33639},"posts\u002Ffr\u002Fposts\u002Ffor-a-friend-from-infrastructure-to-web-development-in-2025.md","Passer de l'infrastructure au développement web en 2025",{"type":7,"value":33238,"toc":33622},[33239,33258,33268,33276,33279,33293,33296,33299,33302,33306,33316,33362,33369,33373,33390,33398,33405,33408,33411,33441],[35,33240,33241,33242,33245,33246,3092,33249,9502,33251,33254,33255,33257],{},"Après un meetup où j'ai donné une présentation sur l'",[6862,33243,33244],{},"optimistic UI",", un ami s'est approché de moi avec une question intéressante. Il travaille principalement dans l'infrastructure, utilisant ",[6862,33247,33248],{},"Linux",[6862,33250,30874],{},[6862,33252,33253],{},"Kubernetes",", et a fait un peu de ",[6862,33256,21389],{}," il y a des années. Il m'a demandé : \"J'aimerais revenir au développement web. Par où devrais-je commencer ?\"",[35,33259,33260,33261,33263,33264,33267],{},"La question m'a semblé à la fois intéressante et très pertinente. Le développement web est un domaine vaste et parfois déroutant de l'extérieur. Il évolue rapidement, avec de nouveaux outils, bibliothèques, frameworks et meilleures pratiques qui émergent constamment. Cela peut sembler intimidant, surtout dans le monde de ",[6862,33262,30765],{},", où la ",[6862,33265,33266],{},"fatigue JS"," est un défi courant.",[938,33269,33270],{},[35,33271,33272,33275],{},[200,33273,33274],{},"!IMPORTANT","\nPeu importe votre expérience, ces conseils s'appliquent universellement. N'oubliez pas que cela repose sur mon expérience, ma vision et mes opinions, et non sur une vérité absolue.",[35,33277,33278],{},"Avant de donner des conseils, il est crucial de comprendre le contexte. La manière dont mon ami envisage d'utiliser le développement web influencera les recommandations. Je vois deux chemins principaux :",[68,33280,33281,33287],{},[71,33282,33283,33286],{},[6862,33284,33285],{},"Le développement web comme passe-temps"," : créer des projets personnels, apprendre et s'amuser.",[71,33288,33289,33292],{},[6862,33290,33291],{},"Le développement web comme carrière"," : travailler dans une entreprise pour créer des produits et collaborer avec une équipe.",[35,33294,33295],{},"L'approche varie également selon la localisation, car différents pays ont des marchés distincts et des outils privilégiés. Ici, nous nous concentrons sur la création de sites web ou d'applications web, et non sur le développement d'outils ou de bibliothèques.",[35,33297,33298],{},"Ces dernières années, j'ai expérimenté de nombreux outils, bibliothèques, frameworks et langages. Trouver la bonne pile technologique peut être difficile et prendre du temps. Avoir des conseils au début de votre parcours peut vous faire économiser beaucoup de temps et prévenir le risque de vous éparpiller trop face à l'afflux constant de nouveaux outils.",[35,33300,33301],{},"Commençons par les essentiels.",[51,33303,33305],{"id":33304},"les-bases","Les Bases",[35,33307,33308,33309,3092,33311,9502,33313,33315],{},"Les fondements du développement web sont ",[6862,33310,30759],{},[6862,33312,30762],{},[6862,33314,30765],{},". Voici quelques ressources pour les apprendre :",[1593,33317,33318,33326,33333,33340,33348,33355],{},[71,33319,33320,33325],{},[39,33321,33324],{"href":33322,"rel":33323},"https:\u002F\u002Fyoutu.be\u002FoEAuNzWXRjM?si=CtsGceAM_z3WNfGz",[77],"Apprendre l'HTML"," avec Grafikart.",[71,33327,33328,33325],{},[39,33329,33332],{"href":33330,"rel":33331},"https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PLjwdMgw5TTLVjTZQocrMwKicV5wsZlRpj&si=J4vZKiGb3LBjiDks",[77],"Découverte du CSS",[71,33334,33335,33325],{},[39,33336,33339],{"href":33337,"rel":33338},"https:\u002F\u002Fwww.youtube.com\u002Fplaylist?list=PLjwdMgw5TTLXgsTQE_1PpRkC_yX47ZcGV",[77],"Apprendre le JavaScript",[71,33341,33342,33347],{},[39,33343,33346],{"href":33344,"rel":33345},"https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9ivBf_eKCPIAYXWzLlPAm6G&si=QTwFntwwlQ3pbkQ-",[77],"HTML & CSS Crash Course Tutorial"," avec le Net Ninja.",[71,33349,33350,33347],{},[39,33351,33354],{"href":33352,"rel":33353},"https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9haFPT7J25Q9GRB_ZkFrQAc&si=RzCATnLejIdUzMgf",[77],"Modern JavaScript Tutorial",[71,33356,33357,33347],{},[39,33358,33361],{"href":33359,"rel":33360},"https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9gfoKa5la9dsdCNpuey2s-V&si=kX5SKcV3x4Tc3BpD",[77],"JavaScript DOM Tutorial",[35,33363,33364,33365,33368],{},"Ces ressources fourniront une base solide. Ne passez pas un temps excessif sur elles : bien que les fondamentaux soient essentiels, ils ne sont que le point de départ. Gardez le ",[6862,33366,33367],{},"Mozilla Developer Network (MDN)"," comme votre guide de référence.",[51,33370,33372],{"id":33371},"backend-ou-frontend","Backend ou Frontend",[35,33374,33375,33376,3092,33378,4727,33380,33382,33383,14181,33386,33389],{},"De nombreux nouveaux venus préfèrent explorer les frameworks frontend comme ",[6862,33377,18003],{},[6862,33379,17998],{},[6862,33381,22922],{},". D'autres peuvent opter pour un backend ",[6862,33384,33385],{},"Node.js",[6862,33387,33388],{},"Express",", tirant parti de leur confort avec JavaScript.",[35,33391,33392,33393,1655,33396,215],{},"Cependant, aucun de ces chemins n'est idéal au début. Utiliser un framework frontend trop tôt ou plonger dans l'écosystème Node.js peut être démoralisant. Je recommande de commencer avec ",[6862,33394,33395],{},"PHP",[6862,33397,19463],{},[938,33399,33400],{},[35,33401,33402,33404],{},[200,33403,944],{},"\nLaravel est un framework d'application web avec une syntaxe expressive et élégante, vous permettant de créer des applications sans effort.",[35,33406,33407],{},"Oubliez les idées préconçues selon lesquelles PHP serait obsolète ou encombrant. Le langage a évolué de manière significative, rendant le développement PHP confortable et efficace.",[35,33409,33410],{},"Voici pourquoi PHP et Laravel sont recommandés :",[1593,33412,33413,33425,33432],{},[71,33414,5778,33415,33418,33419,33424],{},[6862,33416,33417],{},"documentation de Laravel"," sur ",[39,33420,33423],{"href":33421,"rel":33422},"https:\u002F\u002Flaravel.com\u002F",[77],"laravel.com"," est l'une des plus complètes.",[71,33426,33427,33428,33431],{},"L'",[6862,33429,33430],{},"écosystème de packages Laravel"," est vaste, bien entretenu et documenté, offrant des ressources même pour des besoins spécifiques.",[71,33433,33434,33435,33440],{},"La communauté Laravel est accueillante et riche en ressources, avec des plateformes comme ",[39,33436,33439],{"href":33437,"rel":33438},"https:\u002F\u002Flaracasts.com\u002F",[77],"Laracasts"," offrant de nombreux supports d'apprentissage sur divers sujets de développement web. Vous ne trouverez pas de meilleure ressource pour apprendre le développement web.",[7151,33442,33446,33450,33459,33483,33487,33490,33498,33514,33517,33521,33524,33535,33547,33551,33559,33567],{"icon":33443,"text":33444,"to":33445},"i-ph-ticket-duotone","Abonnez-vous à Laracasts avec mon lien de parrainage","https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes",[51,33447,33449],{"id":33448},"apprendre-php-et-laravel","Apprendre PHP et Laravel",[35,33451,33452,33453,33458],{},"Sur Laracasts, je recommande de suivre ",[39,33454,33457],{"href":33455,"rel":33456},"https:\u002F\u002Flaracasts.com\u002Fpath",[77],"le parcours",", qui vous guide des bases de PHP aux concepts avancés de Laravel. Voici quelques cours à considérer :",[1593,33460,33461,33468,33475],{},[71,33462,33463,215],{},[39,33464,33467],{"href":33465,"rel":33466},"https:\u002F\u002Flaracasts.com\u002Fseries\u002Fphp-for-beginners-2023-edition",[77],"PHP pour les débutants",[71,33469,33470,215],{},[39,33471,33474],{"href":33472,"rel":33473},"https:\u002F\u002Flaracasts.com\u002Fseries\u002F30-days-to-learn-laravel-11",[77],"30 jours pour apprendre Laravel",[71,33476,33477,33482],{},[39,33478,33481],{"href":33479,"rel":33480},"https:\u002F\u002Flaracasts.com\u002Fseries\u002Fbuild-a-forum-with-laravel",[77],"Créer un forum avec Laravel",". Ce cours est difficile, mais trouvez des cours associés pour clarifier les concepts si nécessaire.",[51,33484,33486],{"id":33485},"les-choses-commencent-à-se-compliquer","Les choses commencent à se compliquer",[35,33488,33489],{},"Une fois que vous êtes à l'aise avec PHP et Laravel, commencez à construire avec Blade, CSS pur et JavaScript. Reconnaissez pourquoi des outils plus puissants sont nécessaires avant de les adopter.",[35,33491,33492,33493,1655,33495,33497],{},"Lorsque la gestion de CSS et JavaScript devient difficile, apprenez ",[6862,33494,31790],{},[6862,33496,18636],{}," pour une meilleure maintenabilité et interactivité :",[1593,33499,33500,33507],{},[71,33501,33502,33506],{},[39,33503,31790],{"href":33504,"rel":33505},"https:\u002F\u002Ftailwindcss.com\u002F",[77]," est un framework CSS utilitaire, ce qui vous rend plus productif en éliminant les modifications directes des fichiers CSS, les feuilles de style encombrantes et les classes répétitives.",[71,33508,33509,33513],{},[39,33510,18636],{"href":33511,"rel":33512},"https:\u002F\u002Fvuejs.org\u002F",[77]," est un framework JavaScript progressif, facile pour les débutants, présentant des avantages techniques par rapport à React et Angular.",[35,33515,33516],{},"Explorez Laracasts pour des cours sur ces technologies et lisez attentivement leur documentation pour comprendre les capacités et les limitations avant utilisation.",[51,33518,33520],{"id":33519},"devenir-développeur-full-stack","Devenir Développeur Full-Stack",[35,33522,33523],{},"Atteindre la maîtrise du développement full-stack est possible avec la bonne approche et les bons outils.",[35,33525,33526,33527,33530,33531,33534],{},"Évitez de créer une ",[6862,33528,33529],{},"application à page unique (SPA)"," avec une ",[6862,33532,33533],{},"API REST"," à moins que cela soit nécessaire. L'_over engineering complique la maintenance sans justification adéquate.",[35,33536,33537,33538,33540,33541,33546],{},"Une fois familiarisé avec Laravel et Vue.js, apprenez ",[6862,33539,24253],{},", un protocole qui combine la simplicité dirigée par le serveur avec la puissance d'une SPA. ",[39,33542,33545],{"href":33543,"rel":33544},"https:\u002F\u002Flaracasts.com\u002Fseries\u002Fbuild-modern-laravel-apps-using-inertia-js",[77],"Laracasts propose une série à ce sujet",", vous aidant à devenir un développeur full-stack capable de bâtir diverses applications web.",[51,33548,33550],{"id":33549},"quelques-outils-supplémentaires","Quelques Outils Supplémentaires",[35,33552,33553,33554,33558],{},"Pour éviter la fatigue liée à la gestion des packages JavaScript, utilisez ",[39,33555,1365],{"href":33556,"rel":33557},"https:\u002F\u002Fpnpm.io\u002F",[77],", un gestionnaire de paquets rapide et économe en espace, adapté à tous vos besoins, des mono-repos au patching des dépendances locales.",[35,33560,33561,33562,33566],{},"Pour les sites Web nécessitant de la documentation ou centré sur le contenu, utilisez ",[39,33563,2929],{"href":33564,"rel":33565},"https:\u002F\u002Fvitepress.dev\u002F",[77],", un générateur de site statique alimenté par Vue et Vite. La familiarité avec Vue.js facilite son adoption.",[7918,33568,33570,33577,33580,33583,33600,33604,33616,33619],{"id":33569},"674a3932-aaba-445c-82bb-0896ce3a8e7e",[35,33571,33572,33573,33576],{},"Pour les applications nécessitant une intégration d'API, envisagez ",[39,33574,2934],{"href":13829,"rel":33575},[77],", un méta-framework Vue.js soutenant divers styles de construction, offrant une solution puissante et bien documentée.",[35,33578,33579],{},"Vous pourriez trouver d'autres outils intéressants, mais la cohérence mène à la maîtrise. Concentrez-vous sur ces outils recommandés et approfondissez votre connaissance.",[35,33581,33582],{},"En outre, gardez un œil sur :",[1593,33584,33585,33592],{},[71,33586,33587,33591],{},[39,33588,30540],{"href":33589,"rel":33590},"https:\u002F\u002Funjs.io\u002F",[77],", un écosystème en plein essor de bibliothèques JavaScript bien conçues.",[71,33593,33594,33599],{},[39,33595,33598],{"href":33596,"rel":33597},"https:\u002F\u002Fspatie.be\u002F",[77],"Spatie",", une agence de développement web offrant des paquets PHP et Laravel précieux.",[51,33601,33603],{"id":33602},"pour-le-travail","Pour le Travail",[35,33605,33606,33607,14181,33610,1655,33613,33615],{},"Bien que ces recommandations conviennent aux amateurs et offrent une connaissance approfondie du développement web, adaptez-vous aux tendances du marché pour un travail professionnel. En France, par exemple, ",[6862,33608,33609],{},"Java",[6862,33611,33612],{},"Spring Boot",[6862,33614,22922],{}," sont en demande. Concentrez-vous sur les concepts de base pour vous assurer une flexibilité dans les transitions d'outils ou d'entreprises.",[35,33617,33618],{},"Restez motivé au milieu de la courbe d'apprentissage. Le voyage est long mais gratifiant. Embrassez le processus d'apprentissage, connectez-vous avec vos pairs et appréciez le fait de créer. Le développement web est gratifiant et offre des possibilités infinies.",[35,33620,33621],{},"J'espère que ce post vous aidera à vous lancer dans votre parcours de développement web. N'hésitez pas à laisser des commentaires ou à partager vos expériences avec nous. 💜",{"title":13,"searchDepth":14,"depth":14,"links":33623},[33624,33625,33626,33627,33628,33629],{"id":33304,"depth":14,"text":33305},{"id":33371,"depth":14,"text":33372},{"id":33448,"depth":14,"text":33449},{"id":33485,"depth":14,"text":33486},{"id":33519,"depth":14,"text":33520},{"id":33549,"depth":14,"text":33550},"ee9373dc-a101-44db-b3e3-a25b15c00e4e","2024-11-28","Conseils pour passer de l'infrastructure au développement web avec des étapes pratiques, des resources et des recommandations.",{},"\u002Ffr\u002Fposts\u002Ffor-a-friend-from-infrastructure-to-web-development-in-2025","---\ncontentId: ee9373dc-a101-44db-b3e3-a25b15c00e4e\ntitle: \"Passer de l'infrastructure au développement web en 2025\"\ndescription: \"Conseils pour passer de l'infrastructure au développement web avec des étapes pratiques, des resources et des recommandations.\"\ndate: 2024-11-28\n---\n\nAprès un meetup où j'ai donné une présentation sur l'**optimistic UI**, un ami s'est approché de moi avec une question intéressante. Il travaille principalement dans l'infrastructure, utilisant **Linux**, **Terraform**, et **Kubernetes**, et a fait un peu de **développement web** il y a des années. Il m'a demandé : \"J'aimerais revenir au développement web. Par où devrais-je commencer ?\"\n\nLa question m'a semblé à la fois intéressante et très pertinente. Le développement web est un domaine vaste et parfois déroutant de l'extérieur. Il évolue rapidement, avec de nouveaux outils, bibliothèques, frameworks et meilleures pratiques qui émergent constamment. Cela peut sembler intimidant, surtout dans le monde de **JavaScript**, où la **fatigue JS** est un défi courant.\n\n> [!IMPORTANT]\n> Peu importe votre expérience, ces conseils s'appliquent universellement. N'oubliez pas que cela repose sur mon expérience, ma vision et mes opinions, et non sur une vérité absolue.\n\nAvant de donner des conseils, il est crucial de comprendre le contexte. La manière dont mon ami envisage d'utiliser le développement web influencera les recommandations. Je vois deux chemins principaux :\n\n1. **Le développement web comme passe-temps** : créer des projets personnels, apprendre et s'amuser.\n2. **Le développement web comme carrière** : travailler dans une entreprise pour créer des produits et collaborer avec une équipe.\n\nL'approche varie également selon la localisation, car différents pays ont des marchés distincts et des outils privilégiés. Ici, nous nous concentrons sur la création de sites web ou d'applications web, et non sur le développement d'outils ou de bibliothèques.\n\nCes dernières années, j'ai expérimenté de nombreux outils, bibliothèques, frameworks et langages. Trouver la bonne pile technologique peut être difficile et prendre du temps. Avoir des conseils au début de votre parcours peut vous faire économiser beaucoup de temps et prévenir le risque de vous éparpiller trop face à l'afflux constant de nouveaux outils.\n\nCommençons par les essentiels.\n\n## Les Bases\n\nLes fondements du développement web sont **HTML**, **CSS**, et **JavaScript**. Voici quelques ressources pour les apprendre :\n\n- [Apprendre l'HTML](https:\u002F\u002Fyoutu.be\u002FoEAuNzWXRjM?si=CtsGceAM_z3WNfGz) avec Grafikart.\n- [Découverte du CSS](https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PLjwdMgw5TTLVjTZQocrMwKicV5wsZlRpj&si=J4vZKiGb3LBjiDks) avec Grafikart.\n- [Apprendre le JavaScript](https:\u002F\u002Fwww.youtube.com\u002Fplaylist?list=PLjwdMgw5TTLXgsTQE_1PpRkC_yX47ZcGV) avec Grafikart.\n- [HTML & CSS Crash Course Tutorial](https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9ivBf_eKCPIAYXWzLlPAm6G&si=QTwFntwwlQ3pbkQ-) avec le Net Ninja.\n- [Modern JavaScript Tutorial](https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9haFPT7J25Q9GRB_ZkFrQAc&si=RzCATnLejIdUzMgf) avec le Net Ninja.\n- [JavaScript DOM Tutorial](https:\u002F\u002Fyoutube.com\u002Fplaylist?list=PL4cUxeGkcC9gfoKa5la9dsdCNpuey2s-V&si=kX5SKcV3x4Tc3BpD) avec le Net Ninja.\n\nCes ressources fourniront une base solide. Ne passez pas un temps excessif sur elles : bien que les fondamentaux soient essentiels, ils ne sont que le point de départ. Gardez le **Mozilla Developer Network (MDN)** comme votre guide de référence.\n\n## Backend ou Frontend\n\nDe nombreux nouveaux venus préfèrent explorer les frameworks frontend comme **React**, **Vue**, ou **Angular**. D'autres peuvent opter pour un backend **Node.js** avec **Express**, tirant parti de leur confort avec JavaScript.\n\nCependant, aucun de ces chemins n'est idéal au début. Utiliser un framework frontend trop tôt ou plonger dans l'écosystème Node.js peut être démoralisant. Je recommande de commencer avec **PHP** et **Laravel**.\n\n> [!NOTE]\n> Laravel est un framework d'application web avec une syntaxe expressive et élégante, vous permettant de créer des applications sans effort.\n\nOubliez les idées préconçues selon lesquelles PHP serait obsolète ou encombrant. Le langage a évolué de manière significative, rendant le développement PHP confortable et efficace.\n\nVoici pourquoi PHP et Laravel sont recommandés :\n\n- La **documentation de Laravel** sur [laravel.com](https:\u002F\u002Flaravel.com\u002F) est l'une des plus complètes.\n- L'**écosystème de packages Laravel** est vaste, bien entretenu et documenté, offrant des ressources même pour des besoins spécifiques.\n- La communauté Laravel est accueillante et riche en ressources, avec des plateformes comme [Laracasts](https:\u002F\u002Flaracasts.com\u002F) offrant de nombreux supports d'apprentissage sur divers sujets de développement web. Vous ne trouverez pas de meilleure ressource pour apprendre le développement web.\n\n\u003CCallout\n  icon=\"i-ph-ticket-duotone\"\n  text=\"Abonnez-vous à Laracasts avec mon lien de parrainage\"\n  to=\"https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes\"\n\u002F>\n\n## Apprendre PHP et Laravel\n\nSur Laracasts, je recommande de suivre [le parcours](https:\u002F\u002Flaracasts.com\u002Fpath), qui vous guide des bases de PHP aux concepts avancés de Laravel. Voici quelques cours à considérer :\n\n- [PHP pour les débutants](https:\u002F\u002Flaracasts.com\u002Fseries\u002Fphp-for-beginners-2023-edition).\n- [30 jours pour apprendre Laravel](https:\u002F\u002Flaracasts.com\u002Fseries\u002F30-days-to-learn-laravel-11).\n- [Créer un forum avec Laravel](https:\u002F\u002Flaracasts.com\u002Fseries\u002Fbuild-a-forum-with-laravel). Ce cours est difficile, mais trouvez des cours associés pour clarifier les concepts si nécessaire.\n\n## Les choses commencent à se compliquer\n\nUne fois que vous êtes à l'aise avec PHP et Laravel, commencez à construire avec Blade, CSS pur et JavaScript. Reconnaissez pourquoi des outils plus puissants sont nécessaires avant de les adopter.\n\nLorsque la gestion de CSS et JavaScript devient difficile, apprenez **Tailwind CSS** et **Vue.js** pour une meilleure maintenabilité et interactivité :\n\n- [Tailwind CSS](https:\u002F\u002Ftailwindcss.com\u002F) est un framework CSS utilitaire, ce qui vous rend plus productif en éliminant les modifications directes des fichiers CSS, les feuilles de style encombrantes et les classes répétitives.\n- [Vue.js](https:\u002F\u002Fvuejs.org\u002F) est un framework JavaScript progressif, facile pour les débutants, présentant des avantages techniques par rapport à React et Angular.\n\nExplorez Laracasts pour des cours sur ces technologies et lisez attentivement leur documentation pour comprendre les capacités et les limitations avant utilisation.\n\n## Devenir Développeur Full-Stack\n\nAtteindre la maîtrise du développement full-stack est possible avec la bonne approche et les bons outils.\n\nÉvitez de créer une **application à page unique (SPA)** avec une **API REST** à moins que cela soit nécessaire. L'_over engineering complique la maintenance sans justification adéquate.\n\nUne fois familiarisé avec Laravel et Vue.js, apprenez **Inertia**, un protocole qui combine la simplicité dirigée par le serveur avec la puissance d'une SPA. [Laracasts propose une série à ce sujet](https:\u002F\u002Flaracasts.com\u002Fseries\u002Fbuild-modern-laravel-apps-using-inertia-js), vous aidant à devenir un développeur full-stack capable de bâtir diverses applications web.\n\n## Quelques Outils Supplémentaires\n\nPour éviter la fatigue liée à la gestion des packages JavaScript, utilisez [pnpm](https:\u002F\u002Fpnpm.io\u002F), un gestionnaire de paquets rapide et économe en espace, adapté à tous vos besoins, des mono-repos au patching des dépendances locales.\n\nPour les sites Web nécessitant de la documentation ou centré sur le contenu, utilisez [VitePress](https:\u002F\u002Fvitepress.dev\u002F), un générateur de site statique alimenté par Vue et Vite. La familiarité avec Vue.js facilite son adoption.\n\n\u003CCalloutReadMore id=\"674a3932-aaba-445c-82bb-0896ce3a8e7e\" \u002F>\n\nPour les applications nécessitant une intégration d'API, envisagez [Nuxt](https:\u002F\u002Fnuxt.com), un méta-framework Vue.js soutenant divers styles de construction, offrant une solution puissante et bien documentée.\n\nVous pourriez trouver d'autres outils intéressants, mais la cohérence mène à la maîtrise. Concentrez-vous sur ces outils recommandés et approfondissez votre connaissance.\n\nEn outre, gardez un œil sur :\n\n- [UnJS](https:\u002F\u002Funjs.io\u002F), un écosystème en plein essor de bibliothèques JavaScript bien conçues.\n- [Spatie](https:\u002F\u002Fspatie.be\u002F), une agence de développement web offrant des paquets PHP et Laravel précieux.\n\n## Pour le Travail\n\nBien que ces recommandations conviennent aux amateurs et offrent une connaissance approfondie du développement web, adaptez-vous aux tendances du marché pour un travail professionnel. En France, par exemple, **Java** avec **Spring Boot** et **Angular** sont en demande. Concentrez-vous sur les concepts de base pour vous assurer une flexibilité dans les transitions d'outils ou d'entreprises.\n\nRestez motivé au milieu de la courbe d'apprentissage. Le voyage est long mais gratifiant. Embrassez le processus d'apprentissage, connectez-vous avec vos pairs et appréciez le fait de créer. Le développement web est gratifiant et offre des possibilités infinies.\n\nJ'espère que ce post vous aidera à vous lancer dans votre parcours de développement web. N'hésitez pas à laisser des commentaires ou à partager vos expériences avec nous. 💜\n",{"title":33236,"description":33632},{"loc":33634},"fr\u002Fposts\u002Ffor-a-friend-from-infrastructure-to-web-development-in-2025","nJs9_P9jfzKrAzcld6X91lwe9_weyk3qMw0-jZqcdjY",{"id":33641,"title":33642,"body":33643,"contentId":30824,"date":33833,"description":33834,"extension":18,"meta":33835,"navigation":20,"path":33836,"rawbody":33837,"robots":23,"schemaOrg":23,"seo":33838,"sitemap":33839,"stem":33840,"__hash__":33841},"posts\u002Ffr\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements.md","Améliorer l'Engagement : Notifications et Améliorations",{"type":7,"value":33644,"toc":33831},[33645,33656,33667],[35,33646,33647,33648,33651,33652,33655],{},"Au cours des deux dernières semaines, je me suis concentré sur l'amélioration de l'",[6862,33649,33650],{},"interactivité"," de mon site web. Je crois qu'il est essentiel de permettre aux lecteurs de ",[6862,33653,33654],{},"s'engager"," avec le contenu que je partage et d'interagir les uns avec les autres. Cela peut favoriser des conversations et discussions intéressantes, qui sont essentielles pour apprendre, tant pour vous que pour moi.",[35,33657,33658,33659,33662,33663,33666],{},"J'ai commencé par intégrer des ",[6862,33660,33661],{},"réactions"," à mes articles, puis j'ai mis en place les ",[6862,33664,33665],{},"commentaires",". Vous pouvez en savoir plus sur ces fonctionnalités dans les articles ci-dessous :",[7918,33668,33670],{"id":33669},"8c14b61c-fdf9-443a-924b-050b1395910a",[7918,33671,33673,33695,33699,33705,33710],{"id":33672},"d88a0c79-e6a6-48ea-acbd-b003176b3ba6",[35,33674,33675,33676,1655,33679,33683,33684,1655,33687,33690,33691,33694],{},"À la fin de chaque article sur les pages ",[39,33677,33678],{"href":21},"blog",[39,33680,33682],{"href":33681},"\u002Ffr\u002Fseries","séries",", vous trouverez deux nouvelles sections : ",[6862,33685,33686],{},"Réactions",[6862,33688,33689],{},"Commentaires",". Avec la première, vous pouvez exprimer vos sentiments sur l'article à l'aide d'emoji prédéfinis. Ce retour est important car il fournit des informations sur le contenu que je partage. Grâce à la seconde, vous pouvez partager vos réflexions, poser des ",[6862,33692,33693],{},"questions"," et engager des discussions avec moi et d'autres lecteurs. J'ai hâte de voir comment ces interactions se développeront avec le temps.",[51,33696,33698],{"id":33697},"notifications-pour-vous-tenir-informés","Notifications pour Vous Tenir Informés",[35,33700,33701,33702,215],{},"Aujourd'hui, je suis ravi de dévoiler une nouvelle fonctionnalité : ",[6862,33703,33704],{},"notifications",[134,33706,132,33707],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,33708],{"src":33709,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements\u002Fnotifications.mp4",[33711,33712,33713,33724,33731,33734,33738,33741,33755,33766,33782,33788,33793,33797,33807,33810,33812],"callout-support-my-work",{},[35,33714,33715,33716,33719,33720,33723],{},"Auparavant, si vous postiez un ",[6862,33717,33718],{},"commentaire"," sur l'un de mes articles, il n'y avait aucun moyen de savoir si j'avais répondu ou si quelqu'un d'autre avait réagi. Vous deviez revenir régulièrement vérifier l'",[6862,33721,33722],{},"activité",", ce qui n'est pas pratique au point de devenir frustrant. Vous investissez du temps à écrire, et vous vous attendez à une réponse. Mais si vous n'êtes pas informé quand cela se produit, il est difficile de s'engager dans une conversation.",[35,33725,33726,33727,33730],{},"Cela a changé. J'ai mis en place un ",[6862,33728,33729],{},"système de notification"," qui vous alerte lorsque quelqu'un répond à votre commentaire. Dans l'en-tête, une nouvelle icône de cloche affichera un badge lorsque des notifications non lues seront présentes. En cliquant dessus, un panneau s'ouvrira, répertoriant toutes les notifications. Vous pouvez cliquer sur l'une d'elles pour naviguer directement vers l'article et le commentaire. De plus, vous pouvez marquer tout comme lu ou les supprimer.",[35,33732,33733],{},"Si vous souhaitez l'essayer, laissez un commentaire ci-dessous. Je répondrai, et vous recevrez une notification. Je suis enthousiaste à l'idée de voir comment cela va améliorer les interactions sur mon site web.",[51,33735,33737],{"id":33736},"améliorations-supplémentaires","Améliorations Supplémentaires",[35,33739,33740],{},"En plus des notifications, j'ai apporté plusieurs autres améliorations à mon site web.",[35,33742,33743,33744,33747,33748,33751,33752,215],{},"J'ai développé une page de ",[6862,33745,33746],{},"profil",". Vous pouvez y accéder en cliquant sur le lien dans le pied de page. Elle contient toutes les informations concernant votre compte. Pour l'instant, c'est assez basique, mais je prévois d'ajouter plus de fonctionnalités à l'avenir, comme un ",[6862,33749,33750],{},"tableau de bord"," ou une ",[6862,33753,33754],{},"page de paramètres",[35,33756,33757,33758,33761,33762,33765],{},"J'ai inclus votre ",[6862,33759,33760],{},"photo de profil"," au-dessus de l'en-tête \"Ajouter un Commentaire\" dans la ",[6862,33763,33764],{},"section"," des commentaires. C'est un petit détail, mais maintenant, vous savez quel compte vous utilisez pour interagir.",[35,33767,33768,33769,33772,33773,33777,33778,33781],{},"Vous pouvez ",[6862,33770,33771],{},"soumettre un commentaire"," en utilisant le raccourci ",[33774,33775,33776],"kbd",{},"Ctrl"," + ",[33774,33779,33780],{},"Entrée",". C'est un raccourci courant pour la soumission de formulaires, et je pense qu'il est essentiel de le fournir.",[35,33783,33768,33784,33787],{},[6862,33785,33786],{},"aimer un commentaire"," pour montrer votre appréciation sans répondre. C'est un geste simple qui fait plaisir à celui qui le reçoit. Cette fonctionnalité est également intégrée au système de notification. Si quelqu'un aime votre commentaire, vous recevrez une notification.",[134,33789,132,33790],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,33791],{"src":33792,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements\u002Fcomment-likes.mp4",[51,33794,33796],{"id":33795},"en-avant","En Avant",[35,33798,33799,33800,33803,33804,33806],{},"J'espère sincèrement que vous apprécierez ces nouvelles fonctionnalités. N'hésitez pas à partager vos pensées à leur sujet par le biais d'une ",[6862,33801,33802],{},"réaction"," ou d'un ",[6862,33805,33718],{},". 💫",[35,33808,33809],{},"Je suis impatient de continuer à améliorer mon site web et d'ajouter davantage de fonctionnalités, mais j'ai besoin de vos retours. Si vous avez des suggestions ou des idées, n'hésitez pas à les partager avec moi. Je suis ouvert à vos retours qui peuvent m'aider à développer une meilleure expérience utilisateur.",[1158,33811],{},[35,33813,33814,33815,33817,33818,33821,33822,33827,33828,33830],{},"Toutes ces nouvelles fonctionnalités reposent sur une ",[6862,33816,30820],{}," développée avec ",[39,33819,19463],{"href":19459,"rel":33820},[77],". J'utilise principalement ",[39,33823,33826],{"href":33824,"rel":33825},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Fnotifications",[77],"Laravel Notifications"," pour construire le ",[6862,33829,33729],{},". Je suis impressionné par la flexibilité et la puissance de Laravel. Avec juste quelques lignes de code, je peux créer un système robuste qui enrichit considérablement mon site web.",{"title":13,"searchDepth":14,"depth":14,"links":33832},[],"2024-10-24","Troisième mise à jour pour mettre en place un système de notifications pour vous aider à échanger avec les autres et avec moi.",{},"\u002Ffr\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements","---\ncontentId: 9d802f9e-2f40-426f-b636-533b05b8c1a3\ntitle: \"Améliorer l'Engagement : Notifications et Améliorations\"\ndescription: \"Troisième mise à jour pour mettre en place un système de notifications pour vous aider à échanger avec les autres et avec moi.\"\ndate: 2024-10-24\n---\n\nAu cours des deux dernières semaines, je me suis concentré sur l'amélioration de l'**interactivité** de mon site web. Je crois qu'il est essentiel de permettre aux lecteurs de **s'engager** avec le contenu que je partage et d'interagir les uns avec les autres. Cela peut favoriser des conversations et discussions intéressantes, qui sont essentielles pour apprendre, tant pour vous que pour moi.\n\nJ'ai commencé par intégrer des **réactions** à mes articles, puis j'ai mis en place les **commentaires**. Vous pouvez en savoir plus sur ces fonctionnalités dans les articles ci-dessous :\n\n\u003CCalloutReadMore id=\"8c14b61c-fdf9-443a-924b-050b1395910a\" \u002F>\n\n\u003CCalloutReadMore id=\"d88a0c79-e6a6-48ea-acbd-b003176b3ba6\" \u002F>\n\nÀ la fin de chaque article sur les pages [blog](\u002Ffr\u002Fposts) et [séries](\u002Ffr\u002Fseries), vous trouverez deux nouvelles sections : **Réactions** et **Commentaires**. Avec la première, vous pouvez exprimer vos sentiments sur l'article à l'aide d'emoji prédéfinis. Ce retour est important car il fournit des informations sur le contenu que je partage. Grâce à la seconde, vous pouvez partager vos réflexions, poser des **questions** et engager des discussions avec moi et d'autres lecteurs. J'ai hâte de voir comment ces interactions se développeront avec le temps.\n\n## Notifications pour Vous Tenir Informés\n\nAujourd'hui, je suis ravi de dévoiler une nouvelle fonctionnalité : **notifications**.\n\n\u003Cvideo autoplay loop muted playsinline>\n  \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements\u002Fnotifications.mp4\" type=\"video\u002Fmp4\">\n\u003C\u002Fvideo>\n\n\u003CCalloutSupportMyWork \u002F>\n\nAuparavant, si vous postiez un **commentaire** sur l'un de mes articles, il n'y avait aucun moyen de savoir si j'avais répondu ou si quelqu'un d'autre avait réagi. Vous deviez revenir régulièrement vérifier l'**activité**, ce qui n'est pas pratique au point de devenir frustrant. Vous investissez du temps à écrire, et vous vous attendez à une réponse. Mais si vous n'êtes pas informé quand cela se produit, il est difficile de s'engager dans une conversation.\n\nCela a changé. J'ai mis en place un **système de notification** qui vous alerte lorsque quelqu'un répond à votre commentaire. Dans l'en-tête, une nouvelle icône de cloche affichera un badge lorsque des notifications non lues seront présentes. En cliquant dessus, un panneau s'ouvrira, répertoriant toutes les notifications. Vous pouvez cliquer sur l'une d'elles pour naviguer directement vers l'article et le commentaire. De plus, vous pouvez marquer tout comme lu ou les supprimer.\n\nSi vous souhaitez l'essayer, laissez un commentaire ci-dessous. Je répondrai, et vous recevrez une notification. Je suis enthousiaste à l'idée de voir comment cela va améliorer les interactions sur mon site web.\n\n## Améliorations Supplémentaires\n\nEn plus des notifications, j'ai apporté plusieurs autres améliorations à mon site web.\n\nJ'ai développé une page de **profil**. Vous pouvez y accéder en cliquant sur le lien dans le pied de page. Elle contient toutes les informations concernant votre compte. Pour l'instant, c'est assez basique, mais je prévois d'ajouter plus de fonctionnalités à l'avenir, comme un **tableau de bord** ou une **page de paramètres**.\n\nJ'ai inclus votre **photo de profil** au-dessus de l'en-tête \"Ajouter un Commentaire\" dans la **section** des commentaires. C'est un petit détail, mais maintenant, vous savez quel compte vous utilisez pour interagir.\n\nVous pouvez **soumettre un commentaire** en utilisant le raccourci \u003Ckbd>Ctrl\u003C\u002Fkbd> + \u003Ckbd>Entrée\u003C\u002Fkbd>. C'est un raccourci courant pour la soumission de formulaires, et je pense qu'il est essentiel de le fournir.\n\nVous pouvez **aimer un commentaire** pour montrer votre appréciation sans répondre. C'est un geste simple qui fait plaisir à celui qui le reçoit. Cette fonctionnalité est également intégrée au système de notification. Si quelqu'un aime votre commentaire, vous recevrez une notification.\n\n\u003Cvideo autoplay loop muted playsinline>\n  \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements\u002Fcomment-likes.mp4\" type=\"video\u002Fmp4\">\n\u003C\u002Fvideo>\n\n## En Avant\n\nJ'espère sincèrement que vous apprécierez ces nouvelles fonctionnalités. N'hésitez pas à partager vos pensées à leur sujet par le biais d'une **réaction** ou d'un **commentaire**. 💫\n\nJe suis impatient de continuer à améliorer mon site web et d'ajouter davantage de fonctionnalités, mais j'ai besoin de vos retours. Si vous avez des suggestions ou des idées, n'hésitez pas à les partager avec moi. Je suis ouvert à vos retours qui peuvent m'aider à développer une meilleure expérience utilisateur.\n\n---\n\nToutes ces nouvelles fonctionnalités reposent sur une **API** développée avec [Laravel](https:\u002F\u002Flaravel.com). J'utilise principalement [Laravel Notifications](https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Fnotifications) pour construire le **système de notification**. Je suis impressionné par la flexibilité et la puissance de Laravel. Avec juste quelques lignes de code, je peux créer un système robuste qui enrichit considérablement mon site web.\n",{"title":33642,"description":33834},{"loc":33836},"fr\u002Fposts\u002Fenhancing-engagement-notifications-and-other-enhancements","OZJKxxGpsKfoX_In4l7vDUnlX8fQj_0SsLYNz0jpSew",{"id":33843,"title":33844,"body":33845,"contentId":33672,"date":34052,"description":34053,"extension":18,"meta":34054,"navigation":20,"path":34055,"rawbody":34056,"robots":23,"schemaOrg":23,"seo":34057,"sitemap":34058,"stem":34059,"__hash__":34060},"posts\u002Ffr\u002Fposts\u002Fenhancing-engagement-enabling-comments-on-my-articles.md","Améliorer l'Engagement : Les Commentaires sur Mes Articles",{"type":7,"value":33846,"toc":34048},[33847,33858],[35,33848,33849,33850,33854,33855,215],{},"La semaine dernière, j'ai présenté la première phase d'un projet important : ",[39,33851,33853],{"href":33852},".\u002Fenhancing-engagement-enabling-comments-on-my-articles","ajouter des réactions à mes articles",". Aujourd'hui, je suis ravi de dévoiler la deuxième phase : ",[6862,33856,33857],{},"activer les commentaires sur mes articles",[7918,33859,33860,33864,33867,33877,33881,33894,33897],{"id":33669},[51,33861,33863],{"id":33862},"une-deuxième-phase","Une Deuxième Phase",[35,33865,33866],{},"Dans l'article précédent, j'ai détaillé l'approche progressive que je poursuis pour améliorer mon site personnel. Pas à pas, je vise à introduire des fonctionnalités pour interagir avec vous, et créer une plateforme avec des interactions autour de toutes les connaissances que je partage.",[35,33868,33869,33870,33873,33874],{},"C'est un processus progressif parce que je crois que c'est le moyen le plus efficace de livrer, plutôt que d'attendre de tout terminer d'un coup. En même temps, cela me garde motivé et concentré en atteignant de petites victoires et en recevant des retours. Et comme j'apprends ",[39,33871,19463],{"href":19459,"rel":33872},[77],", cela m'évite de me sentir impuissant face à la potentielle complexité du projet. ",[1216,33875,33876],{},"Finalement, c'est un scénario gagnant-gagnant.",[51,33878,33880],{"id":33879},"limportance-des-commentaires","L'Importance des Commentaires",[35,33882,33883,33884,33889,33890,33893],{},"Récemment, j'ai posté sur X ",[39,33885,33888],{"href":33886,"rel":33887},"https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1842987889216672223",[77],"un fil de discussion sur les \"singleton composables\"",". Rapidement, j'ai reçu des commentaires, des suggestions et des idées qui m'ont fait réaliser que je pouvais me tromper. ",[1216,33891,33892],{},"Je me suis trompé."," Et je suis reconnaissant d'avoir eu l'opportunité d'apprendre des retours des autres.",[35,33895,33896],{},"Il y a deux semaines, j'ai également publié un article sur la méthode la plus simple pour créer une bibliothèque de composants Vue.js. À un moment dans l'article, il y avait deux approches pour construire la bibliothèque. J'en ai choisi une, la jugeant la plus simple, mais à travers les commentaires, les gens auraient pu partager leurs pensées et leurs expériences, ce qui aurait pu aider d'autres à prendre de meilleures décisions et à apprendre les uns des autres.",[7918,33898,33899,33902,33906,33909,33914,33920,33970,33976,33979,34021,34024,34035,34037,34043],{"id":21393},[35,33900,33901],{},"Les commentaires sont précieux car ils nous permettent d'exprimer nos pensées, de poser des questions et de participer à des discussions. Ils nous permettent d'apprendre les uns des autres, d'élargir nos connaissances et de grandir collectivement. C'est dans cet esprit que j'ai décidé de mettre en place la possibilité de commenter mes articles.",[51,33903,33905],{"id":33904},"passer-à-la-deuxième-étape","Passer à la Deuxième Étape",[35,33907,33908],{},"Et nous y voilà, la fonctionnalité de commentaires a été développée et lancée.",[134,33910,132,33911],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,33912],{"src":33913,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-enabling-comments-on-my-articles\u002Fdemo.mp4",[35,33915,33916,33917],{},"Cette phase a posé plus de défis que la première. J'ai dû prendre en compte divers facteurs et prendre des décisions tout au long du processus. ",[1216,33918,33919],{},"Il est fascinant d'observer comment la même fonctionnalité peut être mise en œuvre de différentes manières. Au final, il s'agit de trouver un équilibre, cherchant à choisir la meilleure solution pour votre scénario spécifique en fonction de vos besoins et contraintes comme le temps, les ressources et l'expertise.",[1593,33921,33922,33928,33934,33943,33949,33955,33961],{},[71,33923,33924,33927],{},[6862,33925,33926],{},"Quel éditeur utiliser ?"," Comme Notion, Markdown ou WYSIWYG ?",[71,33929,33930,33933],{},[6862,33931,33932],{},"Comment rendre l'éditeur ?"," EasyMDE, CKEditor, Quill ou une simple zone de texte ?",[71,33935,33936,33939,33940],{},[6862,33937,33938],{},"Comment mettre en surbrillance les blocs de code ?"," Prism.js, Highlight.js ou Shiki ? ",[1216,33941,33942],{},"C'est crucial pour mes articles techniques.",[71,33944,33945,33948],{},[6862,33946,33947],{},"Comment prévisualiser les commentaires ?"," En temps réel, à la demande ou dans le navigateur ?",[71,33950,33951,33954],{},[6862,33952,33953],{},"Une réponse est-elle un commentaire ?"," Les commentaires peuvent avoir des réponses, mais les réponses doivent-elles être considérées comme des commentaires ?",[71,33956,33957,33960],{},[6862,33958,33959],{},"La suppression d'un commentaire supprime-t-elle les réponses ?"," Si un commentaire est supprimé, les réponses doivent-elles être supprimées également ?",[71,33962,33963,33966,33967],{},[6862,33964,33965],{},"Qui peut commenter ?"," Tout le monde, utilisateurs authentifiés ou seulement moi ? ",[1216,33968,33969],{},"Ce serait étrange si je suis le seul à pouvoir commenter mes articles.",[35,33971,33972,33973],{},"Le bénéfice de construire le projet de manière incrémentale est que ce sont les seules décisions que j'ai dû prendre. ",[1216,33974,33975],{},"Je n'ai pas eu à me soucier de l'ensemble du projet, juste de la fonctionnalité de commentaires.",[35,33977,33978],{},"Pour répondre à ces questions, j'ai effectué des recherches, expérimenté et fait des compromis.",[1593,33980,33981,33998,34015],{},[71,33982,33983,33986,33987,1655,33992,33997],{},[6862,33984,33985],{},"Recherche"," : J'ai essayé de trouver des articles sur les meilleures pratiques, mais je n'ai rien trouvé de particulièrement utile. J'ai donc plongé dans ",[39,33988,33991],{"href":33989,"rel":33990},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Fdiscussions",[77],"GitHub Discussions",[39,33993,33996],{"href":33994,"rel":33995},"https:\u002F\u002Flaracasts.com\u002Fdiscuss",[77],"Laracasts Forum",". J'ai découvert des modèles et des idées intéressants que je pouvais exploiter.",[71,33999,34000,34003,34004,1655,34009,34014],{},[6862,34001,34002],{},"Expérience"," : Dans des projets précédents, j'ai utilisé ",[39,34005,34008],{"href":34006,"rel":34007},"https:\u002F\u002Fgithub.com\u002FIonaru\u002Feasy-markdown-editor",[77],"EasyMDE",[39,34010,34013],{"href":34011,"rel":34012},"https:\u002F\u002Fquilljs.com\u002F",[77],"Quill",". J'ai également examiné les éditeurs des plateformes mentionnées plus tôt, et mon choix final pourrait vous surprendre. 🫣",[71,34016,34017,34020],{},[6862,34018,34019],{},"Compromis"," : Choisir la simplicité plutôt que la complexité. C'est mon principe directeur, et toutes mes décisions en sont informées. Cependant, la simplicité peut être assez complexe et ne signifie pas faire des compromis sur les fonctionnalités ou l'expérience utilisateur.",[35,34022,34023],{},"Toutes ces décisions ont été prises en pensant à l'utilisateur. J'aspire à rendre l'expérience de commentaire aussi fluide et agréable que possible, vous encourageant à partager vos pensées, poser des questions et participer à des discussions.",[35,34025,34026,34027,34030,34031,34034],{},"Parfois je me trompe, et parfois il existe de meilleures solutions, et ce sont ces moments où ",[6862,34028,34029],{},"j'ai besoin de vos retours",". Avec cette nouvelle fonctionnalité de commentaires, il est désormais plus facile que jamais de partager vos pensées et ",[6862,34032,34033],{},"d'aider tout le monde à apprendre et à grandir",". 💜",[1158,34036],{},[35,34038,34039,34042],{},[6862,34040,34041],{},"Souhaitez-vous approfondir la manière dont j'ai développé la fonctionnalité de commentaires ?"," Faites-le moi savoir dans les commentaires ci-dessous. ⬇️",[35,34044,34045],{},[1216,34046,34047],{},"Je songe à écrire une série d'articles sur ce projet, de l'idéation au développement, avec de nombreux extraits de code et explications.",{"title":13,"searchDepth":14,"depth":14,"links":34049},[34050,34051],{"id":33862,"depth":14,"text":33863},{"id":33879,"depth":14,"text":33880},"2024-10-09","Deuxième phase d'un projet clé en permettant aux lecteurs de commenter mes articles pour favoriser les conversations et les discussions constructives.",{},"\u002Ffr\u002Fposts\u002Fenhancing-engagement-enabling-comments-on-my-articles","---\ncontentId: d88a0c79-e6a6-48ea-acbd-b003176b3ba6\ntitle: \"Améliorer l'Engagement : Les Commentaires sur Mes Articles\"\ndescription: Deuxième phase d'un projet clé en permettant aux lecteurs de commenter mes articles pour favoriser les conversations et les discussions constructives.\ndate: 2024-10-09\n---\n\nLa semaine dernière, j'ai présenté la première phase d'un projet important : [ajouter des réactions à mes articles](.\u002Fenhancing-engagement-enabling-comments-on-my-articles.md). Aujourd'hui, je suis ravi de dévoiler la deuxième phase : **activer les commentaires sur mes articles**.\n\n\u003CCalloutReadMore id=\"8c14b61c-fdf9-443a-924b-050b1395910a\" \u002F>\n\n## Une Deuxième Phase\n\nDans l'article précédent, j'ai détaillé l'approche progressive que je poursuis pour améliorer mon site personnel. Pas à pas, je vise à introduire des fonctionnalités pour interagir avec vous, et créer une plateforme avec des interactions autour de toutes les connaissances que je partage.\n\nC'est un processus progressif parce que je crois que c'est le moyen le plus efficace de livrer, plutôt que d'attendre de tout terminer d'un coup. En même temps, cela me garde motivé et concentré en atteignant de petites victoires et en recevant des retours. Et comme j'apprends [Laravel](https:\u002F\u002Flaravel.com), cela m'évite de me sentir impuissant face à la potentielle complexité du projet. _Finalement, c'est un scénario gagnant-gagnant._\n\n## L'Importance des Commentaires\n\nRécemment, j'ai posté sur X [un fil de discussion sur les \"singleton composables\"](https:\u002F\u002Fx.com\u002Fsoubiran_\u002Fstatus\u002F1842987889216672223). Rapidement, j'ai reçu des commentaires, des suggestions et des idées qui m'ont fait réaliser que je pouvais me tromper. _Je me suis trompé._ Et je suis reconnaissant d'avoir eu l'opportunité d'apprendre des retours des autres.\n\nIl y a deux semaines, j'ai également publié un article sur la méthode la plus simple pour créer une bibliothèque de composants Vue.js. À un moment dans l'article, il y avait deux approches pour construire la bibliothèque. J'en ai choisi une, la jugeant la plus simple, mais à travers les commentaires, les gens auraient pu partager leurs pensées et leurs expériences, ce qui aurait pu aider d'autres à prendre de meilleures décisions et à apprendre les uns des autres.\n\n\u003CCalloutReadMore id=\"75df6bff-de40-4056-af23-fae2401cd21a\" \u002F>\n\nLes commentaires sont précieux car ils nous permettent d'exprimer nos pensées, de poser des questions et de participer à des discussions. Ils nous permettent d'apprendre les uns des autres, d'élargir nos connaissances et de grandir collectivement. C'est dans cet esprit que j'ai décidé de mettre en place la possibilité de commenter mes articles.\n\n## Passer à la Deuxième Étape\n\nEt nous y voilà, la fonctionnalité de commentaires a été développée et lancée.\n\n\u003Cvideo autoplay loop muted playsinline>\n  \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-enabling-comments-on-my-articles\u002Fdemo.mp4\" type=\"video\u002Fmp4\">\n\u003C\u002Fvideo>\n\nCette phase a posé plus de défis que la première. J'ai dû prendre en compte divers facteurs et prendre des décisions tout au long du processus. _Il est fascinant d'observer comment la même fonctionnalité peut être mise en œuvre de différentes manières. Au final, il s'agit de trouver un équilibre, cherchant à choisir la meilleure solution pour votre scénario spécifique en fonction de vos besoins et contraintes comme le temps, les ressources et l'expertise._\n\n- **Quel éditeur utiliser ?** Comme Notion, Markdown ou WYSIWYG ?\n- **Comment rendre l'éditeur ?** EasyMDE, CKEditor, Quill ou une simple zone de texte ?\n- **Comment mettre en surbrillance les blocs de code ?** Prism.js, Highlight.js ou Shiki ? _C'est crucial pour mes articles techniques._\n- **Comment prévisualiser les commentaires ?** En temps réel, à la demande ou dans le navigateur ?\n- **Une réponse est-elle un commentaire ?** Les commentaires peuvent avoir des réponses, mais les réponses doivent-elles être considérées comme des commentaires ?\n- **La suppression d'un commentaire supprime-t-elle les réponses ?** Si un commentaire est supprimé, les réponses doivent-elles être supprimées également ?\n- **Qui peut commenter ?** Tout le monde, utilisateurs authentifiés ou seulement moi ? _Ce serait étrange si je suis le seul à pouvoir commenter mes articles._\n\nLe bénéfice de construire le projet de manière incrémentale est que ce sont les seules décisions que j'ai dû prendre. _Je n'ai pas eu à me soucier de l'ensemble du projet, juste de la fonctionnalité de commentaires._\n\nPour répondre à ces questions, j'ai effectué des recherches, expérimenté et fait des compromis.\n\n- **Recherche** : J'ai essayé de trouver des articles sur les meilleures pratiques, mais je n'ai rien trouvé de particulièrement utile. J'ai donc plongé dans [GitHub Discussions](https:\u002F\u002Fdocs.github.com\u002Fen\u002Fdiscussions) et [Laracasts Forum](https:\u002F\u002Flaracasts.com\u002Fdiscuss). J'ai découvert des modèles et des idées intéressants que je pouvais exploiter.\n- **Expérience** : Dans des projets précédents, j'ai utilisé [EasyMDE](https:\u002F\u002Fgithub.com\u002FIonaru\u002Feasy-markdown-editor) et [Quill](https:\u002F\u002Fquilljs.com\u002F). J'ai également examiné les éditeurs des plateformes mentionnées plus tôt, et mon choix final pourrait vous surprendre. 🫣\n- **Compromis** : Choisir la simplicité plutôt que la complexité. C'est mon principe directeur, et toutes mes décisions en sont informées. Cependant, la simplicité peut être assez complexe et ne signifie pas faire des compromis sur les fonctionnalités ou l'expérience utilisateur.\n\nToutes ces décisions ont été prises en pensant à l'utilisateur. J'aspire à rendre l'expérience de commentaire aussi fluide et agréable que possible, vous encourageant à partager vos pensées, poser des questions et participer à des discussions.\n\nParfois je me trompe, et parfois il existe de meilleures solutions, et ce sont ces moments où **j'ai besoin de vos retours**. Avec cette nouvelle fonctionnalité de commentaires, il est désormais plus facile que jamais de partager vos pensées et **d'aider tout le monde à apprendre et à grandir**. 💜\n\n---\n\n**Souhaitez-vous approfondir la manière dont j'ai développé la fonctionnalité de commentaires ?** Faites-le moi savoir dans les commentaires ci-dessous. ⬇️\n\n_Je songe à écrire une série d'articles sur ce projet, de l'idéation au développement, avec de nombreux extraits de code et explications._\n",{"title":33844,"description":34053},{"loc":34055},"fr\u002Fposts\u002Fenhancing-engagement-enabling-comments-on-my-articles","AwemN2gBlUkr9uIt3sOVYHBWS6jIQV4CKvvFMrbODO8",{"id":34062,"title":34063,"body":34064,"contentId":33669,"date":34285,"description":34286,"extension":18,"meta":34287,"navigation":20,"path":34288,"rawbody":34289,"robots":23,"schemaOrg":23,"seo":34290,"sitemap":34291,"stem":34292,"__hash__":34293},"posts\u002Ffr\u002Fposts\u002Fenhancing-engagement-adding-reactions-to-my-articles.md","Améliorer l'Engagement : Ajouter des Réactions aux Articles",{"type":7,"value":34065,"toc":34279},[34066,34069,34073,34083],[35,34067,34068],{},"Salut ! J'espère que vous allez bien. Je suis ravi de partager avec toi le début d'un projet significatif qui m'accompagnera dans les mois à venir.",[51,34070,34072],{"id":34071},"introduction","Introduction",[35,34074,34075,34076,34079,34080],{},"En mars 2024, j'ai annoncé la refonte de mon site personnel pour la quatrième fois, cette fois en utilisant ",[39,34077,2929],{"href":33564,"rel":34078},[77],". Cette décision a été guidée par le désir d'un outil à la fois simplifié et robuste, qui me permet d'écrire facilement et rapidement tout en conservant le contrôle sur le design et les fonctionnalités. ",[1216,34081,34082],{},"VitePress est vraiment exceptionnel à cet égard.",[7918,34084,34085,34089,34096,34102,34110,34125,34129,34140,34143,34149,34153,34169,34174,34177,34257,34263,34271,34276],{"id":30564},[51,34086,34088],{"id":34087},"explorer-de-nouveaux-horizons","Explorer de Nouveaux Horizons",[35,34090,34091,34092,34095],{},"En juin 2024, j'ai commencé à apprendre ",[39,34093,19463],{"href":19459,"rel":34094},[77]," pour deux raisons.",[35,34097,34098,34099],{},"Tout d'abord, je cherchais à élargir mon expertise et mes compétences en développement web. Je veux créer des applications de A à Z, transformer des idées en réalité sans trop dépendre des services externes. ",[1216,34100,34101],{},"Et je travaille déjà sur mon premier SaaS 🤫.",[35,34103,34104,34105,34109],{},"Deuxièmement, je voulais plus de flexibilité dans mes options de carrière. Depuis le début de mon parcours professionnel, j'ai utilisé ",[39,34106,22922],{"href":34107,"rel":34108},"https:\u002F\u002Fangular.dev\u002F",[77]," comme principal outil. Je cherche à explorer de nouvelles voies car Angular ne me procure plus la même satisfaction.",[35,34111,34112],{},[1216,34113,34114,34115,34120,34121,34124],{},"J'ai envisagé ",[39,34116,34119],{"href":34117,"rel":34118},"https:\u002F\u002Frubyonrails.org\u002F",[77],"Rails",", mais une fois que vous avez expérimenté la documentation de Laravel et ",[39,34122,33439],{"href":33445,"rel":34123},[77],", le choix devient évident.",[51,34126,34128],{"id":34127},"progresser-par-étapes","Progresser par Étapes",[35,34130,34131,34132],{},"S'attaquer à la première raison nécessite un temps et un effort considérables. C'est une initiative à long terme que je perçois comme un défi majeur. Bien que je ne sache peut-être pas exactement par où commencer ou comment le parcours se déroulera, je suis clair sur mon objectif ultime. Comme pour tous les défis, la meilleure approche est de les décomposer en tâches plus petites et gérables, de les résoudre individuellement, puis d'intégrer les solutions. ",[1216,34133,34134,34135,215],{},"C'est l'essence du ",[39,34136,34139],{"href":34137,"rel":34138},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDivide-and-conquer_algorithm",[77],"diviser pour régner",[35,34141,34142],{},"Je crois profondément que le moyen le plus efficace d'apprendre est à travers des projets concrets. Pas juste une simple to-do app, mais quelque chose de fonctionnel avec un vrai but.",[35,34144,34145,34146],{},"Dans le même temps, j'aimerai faciliter l'engagement sur mon site. C'est beaucoup plus agréable de partager et de converser, d'avoir des interactions avec les autres. Pendant des mois, j'ai réfléchi à des ajouts pour mon site mais j'ai eu du mal à trouver une idée pratique, engageante et utile. ",[1216,34147,34148],{},"Jusqu'à présent.",[51,34150,34152],{"id":34151},"faire-le-premier-pas","Faire le Premier Pas",[35,34154,34155,34156,34159,34160,106,34163],{},"Nous y sommes. J'ai décidé de mettre en pace une fonctionnalité permettant de ",[6862,34157,34158],{},"réagir à mes articles en utilisant des emoji prédéfinis",", propulsée par une API développée avec ",[39,34161,19463],{"href":19459,"rel":34162},[77],[1216,34164,34165,34166,215],{},"Et le front-end continue d'être réalisé avec ",[39,34167,2929],{"href":19446,"rel":34168},[77],[134,34170,132,34171],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,34172],{"src":34173,"type":141},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-adding-reactions-to-my-articles\u002Fdemo.mp4",[35,34175,34176],{},"Au départ, cela peut sembler être une fonctionnalité mineure, et c'est vrai ; ce n'est pas si grand, mais pour un premier pas, cela suffit. Dans ce processus, j'ai abordé de nombreux concepts différents :",[1593,34178,34179,34187,34195,34213,34221,34229,34238,34246],{},[71,34180,34181,34186],{},[39,34182,34185],{"href":34183,"rel":34184},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsocialite",[77],"Laravel Socialite"," pour l'authentification GitHub.",[71,34188,34189,34194],{},[39,34190,34193],{"href":34191,"rel":34192},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsanctum",[77],"Laravel Sanctum"," pour l'authentification API.",[71,34196,34197,3092,34202,3092,34207,34212],{},[39,34198,34201],{"href":34199,"rel":34200},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fpint",[77],"Laravel Pint",[39,34203,34206],{"href":34204,"rel":34205},"https:\u002F\u002Fphpstan.org\u002F",[77],"PHPStan",[39,34208,34211],{"href":34209,"rel":34210},"https:\u002F\u002Fgetrector.org\u002F",[77],"Rector"," pour l'assurance qualité du code.",[71,34214,34215,34220],{},[39,34216,34219],{"href":34217,"rel":34218},"https:\u002F\u002Fforge.laravel.com\u002F",[77],"Laravel Forge"," pour le déploiement.",[71,34222,34223,34228],{},[39,34224,34227],{"href":34225,"rel":34226},"https:\u002F\u002Fwww.hetzner.com\u002F",[77],"Hetzner"," pour l'infrastructure serveur.",[71,34230,34231,34232,34237],{},"Commande personnalisée pour les sauvegardes de base de données et ",[39,34233,34236],{"href":34234,"rel":34235},"https:\u002F\u002Fwww.cloudflare.com\u002Fdeveloper-platform\u002Fr2\u002F",[77],"R2"," pour leur stockage.",[71,34239,34240,34245],{},[39,34241,34244],{"href":34242,"rel":34243},"https:\u002F\u002Ffilamentphp.com\u002F",[77],"Filament"," pour la gestion du tableau de bord.",[71,34247,34248,34249],{},"Couverture de tests à 100%. ",[1216,34250,34251,34252,33806],{},"Réaliser avec aisance en utilisant Laravel et ",[39,34253,34256],{"href":34254,"rel":34255},"https:\u002F\u002Fpestphp.com\u002F",[77],"Pest",[35,34258,34259,34260],{},"Et c'est à peu près tout. ",[1216,34261,34262],{},"Pas mal pour cinq jours de travail !",[35,34264,34265,34266],{},"La lecture de tout le code source de ",[1599,34267,34270],{"repo":34268,"name":34269},"pinkary-project\u002Fpinkary.com","Pinkary Project"," a considérablement amélioré ma compréhension de ces outils et du processus de construction d'une application Laravel.",[35,34272,34273],{},[1216,34274,34275],{},"Un des aspects majeurs de cette étape est de poser les bases pour plus de fonctionnalités sur mon site et de nombreux articles pour partager ce que j'ai appris.",[35,34277,34278],{},"Alors, qu'en pensez-vous ? ⬇️",{"title":13,"searchDepth":14,"depth":14,"links":34280},[34281,34282,34283,34284],{"id":34071,"depth":14,"text":34072},{"id":34087,"depth":14,"text":34088},{"id":34127,"depth":14,"text":34128},{"id":34151,"depth":14,"text":34152},"2024-10-01","Phase initiale mais cruciale d'un projet important qui m'accompagnera dans les mois à venir en ouvrant la possibilité de réagir à mes articles.",{},"\u002Ffr\u002Fposts\u002Fenhancing-engagement-adding-reactions-to-my-articles","---\ncontentId: 8c14b61c-fdf9-443a-924b-050b1395910a\ntitle: \"Améliorer l'Engagement : Ajouter des Réactions aux Articles\"\ndescription: Phase initiale mais cruciale d'un projet important qui m'accompagnera dans les mois à venir en ouvrant la possibilité de réagir à mes articles.\ndate: 2024-10-01\n---\n\nSalut ! J'espère que vous allez bien. Je suis ravi de partager avec toi le début d'un projet significatif qui m'accompagnera dans les mois à venir.\n\n## Introduction\n\nEn mars 2024, j'ai annoncé la refonte de mon site personnel pour la quatrième fois, cette fois en utilisant [VitePress](https:\u002F\u002Fvitepress.dev\u002F). Cette décision a été guidée par le désir d'un outil à la fois simplifié et robuste, qui me permet d'écrire facilement et rapidement tout en conservant le contrôle sur le design et les fonctionnalités. _VitePress est vraiment exceptionnel à cet égard._\n\n\u003CCalloutReadMore id=\"a06fbfe1-6283-4eed-b3ec-e1d454613b9a\" \u002F>\n\n## Explorer de Nouveaux Horizons\n\nEn juin 2024, j'ai commencé à apprendre [Laravel](https:\u002F\u002Flaravel.com) pour deux raisons.\n\nTout d'abord, je cherchais à élargir mon expertise et mes compétences en développement web. Je veux créer des applications de A à Z, transformer des idées en réalité sans trop dépendre des services externes. _Et je travaille déjà sur mon premier SaaS 🤫._\n\nDeuxièmement, je voulais plus de flexibilité dans mes options de carrière. Depuis le début de mon parcours professionnel, j'ai utilisé [Angular](https:\u002F\u002Fangular.dev\u002F) comme principal outil. Je cherche à explorer de nouvelles voies car Angular ne me procure plus la même satisfaction.\n\n_J'ai envisagé [Rails](https:\u002F\u002Frubyonrails.org\u002F), mais une fois que vous avez expérimenté la documentation de Laravel et [Laracasts](https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes), le choix devient évident._\n\n## Progresser par Étapes\n\nS'attaquer à la première raison nécessite un temps et un effort considérables. C'est une initiative à long terme que je perçois comme un défi majeur. Bien que je ne sache peut-être pas exactement par où commencer ou comment le parcours se déroulera, je suis clair sur mon objectif ultime. Comme pour tous les défis, la meilleure approche est de les décomposer en tâches plus petites et gérables, de les résoudre individuellement, puis d'intégrer les solutions. _C'est l'essence du [diviser pour régner](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FDivide-and-conquer_algorithm)._\n\nJe crois profondément que le moyen le plus efficace d'apprendre est à travers des projets concrets. Pas juste une simple to-do app, mais quelque chose de fonctionnel avec un vrai but.\n\nDans le même temps, j'aimerai faciliter l'engagement sur mon site. C'est beaucoup plus agréable de partager et de converser, d'avoir des interactions avec les autres. Pendant des mois, j'ai réfléchi à des ajouts pour mon site mais j'ai eu du mal à trouver une idée pratique, engageante et utile. _Jusqu'à présent._\n\n## Faire le Premier Pas\n\nNous y sommes. J'ai décidé de mettre en pace une fonctionnalité permettant de **réagir à mes articles en utilisant des emoji prédéfinis**, propulsée par une API développée avec [Laravel](https:\u002F\u002Flaravel.com). _Et le front-end continue d'être réalisé avec [VitePress](https:\u002F\u002Fvitepress.dev)._\n\n\u003Cvideo autoplay loop muted playsinline>\n  \u003Csource src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fenhancing-engagement-adding-reactions-to-my-articles\u002Fdemo.mp4\" type=\"video\u002Fmp4\">\n\u003C\u002Fvideo>\n\nAu départ, cela peut sembler être une fonctionnalité mineure, et c'est vrai ; ce n'est pas si grand, mais pour un premier pas, cela suffit. Dans ce processus, j'ai abordé de nombreux concepts différents :\n\n- [Laravel Socialite](https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsocialite) pour l'authentification GitHub.\n- [Laravel Sanctum](https:\u002F\u002Flaravel.com\u002Fdocs\u002Fsanctum) pour l'authentification API.\n- [Laravel Pint](https:\u002F\u002Flaravel.com\u002Fdocs\u002Fpint), [PHPStan](https:\u002F\u002Fphpstan.org\u002F), [Rector](https:\u002F\u002Fgetrector.org\u002F) pour l'assurance qualité du code.\n- [Laravel Forge](https:\u002F\u002Fforge.laravel.com\u002F) pour le déploiement.\n- [Hetzner](https:\u002F\u002Fwww.hetzner.com\u002F) pour l'infrastructure serveur.\n- Commande personnalisée pour les sauvegardes de base de données et [R2](https:\u002F\u002Fwww.cloudflare.com\u002Fdeveloper-platform\u002Fr2\u002F) pour leur stockage.\n- [Filament](https:\u002F\u002Ffilamentphp.com\u002F) pour la gestion du tableau de bord.\n- Couverture de tests à 100%. _Réaliser avec aisance en utilisant Laravel et [Pest](https:\u002F\u002Fpestphp.com\u002F). 💫_\n\nEt c'est à peu près tout. _Pas mal pour cinq jours de travail !_\n\nLa lecture de tout le code source de \u003CGitHubLink repo=\"pinkary-project\u002Fpinkary.com\" name=\"Pinkary Project\" \u002F> a considérablement amélioré ma compréhension de ces outils et du processus de construction d'une application Laravel.\n\n_Un des aspects majeurs de cette étape est de poser les bases pour plus de fonctionnalités sur mon site et de nombreux articles pour partager ce que j'ai appris._\n\nAlors, qu'en pensez-vous ? ⬇️\n",{"title":34063,"description":34286},{"loc":34288},"fr\u002Fposts\u002Fenhancing-engagement-adding-reactions-to-my-articles","oiTcCfUTIJYVnsflsj6VK9zGk2MF3DhXd7FO_THoOmk",{"id":34295,"title":34296,"body":34297,"contentId":21393,"date":36298,"description":36299,"extension":18,"meta":36300,"navigation":20,"path":36301,"rawbody":36302,"robots":23,"schemaOrg":23,"seo":36303,"sitemap":36304,"stem":36305,"__hash__":36306},"posts\u002Ffr\u002Fposts\u002Fthe-simplest-method-to-create-a-vue-js-component-library.md","Créer une Bibliothèque de Composants Vue.js Simplement",{"type":7,"value":34298,"toc":36287},[34299,34308,34314,34337,36284],[35,34300,34301,34302],{},"TL;DR: ",[1599,34303,8048,34305],{"repo":34304},"barbapapazes\u002Fvue-library",[1216,34306,34307],{},"Continuez à lire pour comprendre le processus derrière cela et les raisons de mes choix.",[35,34309,34310,34311],{},"Une bibliothèque de composants est une collection de morceaux réutilisables qui peuvent être utilisés dans divers projets. Elle facilite le partage de ressources entre différents projets et équipes. Ces composants peuvent être basiques et génériques comme des boutons, des champs de saisie et des modales, ou des modules plus spécifiques au métier. ",[1216,34312,34313],{},"En fin de compte, c'est une méthode pour partager du code entre les projets, une compétence cruciale qui peut faire économiser beaucoup de temps.",[35,34315,34316,34317,34321,34322,34327,34328,13741,34333,215],{},"Cependant, construire une bibliothèque de composants avec ",[39,34318,18636],{"href":34319,"rel":34320},"https:\u002F\u002Fvuejs.org",[77]," est plus difficile qu'il n'y paraît en raison des ",[39,34323,34326],{"href":34324,"rel":34325},"https:\u002F\u002Fvuejs.org\u002Fguide\u002Fscaling-up\u002Fsfc.html",[77],"Single File Components"," (SFC). Dans un projet TypeScript standard, vous transpilez généralement en JavaScript et regroupez vos fichiers avec un outil comme ",[39,34329,34332],{"href":34330,"rel":34331},"https:\u002F\u002Ftsup.egoist.dev\u002F",[77],"tsup",[39,34334,24399],{"href":34335,"rel":34336},"https:\u002F\u002Fvitejs.dev\u002F",[77],[7918,34338,34339,34353,34356,34359,34485,34488,34580,34586,34589,34595,34602,34662,34671,34807,34811,34818,34824,34830,34859,34866,34878,34892,34896,34912,34915,34918,34930,34933,34942,34947,34953,34976,34983,35125,35133,35137,35153,35171,35180,35345,35352,35356,35362,35413,35419,35425,35428,35439,35443,35446,35471,35474,35486,35511,35515,35518,35528,35605,35635,35645,35651,35802,35808,35814,35827,35831,35838,35841,35844,35851,35857,35884,35890,35911,35914,35926,35937,35943,35952,36032,36035,36054,36063,36067,36070,36077,36089,36102,36119,36124,36190,36202,36213,36216,36230,36233,36237,36240,36260,36269,36277,36282],{"id":30810},[938,34340,34341],{},[35,34342,34343,34345,34346,34349,34350],{},[200,34344,944],{},"\nN'oubliez pas de ",[6862,34347,34348],{},"livrer le code du paquet aussi natif que possible"," et de laisser les outils de l'utilisateur gérer la transpilation et l'optimisation. C'est un principe que je garde toujours à l'esprit lorsque je crée des bibliothèques, ce qui simplifie considérablement le processus. ",[1216,34351,34352],{},"Chaque cas est unique, mais c'est une bonne ligne directrice à suivre.",[35,34354,34355],{},"L'utilisation des SFC de Vue.js complique ce processus. Examinons pourquoi en explorant les défis que vous rencontrerez lors de la construction d'une bibliothèque de composants Vue.js.",[35,34357,34358],{},"Considérons le composant suivant :",[192,34360,34362],{"className":21457,"code":34361,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport { useUser } from '..\u002Fcomposables'\n\nconst { user } = useUser()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-if=\"user\">\n    {{ user.name }}\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[156,34363,34364,34384,34404,34408,34425,34433,34437,34445,34464,34469,34477],{"__ignoreMap":13},[200,34365,34366,34368,34370,34372,34374,34376,34378,34380,34382],{"class":202,"line":203},[200,34367,599],{"class":214},[200,34369,14896],{"class":235},[200,34371,14594],{"class":379},[200,34373,449],{"class":214},[200,34375,610],{"class":214},[200,34377,1383],{"class":245},[200,34379,610],{"class":214},[200,34381,21825],{"class":379},[200,34383,647],{"class":214},[200,34385,34386,34388,34390,34393,34395,34397,34399,34402],{"class":202,"line":14},[200,34387,972],{"class":206},[200,34389,318],{"class":214},[200,34391,34392],{"class":210}," useUser",[200,34394,980],{"class":214},[200,34396,983],{"class":206},[200,34398,242],{"class":214},[200,34400,34401],{"class":245},"..\u002Fcomposables",[200,34403,991],{"class":214},[200,34405,34406],{"class":202,"line":255},[200,34407,528],{"emptyLinePlaceholder":20},[200,34409,34410,34412,34414,34417,34419,34421,34423],{"class":202,"line":272},[200,34411,443],{"class":379},[200,34413,318],{"class":214},[200,34415,34416],{"class":210}," user ",[200,34418,413],{"class":214},[200,34420,509],{"class":214},[200,34422,34392],{"class":223},[200,34424,466],{"class":210},[200,34426,34427,34429,34431],{"class":202,"line":283},[200,34428,680],{"class":214},[200,34430,14896],{"class":235},[200,34432,647],{"class":214},[200,34434,34435],{"class":202,"line":300},[200,34436,528],{"emptyLinePlaceholder":20},[200,34438,34439,34441,34443],{"class":202,"line":310},[200,34440,599],{"class":214},[200,34442,21468],{"class":235},[200,34444,647],{"class":214},[200,34446,34447,34449,34451,34454,34456,34458,34460,34462],{"class":202,"line":336},[200,34448,656],{"class":214},[200,34450,11400],{"class":235},[200,34452,34453],{"class":379}," v-if",[200,34455,449],{"class":214},[200,34457,610],{"class":214},[200,34459,26847],{"class":245},[200,34461,610],{"class":214},[200,34463,647],{"class":214},[200,34465,34466],{"class":202,"line":342},[200,34467,34468],{"class":210},"    {{ user.name }}\n",[200,34470,34471,34473,34475],{"class":202,"line":365},[200,34472,892],{"class":214},[200,34474,11400],{"class":235},[200,34476,647],{"class":214},[200,34478,34479,34481,34483],{"class":202,"line":371},[200,34480,680],{"class":214},[200,34482,21468],{"class":235},[200,34484,647],{"class":214},[35,34486,34487],{},"Le composable est le suivant :",[192,34489,34491],{"className":1380,"code":34490,"language":1383,"meta":13,"style":13},"import { ref } from 'vue'\n\nexport function useUser() {\n  const user = ref({ name: 'John Doe' })\n\n  return {\n    user\n  }\n}\n",[156,34492,34493,34511,34515,34527,34557,34561,34567,34572,34576],{"__ignoreMap":13},[200,34494,34495,34497,34499,34501,34503,34505,34507,34509],{"class":202,"line":203},[200,34496,972],{"class":206},[200,34498,318],{"class":214},[200,34500,18698],{"class":210},[200,34502,980],{"class":214},[200,34504,983],{"class":206},[200,34506,242],{"class":214},[200,34508,12075],{"class":245},[200,34510,991],{"class":214},[200,34512,34513],{"class":202,"line":14},[200,34514,528],{"emptyLinePlaceholder":20},[200,34516,34517,34519,34521,34523,34525],{"class":202,"line":255},[200,34518,1390],{"class":206},[200,34520,5002],{"class":379},[200,34522,34392],{"class":223},[200,34524,1549],{"class":214},[200,34526,280],{"class":214},[200,34528,34529,34531,34534,34536,34538,34540,34542,34544,34546,34548,34551,34553,34555],{"class":202,"line":272},[200,34530,3738],{"class":379},[200,34532,34533],{"class":210}," user",[200,34535,509],{"class":214},[200,34537,18698],{"class":223},[200,34539,227],{"class":235},[200,34541,2767],{"class":214},[200,34543,700],{"class":235},[200,34545,239],{"class":214},[200,34547,242],{"class":214},[200,34549,34550],{"class":245},"John Doe",[200,34552,249],{"class":214},[200,34554,980],{"class":214},[200,34556,416],{"class":235},[200,34558,34559],{"class":202,"line":283},[200,34560,528],{"emptyLinePlaceholder":20},[200,34562,34563,34565],{"class":202,"line":300},[200,34564,3758],{"class":206},[200,34566,280],{"class":214},[200,34568,34569],{"class":202,"line":310},[200,34570,34571],{"class":210},"    user\n",[200,34573,34574],{"class":202,"line":336},[200,34575,4505],{"class":214},[200,34577,34578],{"class":202,"line":342},[200,34579,1129],{"class":214},[35,34581,34582,34583],{},"Le composant utilise un composable importé d'un autre fichier. ",[1216,34584,34585],{},"Cela peut sembler trivial, mais cela pose un problème significatif.",[35,34587,34588],{},"Tout cela suit l'architecture ci-dessous :",[192,34590,34593],{"className":34591,"code":34592,"language":697},[33090],"src\u002F\n  components\u002F\n    User.vue\n  composables\u002F\n    useUser.ts\n  index.ts\n",[156,34594,34592],{"__ignoreMap":13},[35,34596,34597,34598,34601],{},"Le fichier ",[156,34599,34600],{},"index.ts"," sert de point d'entrée de la bibliothèque et ressemble à ceci :",[192,34603,34605],{"className":1380,"code":34604,"language":1383,"meta":13,"style":13},"import User from '.\u002Fcomponents\u002FUser.vue'\n\nexport { User }\n\nexport { useUser } from '.\u002Fcomposables\u002FuseUser'\n",[156,34606,34607,34623,34627,34639,34643],{"__ignoreMap":13},[200,34608,34609,34611,34614,34616,34618,34621],{"class":202,"line":203},[200,34610,972],{"class":206},[200,34612,34613],{"class":210}," User ",[200,34615,1489],{"class":206},[200,34617,242],{"class":214},[200,34619,34620],{"class":245},".\u002Fcomponents\u002FUser.vue",[200,34622,991],{"class":214},[200,34624,34625],{"class":202,"line":14},[200,34626,528],{"emptyLinePlaceholder":20},[200,34628,34629,34631,34633,34636],{"class":202,"line":255},[200,34630,1390],{"class":206},[200,34632,318],{"class":214},[200,34634,34635],{"class":210}," User",[200,34637,34638],{"class":214}," }\n",[200,34640,34641],{"class":202,"line":272},[200,34642,528],{"emptyLinePlaceholder":20},[200,34644,34645,34647,34649,34651,34653,34655,34657,34660],{"class":202,"line":283},[200,34646,1390],{"class":206},[200,34648,318],{"class":214},[200,34650,34392],{"class":210},[200,34652,980],{"class":214},[200,34654,983],{"class":206},[200,34656,242],{"class":214},[200,34658,34659],{"class":245},".\u002Fcomposables\u002FuseUser",[200,34661,991],{"class":214},[35,34663,34664,34665,34667,34668,2186],{},"De plus, le ",[156,34666,31352],{}," inclut quelques ",[156,34669,34670],{},"exports",[192,34672,34674],{"className":28358,"code":34673,"language":28360,"meta":13,"style":13},"{\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \".\u002Fdist\u002Findex.d.ts\",\n      \"import\": \".\u002Fdist\u002Findex.mjs\"\n    }\n  },\n  \"main\": \"dist\u002Findex.mjs\",\n  \"types\": \"dist\u002Findex.d.ts\"\n}\n",[156,34675,34676,34680,34698,34710,34722,34742,34759,34763,34767,34786,34803],{"__ignoreMap":13},[200,34677,34678],{"class":202,"line":203},[200,34679,230],{"class":214},[200,34681,34682,34684,34686,34688,34690,34692,34694,34696],{"class":202,"line":14},[200,34683,28489],{"class":214},[200,34685,3786],{"class":379},[200,34687,610],{"class":214},[200,34689,239],{"class":214},[200,34691,28499],{"class":214},[200,34693,15097],{"class":245},[200,34695,610],{"class":214},[200,34697,252],{"class":214},[200,34699,34700,34702,34704,34706,34708],{"class":202,"line":255},[200,34701,28489],{"class":214},[200,34703,34670],{"class":379},[200,34705,610],{"class":214},[200,34707,239],{"class":214},[200,34709,280],{"class":214},[200,34711,34712,34714,34716,34718,34720],{"class":202,"line":272},[200,34713,28529],{"class":214},[200,34715,215],{"class":1364},[200,34717,610],{"class":214},[200,34719,239],{"class":214},[200,34721,280],{"class":214},[200,34723,34724,34726,34729,34731,34733,34735,34738,34740],{"class":202,"line":283},[200,34725,28542],{"class":214},[200,34727,34728],{"class":2060},"types",[200,34730,610],{"class":214},[200,34732,239],{"class":214},[200,34734,28499],{"class":214},[200,34736,34737],{"class":245},".\u002Fdist\u002Findex.d.ts",[200,34739,610],{"class":214},[200,34741,252],{"class":214},[200,34743,34744,34746,34748,34750,34752,34754,34757],{"class":202,"line":300},[200,34745,28542],{"class":214},[200,34747,972],{"class":2060},[200,34749,610],{"class":214},[200,34751,239],{"class":214},[200,34753,28499],{"class":214},[200,34755,34756],{"class":245},".\u002Fdist\u002Findex.mjs",[200,34758,616],{"class":214},[200,34760,34761],{"class":202,"line":310},[200,34762,4977],{"class":214},[200,34764,34765],{"class":202,"line":336},[200,34766,368],{"class":214},[200,34768,34769,34771,34773,34775,34777,34779,34782,34784],{"class":202,"line":342},[200,34770,28489],{"class":214},[200,34772,32614],{"class":379},[200,34774,610],{"class":214},[200,34776,239],{"class":214},[200,34778,28499],{"class":214},[200,34780,34781],{"class":245},"dist\u002Findex.mjs",[200,34783,610],{"class":214},[200,34785,252],{"class":214},[200,34787,34788,34790,34792,34794,34796,34798,34801],{"class":202,"line":365},[200,34789,28489],{"class":214},[200,34791,34728],{"class":379},[200,34793,610],{"class":214},[200,34795,239],{"class":214},[200,34797,28499],{"class":214},[200,34799,34800],{"class":245},"dist\u002Findex.d.ts",[200,34802,616],{"class":214},[200,34804,34805],{"class":202,"line":371},[200,34806,1129],{"class":214},[51,34808,34810],{"id":34809},"similaire-à-un-projet-typescript-standard","Similaire à un projet TypeScript standard",[938,34812,34813],{},[35,34814,34815,34817],{},[200,34816,944],{},"\nUn bundle est un seul fichier encapsulant tout le code de votre projet. Il optimise le chargement du projet en minimisant le nombre de fichiers à charger. Ce bundle n'est pas transpile (sauf TypeScript vers JavaScript) ni minifié lors de la création d'un package npm.",[35,34819,34820,34821,34823],{},"Maintenant, si nous tentons de regrouper ce projet, notre dossier ",[156,34822,5154],{}," apparaîtra comme ceci :",[192,34825,34828],{"className":34826,"code":34827,"language":697},[33090],"dist\u002F\n  index.cjs\n",[156,34829,34827],{"__ignoreMap":13},[35,34831,34832,34833,34836,34837,34840,34841],{},"En inspectant ",[156,34834,34835],{},"index.cjs",", vous trouverez une surprise : le composable ",[156,34838,34839],{},"useUser"," est intégré, mais le composant est complètement ignoré. ",[1216,34842,34843,34844,34847,34848,13741,34853,34858],{},"S'il fonctionne, l'outil pourrait déclencher une erreur du type ",[156,34845,34846],{},"No loader is configured for \".vue\" files",". Essentiellement, des outils comme ",[39,34849,34852],{"href":34850,"rel":34851},"https:\u002F\u002Fesbuild.github.io",[77],"esbuild",[39,34854,34857],{"href":34855,"rel":34856},"https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fcompiler-options.html",[77],"tsc"," ne savent pas comment gérer les fichiers Vue. C'est logique, car les fichiers Vue ne sont pas des fichiers JavaScript.",[35,34860,34861,34862,34865],{},"Le problème devient évident. Nous avons besoin d'une configuration pour gérer les fichiers Vue. Jetons un coup d'œil rapide sur Internet pour quelque chose comme ",[156,34863,34864],{},"vue loader",", car nous utilisons des fichiers Vue et devons les transpiler en JavaScript.",[35,34867,34868,34869,34874,34875],{},"Le premier résultat est ",[39,34870,34873],{"href":34871,"rel":34872},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fvue-loader",[77],"Vue Loader",", qui est un loader Webpack. ",[1216,34876,34877],{},"Non. Je refuse d'utiliser Webpack. Vite est maintenant un standard, et il n'y a aucune justification pour employer Webpack dans ce contexte.",[35,34879,34880,34881,8048,34883,34891],{},"Mais avant de plonger plus loin, rappelons-nous notre mantra : ",[6862,34882,34348],{},[1216,34884,34885,34886],{},"et ",[39,34887,34890],{"href":34888,"rel":34889},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKISS_principle",[77],"keep it simple, stupid!"," La transpilation des fichiers SFC Vue avec Webpack semble contraire à ce principe.",[51,34893,34895],{"id":34894},"ignorer-les-fichiers-vue","Ignorer les fichiers Vue",[35,34897,34898,34899,34901,34902,34907,34908,215],{},"Pour livrer des fichiers ",[156,34900,11866],{}," sans les transpiler, nous demandons à notre outil de les ignorer. Cela est réalisé à l'aide d'un outil appelé ",[39,34903,34906],{"href":34904,"rel":34905},"https:\u002F\u002Funbuild.unjs.io",[77],"unbuild"," provenant de l'écosystème ",[39,34909,30540],{"href":34910,"rel":34911},"https:\u002F\u002Funjs.io",[77],[35,34913,34914],{},"Unbuild est un outil simple mais hautement configurable car il est construit sur Rollup.",[35,34916,34917],{},"Pour notre petit projet, nous pouvons l'expérimenter directement :",[192,34919,34921],{"className":1354,"code":34920,"language":1357,"meta":13,"style":13},"npx unbuild\n",[156,34922,34923],{"__ignoreMap":13},[200,34924,34925,34927],{"class":202,"line":203},[200,34926,30995],{"class":1364},[200,34928,34929],{"class":245}," unbuild\n",[35,34931,34932],{},"Malheureusement, cela échoue pour les mêmes raisons que précédemment :",[192,34934,34936],{"className":4292,"code":34935,"language":4294,"meta":13,"style":13},"src\u002Fcomponents\u002FShowGitHubUser.vue (1:0): Expression expected (Note that you need plugins to import files that are not JavaScript)\n",[156,34937,34938],{"__ignoreMap":13},[200,34939,34940],{"class":202,"line":203},[200,34941,34935],{},[35,34943,34944],{},[1216,34945,34946],{},"Mais unbuild a le potentiel pour bien plus.",[35,34948,34949,34950,34952],{},"Pour résoudre ce problème, essayons la solution évidente. Dans le fichier ",[156,34951,34600],{},", supprimons l'exportation du composant et exportons uniquement les fichiers TypeScript :",[192,34954,34956],{"className":1380,"code":34955,"language":1383,"meta":13,"style":13},"export { useUser } from '.\u002Fcomposables\u002FuseUser'\n",[156,34957,34958],{"__ignoreMap":13},[200,34959,34960,34962,34964,34966,34968,34970,34972,34974],{"class":202,"line":203},[200,34961,1390],{"class":206},[200,34963,318],{"class":214},[200,34965,34392],{"class":210},[200,34967,980],{"class":214},[200,34969,983],{"class":206},[200,34971,242],{"class":214},[200,34973,34659],{"class":245},[200,34975,991],{"class":214},[35,34977,34978,34979,34982],{},"Maintenant, la commande ",[156,34980,34981],{},"npx unbuild"," fonctionne correctement.",[192,34984,34986],{"className":1354,"code":34985,"language":1357,"meta":13,"style":13},"➜ npx unbuild\nℹ Automatically detected entries: src\u002Findex [esm] [dts]\nℹ Building vue-library\nℹ Cleaning dist directory: .\u002Fdist\n✔ Build succeeded for vue-library\n  dist\u002Findex.mjs (total size: 139 B, chunk size: 139 B, exports: useUser)\n\nΣ Total dist size (byte size): 531 B\n",[156,34987,34988,34997,35024,35034,35050,35065,35099,35103],{"__ignoreMap":13},[200,34989,34990,34993,34995],{"class":202,"line":203},[200,34991,34992],{"class":1364},"➜",[200,34994,31232],{"class":245},[200,34996,34929],{"class":245},[200,34998,34999,35002,35005,35008,35011,35014,35017,35019,35022],{"class":202,"line":14},[200,35000,35001],{"class":1364},"ℹ",[200,35003,35004],{"class":245}," Automatically",[200,35006,35007],{"class":245}," detected",[200,35009,35010],{"class":245}," entries:",[200,35012,35013],{"class":245}," src\u002Findex",[200,35015,35016],{"class":210}," [esm] ",[200,35018,9872],{"class":214},[200,35020,35021],{"class":210},"dts",[200,35023,4511],{"class":214},[200,35025,35026,35028,35031],{"class":202,"line":255},[200,35027,35001],{"class":1364},[200,35029,35030],{"class":245}," Building",[200,35032,35033],{"class":245}," vue-library\n",[200,35035,35036,35038,35041,35044,35047],{"class":202,"line":272},[200,35037,35001],{"class":1364},[200,35039,35040],{"class":245}," Cleaning",[200,35042,35043],{"class":245}," dist",[200,35045,35046],{"class":245}," directory:",[200,35048,35049],{"class":245}," .\u002Fdist\n",[200,35051,35052,35055,35058,35061,35063],{"class":202,"line":283},[200,35053,35054],{"class":1364},"✔",[200,35056,35057],{"class":245}," Build",[200,35059,35060],{"class":245}," succeeded",[200,35062,662],{"class":245},[200,35064,35033],{"class":245},[200,35066,35067,35070,35073,35076,35079,35082,35085,35088,35090,35092,35095,35097],{"class":202,"line":300},[200,35068,35069],{"class":1364},"  dist\u002Findex.mjs",[200,35071,35072],{"class":210}," (total ",[200,35074,35075],{"class":245},"size:",[200,35077,35078],{"class":2060}," 139",[200,35080,35081],{"class":245}," B,",[200,35083,35084],{"class":245}," chunk",[200,35086,35087],{"class":245}," size:",[200,35089,35078],{"class":2060},[200,35091,35081],{"class":245},[200,35093,35094],{"class":245}," exports:",[200,35096,34392],{"class":245},[200,35098,416],{"class":210},[200,35100,35101],{"class":202,"line":310},[200,35102,528],{"emptyLinePlaceholder":20},[200,35104,35105,35108,35111,35113,35116,35119,35122],{"class":202,"line":336},[200,35106,35107],{"class":1364},"Σ",[200,35109,35110],{"class":245}," Total",[200,35112,35043],{"class":245},[200,35114,35115],{"class":245}," size",[200,35117,35118],{"class":210}," (byte ",[200,35120,35121],{"class":245},"size",[200,35123,35124],{"class":210},"): 531 B\n",[35,35126,35127,35128,35130,35131,215],{},"C'est un bon début, mais nos composants restent dans le dossier ",[156,35129,11180],{}," et peu importe combien vous cherchez ardemment, ils sont absents du dossier ",[156,35132,5154],{},[51,35134,35136],{"id":35135},"copier-les-fichiers-vue","Copier les fichiers Vue",[35,35138,35139,35140,35143,35144,35146,35147,35149,35150,215],{},"Maintenant que les fichiers ",[156,35141,35142],{},".ts"," sont traités correctement, nous pouvons tenter de copier les fichiers ",[156,35145,11866],{}," dans le dossier ",[156,35148,5154],{}," avec une simple commande ",[156,35151,35152],{},"cp",[192,35154,35156],{"className":1354,"code":35155,"language":1357,"meta":13,"style":13},"cp -r src\u002Fcomponents\u002F dist\u002Fcomponents\u002F\n",[156,35157,35158],{"__ignoreMap":13},[200,35159,35160,35162,35165,35168],{"class":202,"line":203},[200,35161,35152],{"class":1364},[200,35163,35164],{"class":245}," -r",[200,35166,35167],{"class":245}," src\u002Fcomponents\u002F",[200,35169,35170],{"class":245}," dist\u002Fcomponents\u002F\n",[35,35172,35173,35174,35176,35177,35179],{},"Ensuite, nous ajoutons un champ ",[156,35175,34670],{}," dans le ",[156,35178,31352],{}," pour informer l'utilisateur où localiser les composants :",[192,35181,35183],{"className":28358,"code":35182,"language":28360,"meta":13,"style":13},"{\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \".\u002Fdist\u002Findex.d.ts\",\n      \"import\": \".\u002Fdist\u002Findex.mjs\"\n    },\n    \".\u002Fcomponents\u002F*\": {\n      \"import\": \".\u002Fdist\u002Fcomponents\u002F*.vue\"\n    }\n  },\n  \"main\": \"dist\u002Findex.mjs\",\n  \"types\": \"dist\u002Findex.d.ts\"\n}\n",[156,35184,35185,35189,35207,35219,35231,35249,35265,35269,35282,35299,35303,35307,35325,35341],{"__ignoreMap":13},[200,35186,35187],{"class":202,"line":203},[200,35188,230],{"class":214},[200,35190,35191,35193,35195,35197,35199,35201,35203,35205],{"class":202,"line":14},[200,35192,28489],{"class":214},[200,35194,3786],{"class":379},[200,35196,610],{"class":214},[200,35198,239],{"class":214},[200,35200,28499],{"class":214},[200,35202,15097],{"class":245},[200,35204,610],{"class":214},[200,35206,252],{"class":214},[200,35208,35209,35211,35213,35215,35217],{"class":202,"line":255},[200,35210,28489],{"class":214},[200,35212,34670],{"class":379},[200,35214,610],{"class":214},[200,35216,239],{"class":214},[200,35218,280],{"class":214},[200,35220,35221,35223,35225,35227,35229],{"class":202,"line":272},[200,35222,28529],{"class":214},[200,35224,215],{"class":1364},[200,35226,610],{"class":214},[200,35228,239],{"class":214},[200,35230,280],{"class":214},[200,35232,35233,35235,35237,35239,35241,35243,35245,35247],{"class":202,"line":283},[200,35234,28542],{"class":214},[200,35236,34728],{"class":2060},[200,35238,610],{"class":214},[200,35240,239],{"class":214},[200,35242,28499],{"class":214},[200,35244,34737],{"class":245},[200,35246,610],{"class":214},[200,35248,252],{"class":214},[200,35250,35251,35253,35255,35257,35259,35261,35263],{"class":202,"line":300},[200,35252,28542],{"class":214},[200,35254,972],{"class":2060},[200,35256,610],{"class":214},[200,35258,239],{"class":214},[200,35260,28499],{"class":214},[200,35262,34756],{"class":245},[200,35264,616],{"class":214},[200,35266,35267],{"class":202,"line":310},[200,35268,339],{"class":214},[200,35270,35271,35273,35276,35278,35280],{"class":202,"line":336},[200,35272,28529],{"class":214},[200,35274,35275],{"class":1364},".\u002Fcomponents\u002F*",[200,35277,610],{"class":214},[200,35279,239],{"class":214},[200,35281,280],{"class":214},[200,35283,35284,35286,35288,35290,35292,35294,35297],{"class":202,"line":342},[200,35285,28542],{"class":214},[200,35287,972],{"class":2060},[200,35289,610],{"class":214},[200,35291,239],{"class":214},[200,35293,28499],{"class":214},[200,35295,35296],{"class":245},".\u002Fdist\u002Fcomponents\u002F*.vue",[200,35298,616],{"class":214},[200,35300,35301],{"class":202,"line":365},[200,35302,4977],{"class":214},[200,35304,35305],{"class":202,"line":371},[200,35306,368],{"class":214},[200,35308,35309,35311,35313,35315,35317,35319,35321,35323],{"class":202,"line":398},[200,35310,28489],{"class":214},[200,35312,32614],{"class":379},[200,35314,610],{"class":214},[200,35316,239],{"class":214},[200,35318,28499],{"class":214},[200,35320,34781],{"class":245},[200,35322,610],{"class":214},[200,35324,252],{"class":214},[200,35326,35327,35329,35331,35333,35335,35337,35339],{"class":202,"line":405},[200,35328,28489],{"class":214},[200,35330,34728],{"class":379},[200,35332,610],{"class":214},[200,35334,239],{"class":214},[200,35336,28499],{"class":214},[200,35338,34800],{"class":245},[200,35340,616],{"class":214},[200,35342,35343],{"class":202,"line":410},[200,35344,1129],{"class":214},[35,35346,35347,35348,35351],{},"Ça a l'air prometteur, n'est-ce pas ? ",[1216,35349,35350],{},"Mais ce n'est pas le cas."," 😔",[51,35353,35355],{"id":35354},"le-dilemme-du-chemin-dimportation","Le dilemme du chemin d'importation",[35,35357,35358,35359,35361],{},"Dans notre composant Vue, le composable ",[156,35360,34839],{}," est importé comme ceci :",[192,35363,35365],{"className":21457,"code":35364,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport { useUser } from '..\u002Fcomposables'\n\u003C\u002Fscript>\n",[156,35366,35367,35387,35405],{"__ignoreMap":13},[200,35368,35369,35371,35373,35375,35377,35379,35381,35383,35385],{"class":202,"line":203},[200,35370,599],{"class":214},[200,35372,14896],{"class":235},[200,35374,14594],{"class":379},[200,35376,449],{"class":214},[200,35378,610],{"class":214},[200,35380,1383],{"class":245},[200,35382,610],{"class":214},[200,35384,21825],{"class":379},[200,35386,647],{"class":214},[200,35388,35389,35391,35393,35395,35397,35399,35401,35403],{"class":202,"line":14},[200,35390,972],{"class":206},[200,35392,318],{"class":214},[200,35394,34392],{"class":210},[200,35396,980],{"class":214},[200,35398,983],{"class":206},[200,35400,242],{"class":214},[200,35402,34401],{"class":245},[200,35404,991],{"class":214},[200,35406,35407,35409,35411],{"class":202,"line":255},[200,35408,680],{"class":214},[200,35410,14896],{"class":235},[200,35412,647],{"class":214},[35,35414,35415,35416,35418],{},"Pendant ce temps, notre dossier ",[156,35417,5154],{}," est structuré comme suit :",[192,35420,35423],{"className":35421,"code":35422,"language":697},[33090],"dist\u002F\n  components\u002F\n    User.vue\n  index.d.mts\n  index.d.ts\n  index.mjs\n",[156,35424,35422],{"__ignoreMap":13},[35,35426,35427],{},"Pouvez-vous identifier le problème ? 👀",[35,35429,35430,35431,35434,35435,35438],{},"Le composant est inutilisable et produira une erreur du type ",[156,35432,35433],{},"Cannot find module '..\u002Fcomposables' or its corresponding type declarations",", car le chemin mène nulle part. Nous avons regroupé tous nos fichiers TypeScript dans un seul fichier ",[156,35436,35437],{},"index.mjs",", perdant complètement la structure du projet.",[51,35440,35442],{"id":35441},"regrouper-ou-ne-pas-regrouper","Regrouper ou ne pas regrouper ?",[35,35444,35445],{},"À partir de là, nous avons deux choix :",[68,35447,35448,35459],{},[71,35449,35450,35453,35454,35458],{},[6862,35451,35452],{},"Regrouper les fichiers Vue"," en utilisant ",[39,35455,24399],{"href":35456,"rel":35457},"https:\u002F\u002Fvitejs.dev",[77],", des plugins et une configuration extensive.",[71,35460,35461,35464,35465,35470],{},[6862,35462,35463],{},"Conserver la structure de répertoires"," et utiliser un outil comme ",[39,35466,35469],{"href":35467,"rel":35468},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist",[77],"mkdist"," pour une transpilation fichier à fichier (construction sans bundle) sur les fichiers TypeScript.",[35,35472,35473],{},"La décision dépendra largement de vos besoins spécifiques, mais je suis convaincu que la simplicité est la meilleure approche. Donc, explorons la deuxième option.",[35,35475,35476,35477,35146,35479,35481,35482,35485],{},"L'objectif est de transformer les fichiers TypeScript en fichiers JavaScript, tout en préservant la structure d'origine du dossier ",[156,35478,11180],{},[156,35480,5154],{}," et en ",[1216,35483,35484],{},"ignorant"," les fichiers Vue.",[938,35487,35488],{},[35,35489,35490,35492,35493,35496,35497,35500,35501,35505,35506,215],{},[200,35491,944],{},"\nEn utilisant un bloc ",[156,35494,35495],{},"\u003Cscript setup lang=\"ts\">",", mkdist ignorera les fichiers Vue pour permettre au compilateur Vue de générer des props d'exécution. Sinon, il générera un fichier ",[156,35498,35499],{},".vue.d.ts"," pour offrir des types pour le composant Vue. Rappelez-vous que le ",[39,35502,11661],{"href":35503,"rel":35504},"https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue\u002Ftree\u002Fmain\u002Fpackages\u002Fplugin-vue",[77]," et Vite comprennent intrinsèquement les blocs de script TypeScript. Pour plus de détails, consultez ",[39,35507,35510],{"href":35508,"rel":35509},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist\u002Fissues\u002F14",[77],"issue mkdist#14",[51,35512,35514],{"id":35513},"configurer-unbuild","Configurer Unbuild",[35,35516,35517],{},"Oui, nous allons configurer unbuild car il intègre mkdist, ce qui le rend aggréable à utiliser.",[35,35519,35520,35521,35524,35525,35527],{},"Commencez par créer un fichier ",[156,35522,35523],{},"build.config.ts"," à la racine du projet. Unbuild lira ce fichier de configuration pour comprendre comment traiter le projet. Il suppose des valeurs par défaut et infère de nombreux aspects à partir du ",[156,35526,31352],{},", mais nous devons lui dire d'utiliser mkdist.",[192,35529,35531],{"className":1380,"code":35530,"language":1383,"meta":13,"style":13},"import { defineBuildConfig } from 'unbuild'\n\nexport default defineBuildConfig({\n  entries: ['.\u002Fsrc\u002F'],\n  declaration: true,\n})\n",[156,35532,35533,35552,35556,35568,35588,35599],{"__ignoreMap":13},[200,35534,35535,35537,35539,35542,35544,35546,35548,35550],{"class":202,"line":203},[200,35536,972],{"class":206},[200,35538,318],{"class":214},[200,35540,35541],{"class":210}," defineBuildConfig",[200,35543,980],{"class":214},[200,35545,983],{"class":206},[200,35547,242],{"class":214},[200,35549,34906],{"class":245},[200,35551,991],{"class":214},[200,35553,35554],{"class":202,"line":14},[200,35555,528],{"emptyLinePlaceholder":20},[200,35557,35558,35560,35562,35564,35566],{"class":202,"line":255},[200,35559,1390],{"class":206},[200,35561,1393],{"class":206},[200,35563,35541],{"class":223},[200,35565,227],{"class":210},[200,35567,230],{"class":214},[200,35569,35570,35573,35575,35577,35579,35582,35584,35586],{"class":202,"line":272},[200,35571,35572],{"class":235},"  entries",[200,35574,239],{"class":214},[200,35576,350],{"class":210},[200,35578,249],{"class":214},[200,35580,35581],{"class":245},".\u002Fsrc\u002F",[200,35583,249],{"class":214},[200,35585,360],{"class":210},[200,35587,252],{"class":214},[200,35589,35590,35593,35595,35597],{"class":202,"line":283},[200,35591,35592],{"class":235},"  declaration",[200,35594,239],{"class":214},[200,35596,2439],{"class":2438},[200,35598,252],{"class":214},[200,35600,35601,35603],{"class":202,"line":300},[200,35602,413],{"class":214},[200,35604,416],{"class":210},[35,35606,35607,35608,35611,35612,35614,35615,35617,35618,35620,35621,35624,35625,35627,35628,35631,35632,1569],{},"C'est un fichier de configuration très simple. ",[1216,35609,35610],{},"Et je l'adore."," La clé ",[156,35613,6465],{}," informe unbuild du fichier initial à commencer à transpiler. Cependant, ",[156,35616,35581],{}," est un répertoire, indiqué par le ",[156,35619,2898],{}," à la fin. Avec cet indice, unbuild contourne le regroupement par défaut ",[156,35622,35623],{},"rollup"," en faveur de ",[156,35626,35469],{},". La clé ",[156,35629,35630],{},"declaration"," incite unbuild à créer des fichiers de déclaration TypeScript (",[156,35633,35634],{},".d.ts",[35,35636,35637,35638,35176,35640,35642,35643,215],{},"Nous pouvons également supprimer le champ ",[156,35639,34670],{},[156,35641,31352],{}," puisque nous ne regrouperons plus les fichiers et restaurer l'exportation du composant dans le fichier ",[156,35644,34600],{},[35,35646,35647,35648,35650],{},"Relaçons la commande ",[156,35649,34981],{}," et magie :",[192,35652,35654],{"className":1354,"code":35653,"language":1357,"meta":13,"style":13},"➜ npx unbuild\nℹ Building vue-library\nℹ Cleaning dist directory: .\u002Fdist\n✔ Build succeeded for vue-library\n  dist (total size: 600 B)\n  └─ dist\u002Findex.d.ts (108 B)\n  └─ dist\u002Findex.mjs (112 B)\n  └─ dist\u002Fcomposables\u002FuseUser.d.ts (79 B)\n  └─ dist\u002Fcomposables\u002FuseUser.mjs (124 B)\n  └─ dist\u002Fcomponents\u002FUser.vue (177 B)\nΣ Total dist size (byte size): 600 B\n",[156,35655,35656,35664,35672,35684,35696,35713,35729,35743,35757,35771,35785],{"__ignoreMap":13},[200,35657,35658,35660,35662],{"class":202,"line":203},[200,35659,34992],{"class":1364},[200,35661,31232],{"class":245},[200,35663,34929],{"class":245},[200,35665,35666,35668,35670],{"class":202,"line":14},[200,35667,35001],{"class":1364},[200,35669,35030],{"class":245},[200,35671,35033],{"class":245},[200,35673,35674,35676,35678,35680,35682],{"class":202,"line":255},[200,35675,35001],{"class":1364},[200,35677,35040],{"class":245},[200,35679,35043],{"class":245},[200,35681,35046],{"class":245},[200,35683,35049],{"class":245},[200,35685,35686,35688,35690,35692,35694],{"class":202,"line":272},[200,35687,35054],{"class":1364},[200,35689,35057],{"class":245},[200,35691,35060],{"class":245},[200,35693,662],{"class":245},[200,35695,35033],{"class":245},[200,35697,35698,35701,35703,35705,35708,35711],{"class":202,"line":283},[200,35699,35700],{"class":1364},"  dist",[200,35702,35072],{"class":210},[200,35704,35075],{"class":245},[200,35706,35707],{"class":2060}," 600",[200,35709,35710],{"class":245}," B",[200,35712,416],{"class":210},[200,35714,35715,35718,35721,35724,35727],{"class":202,"line":300},[200,35716,35717],{"class":1364},"  └─",[200,35719,35720],{"class":245}," dist\u002Findex.d.ts",[200,35722,35723],{"class":210}," (108 ",[200,35725,35726],{"class":245},"B",[200,35728,416],{"class":210},[200,35730,35731,35733,35736,35739,35741],{"class":202,"line":310},[200,35732,35717],{"class":1364},[200,35734,35735],{"class":245}," dist\u002Findex.mjs",[200,35737,35738],{"class":210}," (112 ",[200,35740,35726],{"class":245},[200,35742,416],{"class":210},[200,35744,35745,35747,35750,35753,35755],{"class":202,"line":336},[200,35746,35717],{"class":1364},[200,35748,35749],{"class":245}," dist\u002Fcomposables\u002FuseUser.d.ts",[200,35751,35752],{"class":210}," (79 ",[200,35754,35726],{"class":245},[200,35756,416],{"class":210},[200,35758,35759,35761,35764,35767,35769],{"class":202,"line":342},[200,35760,35717],{"class":1364},[200,35762,35763],{"class":245}," dist\u002Fcomposables\u002FuseUser.mjs",[200,35765,35766],{"class":210}," (124 ",[200,35768,35726],{"class":245},[200,35770,416],{"class":210},[200,35772,35773,35775,35778,35781,35783],{"class":202,"line":365},[200,35774,35717],{"class":1364},[200,35776,35777],{"class":245}," dist\u002Fcomponents\u002FUser.vue",[200,35779,35780],{"class":210}," (177 ",[200,35782,35726],{"class":245},[200,35784,416],{"class":210},[200,35786,35787,35789,35791,35793,35795,35797,35799],{"class":202,"line":371},[200,35788,35107],{"class":1364},[200,35790,35110],{"class":245},[200,35792,35043],{"class":245},[200,35794,35115],{"class":245},[200,35796,35118],{"class":210},[200,35798,35121],{"class":245},[200,35800,35801],{"class":210},"): 600 B\n",[35,35803,35804,35805,35807],{},"Maintenant, le dossier ",[156,35806,5154],{}," apparaît comme ceci :",[192,35809,35812],{"className":35810,"code":35811,"language":697},[33090],"dist\u002F\n  components\u002F\n    User.vue\n  composables\u002F\n    useUser.d.ts\n    useUser.mjs\n  index.d.ts\n  index.mjs\n",[156,35813,35811],{"__ignoreMap":13},[35,35815,35816,35817,35819,35820,35823,35824,35826],{},"Cela reflète la structure du dossier ",[156,35818,11180],{},", et le composant ",[156,35821,35822],{},"User.vue"," est maintenant utilisable dans le dossier ",[156,35825,5154],{},". Le chemin d'importation relatif reste intact, et le composant peut être utilisé dans n'importe quel projet Vue. :partying_face:",[51,35828,35830],{"id":35829},"développement-local","Développement local",[35,35832,35833,35834,35837],{},"De nombreux tutoriels s'arrêtent ici, suggérant que vous êtes prêt à publier votre package sur npm. Mais comment pouvez-vous créer une bibliothèque complexe si vous ne pouvez pas ",[6862,35835,35836],{},"voir le résultat de votre travail"," ? Si vous ne pouvez pas la tester tout en la construisant ?",[35,35839,35840],{},"Vous ne pouvez pas.",[35,35842,35843],{},"Examinons comment utiliser la bibliothèque dans un projet local, au sein du même dépôt pour plus de simplicité.",[35,35845,35846,35847,215],{},"Je vais expliquer la méthode la plus simple en utilisant un ",[39,35848,35850],{"href":33556,"rel":35849},[77],"pnpm workspace",[35,35852,35520,35853,35856],{},[156,35854,35855],{},"pnpm-workspace.yaml"," à la racine du projet :",[192,35858,35860],{"className":32328,"code":35859,"language":32330,"meta":13,"style":13},"packages:\n  - .\n  - playground\n",[156,35861,35862,35869,35877],{"__ignoreMap":13},[200,35863,35864,35867],{"class":202,"line":203},[200,35865,35866],{"class":235},"packages",[200,35868,19494],{"class":214},[200,35870,35871,35874],{"class":202,"line":14},[200,35872,35873],{"class":214},"  -",[200,35875,35876],{"class":2060}," .\n",[200,35878,35879,35881],{"class":202,"line":255},[200,35880,35873],{"class":214},[200,35882,35883],{"class":245}," playground\n",[35,35885,35886,35887,2186],{},"Ensuite, créez un nouveau projet Vite dans un dossier ",[156,35888,35889],{},"playground",[192,35891,35893],{"className":1354,"code":35892,"language":1357,"meta":13,"style":13},"npx create-vite playground --template vue-ts\n",[156,35894,35895],{"__ignoreMap":13},[200,35896,35897,35899,35902,35905,35908],{"class":202,"line":203},[200,35898,30995],{"class":1364},[200,35900,35901],{"class":245}," create-vite",[200,35903,35904],{"class":245}," playground",[200,35906,35907],{"class":245}," --template",[200,35909,35910],{"class":245}," vue-ts\n",[35,35912,35913],{},"Puisque nous utilisons un pnpm workspace, installez les dépendances à partir de la racine du projet :",[192,35915,35917],{"className":1354,"code":35916,"language":1357,"meta":13,"style":13},"pnpm install\n",[156,35918,35919],{"__ignoreMap":13},[200,35920,35921,35923],{"class":202,"line":203},[200,35922,1365],{"class":1364},[200,35924,35925],{"class":245}," install\n",[938,35927,35928],{},[35,35929,35930,35932,35933,35936],{},[200,35931,944],{},"\nCette étape n'est pas obligatoire. Vous pouvez créer un nouveau projet Vite et installer les dépendances à l'intérieur. Pnpm simplifie cela en installant les dépendances pour tous les espaces de travail à la racine. Une simple commande ",[156,35934,35935],{},"pnpm install"," installera les dépendances de tous les espaces de travail.",[35,35938,35939,35940,35942],{},"Maintenant, nous pouvons utiliser cette bibliothèque ",[156,35941,35889],{}," dans un projet \"réel\" pour tester notre bibliothèque.",[35,35944,35945,35946,35948,35949,2186],{},"Par exemple, ouvrez le fichier ",[156,35947,22039],{}," et importez le composant ",[156,35950,35951],{},"User",[192,35953,35955],{"className":21457,"code":35954,"language":12075,"meta":13,"style":13},"\u003Cscript setup lang=\"ts\">\nimport { User } from '..\u002F..\u002Fsrc'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUser \u002F>\n\u003C\u002Ftemplate>\n",[156,35956,35957,35977,35996,36004,36008,36016,36024],{"__ignoreMap":13},[200,35958,35959,35961,35963,35965,35967,35969,35971,35973,35975],{"class":202,"line":203},[200,35960,599],{"class":214},[200,35962,14896],{"class":235},[200,35964,21825],{"class":379},[200,35966,14594],{"class":379},[200,35968,449],{"class":214},[200,35970,610],{"class":214},[200,35972,1383],{"class":245},[200,35974,610],{"class":214},[200,35976,647],{"class":214},[200,35978,35979,35981,35983,35985,35987,35989,35991,35994],{"class":202,"line":14},[200,35980,972],{"class":206},[200,35982,318],{"class":214},[200,35984,34635],{"class":210},[200,35986,980],{"class":214},[200,35988,983],{"class":206},[200,35990,242],{"class":214},[200,35992,35993],{"class":245},"..\u002F..\u002Fsrc",[200,35995,991],{"class":214},[200,35997,35998,36000,36002],{"class":202,"line":255},[200,35999,680],{"class":214},[200,36001,14896],{"class":235},[200,36003,647],{"class":214},[200,36005,36006],{"class":202,"line":272},[200,36007,528],{"emptyLinePlaceholder":20},[200,36009,36010,36012,36014],{"class":202,"line":283},[200,36011,599],{"class":214},[200,36013,21468],{"class":235},[200,36015,647],{"class":214},[200,36017,36018,36020,36022],{"class":202,"line":300},[200,36019,656],{"class":214},[200,36021,35951],{"class":235},[200,36023,21527],{"class":214},[200,36025,36026,36028,36030],{"class":202,"line":310},[200,36027,680],{"class":214},[200,36029,21468],{"class":235},[200,36031,647],{"class":214},[35,36033,36034],{},"Et exécutez le projet :",[192,36036,36038],{"className":1354,"code":36037,"language":1357,"meta":13,"style":13},"cd playground && pnpm dev\n",[156,36039,36040],{"__ignoreMap":13},[200,36041,36042,36045,36047,36049,36052],{"class":202,"line":203},[200,36043,36044],{"class":223},"cd",[200,36046,35904],{"class":245},[200,36048,14932],{"class":214},[200,36050,36051],{"class":1364}," pnpm",[200,36053,13704],{"class":245},[35,36055,36056,36057,36059,36060],{},"Vous verrez le composant ",[156,36058,35951],{}," s'afficher dans le navigateur. 🎉 Si simple, mais si puissant. ",[1216,36061,36062],{},"J'utilise cette méthode pour presque toutes les bibliothèques que je construis, et cela fonctionne parfaitement.",[51,36064,36066],{"id":36065},"publication-sur-npm","Publication sur npm",[35,36068,36069],{},"Cette partie est simple une fois que vous avez compris le flux de travail.",[35,36071,36072,36073,36076],{},"Commencez par nommer votre bibliothèque. J'utiliserai ",[156,36074,36075],{},"@barbapapazes\u002Fvue-library"," pour cet exemple.",[35,36078,36079,36080,36085,36086,215],{},"Ensuite, créez un compte sur ",[39,36081,36084],{"href":36082,"rel":36083},"https:\u002F\u002Fwww.npmjs.com",[77],"npm"," et connectez-vous en utilisant la commande ",[156,36087,36088],{},"npm login",[35,36090,36091,36092,36097,36098,36101],{},"Puis, installez ",[39,36093,36096],{"href":36094,"rel":36095},"https:\u002F\u002Fchangelogen.unjs.io",[77],"changelogen"," pour générer un changelog, mettre à jour le numéro de version selon la gestion sémantique des versions et les ",[1216,36099,36100],{},"conventional commits",", et pré-remplir la version sur GitHub.",[192,36103,36105],{"className":1354,"code":36104,"language":1357,"meta":13,"style":13},"pnpm i -D changelogen\n",[156,36106,36107],{"__ignoreMap":13},[200,36108,36109,36111,36114,36116],{"class":202,"line":203},[200,36110,1365],{"class":1364},[200,36112,36113],{"class":245}," i",[200,36115,1371],{"class":245},[200,36117,36118],{"class":245}," changelogen\n",[35,36120,36121,36122,2186],{},"Ensuite, ajoutez deux scripts à votre ",[156,36123,31352],{},[192,36125,36127],{"className":28358,"code":36126,"language":28360,"meta":13,"style":13},"{\n  \"scripts\": {\n    \"prepack\": \"unbuild\",\n    \"release\": \"changelogen --release && npm publish --access public && git push --follow-tags\"\n  }\n}\n",[156,36128,36129,36133,36145,36164,36182,36186],{"__ignoreMap":13},[200,36130,36131],{"class":202,"line":203},[200,36132,230],{"class":214},[200,36134,36135,36137,36139,36141,36143],{"class":202,"line":14},[200,36136,28489],{"class":214},[200,36138,31368],{"class":379},[200,36140,610],{"class":214},[200,36142,239],{"class":214},[200,36144,280],{"class":214},[200,36146,36147,36149,36152,36154,36156,36158,36160,36162],{"class":202,"line":255},[200,36148,28529],{"class":214},[200,36150,36151],{"class":1364},"prepack",[200,36153,610],{"class":214},[200,36155,239],{"class":214},[200,36157,28499],{"class":214},[200,36159,34906],{"class":245},[200,36161,610],{"class":214},[200,36163,252],{"class":214},[200,36165,36166,36168,36171,36173,36175,36177,36180],{"class":202,"line":272},[200,36167,28529],{"class":214},[200,36169,36170],{"class":1364},"release",[200,36172,610],{"class":214},[200,36174,239],{"class":214},[200,36176,28499],{"class":214},[200,36178,36179],{"class":245},"changelogen --release && npm publish --access public && git push --follow-tags",[200,36181,616],{"class":214},[200,36183,36184],{"class":202,"line":283},[200,36185,4505],{"class":214},[200,36187,36188],{"class":202,"line":300},[200,36189,1129],{"class":214},[35,36191,36192,36193,36195,36196,36198,36199,36201],{},"Le script ",[156,36194,36151],{}," exécute la commande ",[156,36197,34906],{}," avant de publier le package sur npm. Le script ",[156,36200,36170],{}," génère un changelog, publie le package sur npm, et pousse les tags sur GitHub.",[938,36203,36204],{},[35,36205,36206,36208,36209,36212],{},[200,36207,944],{},"\nVotre projet doit être sur GitHub pour que l'option ",[156,36210,36211],{},"--release"," fonctionne. Sinon, omettez-là et créez la version manuellement sur votre dépôt.",[35,36214,36215],{},"Maintenant, publiez votre package sur npm en utilisant la commande suivante :",[192,36217,36219],{"className":1354,"code":36218,"language":1357,"meta":13,"style":13},"npm run release\n",[156,36220,36221],{"__ignoreMap":13},[200,36222,36223,36225,36227],{"class":202,"line":203},[200,36224,36084],{"class":1364},[200,36226,32462],{"class":245},[200,36228,36229],{"class":245}," release\n",[35,36231,36232],{},"Et c'est tout ! Vous venez de publier votre première bibliothèque de composants Vue.js sur npm. 🚀",[51,36234,36236],{"id":36235},"tout-est-prêt","Tout est prêt",[35,36238,36239],{},"Nous avons terminé pour aujourd'hui. Nous avons couvert de nombreux sujets :",[1593,36241,36242,36245,36248,36251,36254,36257],{},[71,36243,36244],{},"Comment construire une bibliothèque de composants Vue.js avec TypeScript.",[71,36246,36247],{},"Comment gérer les fichiers Vue dans un projet TypeScript.",[71,36249,36250],{},"Comment utiliser unbuild pour transpiler les fichiers TypeScript sans regrouper les fichiers Vue.",[71,36252,36253],{},"Comment tirer parti de mkdist pour maintenir la structure du projet.",[71,36255,36256],{},"Comment utiliser un pnpm workspace pour tester la bibliothèque dans un projet local.",[71,36258,36259],{},"Comment publier la bibliothèque sur npm.",[35,36261,36262,36263],{},"Pour un exemple plus détaillé et complexe, consultez GitHub : ",[1599,36264,36265,36266,215],{"repo":34304}," mais tout ce qui a été expliqué ici provient de ",[6862,36267,36268],{},"mon expérience dans le monde réel",[35,36270,36271,36272,215],{},"J'espère que vous avez apprécié ce tutoriel et qu'il vous aidera à construire votre propre bibliothèque de composants Vue.js. Si vous avez des questions, n'hésitez pas à me contacter sur ",[39,36273,36276],{"href":36274,"rel":36275},"https:\u002F\u002Fx.com\u002Fsoubiran_",[77],"X (Twitter)",[35,36278,36279],{},[1216,36280,36281],{},"Vous pouvez aller encore plus loin en utilisant Vite pour construire votre bibliothèque de composants :",[7918,36283],{"id":30810},[1170,36285,36286],{},"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 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 .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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":13,"searchDepth":14,"depth":14,"links":36288},[36289,36290,36291,36292,36293,36294,36295,36296,36297],{"id":34809,"depth":14,"text":34810},{"id":34894,"depth":14,"text":34895},{"id":35135,"depth":14,"text":35136},{"id":35354,"depth":14,"text":35355},{"id":35441,"depth":14,"text":35442},{"id":35513,"depth":14,"text":35514},{"id":35829,"depth":14,"text":35830},{"id":36065,"depth":14,"text":36066},{"id":36235,"depth":14,"text":36236},"2024-09-24","Gérer les complexités des SFC de Vue avec des outils puissants comme unbuild et mkdist pour créer une bibliothèque de composants.",{},"\u002Ffr\u002Fposts\u002Fthe-simplest-method-to-create-a-vue-js-component-library","---\ncontentId: 75df6bff-de40-4056-af23-fae2401cd21a\ntitle: Créer une Bibliothèque de Composants Vue.js Simplement\ndescription: Gérer les complexités des SFC de Vue avec des outils puissants comme unbuild et mkdist pour créer une bibliothèque de composants.\ndate: 2024-09-24\n---\n\nTL;DR: \u003CGitHubLink repo=\"barbapapazes\u002Fvue-library\" \u002F> _Continuez à lire pour comprendre le processus derrière cela et les raisons de mes choix._\n\nUne bibliothèque de composants est une collection de morceaux réutilisables qui peuvent être utilisés dans divers projets. Elle facilite le partage de ressources entre différents projets et équipes. Ces composants peuvent être basiques et génériques comme des boutons, des champs de saisie et des modales, ou des modules plus spécifiques au métier. _En fin de compte, c'est une méthode pour partager du code entre les projets, une compétence cruciale qui peut faire économiser beaucoup de temps._\n\nCependant, construire une bibliothèque de composants avec [Vue.js](https:\u002F\u002Fvuejs.org) est plus difficile qu'il n'y paraît en raison des [Single File Components](https:\u002F\u002Fvuejs.org\u002Fguide\u002Fscaling-up\u002Fsfc.html) (SFC). Dans un projet TypeScript standard, vous transpilez généralement en JavaScript et regroupez vos fichiers avec un outil comme [tsup](https:\u002F\u002Ftsup.egoist.dev\u002F) ou [Vite](https:\u002F\u002Fvitejs.dev\u002F).\n\n\u003CCalloutReadMore id=\"1fafed86-05bb-4de4-b7d0-bb0bd6abc9d2\" \u002F>\n\n> [!NOTE]\n> N'oubliez pas de **livrer le code du paquet aussi natif que possible** et de laisser les outils de l'utilisateur gérer la transpilation et l'optimisation. C'est un principe que je garde toujours à l'esprit lorsque je crée des bibliothèques, ce qui simplifie considérablement le processus. _Chaque cas est unique, mais c'est une bonne ligne directrice à suivre._\n\nL'utilisation des SFC de Vue.js complique ce processus. Examinons pourquoi en explorant les défis que vous rencontrerez lors de la construction d'une bibliothèque de composants Vue.js.\n\nConsidérons le composant suivant :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nimport { useUser } from '..\u002Fcomposables'\n\nconst { user } = useUser()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-if=\"user\">\n    {{ user.name }}\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nLe composable est le suivant :\n\n```ts\nimport { ref } from 'vue'\n\nexport function useUser() {\n  const user = ref({ name: 'John Doe' })\n\n  return {\n    user\n  }\n}\n```\n\nLe composant utilise un composable importé d'un autre fichier. _Cela peut sembler trivial, mais cela pose un problème significatif._\n\nTout cela suit l'architecture ci-dessous :\n\n```\nsrc\u002F\n  components\u002F\n    User.vue\n  composables\u002F\n    useUser.ts\n  index.ts\n```\n\nLe fichier `index.ts` sert de point d'entrée de la bibliothèque et ressemble à ceci :\n\n```ts\nimport User from '.\u002Fcomponents\u002FUser.vue'\n\nexport { User }\n\nexport { useUser } from '.\u002Fcomposables\u002FuseUser'\n```\n\nDe plus, le `package.json` inclut quelques `exports` :\n\n```json\n{\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \".\u002Fdist\u002Findex.d.ts\",\n      \"import\": \".\u002Fdist\u002Findex.mjs\"\n    }\n  },\n  \"main\": \"dist\u002Findex.mjs\",\n  \"types\": \"dist\u002Findex.d.ts\"\n}\n```\n\n## Similaire à un projet TypeScript standard\n\n> [!NOTE]\n> Un bundle est un seul fichier encapsulant tout le code de votre projet. Il optimise le chargement du projet en minimisant le nombre de fichiers à charger. Ce bundle n'est pas transpile (sauf TypeScript vers JavaScript) ni minifié lors de la création d'un package npm.\n\nMaintenant, si nous tentons de regrouper ce projet, notre dossier `dist` apparaîtra comme ceci :\n\n```\ndist\u002F\n  index.cjs\n```\n\nEn inspectant `index.cjs`, vous trouverez une surprise : le composable `useUser` est intégré, mais le composant est complètement ignoré. _S'il fonctionne, l'outil pourrait déclencher une erreur du type `No loader is configured for \".vue\" files`. Essentiellement, des outils comme [esbuild](https:\u002F\u002Fesbuild.github.io) ou [tsc](https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002Fcompiler-options.html) ne savent pas comment gérer les fichiers Vue. C'est logique, car les fichiers Vue ne sont pas des fichiers JavaScript._\n\nLe problème devient évident. Nous avons besoin d'une configuration pour gérer les fichiers Vue. Jetons un coup d'œil rapide sur Internet pour quelque chose comme `vue loader`, car nous utilisons des fichiers Vue et devons les transpiler en JavaScript.\n\nLe premier résultat est [Vue Loader](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fvue-loader), qui est un loader Webpack. _Non. Je refuse d'utiliser Webpack. Vite est maintenant un standard, et il n'y a aucune justification pour employer Webpack dans ce contexte._\n\nMais avant de plonger plus loin, rappelons-nous notre mantra : **livrer le code du paquet aussi natif que possible** _et [keep it simple, stupid!](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKISS_principle)_ La transpilation des fichiers SFC Vue avec Webpack semble contraire à ce principe.\n\n## Ignorer les fichiers Vue\n\nPour livrer des fichiers `.vue` sans les transpiler, nous demandons à notre outil de les ignorer. Cela est réalisé à l'aide d'un outil appelé [unbuild](https:\u002F\u002Funbuild.unjs.io) provenant de l'écosystème [UnJS](https:\u002F\u002Funjs.io).\n\nUnbuild est un outil simple mais hautement configurable car il est construit sur Rollup.\n\nPour notre petit projet, nous pouvons l'expérimenter directement :\n\n```sh\nnpx unbuild\n```\n\nMalheureusement, cela échoue pour les mêmes raisons que précédemment :\n\n```txt\nsrc\u002Fcomponents\u002FShowGitHubUser.vue (1:0): Expression expected (Note that you need plugins to import files that are not JavaScript)\n```\n\n_Mais unbuild a le potentiel pour bien plus._\n\nPour résoudre ce problème, essayons la solution évidente. Dans le fichier `index.ts`, supprimons l'exportation du composant et exportons uniquement les fichiers TypeScript :\n\n```ts\nexport { useUser } from '.\u002Fcomposables\u002FuseUser'\n```\n\nMaintenant, la commande `npx unbuild` fonctionne correctement.\n\n```sh\n➜ npx unbuild\nℹ Automatically detected entries: src\u002Findex [esm] [dts]\nℹ Building vue-library\nℹ Cleaning dist directory: .\u002Fdist\n✔ Build succeeded for vue-library\n  dist\u002Findex.mjs (total size: 139 B, chunk size: 139 B, exports: useUser)\n\nΣ Total dist size (byte size): 531 B\n```\n\nC'est un bon début, mais nos composants restent dans le dossier `src` et peu importe combien vous cherchez ardemment, ils sont absents du dossier `dist`.\n\n## Copier les fichiers Vue\n\nMaintenant que les fichiers `.ts` sont traités correctement, nous pouvons tenter de copier les fichiers `.vue` dans le dossier `dist` avec une simple commande `cp`.\n\n```sh\ncp -r src\u002Fcomponents\u002F dist\u002Fcomponents\u002F\n```\n\nEnsuite, nous ajoutons un champ `exports` dans le `package.json` pour informer l'utilisateur où localiser les composants :\n\n```json\n{\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \".\u002Fdist\u002Findex.d.ts\",\n      \"import\": \".\u002Fdist\u002Findex.mjs\"\n    },\n    \".\u002Fcomponents\u002F*\": {\n      \"import\": \".\u002Fdist\u002Fcomponents\u002F*.vue\"\n    }\n  },\n  \"main\": \"dist\u002Findex.mjs\",\n  \"types\": \"dist\u002Findex.d.ts\"\n}\n```\n\nÇa a l'air prometteur, n'est-ce pas ? _Mais ce n'est pas le cas._ :pensive:\n\n## Le dilemme du chemin d'importation\n\nDans notre composant Vue, le composable `useUser` est importé comme ceci :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nimport { useUser } from '..\u002Fcomposables'\n\u003C\u002Fscript>\n```\n\nPendant ce temps, notre dossier `dist` est structuré comme suit :\n\n```\ndist\u002F\n  components\u002F\n    User.vue\n  index.d.mts\n  index.d.ts\n  index.mjs\n```\n\nPouvez-vous identifier le problème ? :eyes:\n\nLe composant est inutilisable et produira une erreur du type `Cannot find module '..\u002Fcomposables' or its corresponding type declarations`, car le chemin mène nulle part. Nous avons regroupé tous nos fichiers TypeScript dans un seul fichier `index.mjs`, perdant complètement la structure du projet.\n\n## Regrouper ou ne pas regrouper ?\n\nÀ partir de là, nous avons deux choix :\n\n1. **Regrouper les fichiers Vue** en utilisant [Vite](https:\u002F\u002Fvitejs.dev), des plugins et une configuration extensive.\n2. **Conserver la structure de répertoires** et utiliser un outil comme [mkdist](https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist) pour une transpilation fichier à fichier (construction sans bundle) sur les fichiers TypeScript.\n\nLa décision dépendra largement de vos besoins spécifiques, mais je suis convaincu que la simplicité est la meilleure approche. Donc, explorons la deuxième option.\n\nL'objectif est de transformer les fichiers TypeScript en fichiers JavaScript, tout en préservant la structure d'origine du dossier `src` dans le dossier `dist` et en _ignorant_ les fichiers Vue.\n\n> [!NOTE]\n> En utilisant un bloc `\u003Cscript setup lang=\"ts\">`, mkdist ignorera les fichiers Vue pour permettre au compilateur Vue de générer des props d'exécution. Sinon, il générera un fichier `.vue.d.ts` pour offrir des types pour le composant Vue. Rappelez-vous que le [@vitejs\u002Fplugin-vue](https:\u002F\u002Fgithub.com\u002Fvitejs\u002Fvite-plugin-vue\u002Ftree\u002Fmain\u002Fpackages\u002Fplugin-vue) et Vite comprennent intrinsèquement les blocs de script TypeScript. Pour plus de détails, consultez [issue mkdist#14](https:\u002F\u002Fgithub.com\u002Funjs\u002Fmkdist\u002Fissues\u002F14).\n\n## Configurer Unbuild\n\nOui, nous allons configurer unbuild car il intègre mkdist, ce qui le rend aggréable à utiliser.\n\nCommencez par créer un fichier `build.config.ts` à la racine du projet. Unbuild lira ce fichier de configuration pour comprendre comment traiter le projet. Il suppose des valeurs par défaut et infère de nombreux aspects à partir du `package.json`, mais nous devons lui dire d'utiliser mkdist.\n\n```ts\nimport { defineBuildConfig } from 'unbuild'\n\nexport default defineBuildConfig({\n  entries: ['.\u002Fsrc\u002F'],\n  declaration: true,\n})\n```\n\nC'est un fichier de configuration très simple. _Et je l'adore._ La clé `entries` informe unbuild du fichier initial à commencer à transpiler. Cependant, `.\u002Fsrc\u002F` est un répertoire, indiqué par le `\u002F` à la fin. Avec cet indice, unbuild contourne le regroupement par défaut `rollup` en faveur de `mkdist`. La clé `declaration` incite unbuild à créer des fichiers de déclaration TypeScript (`.d.ts`).\n\nNous pouvons également supprimer le champ `exports` dans le `package.json` puisque nous ne regrouperons plus les fichiers et restaurer l'exportation du composant dans le fichier `index.ts`.\n\nRelaçons la commande `npx unbuild` et magie :\n\n```sh\n➜ npx unbuild\nℹ Building vue-library\nℹ Cleaning dist directory: .\u002Fdist\n✔ Build succeeded for vue-library\n  dist (total size: 600 B)\n  └─ dist\u002Findex.d.ts (108 B)\n  └─ dist\u002Findex.mjs (112 B)\n  └─ dist\u002Fcomposables\u002FuseUser.d.ts (79 B)\n  └─ dist\u002Fcomposables\u002FuseUser.mjs (124 B)\n  └─ dist\u002Fcomponents\u002FUser.vue (177 B)\nΣ Total dist size (byte size): 600 B\n```\n\nMaintenant, le dossier `dist` apparaît comme ceci :\n\n```\ndist\u002F\n  components\u002F\n    User.vue\n  composables\u002F\n    useUser.d.ts\n    useUser.mjs\n  index.d.ts\n  index.mjs\n```\n\nCela reflète la structure du dossier `src`, et le composant `User.vue` est maintenant utilisable dans le dossier `dist`. Le chemin d'importation relatif reste intact, et le composant peut être utilisé dans n'importe quel projet Vue. :partying_face:\n\n## Développement local\n\nDe nombreux tutoriels s'arrêtent ici, suggérant que vous êtes prêt à publier votre package sur npm. Mais comment pouvez-vous créer une bibliothèque complexe si vous ne pouvez pas **voir le résultat de votre travail** ? Si vous ne pouvez pas la tester tout en la construisant ?\n\nVous ne pouvez pas.\n\nExaminons comment utiliser la bibliothèque dans un projet local, au sein du même dépôt pour plus de simplicité.\n\nJe vais expliquer la méthode la plus simple en utilisant un [pnpm workspace](https:\u002F\u002Fpnpm.io\u002F).\n\nCommencez par créer un fichier `pnpm-workspace.yaml` à la racine du projet :\n\n```yaml\npackages:\n  - .\n  - playground\n```\n\nEnsuite, créez un nouveau projet Vite dans un dossier `playground` :\n\n```sh\nnpx create-vite playground --template vue-ts\n```\n\nPuisque nous utilisons un pnpm workspace, installez les dépendances à partir de la racine du projet :\n\n```sh\npnpm install\n```\n\n> [!NOTE]\n> Cette étape n'est pas obligatoire. Vous pouvez créer un nouveau projet Vite et installer les dépendances à l'intérieur. Pnpm simplifie cela en installant les dépendances pour tous les espaces de travail à la racine. Une simple commande `pnpm install` installera les dépendances de tous les espaces de travail.\n\nMaintenant, nous pouvons utiliser cette bibliothèque `playground` dans un projet \"réel\" pour tester notre bibliothèque.\n\nPar exemple, ouvrez le fichier `src\u002FApp.vue` et importez le composant `User` :\n\n```vue\n\u003Cscript setup lang=\"ts\">\nimport { User } from '..\u002F..\u002Fsrc'\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUser \u002F>\n\u003C\u002Ftemplate>\n```\n\nEt exécutez le projet :\n\n```sh\ncd playground && pnpm dev\n```\n\nVous verrez le composant `User` s'afficher dans le navigateur. :tada: Si simple, mais si puissant. _J'utilise cette méthode pour presque toutes les bibliothèques que je construis, et cela fonctionne parfaitement._\n\n## Publication sur npm\n\nCette partie est simple une fois que vous avez compris le flux de travail.\n\nCommencez par nommer votre bibliothèque. J'utiliserai `@barbapapazes\u002Fvue-library` pour cet exemple.\n\nEnsuite, créez un compte sur [npm](https:\u002F\u002Fwww.npmjs.com) et connectez-vous en utilisant la commande `npm login`.\n\nPuis, installez [changelogen](https:\u002F\u002Fchangelogen.unjs.io) pour générer un changelog, mettre à jour le numéro de version selon la gestion sémantique des versions et les _conventional commits_, et pré-remplir la version sur GitHub.\n\n```sh\npnpm i -D changelogen\n```\n\nEnsuite, ajoutez deux scripts à votre `package.json` :\n\n```json\n{\n  \"scripts\": {\n    \"prepack\": \"unbuild\",\n    \"release\": \"changelogen --release && npm publish --access public && git push --follow-tags\"\n  }\n}\n```\n\nLe script `prepack` exécute la commande `unbuild` avant de publier le package sur npm. Le script `release` génère un changelog, publie le package sur npm, et pousse les tags sur GitHub.\n\n> [!NOTE]\n> Votre projet doit être sur GitHub pour que l'option `--release` fonctionne. Sinon, omettez-là et créez la version manuellement sur votre dépôt.\n\nMaintenant, publiez votre package sur npm en utilisant la commande suivante :\n\n```sh\nnpm run release\n```\n\nEt c'est tout ! Vous venez de publier votre première bibliothèque de composants Vue.js sur npm. :rocket:\n\n## Tout est prêt\n\nNous avons terminé pour aujourd'hui. Nous avons couvert de nombreux sujets :\n\n- Comment construire une bibliothèque de composants Vue.js avec TypeScript.\n- Comment gérer les fichiers Vue dans un projet TypeScript.\n- Comment utiliser unbuild pour transpiler les fichiers TypeScript sans regrouper les fichiers Vue.\n- Comment tirer parti de mkdist pour maintenir la structure du projet.\n- Comment utiliser un pnpm workspace pour tester la bibliothèque dans un projet local.\n- Comment publier la bibliothèque sur npm.\n\nPour un exemple plus détaillé et complexe, consultez GitHub : \u003CGitHubLink repo=\"barbapapazes\u002Fvue-library\" \u002F> mais tout ce qui a été expliqué ici provient de **mon expérience dans le monde réel**.\n\nJ'espère que vous avez apprécié ce tutoriel et qu'il vous aidera à construire votre propre bibliothèque de composants Vue.js. Si vous avez des questions, n'hésitez pas à me contacter sur [X (Twitter)](https:\u002F\u002Fx.com\u002Fsoubiran_).\n\n_Vous pouvez aller encore plus loin en utilisant Vite pour construire votre bibliothèque de composants :_\n\n\u003CCalloutReadMore id=\"1fafed86-05bb-4de4-b7d0-bb0bd6abc9d2\" \u002F>\n",{"title":34296,"description":36299},{"loc":36301},"fr\u002Fposts\u002Fthe-simplest-method-to-create-a-vue-js-component-library","leiu4ugGnVZbNxLN54wTrqoPqm1ZAPeO0Ix356BJhgc",{"id":36308,"title":36309,"body":36310,"contentId":30678,"date":36670,"description":36671,"extension":18,"meta":36672,"navigation":20,"path":36673,"rawbody":36674,"robots":23,"schemaOrg":23,"seo":36675,"sitemap":36676,"stem":36696,"__hash__":36697},"posts\u002Ffr\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js.md","PragVue 2024: Voyage, Nourriture et Beaucoup de Rencontres",{"type":7,"value":36311,"toc":36663},[36312,36325,36331,36335,36338,36344,36350,36354,36357,36378,36384,36390,36399,36405,36411,36416,36419,36425,36428,36443,36449,36452,36464,36470,36473,36476,36492,36498,36504,36507,36513,36516,36520,36525,36531,36534,36563,36566,36584,36590,36596,36599,36605,36609,36623,36629,36635,36641,36645,36651,36654,36657],[35,36313,36314,36315,36320,36321,36324],{},"Avant de plonger dans mon voyage à Prague, laissez-moi vous expliquer comment j'y suis arrivé. En mars 2024, ",[39,36316,36319],{"href":36317,"rel":36318},"https:\u002F\u002Fx.com\u002Fkissu_io",[77],"Konstantin"," m'a contacté sur X, m'invitant à parler lors d'une nouvelle conférence ",[39,36322,17998],{"href":34319,"rel":36323},[77]," à Prague.",[35,36326,36327,36328],{},"Après avoir recueilli plus de détails, j'ai accepté l'invitation avec enthousiasme. ",[1216,36329,36330],{},"Bien que j'aie accepté rapidement, j'étais légèrement nerveux car ce serait ma première fois à présenter en anglais lors d'une conférence. Mais tout nécessite un premier essai, donc j'ai décidé de saisir l'opportunité.",[51,36332,36334],{"id":36333},"en-route-pour-prague","En Route pour Prague",[35,36336,36337],{},"Le samedi 14 septembre, j'ai volé de Paris à Prague. La conférence était prévue pour le 17, mais j'ai choisi d'arriver quelques jours plus tôt pour explorer la ville.",[35,36339,36340],{},[1707,36341],{"alt":36342,"src":36343},"La vue de mon siège fenêtre pendant le vol.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fcdg-to-pra-flight.jpeg",[35,36345,36346,36347],{},"J'ai atteint mon hôtel, situé au centre de Prague, à 22h30. Avec une forte pluie à l'extérieur et me sentant épuisé, j'ai opté pour le repos. ",[1216,36348,36349],{},"Heureusement, j'ai deux jours pour explorer avant la conférence.",[51,36351,36353],{"id":36352},"explorer-la-ville","Explorer la Ville",[35,36355,36356],{},"Le lendemain, dimanche, le temps restait aussi maussade que la veille, avec des vents forts et de la pluie. Néanmoins, j'étais déterminé à explorer, alors j'ai pris mon petit déjeuner avant de sortir.",[35,36358,36359,36360,36365,36366,36371,36372,36377],{},"Sans plan concret, j'ai décidé de visiter quelques lieux notables. J'ai commencé par la ",[39,36361,36364],{"href":36362,"rel":36363},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOld_Town_Square",[77],"Place de la Vieille Ville",", un endroit magnifique mais très fréquenté par les touristes. Mon exploration m'a conduit à l'",[39,36367,36370],{"href":36368,"rel":36369},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrague_astronomical_clock",[77],"Horloge astronomique",", suivie de la traversée du ",[39,36373,36376],{"href":36374,"rel":36375},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCharles_Bridge",[77],"Pont Charles"," sur la rivière Vltava.",[35,36379,36380],{},[1707,36381],{"alt":36382,"src":36383},"Place de la Vieille Ville.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fold-town-square.jpeg",[35,36385,36386],{},[1707,36387],{"alt":36388,"src":36389},"Pont Charles et le château de Prague en arrière-plan.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fcharles-bridge.jpeg",[35,36391,36392,36393,36398],{},"En traversant le pont, j'ai remarqué une tour au sommet d'une colline. Curieux, je me suis dirigé vers là et j'ai découvert la mini Tour Eiffel, la ",[39,36394,36397],{"href":36395,"rel":36396},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPet%C5%99%C3%ADn_Lookout_Tower",[77],"Tour de Petřín",". On peut y monter, alors j'y suis allé. La vue panoramique du sommet était à couper le souffle.",[35,36400,36401],{},[1707,36402],{"alt":36403,"src":36404},"La Tour de Petřín.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpetrin-tower.jpeg",[35,36406,36407],{},[1707,36408],{"alt":36409,"src":36410},"La vue depuis la Tour de Petřín.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fview-from-petrin-tower.jpeg",[35,36412,36413],{},[1216,36414,36415],{},"Bien que la tempête ait fait osciller la tour, ce fut une expérience exaltante pour la vue incroyable.",[35,36417,36418],{},"Avec la pluie qui s'intensifiait, j'ai cherché un restaurant à proximité. J'en ai trouvé un où j'ai pu déguster un goulash traditionnel accompagné d'une bière locale. Le repas était délicieux et m'a réchauffé considérablement.",[35,36420,36421],{},[1707,36422],{"alt":36423,"src":36424},"Le goulash tchèque traditionnel.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fgoulash.jpeg",[35,36426,36427],{},"Même après le repas, la pluie persistait, me laissant assez trempé, alors je suis retourné à l'hôtel. J'ai répété ma présentation pendant plusieurs heures, espérant que le temps s'améliorerait plus tard.",[35,36429,36430,36431,36436,36437,36442],{},"En fin d'après-midi, la pluie cessa, me permettant de reprendre mes explorations. Je me suis promené au ",[39,36432,36435],{"href":36433,"rel":36434},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLetn%C3%A1_Park",[77],"Parc Letná"," et j'ai continué jusqu'au ",[39,36438,36441],{"href":36439,"rel":36440},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FStromovka",[77],"Parc Stromovka",". C'était une promenade agréable et tranquille, offrant une perspective plus naturelle de la ville, loin des lieux touristiques typiques.",[35,36444,36445],{},[1707,36446],{"alt":36447,"src":36448},"Quelques bâtiments à Prague.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fprague-buildings.jpeg",[35,36450,36451],{},"Prague est ornée de bâtiments colorés, rendant les promenades en ville très agréables.",[35,36453,36454,36455,36460,36461],{},"Pour le dîner, j'ai trouvé un authentique restaurant local où j'ai savouré ",[39,36456,36459],{"href":36457,"rel":36458},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSv%C3%AD%C4%8Dkov%C3%A1",[77],"Svíčková"," avec une autre bière locale. ",[1216,36462,36463],{},"Encore une ! La bière à Prague est excellente et même plus abordable que l'eau.",[35,36465,36466],{},[1707,36467],{"alt":36468,"src":36469},"Un Svíčková, le plat traditionnel tchèque.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fsvickova.jpeg",[35,36471,36472],{},"Après ce repas savoureux, il était temps de retourner à l'hôtel pour une nuit de sommeil réparateur.",[35,36474,36475],{},"Le lundi matin, le temps ne s'était pas amélioré, toujours de fortes pluies et du vent. L'objectif de la journée était d'être à l'hôtel de la conférence à 14h00 pour rencontrer d'autres conférenciers et se préparer avant le dîner des conférenciers à 19h00.",[35,36477,36478,36479,36484,36485,36488,36489],{},"Cet hôtel était à une heure de marche de mon hébergement. J'ai décidé de marcher, saisissant l'opportunité de voir plus de la ville. Je n'avais pas de plans spécifiques à part errer sans but. Par un heureux hasard, je suis tombé sur le ",[39,36480,36483],{"href":36481,"rel":36482},"https:\u002F\u002Fmuseumofbricks.cz\u002Fen",[77],"Musée des Briques",", la plus grande collection privée de LEGO au monde. ",[1216,36486,36487],{},"En tant qu'enthousiaste de LEGO, je ne pouvais pas manquer cette chance de visiter."," Ce musée, l'un des cinq en République tchèque, s'étend sur une superficie de 420 m² et possède 3 000 modèles uniques fabriqués à partir de plus d'un million de briques. ",[1216,36490,36491],{},"C'était nostalgique de voir certains LEGO de mon enfance.",[35,36493,36494],{},[1707,36495],{"alt":36496,"src":36497},"Le musée des Briques.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fmuseum-of-bricks.jpeg",[35,36499,36500],{},[1707,36501],{"alt":36502,"src":36503},"Quelques LEGO Star Wars. Une très petite partie de la collection.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fstar-wars-lego.jpeg",[35,36505,36506],{},"Cette visite était merveilleuse, marquant le début de mon voyage vers l'hôtel de la conférence. Temps de se promener à nouveau et d'éviter tout retard.",[35,36508,36509],{},[1707,36510],{"alt":36511,"src":36512},"Une autre vue de Prague pendant la marche","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fprague-view.jpeg",[35,36514,36515],{},"Après cette longue marche, je suis arrivé à l'hôtel. J'ai rencontré les conférenciers et assisté au dîner. C'était une excellente occasion de rencontrer tout le monde et de faire connaissance avant la conférence.",[51,36517,36519],{"id":36518},"pragvue","PragVue",[938,36521,36522],{},[35,36523,36524],{},"PragVue est une nouvelle conférence à Prague, République tchèque, axée sur Vue.js et son écosystème. C'est un événement d'une journée avec 8 conférenciers de la communauté Vue.js européenne.",[35,36526,36527,36528],{},"C'est le jour principal de l'événement, le jour de la conférence. J'étais légèrement nerveux mais entièrement préparé. ",[1216,36529,36530],{},"J'avais méticuleusement préparé ma présentation et connaissais bien mon contenu. Anecdote, beaucoup de conférenciers ont terminé leurs diapositives la veille ou même le matin de la conférence. Mais je ne citerai pas de noms. 🤫",[35,36532,36533],{},"La conférence s'est tenue chez la société Eviden avec l'agenda suivant :",[1593,36535,36536,36539,36542,36545,36548,36551,36554,36557,36560],{},[71,36537,36538],{},"Vue App in 15 mins par Alois Seckár",[71,36540,36541],{},"What's New in Nuxt 4 par Daniel Roe",[71,36543,36544],{},"Fullstack Application with Nuxt par Estéban Soubiran",[71,36546,36547],{},"Vue Practical Tips and Tricks par Alexander Lichter",[71,36549,36550],{},"Plants vs Thieves: Automated Tests in the World of Web Security par Ramona Schwering",[71,36552,36553],{},"Playing with Vue in 3D par Thorsten Seyschab",[71,36555,36556],{},"Implementing Nuxt frontend in real projects par Alois Seckár",[71,36558,36559],{},"Ship Vuejs across 5+ platforms par Konstantin Bifert",[71,36561,36562],{},"Vue Realtime with Firebase par Juan Andrés Núñez",[35,36564,36565],{},"Toutes les présentations étaient captivantes, avec le public passionné pour ne rien manquer.",[35,36567,36568,36569,36574,36575,36580,36581],{},"Ma session était centrée sur les capacités full-stack de Nuxt, où j'ai présenté ",[39,36570,36573],{"href":36571,"rel":36572},"https:\u002F\u002Fnitro.unjs.io\u002F",[77],"Nitro"," et le projet ",[39,36576,36579],{"href":36577,"rel":36578},"https:\u002F\u002Fhub.nuxt.com\u002F",[77],"NuxtHub",". Ces outils permettent aux développeurs de créer et de déployer des applications full-stack utilisant Nuxt sur la plateforme Cloudflare. Tout s'est déroulé parfaitement, un plaisir de réaliser cette présentation. ",[1216,36582,36583],{},"Cette expérience fut véritablement gratifiante, et j'espère pouvoir la reproduire à l'avenir.",[35,36585,36586],{},[1707,36587],{"alt":36588,"src":36589},"Moi présentant les capacités full-stack de Nuxt (Photo de Daniel Roe).","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fme-presenting-nuxt.jpeg",[35,36591,36592],{},[1707,36593],{"alt":36594,"src":36595},"Tous les conférenciers à PragVue.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpragvue-speakers.jpeg",[35,36597,36598],{},"Après la conférence, nous avons célébré la conclusion de la première édition de PragVue dans un bar. C'était une excellente opportunité de converser avec les conférenciers et les participants.",[35,36600,36601],{},[1707,36602],{"alt":36603,"src":36604},"En direction du bar avec Daniel, Alexander, et Thorsten.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpragvue-bar-walk.jpg",[51,36606,36608],{"id":36607},"escalade-avant-le-départ","Escalade avant le départ",[35,36610,36611,36612,1655,36615,36619,36620],{},"Le mercredi, le jour après la conférence, la plupart des participants étaient déjà partis. Avec ",[39,36613,36319],{"href":36317,"rel":36614},[77],[39,36616,1896],{"href":36617,"rel":36618},"https:\u002F\u002Fx.com\u002Fdanielcroe",[77],", nous sommes allés dans une salle d'escalade. Cela faisait des années que je n'avais pas grimpé, et j'ai pris plaisir à renouer avec cette activité. ",[1216,36621,36622],{},"C'était un excellent moyen de renforcer des liens, de s'amuser, et de conclure le voyage sur une note positive. Merci, Daniel, pour la suggestion.",[35,36624,36625],{},[1707,36626],{"alt":36627,"src":36628},"Daniel grimpant avec aisance.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fdaniel-bouldering.jpeg",[35,36630,36631],{},[1707,36632],{"alt":36633,"src":36634},"Konstantin essayant l'escalade pour la première fois.","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fkonstantin-bouldering.jpeg",[35,36636,36637],{},[1707,36638],{"alt":36639,"src":36640},"Moi, profitant de l'escalade (Photo de Daniel Roe).","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fme-bouldering.jpeg",[51,36642,36644],{"id":36643},"retour-à-paris","Retour à Paris",[35,36646,36647,36648],{},"Cette aventure à Prague a été un voyage remarquable. J'ai rencontré des personnes formidables, enfin en personne, plutôt que juste des photos de profil sur GitHub ou X, dégusté la cuisine locale, et exploré la ville. J'ai également donné ma première présentation en anglais lors d'une conférence. ",[1216,36649,36650],{},"Ces quatre jours ont été intenses mais hors du temps. 💜",[35,36652,36653],{},"Savoir que tout cela est grâce à l'open-source et à l'univers de la tech est incroyable. Je suis reconnaissant pour toutes les opportunités, et j'espère continuer à rencontrer des gens, voyager et partager mes aventures.",[35,36655,36656],{},"À la prochaine !",[35,36658,36659],{},[1707,36660],{"alt":36661,"src":36662},"La vue depuis le Parc Letna (Photo de Thorsten Seyschab).","\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fletna-park.jpg",{"title":13,"searchDepth":14,"depth":14,"links":36664},[36665,36666,36667,36668,36669],{"id":36333,"depth":14,"text":36334},{"id":36352,"depth":14,"text":36353},{"id":36518,"depth":14,"text":36519},{"id":36607,"depth":14,"text":36608},{"id":36643,"depth":14,"text":36644},"2024-09-18","J'étais à Prague pour la conférence PragVue 2024. En tant qu'orateur, j'ai eu l'occasion de présenter les capacités de la pile complète de Nuxt.",{},"\u002Ffr\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js","---\ncontentId: 74e76e2e-0d34-46a3-826f-efcab800ec8a\ntitle: \"PragVue 2024: Voyage, Nourriture et Beaucoup de Rencontres\"\ndescription: J'étais à Prague pour la conférence PragVue 2024. En tant qu'orateur, j'ai eu l'occasion de présenter les capacités de la pile complète de Nuxt.\ndate: 2024-09-18\n---\n\nAvant de plonger dans mon voyage à Prague, laissez-moi vous expliquer comment j'y suis arrivé. En mars 2024, [Konstantin](https:\u002F\u002Fx.com\u002Fkissu_io) m'a contacté sur X, m'invitant à parler lors d'une nouvelle conférence [Vue](https:\u002F\u002Fvuejs.org) à Prague.\n\nAprès avoir recueilli plus de détails, j'ai accepté l'invitation avec enthousiasme. _Bien que j'aie accepté rapidement, j'étais légèrement nerveux car ce serait ma première fois à présenter en anglais lors d'une conférence. Mais tout nécessite un premier essai, donc j'ai décidé de saisir l'opportunité._\n\n## En Route pour Prague\n\nLe samedi 14 septembre, j'ai volé de Paris à Prague. La conférence était prévue pour le 17, mais j'ai choisi d'arriver quelques jours plus tôt pour explorer la ville.\n\n![La vue de mon siège fenêtre pendant le vol.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fcdg-to-pra-flight.jpeg)\n\nJ'ai atteint mon hôtel, situé au centre de Prague, à 22h30. Avec une forte pluie à l'extérieur et me sentant épuisé, j'ai opté pour le repos. _Heureusement, j'ai deux jours pour explorer avant la conférence._\n\n## Explorer la Ville\n\nLe lendemain, dimanche, le temps restait aussi maussade que la veille, avec des vents forts et de la pluie. Néanmoins, j'étais déterminé à explorer, alors j'ai pris mon petit déjeuner avant de sortir.\n\nSans plan concret, j'ai décidé de visiter quelques lieux notables. J'ai commencé par la [Place de la Vieille Ville](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOld_Town_Square), un endroit magnifique mais très fréquenté par les touristes. Mon exploration m'a conduit à l'[Horloge astronomique](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrague_astronomical_clock), suivie de la traversée du [Pont Charles](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCharles_Bridge) sur la rivière Vltava.\n\n![Place de la Vieille Ville.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fold-town-square.jpeg)\n\n![Pont Charles et le château de Prague en arrière-plan.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fcharles-bridge.jpeg)\n\nEn traversant le pont, j'ai remarqué une tour au sommet d'une colline. Curieux, je me suis dirigé vers là et j'ai découvert la mini Tour Eiffel, la [Tour de Petřín](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPet%C5%99%C3%ADn_Lookout_Tower). On peut y monter, alors j'y suis allé. La vue panoramique du sommet était à couper le souffle.\n\n![La Tour de Petřín.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpetrin-tower.jpeg)\n\n![La vue depuis la Tour de Petřín.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fview-from-petrin-tower.jpeg)\n\n_Bien que la tempête ait fait osciller la tour, ce fut une expérience exaltante pour la vue incroyable._\n\nAvec la pluie qui s'intensifiait, j'ai cherché un restaurant à proximité. J'en ai trouvé un où j'ai pu déguster un goulash traditionnel accompagné d'une bière locale. Le repas était délicieux et m'a réchauffé considérablement.\n\n![Le goulash tchèque traditionnel.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fgoulash.jpeg)\n\nMême après le repas, la pluie persistait, me laissant assez trempé, alors je suis retourné à l'hôtel. J'ai répété ma présentation pendant plusieurs heures, espérant que le temps s'améliorerait plus tard.\n\nEn fin d'après-midi, la pluie cessa, me permettant de reprendre mes explorations. Je me suis promené au [Parc Letná](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FLetn%C3%A1_Park) et j'ai continué jusqu'au [Parc Stromovka](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FStromovka). C'était une promenade agréable et tranquille, offrant une perspective plus naturelle de la ville, loin des lieux touristiques typiques.\n\n![Quelques bâtiments à Prague.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fprague-buildings.jpeg)\n\nPrague est ornée de bâtiments colorés, rendant les promenades en ville très agréables.\n\nPour le dîner, j'ai trouvé un authentique restaurant local où j'ai savouré [Svíčková](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSv%C3%AD%C4%8Dkov%C3%A1) avec une autre bière locale. _Encore une ! La bière à Prague est excellente et même plus abordable que l'eau._\n\n![Un Svíčková, le plat traditionnel tchèque.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fsvickova.jpeg)\n\nAprès ce repas savoureux, il était temps de retourner à l'hôtel pour une nuit de sommeil réparateur.\n\nLe lundi matin, le temps ne s'était pas amélioré, toujours de fortes pluies et du vent. L'objectif de la journée était d'être à l'hôtel de la conférence à 14h00 pour rencontrer d'autres conférenciers et se préparer avant le dîner des conférenciers à 19h00.\n\nCet hôtel était à une heure de marche de mon hébergement. J'ai décidé de marcher, saisissant l'opportunité de voir plus de la ville. Je n'avais pas de plans spécifiques à part errer sans but. Par un heureux hasard, je suis tombé sur le [Musée des Briques](https:\u002F\u002Fmuseumofbricks.cz\u002Fen), la plus grande collection privée de LEGO au monde. _En tant qu'enthousiaste de LEGO, je ne pouvais pas manquer cette chance de visiter._ Ce musée, l'un des cinq en République tchèque, s'étend sur une superficie de 420 m² et possède 3 000 modèles uniques fabriqués à partir de plus d'un million de briques. _C'était nostalgique de voir certains LEGO de mon enfance._\n\n![Le musée des Briques.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fmuseum-of-bricks.jpeg)\n\n![Quelques LEGO Star Wars. Une très petite partie de la collection.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fstar-wars-lego.jpeg)\n\nCette visite était merveilleuse, marquant le début de mon voyage vers l'hôtel de la conférence. Temps de se promener à nouveau et d'éviter tout retard.\n\n![Une autre vue de Prague pendant la marche](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fprague-view.jpeg)\n\nAprès cette longue marche, je suis arrivé à l'hôtel. J'ai rencontré les conférenciers et assisté au dîner. C'était une excellente occasion de rencontrer tout le monde et de faire connaissance avant la conférence.\n\n## PragVue\n\n> PragVue est une nouvelle conférence à Prague, République tchèque, axée sur Vue.js et son écosystème. C'est un événement d'une journée avec 8 conférenciers de la communauté Vue.js européenne.\n\nC'est le jour principal de l'événement, le jour de la conférence. J'étais légèrement nerveux mais entièrement préparé. _J'avais méticuleusement préparé ma présentation et connaissais bien mon contenu. Anecdote, beaucoup de conférenciers ont terminé leurs diapositives la veille ou même le matin de la conférence. Mais je ne citerai pas de noms. 🤫_\n\nLa conférence s'est tenue chez la société Eviden avec l'agenda suivant :\n\n- Vue App in 15 mins par Alois Seckár\n- What's New in Nuxt 4 par Daniel Roe\n- Fullstack Application with Nuxt par Estéban Soubiran\n- Vue Practical Tips and Tricks par Alexander Lichter\n- Plants vs Thieves: Automated Tests in the World of Web Security par Ramona Schwering\n- Playing with Vue in 3D par Thorsten Seyschab\n- Implementing Nuxt frontend in real projects par Alois Seckár\n- Ship Vuejs across 5+ platforms par Konstantin Bifert\n- Vue Realtime with Firebase par Juan Andrés Núñez\n\nToutes les présentations étaient captivantes, avec le public passionné pour ne rien manquer.\n\nMa session était centrée sur les capacités full-stack de Nuxt, où j'ai présenté [Nitro](https:\u002F\u002Fnitro.unjs.io\u002F) et le projet [NuxtHub](https:\u002F\u002Fhub.nuxt.com\u002F). Ces outils permettent aux développeurs de créer et de déployer des applications full-stack utilisant Nuxt sur la plateforme Cloudflare. Tout s'est déroulé parfaitement, un plaisir de réaliser cette présentation. _Cette expérience fut véritablement gratifiante, et j'espère pouvoir la reproduire à l'avenir._\n\n![Moi présentant les capacités full-stack de Nuxt (Photo de Daniel Roe).](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fme-presenting-nuxt.jpeg)\n\n![Tous les conférenciers à PragVue.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpragvue-speakers.jpeg)\n\nAprès la conférence, nous avons célébré la conclusion de la première édition de PragVue dans un bar. C'était une excellente opportunité de converser avec les conférenciers et les participants.\n\n![En direction du bar avec Daniel, Alexander, et Thorsten.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fpragvue-bar-walk.jpg)\n\n## Escalade avant le départ\n\nLe mercredi, le jour après la conférence, la plupart des participants étaient déjà partis. Avec [Konstantin](https:\u002F\u002Fx.com\u002Fkissu_io) et [Daniel](https:\u002F\u002Fx.com\u002Fdanielcroe), nous sommes allés dans une salle d'escalade. Cela faisait des années que je n'avais pas grimpé, et j'ai pris plaisir à renouer avec cette activité. _C'était un excellent moyen de renforcer des liens, de s'amuser, et de conclure le voyage sur une note positive. Merci, Daniel, pour la suggestion._\n\n![Daniel grimpant avec aisance.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fdaniel-bouldering.jpeg)\n\n![Konstantin essayant l'escalade pour la première fois.](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fkonstantin-bouldering.jpeg)\n\n![Moi, profitant de l'escalade (Photo de Daniel Roe).](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fme-bouldering.jpeg)\n\n## Retour à Paris\n\nCette aventure à Prague a été un voyage remarquable. J'ai rencontré des personnes formidables, enfin en personne, plutôt que juste des photos de profil sur GitHub ou X, dégusté la cuisine locale, et exploré la ville. J'ai également donné ma première présentation en anglais lors d'une conférence. _Ces quatre jours ont été intenses mais hors du temps. 💜_\n\nSavoir que tout cela est grâce à l'open-source et à l'univers de la tech est incroyable. Je suis reconnaissant pour toutes les opportunités, et j'espère continuer à rencontrer des gens, voyager et partager mes aventures.\n\nÀ la prochaine !\n\n![La vue depuis le Parc Letna (Photo de Thorsten Seyschab).](\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js\u002Fletna-park.jpg)\n",{"title":36309,"description":36671},{"loc":36673,"images":36677},[36678,36679,36680,36681,36682,36683,36684,36685,36686,36687,36688,36689,36690,36691,36692,36693,36694,36695],{"loc":36343},{"loc":36383},{"loc":36389},{"loc":36404},{"loc":36410},{"loc":36424},{"loc":36448},{"loc":36469},{"loc":36497},{"loc":36503},{"loc":36512},{"loc":36589},{"loc":36595},{"loc":36604},{"loc":36628},{"loc":36634},{"loc":36640},{"loc":36662},"fr\u002Fposts\u002Fpragvue-2024-travel-food-people-and-a-lot-of-vue-js","TA7YGHHdvNGyHcPxW5zQQWQovOa9xa134Yto9OEyjRc",{"id":36699,"title":36700,"body":36701,"contentId":37843,"date":37844,"description":37845,"extension":18,"meta":37846,"navigation":20,"path":37847,"rawbody":37848,"robots":23,"schemaOrg":23,"seo":37849,"sitemap":37850,"stem":37855,"__hash__":37856},"posts\u002Ffr\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests.md","Tester est Plus Vastes que la Simple Écriture de Tests",{"type":7,"value":36702,"toc":37840},[36703,36710,36713,36716,36775,36782,36785,36789,36792,36798,36820,37297,37304,37563,37569,37575,37584,37587,37599,37605,37608,37622,37628,37637,37643,37649,37652,37659,37670,37673,37676,37679,37837],[938,36704,36705],{},[35,36706,36707,36709],{},[200,36708,944],{},"\nJe ne suis pas un expert en tests. Je partage simplement mes réflexions sur les tests en tant que développeur, dans un article que j'aurais apprécié lorsque j'ai commencé à coder.",[35,36711,36712],{},"J'ai commencé à coder il y a six ans. Depuis, j'ai entendu beaucoup parler des tests, mais j'ai vu peu d'exemples réels et pratiques.",[35,36714,36715],{},"Dans de nombreux articles que j'ai lus, les tests ressemblent à ça :",[192,36717,36719],{"className":25070,"code":36718,"language":25072,"meta":13,"style":13},"it('should return 2 when 1 + 1', () => {\n  expect(1 + 1).toBe(2)\n})\n",[156,36720,36721,36743,36769],{"__ignoreMap":13},[200,36722,36723,36726,36728,36730,36733,36735,36737,36739,36741],{"class":202,"line":203},[200,36724,36725],{"class":223},"it",[200,36727,227],{"class":210},[200,36729,249],{"class":214},[200,36731,36732],{"class":245},"should return 2 when 1 + 1",[200,36734,249],{"class":214},[200,36736,549],{"class":214},[200,36738,3729],{"class":214},[200,36740,393],{"class":379},[200,36742,280],{"class":214},[200,36744,36745,36748,36750,36752,36754,36756,36758,36760,36763,36765,36767],{"class":202,"line":14},[200,36746,36747],{"class":223},"  expect",[200,36749,227],{"class":235},[200,36751,3203],{"class":2060},[200,36753,13215],{"class":214},[200,36755,3953],{"class":2060},[200,36757,2104],{"class":235},[200,36759,215],{"class":214},[200,36761,36762],{"class":223},"toBe",[200,36764,227],{"class":235},[200,36766,4797],{"class":2060},[200,36768,416],{"class":235},[200,36770,36771,36773],{"class":202,"line":255},[200,36772,413],{"class":214},[200,36774,416],{"class":210},[35,36776,36777,36778,36781],{},"C'est ",[6862,36779,36780],{},"loin des scénarios du monde réel"," et cela n'aide pas à comprendre la véritable valeur des tests.",[35,36783,36784],{},"Je dois admettre que, quand je ne comprends pas quelque chose, j'évite de l'utiliser. Néanmoins, depuis ma première rencontre avec le concept de tests, cela a persisté dans mon esprit, me poussant à rassembler des connaissances à ce sujet.",[51,36786,36788],{"id":36787},"un-long-voyage","Un Long Voyage",[35,36790,36791],{},"J'ai d'abord entendu parler des tests lorsque j'ai appris Vue 2 en 2019. Dans la section des outils, sous la page \"Single File Component\", j'ai trouvé une section dédiée aux \"Tests\".",[35,36793,36794,36795],{},"Je pense que j'ai lu le début de la page mais je n'ai jamais été plus loin. Il y avait trop de nouveaux concepts à saisir, trop d'outils à apprendre, et trop de choix à faire. ",[1216,36796,36797],{},"À ce moment-là, j'ai compris que les tests front-end sont de loin la partie la plus difficile des tests.",[35,36799,36800,36801,36806,36807,36812,36813,36816,36817],{},"En 2020, avec quelques amis, nous avons développé une plateforme de tutorat. Le serveur API a été construit avec ",[39,36802,36805],{"href":36803,"rel":36804},"https:\u002F\u002Fcrow.docs.feathersjs.com\u002F",[77],"Feathers.js 4",". À ce moment-là, j'avais encore du mal avec les tests. Je me souviens avoir cherché ",[39,36808,36811],{"href":36809,"rel":36810},"https:\u002F\u002Fcrow.docs.feathersjs.com\u002Fguides\u002Fbasics\u002Ftesting.html#writing-tests",[77],"la page des tests dans la documentation de Feathers.js"," et même l'avoir lue. Malgré le fait de ne pas bien la comprendre, j'ai écrit de nombreux tests. C'était une vraie lutte, car la distinction entre les tests unitaires et les tests d'intégration m'était floue, et les exécuter dans un CI était très difficile. ",[1216,36814,36815],{},"J'utilisais MongoDB et Azure CI. En y repensant, je pense que je voulais souffrir."," Je me souviens aussi avoir essayé d'atteindre 100 % de couverture. ",[1216,36818,36819],{},"J'étais tellement naïf.",[192,36821,36823],{"className":1380,"code":36822,"language":1383,"meta":13,"style":13},"it('should patch', async () => {\n  expect.assertions(5)\n\n  const patchedResult: Department = await app\n    .service(serviceName)\n    .patch(result._id, anotherDepartment)\n\n  expect(patchedResult).toBeDefined()\n  expect(patchedResult).toHaveProperty('_id')\n  expect(patchedResult).toHaveProperty(\n    'name',\n    anotherDepartment.name.toLowerCase()\n  )\n  expect(patchedResult).toHaveProperty('createdAt')\n  expect(patchedResult).toHaveProperty('updatedAt')\n})\n\nit('should delete', async () => {\n  expect.assertions(5)\n\n  const deleteResult: Department = await app\n    .service(serviceName)\n    .remove(result._id)\n\n  expect(deleteResult).toBeDefined()\n  expect(deleteResult).toHaveProperty('_id')\n  expect(deleteResult).toHaveProperty('name', result.name.toLowerCase())\n  expect(deleteResult).toHaveProperty('createdAt')\n  expect(deleteResult).toHaveProperty('updatedAt')\n})\n",[156,36824,36825,36848,36864,36868,36886,36900,36923,36927,36945,36970,36986,36996,37011,37015,37040,37065,37071,37075,37098,37112,37116,37133,37145,37162,37166,37183,37207,37243,37267,37291],{"__ignoreMap":13},[200,36826,36827,36829,36831,36833,36836,36838,36840,36842,36844,36846],{"class":202,"line":203},[200,36828,36725],{"class":223},[200,36830,227],{"class":210},[200,36832,249],{"class":214},[200,36834,36835],{"class":245},"should patch",[200,36837,249],{"class":214},[200,36839,549],{"class":214},[200,36841,380],{"class":379},[200,36843,3729],{"class":214},[200,36845,393],{"class":379},[200,36847,280],{"class":214},[200,36849,36850,36852,36854,36857,36859,36862],{"class":202,"line":14},[200,36851,36747],{"class":210},[200,36853,215],{"class":214},[200,36855,36856],{"class":223},"assertions",[200,36858,227],{"class":235},[200,36860,36861],{"class":2060},"5",[200,36863,416],{"class":235},[200,36865,36866],{"class":202,"line":255},[200,36867,528],{"emptyLinePlaceholder":20},[200,36869,36870,36872,36875,36877,36880,36882,36884],{"class":202,"line":272},[200,36871,3738],{"class":379},[200,36873,36874],{"class":210}," patchedResult",[200,36876,239],{"class":214},[200,36878,36879],{"class":1364}," Department",[200,36881,509],{"class":214},[200,36883,452],{"class":206},[200,36885,31187],{"class":210},[200,36887,36888,36890,36893,36895,36898],{"class":202,"line":283},[200,36889,3770],{"class":214},[200,36891,36892],{"class":223},"service",[200,36894,227],{"class":235},[200,36896,36897],{"class":210},"serviceName",[200,36899,416],{"class":235},[200,36901,36902,36904,36907,36909,36911,36913,36916,36918,36921],{"class":202,"line":300},[200,36903,3770],{"class":214},[200,36905,36906],{"class":223},"patch",[200,36908,227],{"class":235},[200,36910,3546],{"class":210},[200,36912,215],{"class":214},[200,36914,36915],{"class":210},"_id",[200,36917,549],{"class":214},[200,36919,36920],{"class":210}," anotherDepartment",[200,36922,416],{"class":235},[200,36924,36925],{"class":202,"line":310},[200,36926,528],{"emptyLinePlaceholder":20},[200,36928,36929,36931,36933,36936,36938,36940,36943],{"class":202,"line":336},[200,36930,36747],{"class":223},[200,36932,227],{"class":235},[200,36934,36935],{"class":210},"patchedResult",[200,36937,2104],{"class":235},[200,36939,215],{"class":214},[200,36941,36942],{"class":223},"toBeDefined",[200,36944,466],{"class":235},[200,36946,36947,36949,36951,36953,36955,36957,36960,36962,36964,36966,36968],{"class":202,"line":342},[200,36948,36747],{"class":223},[200,36950,227],{"class":235},[200,36952,36935],{"class":210},[200,36954,2104],{"class":235},[200,36956,215],{"class":214},[200,36958,36959],{"class":223},"toHaveProperty",[200,36961,227],{"class":235},[200,36963,249],{"class":214},[200,36965,36915],{"class":245},[200,36967,249],{"class":214},[200,36969,416],{"class":235},[200,36971,36972,36974,36976,36978,36980,36982,36984],{"class":202,"line":365},[200,36973,36747],{"class":223},[200,36975,227],{"class":235},[200,36977,36935],{"class":210},[200,36979,2104],{"class":235},[200,36981,215],{"class":214},[200,36983,36959],{"class":223},[200,36985,3130],{"class":235},[200,36987,36988,36990,36992,36994],{"class":202,"line":371},[200,36989,1415],{"class":214},[200,36991,20112],{"class":245},[200,36993,249],{"class":214},[200,36995,252],{"class":214},[200,36997,36998,37001,37003,37005,37007,37009],{"class":202,"line":398},[200,36999,37000],{"class":210},"    anotherDepartment",[200,37002,215],{"class":214},[200,37004,20112],{"class":210},[200,37006,215],{"class":214},[200,37008,3847],{"class":223},[200,37010,466],{"class":235},[200,37012,37013],{"class":202,"line":405},[200,37014,29966],{"class":235},[200,37016,37017,37019,37021,37023,37025,37027,37029,37031,37033,37036,37038],{"class":202,"line":410},[200,37018,36747],{"class":223},[200,37020,227],{"class":235},[200,37022,36935],{"class":210},[200,37024,2104],{"class":235},[200,37026,215],{"class":214},[200,37028,36959],{"class":223},[200,37030,227],{"class":235},[200,37032,249],{"class":214},[200,37034,37035],{"class":245},"createdAt",[200,37037,249],{"class":214},[200,37039,416],{"class":235},[200,37041,37042,37044,37046,37048,37050,37052,37054,37056,37058,37061,37063],{"class":202,"line":860},[200,37043,36747],{"class":223},[200,37045,227],{"class":235},[200,37047,36935],{"class":210},[200,37049,2104],{"class":235},[200,37051,215],{"class":214},[200,37053,36959],{"class":223},[200,37055,227],{"class":235},[200,37057,249],{"class":214},[200,37059,37060],{"class":245},"updatedAt",[200,37062,249],{"class":214},[200,37064,416],{"class":235},[200,37066,37067,37069],{"class":202,"line":889},[200,37068,413],{"class":214},[200,37070,416],{"class":210},[200,37072,37073],{"class":202,"line":899},[200,37074,528],{"emptyLinePlaceholder":20},[200,37076,37077,37079,37081,37083,37086,37088,37090,37092,37094,37096],{"class":202,"line":904},[200,37078,36725],{"class":223},[200,37080,227],{"class":210},[200,37082,249],{"class":214},[200,37084,37085],{"class":245},"should delete",[200,37087,249],{"class":214},[200,37089,549],{"class":214},[200,37091,380],{"class":379},[200,37093,3729],{"class":214},[200,37095,393],{"class":379},[200,37097,280],{"class":214},[200,37099,37100,37102,37104,37106,37108,37110],{"class":202,"line":930},[200,37101,36747],{"class":210},[200,37103,215],{"class":214},[200,37105,36856],{"class":223},[200,37107,227],{"class":235},[200,37109,36861],{"class":2060},[200,37111,416],{"class":235},[200,37113,37114],{"class":202,"line":3655},[200,37115,528],{"emptyLinePlaceholder":20},[200,37117,37118,37120,37123,37125,37127,37129,37131],{"class":202,"line":3660},[200,37119,3738],{"class":379},[200,37121,37122],{"class":210}," deleteResult",[200,37124,239],{"class":214},[200,37126,36879],{"class":1364},[200,37128,509],{"class":214},[200,37130,452],{"class":206},[200,37132,31187],{"class":210},[200,37134,37135,37137,37139,37141,37143],{"class":202,"line":4035},[200,37136,3770],{"class":214},[200,37138,36892],{"class":223},[200,37140,227],{"class":235},[200,37142,36897],{"class":210},[200,37144,416],{"class":235},[200,37146,37147,37149,37152,37154,37156,37158,37160],{"class":202,"line":4490},[200,37148,3770],{"class":214},[200,37150,37151],{"class":223},"remove",[200,37153,227],{"class":235},[200,37155,3546],{"class":210},[200,37157,215],{"class":214},[200,37159,36915],{"class":210},[200,37161,416],{"class":235},[200,37163,37164],{"class":202,"line":4496},[200,37165,528],{"emptyLinePlaceholder":20},[200,37167,37168,37170,37172,37175,37177,37179,37181],{"class":202,"line":4502},[200,37169,36747],{"class":223},[200,37171,227],{"class":235},[200,37173,37174],{"class":210},"deleteResult",[200,37176,2104],{"class":235},[200,37178,215],{"class":214},[200,37180,36942],{"class":223},[200,37182,466],{"class":235},[200,37184,37185,37187,37189,37191,37193,37195,37197,37199,37201,37203,37205],{"class":202,"line":4508},[200,37186,36747],{"class":223},[200,37188,227],{"class":235},[200,37190,37174],{"class":210},[200,37192,2104],{"class":235},[200,37194,215],{"class":214},[200,37196,36959],{"class":223},[200,37198,227],{"class":235},[200,37200,249],{"class":214},[200,37202,36915],{"class":245},[200,37204,249],{"class":214},[200,37206,416],{"class":235},[200,37208,37209,37211,37213,37215,37217,37219,37221,37223,37225,37227,37229,37231,37233,37235,37237,37239,37241],{"class":202,"line":5469},[200,37210,36747],{"class":223},[200,37212,227],{"class":235},[200,37214,37174],{"class":210},[200,37216,2104],{"class":235},[200,37218,215],{"class":214},[200,37220,36959],{"class":223},[200,37222,227],{"class":235},[200,37224,249],{"class":214},[200,37226,20112],{"class":245},[200,37228,249],{"class":214},[200,37230,549],{"class":214},[200,37232,3561],{"class":210},[200,37234,215],{"class":214},[200,37236,20112],{"class":210},[200,37238,215],{"class":214},[200,37240,3847],{"class":223},[200,37242,10962],{"class":235},[200,37244,37245,37247,37249,37251,37253,37255,37257,37259,37261,37263,37265],{"class":202,"line":5474},[200,37246,36747],{"class":223},[200,37248,227],{"class":235},[200,37250,37174],{"class":210},[200,37252,2104],{"class":235},[200,37254,215],{"class":214},[200,37256,36959],{"class":223},[200,37258,227],{"class":235},[200,37260,249],{"class":214},[200,37262,37035],{"class":245},[200,37264,249],{"class":214},[200,37266,416],{"class":235},[200,37268,37269,37271,37273,37275,37277,37279,37281,37283,37285,37287,37289],{"class":202,"line":13237},[200,37270,36747],{"class":223},[200,37272,227],{"class":235},[200,37274,37174],{"class":210},[200,37276,2104],{"class":235},[200,37278,215],{"class":214},[200,37280,36959],{"class":223},[200,37282,227],{"class":235},[200,37284,249],{"class":214},[200,37286,37060],{"class":245},[200,37288,249],{"class":214},[200,37290,416],{"class":235},[200,37292,37293,37295],{"class":202,"line":13263},[200,37294,413],{"class":214},[200,37296,416],{"class":210},[35,37298,37299,37300,37303],{},"Mais plus j'ajoutais de fonctionnalités à la plateforme, plus je réalisais la valeur des tests. Ils peuvent ",[6862,37301,37302],{},"garantir qu'une nouvelle fonctionnalité ne casse pas une fonctionnalité existante"," sans l'ennui de tester manuellement tout. Cela était particulièrement évident parce que Feathers.js a de nombreuses parties complexes et des services qui peuvent facilement se casser lorsqu'une partie est modifiée.",[192,37305,37307],{"className":1380,"code":37306,"language":1383,"meta":13,"style":13},"export default {\n  before: {\n    all: [],\n    find: [],\n    get: [],\n    create: [checkData(checkDataOptions)],\n    update: [disallow()],\n    patch: [checkData(checkDataOptions)],\n    remove: [],\n  },\n\n  after: {\n    all: [],\n    find: [iff(isProvider('external'), pickResult())],\n    get: [iff(isProvider('external'), pickResult())],\n    create: [],\n    update: [],\n    patch: [],\n    remove: [],\n  },\n}\n",[156,37308,37309,37317,37326,37337,37348,37359,37376,37393,37408,37419,37423,37427,37436,37446,37483,37515,37525,37535,37545,37555,37559],{"__ignoreMap":13},[200,37310,37311,37313,37315],{"class":202,"line":203},[200,37312,1390],{"class":206},[200,37314,1393],{"class":206},[200,37316,280],{"class":214},[200,37318,37319,37322,37324],{"class":202,"line":14},[200,37320,37321],{"class":235},"  before",[200,37323,239],{"class":214},[200,37325,280],{"class":214},[200,37327,37328,37331,37333,37335],{"class":202,"line":255},[200,37329,37330],{"class":235},"    all",[200,37332,239],{"class":214},[200,37334,27681],{"class":210},[200,37336,252],{"class":214},[200,37338,37339,37342,37344,37346],{"class":202,"line":272},[200,37340,37341],{"class":235},"    find",[200,37343,239],{"class":214},[200,37345,27681],{"class":210},[200,37347,252],{"class":214},[200,37349,37350,37353,37355,37357],{"class":202,"line":283},[200,37351,37352],{"class":235},"    get",[200,37354,239],{"class":214},[200,37356,27681],{"class":210},[200,37358,252],{"class":214},[200,37360,37361,37364,37366,37368,37371,37374],{"class":202,"line":300},[200,37362,37363],{"class":235},"    create",[200,37365,239],{"class":214},[200,37367,350],{"class":210},[200,37369,37370],{"class":223},"checkData",[200,37372,37373],{"class":210},"(checkDataOptions)]",[200,37375,252],{"class":214},[200,37377,37378,37381,37383,37385,37388,37391],{"class":202,"line":310},[200,37379,37380],{"class":235},"    update",[200,37382,239],{"class":214},[200,37384,350],{"class":210},[200,37386,37387],{"class":223},"disallow",[200,37389,37390],{"class":210},"()]",[200,37392,252],{"class":214},[200,37394,37395,37398,37400,37402,37404,37406],{"class":202,"line":336},[200,37396,37397],{"class":235},"    patch",[200,37399,239],{"class":214},[200,37401,350],{"class":210},[200,37403,37370],{"class":223},[200,37405,37373],{"class":210},[200,37407,252],{"class":214},[200,37409,37410,37413,37415,37417],{"class":202,"line":342},[200,37411,37412],{"class":235},"    remove",[200,37414,239],{"class":214},[200,37416,27681],{"class":210},[200,37418,252],{"class":214},[200,37420,37421],{"class":202,"line":365},[200,37422,368],{"class":214},[200,37424,37425],{"class":202,"line":371},[200,37426,528],{"emptyLinePlaceholder":20},[200,37428,37429,37432,37434],{"class":202,"line":398},[200,37430,37431],{"class":235},"  after",[200,37433,239],{"class":214},[200,37435,280],{"class":214},[200,37437,37438,37440,37442,37444],{"class":202,"line":405},[200,37439,37330],{"class":235},[200,37441,239],{"class":214},[200,37443,27681],{"class":210},[200,37445,252],{"class":214},[200,37447,37448,37450,37452,37454,37457,37459,37462,37464,37466,37469,37471,37473,37475,37478,37481],{"class":202,"line":410},[200,37449,37341],{"class":235},[200,37451,239],{"class":214},[200,37453,350],{"class":210},[200,37455,37456],{"class":223},"iff",[200,37458,227],{"class":210},[200,37460,37461],{"class":223},"isProvider",[200,37463,227],{"class":210},[200,37465,249],{"class":214},[200,37467,37468],{"class":245},"external",[200,37470,249],{"class":214},[200,37472,2104],{"class":210},[200,37474,549],{"class":214},[200,37476,37477],{"class":223}," pickResult",[200,37479,37480],{"class":210},"())]",[200,37482,252],{"class":214},[200,37484,37485,37487,37489,37491,37493,37495,37497,37499,37501,37503,37505,37507,37509,37511,37513],{"class":202,"line":860},[200,37486,37352],{"class":235},[200,37488,239],{"class":214},[200,37490,350],{"class":210},[200,37492,37456],{"class":223},[200,37494,227],{"class":210},[200,37496,37461],{"class":223},[200,37498,227],{"class":210},[200,37500,249],{"class":214},[200,37502,37468],{"class":245},[200,37504,249],{"class":214},[200,37506,2104],{"class":210},[200,37508,549],{"class":214},[200,37510,37477],{"class":223},[200,37512,37480],{"class":210},[200,37514,252],{"class":214},[200,37516,37517,37519,37521,37523],{"class":202,"line":889},[200,37518,37363],{"class":235},[200,37520,239],{"class":214},[200,37522,27681],{"class":210},[200,37524,252],{"class":214},[200,37526,37527,37529,37531,37533],{"class":202,"line":899},[200,37528,37380],{"class":235},[200,37530,239],{"class":214},[200,37532,27681],{"class":210},[200,37534,252],{"class":214},[200,37536,37537,37539,37541,37543],{"class":202,"line":904},[200,37538,37397],{"class":235},[200,37540,239],{"class":214},[200,37542,27681],{"class":210},[200,37544,252],{"class":214},[200,37546,37547,37549,37551,37553],{"class":202,"line":930},[200,37548,37412],{"class":235},[200,37550,239],{"class":214},[200,37552,27681],{"class":210},[200,37554,252],{"class":214},[200,37556,37557],{"class":202,"line":3655},[200,37558,368],{"class":214},[200,37560,37561],{"class":202,"line":3660},[200,37562,1129],{"class":214},[35,37564,37565,37566],{},"La complexité de l'application a crû de manière exponentielle, fonctionnalité après fonctionnalité. Cela était plus lié à l'architecture de Feathers.js, mais combiné avec les défis liés à l'écriture de tests efficaces, c'était un cauchemar à maintenir. ",[1216,37567,37568],{},"Chaque changement faisait échouer tant de tests que j'étais incertain du bon fonctionnement de l'application.",[35,37570,37571,37572],{},"À partir de ce moment-là, j'ai décidé que la capacité à tester l'application était un critère pour choisir une nouvelle pile. ",[1216,37573,37574],{},"Et ce sera un long voyage, car en 2024, j'apprends encore beaucoup mais j'écris plus de tests que jamais.",[35,37576,37577,37578,37583],{},"Début 2021, Strapi gagnait en popularité, et je l'ai envisagé comme un remplacement potentiel pour Feathers.js, toujours pour construire la plateforme de tutorat. J'étais heureux de voir que Strapi avait une ",[39,37579,37582],{"href":37580,"rel":37581},"https:\u002F\u002Fdocs.strapi.io\u002Fdev-docs\u002Ftesting",[77],"page dédiée aux tests"," dans sa documentation.",[35,37585,37586],{},"Alors, je me suis lancé et j'ai créé un nouveau projet avec Strapi pour commencer et voir si les tests étaient efficaces.",[35,37588,37589,37590,37595,37596],{},"Malheureusement, j'ai immédiatement ",[39,37591,37594],{"href":37592,"rel":37593},"https:\u002F\u002Fgithub.com\u002Fstrapi\u002Fstrapi\u002Fissues\u002F9376",[77],"rencontré un problème qui n'a jamais été résolu",". Incapable d'exécuter les tests, j'ai abandonné et la plateforme de tutorat n'a jamais été migrée vers Strapi, ni déployée en production. ",[1216,37597,37598],{},"J'ai beaucoup appris de ce projet annexe. Souvent, le voyage est plus important que la destination.",[35,37600,37601],{},[1707,37602],{"alt":37603,"src":37604},"Capture d'écran de ce problème 9376","\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Fstrapi-testing-issue.png",[35,37606,37607],{},"Alors, récapitulons :",[1593,37609,37610,37613,37616,37619],{},[71,37611,37612],{},"Je ne connais que JavaScript.",[71,37614,37615],{},"Feathers.js a une architecture complexe et une documentation médiocre sur les tests.",[71,37617,37618],{},"Strapi a une bonne documentation sur les tests, mais je n'ai pas pu exécuter les tests.",[71,37620,37621],{},"Je dois trouver un nouveau projet annexe pour apprendre les tests.",[35,37623,37624,37625],{},"Quelques mois plus tard, j'ai entendu parler d'AdonisJS. Je ne me souviens pas comment, mais à ce moment-là, la version 5 était en bêta. C'était une bouffée d'air frais car la documentation semblait très bonne, et le framework était riche en fonctionnalités. Plein de fonctionnalités, oui, mais la partie tests n'était pas encore là. ",[1216,37626,37627],{},"L'équipe principale a publié Japa un an plus tard. C'est un cadre de test agréable pour Node.js.",[35,37629,37630,37631,8048,37634],{},"Quoi qu'il en soit, j'ai tellement appris sur le développement backend et l'architecture avec AdonisJS que les tests n'étaient clairement pas une priorité. C'était la première fois que je pouvais séparer les fonctionnalités et éviter les dépendances internes. ",[1216,37632,37633],{},"Bien plus propre et plus facile à entretenir que Feathers.js ou NestJS.",[1216,37635,37636],{},"Oui, j'ai aussi essayé NestJS, mais ce framework est une véritable blague pour moi.",[35,37638,37639,37640],{},"À la fin d'août 2021, je me suis lancé dans un nouveau projet annexe appelé Insamee, un écosystème étudiant. C'était le successeur de la plateforme de tutorat. L'architecture est importante à clarifier. À l'origine, c'était un serveur API avec AdonisJS et quatre applications front-end utilisant Nuxt. ",[1216,37641,37642],{},"Trop lourd et trop compliqué, comme je le réalise maintenant.",[35,37644,37645],{},[1707,37646],{"alt":37647,"src":37648},"Capture d'écran d'Insamee","\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Finsameeapp.png",[35,37650,37651],{},"À la fin de 2021, j'ai réécrit le projet depuis le début en utilisant uniquement Adonis et un seul serveur. Plus de tests.",[35,37653,37654,37655,37658],{},"J'ai réussi à me faire dispenser de cours pour me concentrer sur mon projet. ",[1216,37656,37657],{},"Une véritable opportunité."," Pour valider mon semestre, je devais écrire un rapport et faire une présentation en direct. Lors de la présentation, je n'ai pas parlé des tests (il n'y en avait pas). Cependant, le jury m'a interrogé à ce sujet. J'ai simplement dit que le runner n'était pas encore prêt, donc je n'avais écrit aucun test. Ils ont pris quelques minutes pour expliquer que les tests sont cruciaux, et j'ai clairement compris qu'un projet réel et professionnel doit avoir des tests, c'est non-négociable. Dans mon esprit, quelque chose a cliqué.",[35,37660,37661,37662,37665,37666,37669],{},"Par la suite, d'avril à août 2022, j'ai fait un stage de quatre mois dans une société de conseil à Paris. J'étais responsable de la création, depuis zéro, d'un panneau d'administration pour un projet de cluster de calcul hautes performances utilisant Angular 14. C'était une première pour moi. Je n'avais jamais utilisé Angular auparavant. J'ai mis des mois à saisir le concept de ",[156,37663,37664],{},"TestBed"," et le système d'injection de dépendances (DI). ",[1216,37667,37668],{},"J'étais tellement perdu, mais plus tard, j'ai découvert que l'injection de dépendances est importante."," Pendant cette période, j'ai appris des concepts mais je n'avais pas de pratique réelle.",[35,37671,37672],{},"En 2024, j'ai effectué mon stage de six mois dans une entreprise de cloud suisse. Je faisais partie d'une équipe développant des composants pour d'autres projets. C'est là que j'ai vraiment commencé à apprécier les tests. Tout d'abord, la couverture était de 90 %. Cela signifie que si la couverture tombe en dessous de 90 %, vous n'avez d'autre choix que d'écrire des tests pour soumettre un MR. Le projet avait huit mois, donc il y avait beaucoup de tests à référence. J'ai également écrit de nombreux tests de bout en bout en utilisant Playwright.",[35,37674,37675],{},"Dans le même temps, j'ai regardé un live stream de Romain Lanz discutant de \"l'écriture de code testable\" et expliquant le DI dans Adonis. Avec ces explications et les exemples concrets que j'ai rencontrés pendant mon stage, les choses ont commencé à s'assembler et à avoir du sens.",[35,37677,37678],{},"Cette année, à Devoxx 2024, j'ai essayé d'assister à autant de conférences que possible sur le TDD et le DDD. Entendre des experts discuter d'exemples du monde réel et expliquer comment ils architecturent leurs applications pour les rendre testables et à l'épreuve du temps a été véritablement éclairant.",[7918,37680,37681,37684,37701,37714,37720,37726],{"id":20859},[35,37682,37683],{},"Malgré tout cela, j'ai encore du mal à trouver des exemples de tests authentiques et pratiques. C'est tellement frustrant de connaître la théorie mais de ne pas pouvoir l'appliquer.",[35,37685,37686,37687,37692,37693],{},"Mais je crois que pour apprendre, il n'y a rien de plus efficace que d'agir. Alors, j'ai pris le framework que je connais le mieux, Nuxt, et j'ai commencé à expérimenter avec les ",[39,37688,37691],{"href":37689,"rel":37690},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ftest-utils",[77],"outils de test disponibles",". J'ai utilisé l'intégration de bout en bout avec Playwright, mais ce n'était pas une expérience agréable. Les tests prenaient trop de temps à s'exécuter. ",[1216,37694,37695,37696,215],{},"Étrangement, l'expérience pendant mon stage était l'opposée. J'ai découvert que l'intégration Nuxt reconstruit l'application avant chaque test. J'ai fait ",[39,37697,37700],{"href":37698,"rel":37699},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ftest-utils\u002Fpull\u002F881",[77],"une PR pour permettre au runner de réutiliser un serveur existant",[35,37702,37703,37704,37709,37710,37713],{},"Dans le même temps, j'ai reçu des retours sur ",[39,37705,37708],{"href":37706,"rel":37707},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fplausible-tracker",[77],"mon fork du tracker Plausible",". Certains bugs ont été signalés, et de nouvelles fonctionnalités ont été demandées. Le problème était que le projet n'avait pas de tests, et je me souvenais clairement à quel point cela avait été douloureux sur la plateforme de tutorat. J'ai également remarqué que je n'avais pas touché le projet depuis un certain temps, et que les tests manuels ne suffiraient pas pour garantir la qualité du projet à long terme. ",[1216,37711,37712],{},"Lorsque vous n'avez pas touché un projet depuis un certain temps, vous devez tout réapprendre. Avec des tests fiables, vous pouvez être sûr que le projet fonctionne comme prévu, même après des refactorings, des corrections de bugs et de nouvelles fonctionnalités."," J'ai décidé d'écrire des tests avant de toucher quoi que ce soit d'autre. Cela m'a pris trois jours, mais maintenant, je suis confiant que le projet fonctionne comme attendu. C'est satisfaisant, et il n'y a plus de crainte de toucher au projet.",[35,37715,37716],{},[1707,37717],{"alt":37718,"src":37719},"Tests réussis du Plausible Tracker","\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Fplausible-tracker-passing-tests.png",[35,37721,37722,37723],{},"Enfin, à peu près au même moment, j'ai découvert Laracasts. J'ai appris PHP et rencontré des tests dans Laravel. C'était révélateur. Les tests sont bien expliqués, écrire des tests, même avec une base de données, est facile, et cela s'exécute très rapidement. Écrire des tests avec Pest est vraiment agréable. ",[1216,37724,37725],{},"Si vous ne l'avez jamais essayé, vous devriez.",[7151,37727,37730,37734,37737,37740,37819,37825,37828],{"icon":37728,"text":37729,"to":33445},"i-ph-book-duotone","Essayez Laracasts",[51,37731,37733],{"id":37732},"comprendre-la-valeur-des-tests","Comprendre la Valeur des Tests",[35,37735,37736],{},"Il m'a fallu six ans pour saisir la valeur des tests et écrire des tests significatifs. Est-ce long ? Je ne sais pas. Cela démontre que l'apprentissage est un processus itératif, avec des échecs et des réessais. Avec de la persévérance, on finit par y arriver.",[35,37738,37739],{},"Tout au long de ce voyage, je me suis posé de nombreuses questions. Voici quelques réponses que j'aurais aimé trouver plus tôt.",[68,37741,37742,37745,37751,37757,37763,37769,37775,37781,37787,37802,37807,37813],{},[71,37743,37744],{},"Les tests ne doivent pas être difficiles.",[71,37746,37747,37748,215],{},"Cependant, tester est plus compliqué que ",[156,37749,37750],{},"expect(1 + 1).toBe(2)",[71,37752,37753,37754],{},"La difficulté des tests est souvent liée à l'architecture de l'application et à la capacité de séparer les fonctionnalités. ",[1216,37755,37756],{},"Plus l'application est modulaire, plus il est facile de tester.",[71,37758,37759,37760],{},"Avant de choisir un outil, considérez la capacité de tester votre application. ",[1216,37761,37762],{},"Si vous ne pouvez pas le tester, ça ne vaut pas le coup.",[71,37764,37765,37766],{},"Aucun test n'est acceptable. ",[1216,37767,37768],{},"C'est un compromis. Utilisez-le judicieusement.",[71,37770,37771,37772],{},"La règle 5 n'invalide pas les règles 4 ou 3. ",[1216,37773,37774],{},"Si vous finissez par écrire des tests, vous vous en remercierez.",[71,37776,37777,37778],{},"Viser 100 % de couverture n'a pas de sens en soi. ",[1216,37779,37780],{},"C'est un piège qui peut mener à de mauvaises pratiques.",[71,37782,37783,37784],{},"Les tests unitaires, d'intégration et de bout en bout ont de la valeur. ",[1216,37785,37786],{},"Choisissez celui qui correspond le mieux à vos besoins.",[71,37788,37789,37790,3092,37793,1655,37796,106,37799],{},"Structurez votre test avec la méthode ",[156,37791,37792],{},"arrange",[156,37794,37795],{},"act",[156,37797,37798],{},"assert",[1216,37800,37801],{},"C'est une bonne méthode pour organiser vos tests et commencer à les écrire.",[71,37803,37804,37805,215],{},"Écrivez d'abord la partie ",[156,37806,37798],{},[71,37808,37809,37810],{},"Pensez à un test comme une description d'une spécification de l'application. ",[1216,37811,37812],{},"Mon application devrait afficher\u002Ffaire cela lorsque cela se produit.",[71,37814,37815,37816],{},"Et le plus important, écrivez du code testable avant d'écrire des tests. ",[1216,37817,37818],{},"Cette approche rendra votre code plus lisible, maintenable et testable.",[35,37820,37821,37822],{},"Enfin, ne tentez pas de tout tester dès le premier jour de votre voyage. Commencez petit, apprenez, écrivez des tests unitaires simples, comprenez comment modulariser votre code et progressez à partir de là. ",[1216,37823,37824],{},"C'est un long voyage, mais cela en vaut la peine.",[35,37826,37827],{},"J'apprends encore beaucoup, mais je suis confiant que je suis sur la bonne voie. À bientôt dans le prochain article !",[35,37829,37830],{},[1216,37831,37832,37833,215],{},"Laracasts est la meilleure ressource que j'ai trouvée pour apprendre les tests. PHP est génial, et apprendre des concepts généraux est encore mieux. ",[39,37834,37836],{"href":33445,"rel":37835},[77],"Essayez-le maintenant",[1170,37838,37839],{},"html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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}",{"title":13,"searchDepth":14,"depth":14,"links":37841},[37842],{"id":36787,"depth":14,"text":36788},"eb021b0f-2f6c-467a-9d88-1cc99e17d6d9","2024-08-30","Tester, c'est une longue expédition pour découvrir comment l'architecture et les dépendances d'une app peuvent avoir un impact direct sur sa testabilité.",{},"\u002Ffr\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests","---\ncontentId: eb021b0f-2f6c-467a-9d88-1cc99e17d6d9\ntitle: Tester est Plus Vastes que la Simple Écriture de Tests\ndescription: Tester, c'est une longue expédition pour découvrir comment l'architecture et les dépendances d'une app peuvent avoir un impact direct sur sa testabilité.\ndate: 2024-08-30\n---\n\n> [!NOTE]\n> Je ne suis pas un expert en tests. Je partage simplement mes réflexions sur les tests en tant que développeur, dans un article que j'aurais apprécié lorsque j'ai commencé à coder.\n\nJ'ai commencé à coder il y a six ans. Depuis, j'ai entendu beaucoup parler des tests, mais j'ai vu peu d'exemples réels et pratiques.\n\nDans de nombreux articles que j'ai lus, les tests ressemblent à ça :\n\n```js\nit('should return 2 when 1 + 1', () => {\n  expect(1 + 1).toBe(2)\n})\n```\n\nC'est **loin des scénarios du monde réel** et cela n'aide pas à comprendre la véritable valeur des tests.\n\nJe dois admettre que, quand je ne comprends pas quelque chose, j'évite de l'utiliser. Néanmoins, depuis ma première rencontre avec le concept de tests, cela a persisté dans mon esprit, me poussant à rassembler des connaissances à ce sujet.\n\n## Un Long Voyage\n\nJ'ai d'abord entendu parler des tests lorsque j'ai appris Vue 2 en 2019. Dans la section des outils, sous la page \"Single File Component\", j'ai trouvé une section dédiée aux \"Tests\".\n\nJe pense que j'ai lu le début de la page mais je n'ai jamais été plus loin. Il y avait trop de nouveaux concepts à saisir, trop d'outils à apprendre, et trop de choix à faire. _À ce moment-là, j'ai compris que les tests front-end sont de loin la partie la plus difficile des tests._\n\nEn 2020, avec quelques amis, nous avons développé une plateforme de tutorat. Le serveur API a été construit avec [Feathers.js 4](https:\u002F\u002Fcrow.docs.feathersjs.com\u002F). À ce moment-là, j'avais encore du mal avec les tests. Je me souviens avoir cherché [la page des tests dans la documentation de Feathers.js](https:\u002F\u002Fcrow.docs.feathersjs.com\u002Fguides\u002Fbasics\u002Ftesting.html#writing-tests) et même l'avoir lue. Malgré le fait de ne pas bien la comprendre, j'ai écrit de nombreux tests. C'était une vraie lutte, car la distinction entre les tests unitaires et les tests d'intégration m'était floue, et les exécuter dans un CI était très difficile. _J'utilisais MongoDB et Azure CI. En y repensant, je pense que je voulais souffrir._ Je me souviens aussi avoir essayé d'atteindre 100 % de couverture. _J'étais tellement naïf._\n\n```ts\nit('should patch', async () => {\n  expect.assertions(5)\n\n  const patchedResult: Department = await app\n    .service(serviceName)\n    .patch(result._id, anotherDepartment)\n\n  expect(patchedResult).toBeDefined()\n  expect(patchedResult).toHaveProperty('_id')\n  expect(patchedResult).toHaveProperty(\n    'name',\n    anotherDepartment.name.toLowerCase()\n  )\n  expect(patchedResult).toHaveProperty('createdAt')\n  expect(patchedResult).toHaveProperty('updatedAt')\n})\n\nit('should delete', async () => {\n  expect.assertions(5)\n\n  const deleteResult: Department = await app\n    .service(serviceName)\n    .remove(result._id)\n\n  expect(deleteResult).toBeDefined()\n  expect(deleteResult).toHaveProperty('_id')\n  expect(deleteResult).toHaveProperty('name', result.name.toLowerCase())\n  expect(deleteResult).toHaveProperty('createdAt')\n  expect(deleteResult).toHaveProperty('updatedAt')\n})\n```\n\nMais plus j'ajoutais de fonctionnalités à la plateforme, plus je réalisais la valeur des tests. Ils peuvent **garantir qu'une nouvelle fonctionnalité ne casse pas une fonctionnalité existante** sans l'ennui de tester manuellement tout. Cela était particulièrement évident parce que Feathers.js a de nombreuses parties complexes et des services qui peuvent facilement se casser lorsqu'une partie est modifiée.\n\n```ts\nexport default {\n  before: {\n    all: [],\n    find: [],\n    get: [],\n    create: [checkData(checkDataOptions)],\n    update: [disallow()],\n    patch: [checkData(checkDataOptions)],\n    remove: [],\n  },\n\n  after: {\n    all: [],\n    find: [iff(isProvider('external'), pickResult())],\n    get: [iff(isProvider('external'), pickResult())],\n    create: [],\n    update: [],\n    patch: [],\n    remove: [],\n  },\n}\n```\n\nLa complexité de l'application a crû de manière exponentielle, fonctionnalité après fonctionnalité. Cela était plus lié à l'architecture de Feathers.js, mais combiné avec les défis liés à l'écriture de tests efficaces, c'était un cauchemar à maintenir. _Chaque changement faisait échouer tant de tests que j'étais incertain du bon fonctionnement de l'application._\n\nÀ partir de ce moment-là, j'ai décidé que la capacité à tester l'application était un critère pour choisir une nouvelle pile. _Et ce sera un long voyage, car en 2024, j'apprends encore beaucoup mais j'écris plus de tests que jamais._\n\nDébut 2021, Strapi gagnait en popularité, et je l'ai envisagé comme un remplacement potentiel pour Feathers.js, toujours pour construire la plateforme de tutorat. J'étais heureux de voir que Strapi avait une [page dédiée aux tests](https:\u002F\u002Fdocs.strapi.io\u002Fdev-docs\u002Ftesting) dans sa documentation.\n\nAlors, je me suis lancé et j'ai créé un nouveau projet avec Strapi pour commencer et voir si les tests étaient efficaces.\n\nMalheureusement, j'ai immédiatement [rencontré un problème qui n'a jamais été résolu](https:\u002F\u002Fgithub.com\u002Fstrapi\u002Fstrapi\u002Fissues\u002F9376). Incapable d'exécuter les tests, j'ai abandonné et la plateforme de tutorat n'a jamais été migrée vers Strapi, ni déployée en production. _J'ai beaucoup appris de ce projet annexe. Souvent, le voyage est plus important que la destination._\n\n![Capture d'écran de ce problème 9376](\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Fstrapi-testing-issue.png)\n\nAlors, récapitulons :\n\n- Je ne connais que JavaScript.\n- Feathers.js a une architecture complexe et une documentation médiocre sur les tests.\n- Strapi a une bonne documentation sur les tests, mais je n'ai pas pu exécuter les tests.\n- Je dois trouver un nouveau projet annexe pour apprendre les tests.\n\nQuelques mois plus tard, j'ai entendu parler d'AdonisJS. Je ne me souviens pas comment, mais à ce moment-là, la version 5 était en bêta. C'était une bouffée d'air frais car la documentation semblait très bonne, et le framework était riche en fonctionnalités. Plein de fonctionnalités, oui, mais la partie tests n'était pas encore là. _L'équipe principale a publié Japa un an plus tard. C'est un cadre de test agréable pour Node.js._\n\nQuoi qu'il en soit, j'ai tellement appris sur le développement backend et l'architecture avec AdonisJS que les tests n'étaient clairement pas une priorité. C'était la première fois que je pouvais séparer les fonctionnalités et éviter les dépendances internes. _Bien plus propre et plus facile à entretenir que Feathers.js ou NestJS._ _Oui, j'ai aussi essayé NestJS, mais ce framework est une véritable blague pour moi._\n\nÀ la fin d'août 2021, je me suis lancé dans un nouveau projet annexe appelé Insamee, un écosystème étudiant. C'était le successeur de la plateforme de tutorat. L'architecture est importante à clarifier. À l'origine, c'était un serveur API avec AdonisJS et quatre applications front-end utilisant Nuxt. _Trop lourd et trop compliqué, comme je le réalise maintenant._\n\n![Capture d'écran d'Insamee](\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Finsameeapp.png)\n\nÀ la fin de 2021, j'ai réécrit le projet depuis le début en utilisant uniquement Adonis et un seul serveur. Plus de tests.\n\nJ'ai réussi à me faire dispenser de cours pour me concentrer sur mon projet. _Une véritable opportunité._ Pour valider mon semestre, je devais écrire un rapport et faire une présentation en direct. Lors de la présentation, je n'ai pas parlé des tests (il n'y en avait pas). Cependant, le jury m'a interrogé à ce sujet. J'ai simplement dit que le runner n'était pas encore prêt, donc je n'avais écrit aucun test. Ils ont pris quelques minutes pour expliquer que les tests sont cruciaux, et j'ai clairement compris qu'un projet réel et professionnel doit avoir des tests, c'est non-négociable. Dans mon esprit, quelque chose a cliqué.\n\nPar la suite, d'avril à août 2022, j'ai fait un stage de quatre mois dans une société de conseil à Paris. J'étais responsable de la création, depuis zéro, d'un panneau d'administration pour un projet de cluster de calcul hautes performances utilisant Angular 14. C'était une première pour moi. Je n'avais jamais utilisé Angular auparavant. J'ai mis des mois à saisir le concept de `TestBed` et le système d'injection de dépendances (DI). _J'étais tellement perdu, mais plus tard, j'ai découvert que l'injection de dépendances est importante._ Pendant cette période, j'ai appris des concepts mais je n'avais pas de pratique réelle.\n\nEn 2024, j'ai effectué mon stage de six mois dans une entreprise de cloud suisse. Je faisais partie d'une équipe développant des composants pour d'autres projets. C'est là que j'ai vraiment commencé à apprécier les tests. Tout d'abord, la couverture était de 90 %. Cela signifie que si la couverture tombe en dessous de 90 %, vous n'avez d'autre choix que d'écrire des tests pour soumettre un MR. Le projet avait huit mois, donc il y avait beaucoup de tests à référence. J'ai également écrit de nombreux tests de bout en bout en utilisant Playwright.\n\nDans le même temps, j'ai regardé un live stream de Romain Lanz discutant de \"l'écriture de code testable\" et expliquant le DI dans Adonis. Avec ces explications et les exemples concrets que j'ai rencontrés pendant mon stage, les choses ont commencé à s'assembler et à avoir du sens.\n\nCette année, à Devoxx 2024, j'ai essayé d'assister à autant de conférences que possible sur le TDD et le DDD. Entendre des experts discuter d'exemples du monde réel et expliquer comment ils architecturent leurs applications pour les rendre testables et à l'épreuve du temps a été véritablement éclairant.\n\n\u003CCalloutReadMore id=\"f3e97a6a-ce47-42b8-8493-06eb37f8e964\" \u002F>\n\nMalgré tout cela, j'ai encore du mal à trouver des exemples de tests authentiques et pratiques. C'est tellement frustrant de connaître la théorie mais de ne pas pouvoir l'appliquer.\n\nMais je crois que pour apprendre, il n'y a rien de plus efficace que d'agir. Alors, j'ai pris le framework que je connais le mieux, Nuxt, et j'ai commencé à expérimenter avec les [outils de test disponibles](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ftest-utils). J'ai utilisé l'intégration de bout en bout avec Playwright, mais ce n'était pas une expérience agréable. Les tests prenaient trop de temps à s'exécuter. _Étrangement, l'expérience pendant mon stage était l'opposée. J'ai découvert que l'intégration Nuxt reconstruit l'application avant chaque test. J'ai fait [une PR pour permettre au runner de réutiliser un serveur existant](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Ftest-utils\u002Fpull\u002F881)._\n\nDans le même temps, j'ai reçu des retours sur [mon fork du tracker Plausible](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Fplausible-tracker). Certains bugs ont été signalés, et de nouvelles fonctionnalités ont été demandées. Le problème était que le projet n'avait pas de tests, et je me souvenais clairement à quel point cela avait été douloureux sur la plateforme de tutorat. J'ai également remarqué que je n'avais pas touché le projet depuis un certain temps, et que les tests manuels ne suffiraient pas pour garantir la qualité du projet à long terme. _Lorsque vous n'avez pas touché un projet depuis un certain temps, vous devez tout réapprendre. Avec des tests fiables, vous pouvez être sûr que le projet fonctionne comme prévu, même après des refactorings, des corrections de bugs et de nouvelles fonctionnalités._ J'ai décidé d'écrire des tests avant de toucher quoi que ce soit d'autre. Cela m'a pris trois jours, mais maintenant, je suis confiant que le projet fonctionne comme attendu. C'est satisfaisant, et il n'y a plus de crainte de toucher au projet.\n\n![Tests réussis du Plausible Tracker](\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests\u002Fplausible-tracker-passing-tests.png)\n\nEnfin, à peu près au même moment, j'ai découvert Laracasts. J'ai appris PHP et rencontré des tests dans Laravel. C'était révélateur. Les tests sont bien expliqués, écrire des tests, même avec une base de données, est facile, et cela s'exécute très rapidement. Écrire des tests avec Pest est vraiment agréable. _Si vous ne l'avez jamais essayé, vous devriez._\n\n\u003CCallout icon=\"i-ph-book-duotone\" text=\"Essayez Laracasts\" to=\"https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes\" \u002F>\n\n## Comprendre la Valeur des Tests\n\nIl m'a fallu six ans pour saisir la valeur des tests et écrire des tests significatifs. Est-ce long ? Je ne sais pas. Cela démontre que l'apprentissage est un processus itératif, avec des échecs et des réessais. Avec de la persévérance, on finit par y arriver.\n\nTout au long de ce voyage, je me suis posé de nombreuses questions. Voici quelques réponses que j'aurais aimé trouver plus tôt.\n\n1. Les tests ne doivent pas être difficiles.\n2. Cependant, tester est plus compliqué que `expect(1 + 1).toBe(2)`.\n3. La difficulté des tests est souvent liée à l'architecture de l'application et à la capacité de séparer les fonctionnalités. _Plus l'application est modulaire, plus il est facile de tester._\n4. Avant de choisir un outil, considérez la capacité de tester votre application. _Si vous ne pouvez pas le tester, ça ne vaut pas le coup._\n5. Aucun test n'est acceptable. _C'est un compromis. Utilisez-le judicieusement._\n6. La règle 5 n'invalide pas les règles 4 ou 3. _Si vous finissez par écrire des tests, vous vous en remercierez._\n7. Viser 100 % de couverture n'a pas de sens en soi. _C'est un piège qui peut mener à de mauvaises pratiques._\n8. Les tests unitaires, d'intégration et de bout en bout ont de la valeur. _Choisissez celui qui correspond le mieux à vos besoins._\n9. Structurez votre test avec la méthode `arrange`, `act` et `assert`. _C'est une bonne méthode pour organiser vos tests et commencer à les écrire._\n10. Écrivez d'abord la partie `assert`.\n11. Pensez à un test comme une description d'une spécification de l'application. _Mon application devrait afficher\u002Ffaire cela lorsque cela se produit._\n12. Et le plus important, écrivez du code testable avant d'écrire des tests. _Cette approche rendra votre code plus lisible, maintenable et testable._\n\nEnfin, ne tentez pas de tout tester dès le premier jour de votre voyage. Commencez petit, apprenez, écrivez des tests unitaires simples, comprenez comment modulariser votre code et progressez à partir de là. _C'est un long voyage, mais cela en vaut la peine._\n\nJ'apprends encore beaucoup, mais je suis confiant que je suis sur la bonne voie. À bientôt dans le prochain article !\n\n_Laracasts est la meilleure ressource que j'ai trouvée pour apprendre les tests. PHP est génial, et apprendre des concepts généraux est encore mieux. [Essayez-le maintenant](https:\u002F\u002Flaracasts.com\u002Freferral\u002FBarbapapazes)._\n",{"title":36700,"description":37845},{"loc":37847,"images":37851},[37852,37853,37854],{"loc":37604},{"loc":37648},{"loc":37719},"fr\u002Fposts\u002Ftesting-is-more-larger-than-just-writing-some-tests","Xzs_iKEludgctd5i2QXeGODw4CcRlVDcjQS-tG2ijjc",{"id":37858,"title":37859,"body":37860,"contentId":38094,"date":38095,"description":38096,"extension":18,"meta":38097,"navigation":20,"path":38098,"rawbody":38099,"robots":23,"schemaOrg":23,"seo":38100,"sitemap":38101,"stem":38105,"__hash__":38106},"posts\u002Ffr\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie.md","Construire des Apps SaaS sur NuxtHub avec Gavarnie",{"type":7,"value":37861,"toc":38090},[37862,37873,37881,37887,37890,37913,37926,37936,37941,37944,37947,37953,37960,37976,37992,37997,38001,38004,38011,38020,38028,38037,38050,38053],[938,37863,37864],{},[35,37865,37866,37868,37869,215],{},[200,37867,944],{},"\nCe starter a été conçu pendant mon temps libre. Si vous le trouvez utile, pensez à ",[39,37870,37872],{"href":37871},"\u002Ffr\u002Fsponsorship","soutenir mon travail",[35,37874,37875,37876],{},"TL;DR : ",[39,37877,37880],{"href":37878,"rel":37879},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie",[77],"Gavarnie",[35,37882,37883],{},[1707,37884],{"alt":37885,"src":37886},"Image OpenGraph du projet Gavarnie","\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fog.png",[35,37888,37889],{},"Avant d'explorer ce starter, permettez-moi de partager la motivation derrière sa création.",[35,37891,37892,37893,37898,37899,37903,37904,37909,37910],{},"Il y a plusieurs mois, ",[39,37894,37897],{"href":37895,"rel":37896},"https:\u002F\u002Fgithub.com\u002Fatinux",[77],"Sébastien Chopin",", le créateur de Nuxt, a introduit ",[39,37900,36579],{"href":37901,"rel":37902},"https:\u002F\u002Fhub.nuxt.com",[77],", une plateforme pour déployer et gérer des applications Nuxt, alimentée par ",[39,37905,37908],{"href":37906,"rel":37907},"https:\u002F\u002Fcloudflare.com",[77],"Cloudflare",". En résumé, vous pouvez créer des applications en tirant parti de l'infrastructure de Cloudflare comme D1 (base de données) et R2 (stockage), permettant un déploiement et une gestion aisés. ",[1216,37911,37912],{},"C'est semblable à Vercel ou Netlify mais pour les applications Nuxt, et je la trouve exceptionnel.",[35,37914,37915,37916,37919,37920,37925],{},"Cependant, cela fonctionne sur ce qu'on appelle ",[6862,37917,37918],{},"l'edge",", ce qui diffère des méthodes de déploiement d'applications traditionnelles comme VPS. L'edge utilise des workers, similaires aux web workers dans votre navigateur, mais côté serveur. Ce système permet un déploiement rapide et rentable, bien qu'il comporte plusieurs contraintes telles que les ",[39,37921,37924],{"href":37922,"rel":37923},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fruntime-apis\u002F",[77],"API disponibles"," et un temps de calcul limité. C'est un compromis.",[35,37927,37928,37929,37932,37933],{},"L'edge introduit une nouvelle approche conceptuelle où les limitations nécessitent des solutions innovantes. ",[1216,37930,37931],{},"Bien que des défis existent, soyez assuré que vous pouvez toujours atteindre vos résultats souhaités."," Ce nouvel environnement nécessite la construction d'un écosystème complet autour de lui. ",[1216,37934,37935],{},"Ne vous attendez pas à des solutions prêtes à l'emploi pour tout ; j'élargirai ce point dans un autre article.",[938,37937,37938],{},[35,37939,37940],{},"Petit à petit, l'edge peut être être maîtrisé.",[35,37942,37943],{},"C'est ici que Gavarnie entre en jeu.",[51,37945,37880],{"id":37946},"gavarnie",[35,37948,37949],{},[1707,37950],{"alt":37951,"src":37952},"Cirque de Gavarnie par Jean-Christophe BENOIST,\u003Cbr>CC BY 3.0 https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F3.0, via Wikimedia Commons","\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fgavarnie.jpg",[35,37954,37955,37956,37959],{},"Gavarnie est un starter destiné à être utilisé avec NuxtHub, spécifiquement sur l'infrastructure Cloudflare. ",[1216,37957,37958],{},"Si vous avez besoin de compatibilité avec Supabase, Turso ou Vercel, cela ne sera pas le bon choix."," Il propose une authentification sociale préconfigurée avec plusieurs fournisseurs, une vérification d'email, et met l'accent sur les meilleures pratiques architecturales et de sécurité.",[35,37961,37962,37963,1655,37966,37969,37970,1655,37973,215],{},"Le plus important, c'est qu'il est ",[6862,37964,37965],{},"gratuit",[6862,37967,37968],{},"open source",", vous permettant de commencer votre projet SaaS dès maintenant ou de l'utiliser ",[6862,37971,37972],{},"pour apprendre",[6862,37974,37975],{},"favoriser le développement de l'écosystème",[35,37977,37978,37979,3092,37982,37985,37986,106,37989],{},"En effet, l'un de mes objectifs avec Gavarnie n'est pas de créer juste un autre starter, mais de faire avancer l'initiative pour promouvoir Nuxt et NuxtHub. Je suis convaincu qu'en établissant des bases communes et solides, nous pouvons collectivement renforcer l'écosystème autour de NuxtHub et de l'edge. Ainsi, Gavarnie sert de ",[6862,37980,37981],{},"point de départ",[6862,37983,37984],{},"et non de fin",", et c'est à ",[6862,37987,37988],{},"nous de nourrir sa croissance",[1216,37990,37991],{},"Je vous en prie, rejoignez-moi pour l'améliorer.",[938,37993,37994],{},[35,37995,37996],{},"Gavarnie est un début, pas une conclusion.",[51,37998,38000],{"id":37999},"caractéristiques","Caractéristiques",[35,38002,38003],{},"Gavarnie est développé sur Nuxt et NuxtHub. Ces deux éléments constituent l'épine dorsale de ce projet, avec tous les éléments construits autour d'eux.",[35,38005,38006,38007,38010],{},"Sous sa forme actuelle, ",[39,38008,37880],{"href":37878,"rel":38009},[77]," permet aux utilisateurs de créer un compte en utilisant leurs profils sociaux (GitHub et Twitch), de lier des comptes sociaux, de changer leur adresse e-mail via un mécanisme de vérification, de mettre à jour leur profil et leur photo de profil, et de supprimer leur compte.",[35,38012,38013,38014,38019],{},"Le service d'email est facilité par ",[39,38015,38018],{"href":38016,"rel":38017},"https:\u002F\u002Fresend.io",[77],"Resend",", qui simplifie les transactions par email.",[35,38021,38022,38023,38027],{},"L'interface utilisateur est alimentée par ",[39,38024,38026],{"href":14207,"rel":38025},[77],"Nuxt UI Pro",", un ensemble d'outils UI fourni par l'équipe Nuxt pour les applications Nuxt.",[35,38029,38030,38031,38036],{},"L'authentification sociale est proposée via le module ",[39,38032,38035],{"href":38033,"rel":38034},"https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils",[77],"nuxt-auth-utils",", qui simplifie l'authentification sociale dans Nuxt. Elle prend en charge plusieurs fournisseurs tels que Google, Facebook, X (Twitter), GitHub, entre autres.",[35,38038,38039,38040,38045,38046,38049],{},"De plus, j'ai incorporé le module ",[39,38041,38044],{"href":38042,"rel":38043},"https:\u002F\u002Fnuxt.com\u002Fmodules\u002Fsecurity",[77],"Nuxt Security"," pour améliorer la sécurité de l'application. Ce starter comprend une CSP préconfigurée, un CSRF ",[1216,38047,38048],{},"que je prévois d'améliorer",", et un limiteur de débit pour prévenir les abus des points de terminaison sensibles.",[35,38051,38052],{},"Gavarnie est une continuité pour apporter des capacités full-stack aux applications Nuxt.",[7918,38054,38055],{"id":30599},[7918,38056,38057,38064,38068,38075,38081,38087],{"id":30604},[35,38058,38059,38060],{},"Explorez le dépôt ",[1599,38061,38063],{"repo":38062},"barbapapazes\u002Fgavarnie"," pour découvrir plus sur ses fonctionnalités et comment les utiliser.",[51,38065,38067],{"id":38066},"étape-par-étape","Étape par Étape",[35,38069,38070,38071,38074],{},"Ceci n'est que le ",[6862,38072,38073],{},"début"," de l'aventure.",[35,38076,38077,38078],{},"Gavarnie est un projet dynamique qui évoluera. Nous pouvons envisager d'incorporer une meilleure gestion des sessions, un tableau de bord administrateur, un système de jetons API, une solution de facturation, et plus encore. ",[1216,38079,38080],{},"Les opportunités sont vastes, et c'est à nous d'impulser son développement.",[35,38082,38083,38084],{},"Ayant pris connaissance de ",[1599,38085,38086],{"repo":38062},", n'hésitez pas à l'explorer, à l'étendre, à expérimenter et à créer des applications pratiques avec.",[35,38088,38089],{},"N'hésitez pas à donner une étoile, à engager une discussion sur le dépôt GitHub pour toute question ou suggestion visant à améliorer le starter ! 💛",{"title":13,"searchDepth":14,"depth":14,"links":38091},[38092,38093],{"id":37946,"depth":14,"text":37880},{"id":37999,"depth":14,"text":38000},"a1efd078-cbb8-49b6-b557-258edfda5f59","2024-08-20","Construisez et déployez des applications SaaS sur NuxtHub avec Gavarnie. Il offre un login social, une vérification par email, il est gratuit et open-source !",{},"\u002Ffr\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie","---\ncontentId: a1efd078-cbb8-49b6-b557-258edfda5f59\ntitle: Construire des Apps SaaS sur NuxtHub avec Gavarnie\ndescription: Construisez et déployez des applications SaaS sur NuxtHub avec Gavarnie. Il offre un login social, une vérification par email, il est gratuit et open-source !\ndate: 2024-08-20\n---\n\n> [!NOTE]\n> Ce starter a été conçu pendant mon temps libre. Si vous le trouvez utile, pensez à [soutenir mon travail](\u002Ffr\u002Fsponsorship).\n\nTL;DR : [Gavarnie](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie)\n\n![Image OpenGraph du projet Gavarnie](\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fog.png)\n\nAvant d'explorer ce starter, permettez-moi de partager la motivation derrière sa création.\n\nIl y a plusieurs mois, [Sébastien Chopin](https:\u002F\u002Fgithub.com\u002Fatinux), le créateur de Nuxt, a introduit [NuxtHub](https:\u002F\u002Fhub.nuxt.com), une plateforme pour déployer et gérer des applications Nuxt, alimentée par [Cloudflare](https:\u002F\u002Fcloudflare.com). En résumé, vous pouvez créer des applications en tirant parti de l'infrastructure de Cloudflare comme D1 (base de données) et R2 (stockage), permettant un déploiement et une gestion aisés. _C'est semblable à Vercel ou Netlify mais pour les applications Nuxt, et je la trouve exceptionnel._\n\nCependant, cela fonctionne sur ce qu'on appelle **l'edge**, ce qui diffère des méthodes de déploiement d'applications traditionnelles comme VPS. L'edge utilise des workers, similaires aux web workers dans votre navigateur, mais côté serveur. Ce système permet un déploiement rapide et rentable, bien qu'il comporte plusieurs contraintes telles que les [API disponibles](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fruntime-apis\u002F) et un temps de calcul limité. C'est un compromis.\n\nL'edge introduit une nouvelle approche conceptuelle où les limitations nécessitent des solutions innovantes. _Bien que des défis existent, soyez assuré que vous pouvez toujours atteindre vos résultats souhaités._ Ce nouvel environnement nécessite la construction d'un écosystème complet autour de lui. _Ne vous attendez pas à des solutions prêtes à l'emploi pour tout ; j'élargirai ce point dans un autre article._\n\n> Petit à petit, l'edge peut être être maîtrisé.\n\nC'est ici que Gavarnie entre en jeu.\n\n## Gavarnie\n\n![Cirque de Gavarnie par Jean-Christophe BENOIST,\u003Cbr>CC BY 3.0 \u003Chttps:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F3.0>, via Wikimedia Commons](\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fgavarnie.jpg)\n\nGavarnie est un starter destiné à être utilisé avec NuxtHub, spécifiquement sur l'infrastructure Cloudflare. _Si vous avez besoin de compatibilité avec Supabase, Turso ou Vercel, cela ne sera pas le bon choix._ Il propose une authentification sociale préconfigurée avec plusieurs fournisseurs, une vérification d'email, et met l'accent sur les meilleures pratiques architecturales et de sécurité.\n\nLe plus important, c'est qu'il est **gratuit** et **open source**, vous permettant de commencer votre projet SaaS dès maintenant ou de l'utiliser **pour apprendre** et **favoriser le développement de l'écosystème**.\n\nEn effet, l'un de mes objectifs avec Gavarnie n'est pas de créer juste un autre starter, mais de faire avancer l'initiative pour promouvoir Nuxt et NuxtHub. Je suis convaincu qu'en établissant des bases communes et solides, nous pouvons collectivement renforcer l'écosystème autour de NuxtHub et de l'edge. Ainsi, Gavarnie sert de **point de départ**, **et non de fin**, et c'est à **nous de nourrir sa croissance**. _Je vous en prie, rejoignez-moi pour l'améliorer._\n\n> Gavarnie est un début, pas une conclusion.\n\n## Caractéristiques\n\nGavarnie est développé sur Nuxt et NuxtHub. Ces deux éléments constituent l'épine dorsale de ce projet, avec tous les éléments construits autour d'eux.\n\nSous sa forme actuelle, [Gavarnie](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie) permet aux utilisateurs de créer un compte en utilisant leurs profils sociaux (GitHub et Twitch), de lier des comptes sociaux, de changer leur adresse e-mail via un mécanisme de vérification, de mettre à jour leur profil et leur photo de profil, et de supprimer leur compte.\n\nLe service d'email est facilité par [Resend](https:\u002F\u002Fresend.io), qui simplifie les transactions par email.\n\nL'interface utilisateur est alimentée par [Nuxt UI Pro](https:\u002F\u002Fui.nuxt.com), un ensemble d'outils UI fourni par l'équipe Nuxt pour les applications Nuxt.\n\nL'authentification sociale est proposée via le module [nuxt-auth-utils](https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils), qui simplifie l'authentification sociale dans Nuxt. Elle prend en charge plusieurs fournisseurs tels que Google, Facebook, X (Twitter), GitHub, entre autres.\n\nDe plus, j'ai incorporé le module [Nuxt Security](https:\u002F\u002Fnuxt.com\u002Fmodules\u002Fsecurity) pour améliorer la sécurité de l'application. Ce starter comprend une CSP préconfigurée, un CSRF _que je prévois d'améliorer_, et un limiteur de débit pour prévenir les abus des points de terminaison sensibles.\n\nGavarnie est une continuité pour apporter des capacités full-stack aux applications Nuxt.\n\n\u003CCalloutReadMore id=\"8c077ba8-63ca-4784-b693-0e1fa8623bf9\" \u002F>\n\n\u003CCalloutReadMore id=\"05da35d9-c21f-4b45-ba86-e88d61339502\" \u002F>\n\nExplorez le dépôt \u003CGitHubLink repo=\"barbapapazes\u002Fgavarnie\" \u002F> pour découvrir plus sur ses fonctionnalités et comment les utiliser.\n\n## Étape par Étape\n\nCeci n'est que le **début** de l'aventure.\n\nGavarnie est un projet dynamique qui évoluera. Nous pouvons envisager d'incorporer une meilleure gestion des sessions, un tableau de bord administrateur, un système de jetons API, une solution de facturation, et plus encore. _Les opportunités sont vastes, et c'est à nous d'impulser son développement._\n\nAyant pris connaissance de \u003CGitHubLink repo=\"barbapapazes\u002Fgavarnie\" \u002F>, n'hésitez pas à l'explorer, à l'étendre, à expérimenter et à créer des applications pratiques avec.\n\nN'hésitez pas à donner une étoile, à engager une discussion sur le dépôt GitHub pour toute question ou suggestion visant à améliorer le starter ! 💛\n",{"title":37859,"description":38096},{"loc":38098,"images":38102},[38103,38104],{"loc":37886},{"loc":37952},"fr\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie","yA8CxYtcMTZ_KfBwTeVV9rSc5DUfLRcLFZFbSXNnxo8",{"id":38108,"title":38109,"body":38110,"contentId":30735,"date":38262,"description":38263,"extension":18,"meta":38264,"navigation":20,"path":38265,"rawbody":38266,"robots":23,"schemaOrg":23,"seo":38267,"sitemap":38268,"stem":38274,"__hash__":38275},"posts\u002Ffr\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter.md","La Fin de Mes Études et le Début d'un Nouveau Chapitre",{"type":7,"value":38111,"toc":38259},[38112,38128,38134,38142,38145,38148,38151,38154,38164,38170,38183,38189,38192,38203,38206,38212,38215,38219,38222,38228,38248,38253],[938,38113,38114],{},[35,38115,38116,38118,38119,3092,38122,13741,38125,215],{},[200,38117,33274],{},"\nJe suis ouvert à de nouvelles opportunités, près de Paris ou en remote ! Si vous pensez que je peux contribuer à votre entreprise ou votre projet, n'hésitez pas à me contacter sur ",[39,38120,36276],{"href":36274,"rel":38121},[77],[39,38123,7255],{"href":7043,"rel":38124},[77],[39,38126,38127],{"href":20761},"Email",[35,38129,38130,38131],{},"Il y a six ans, j'ai débuté mes études à l'INSA Centre Val de Loire pour me spécialiser en cybersécurité. Lorsque j'ai choisi cette école sur Parcoursup, j'étais incertain. À l'époque, ma seule aspiration était de travailler dans l'informatique. ",[1216,38132,38133],{},"Oui, même le web ne m'avait pas traversé l'esprit.",[35,38135,38136,38137,38141],{},"J'ai découvert ma passion pour le développement web lors d'un concours appelé la ",[39,38138,38140],{"href":8217,"rel":38139},[77],"Nuit de l'info"," auquel j'ai participé en décembre 2018. La liberté et la créativité que le développement web offre ont été une révélation pour moi. J'ai beaucoup appris, et l'un de mes camarades a ajouté des objets 3D à notre projet. C'était incroyable !",[35,38143,38144],{},"Après cette expérience qui a profondément transformé mon avenir, j'ai participé chaque année à ce concours. Pendant mon temps libre, je continuais à me former, de HTML à JavaScript en passant par TypeScript. Chaque moment passé sur le web était un délice. J'étais enthousiaste à l'idée d'acquérir de plus en plus de connaissances.",[35,38146,38147],{},"J'ai rencontré de nombreuses personnes brillantes à l'école. Nous pouvions compter les uns sur les autres pendant les deux années de classes préparatoires. Ensemble, nous avons grandi et appris beaucoup. Je leur en suis reconnaissant et heureux de les avoir rencontrés.",[35,38149,38150],{},"Avec ces amis, nous avons créé de nombreux projets allant de l'IoT à une plateforme de tutorat et le site web du campus de l'école. Nous avons acquis des connaissances précieuses et nous nous sommes beaucoup amusés. Certains de ces projets ont été couronnés de succès, tandis que d'autres ne l'ont pas été. Mais au final, nous avons gagné de l'expérience.",[35,38152,38153],{},"J'ai investi une quantité significative de temps dans les associations de l'école dès le premier jour. J'ai initié une association pour le développement durable et j'ai été vice-président de l'association nationale des élèves-ingénieurs en France. J'ai beaucoup appris sur la gestion, la communication et le travail d'équipe. Ces expériences ont eu un impact durable sur moi et m'ont vraiment transformé pour le mieux.",[35,38155,38156,38157,38160,38161],{},"Après ce début intense, j'ai fait un stage de 4 mois dans une entreprise de conseil à Paris. J'ai appris Angular. Au sein de la même entreprise, et pour consolider mon intérêt pour le développement web et mieux me connaître, j'ai pris une année de césure. J'ai continué à travailler dans cette entreprise ainsi qu'à me porter volontaire pour un projet appelé ",[39,38158,18057],{"href":18055,"rel":38159},[77],". La gestion de projet, la gestion des réseaux sociaux, l'organisation d'événements et le contact avec des associations, des entreprises et des écoles faisaient partie de mes tâches quotidiennes. ",[1216,38162,38163],{},"Nous avons privatisé l'Opéra Bastille pendant une journée ! Quelle journée !",[35,38165,38166],{},[1707,38167],{"alt":38168,"src":38169},"À l'Opéra Bastille pendant Le Classement","\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Flcda.jpg",[35,38171,38172,38173,1655,38176,38179,38180],{},"Cette année de césure m'a donné beaucoup de temps pour faire de l'open-source et contribuer à des projets comme ",[39,38174,2934],{"href":13829,"rel":38175},[77],[39,38177,30540],{"href":34910,"rel":38178},[77],". J'ai participé à de nombreux meetups et donné ma première conférence à Devoxx France. ",[1216,38181,38182],{},"Une expérience incroyable !",[35,38184,38185],{},[1707,38186],{"alt":38187,"src":38188},"Ma présentation à Devoxx France","\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fdevoxxfr.jpeg",[35,38190,38191],{},"L'année 2023 a été intense avec un tournant dans ma compréhension du développement web.",[35,38193,38194,38195,38198,38199,38202],{},"Je suis retourné à l'école pour terminer mes études et j'ai fait un stage de 6 mois chez ",[39,38196,21156],{"href":21154,"rel":38197},[77],". J'ai postulé, sans grand espoir, mais j'ai été accepté. Ma mission était de créer des composants pour le système de design en utilisant Angular. J'ai écrit de nombreux tests, tant unitaires qu'end-to-end, et de la documentation. ",[1216,38200,38201],{},"J'aime quand les choses sont claires et bien documentées !"," J'ai également mis en place un CI écosystème interne pour tester les projets à chaque demande de fusion. J'ai rencontré beaucoup de personnes incroyables dans une entreprise qui respecte ses valeurs. Une véritable aventure humaine et technique.",[35,38204,38205],{},"J'ai pris du temps pour marcher en Suisse, construire ce nouveau blog, faire du live coding, écrire des articles, apprendre PHP et Laravel, coder, commencer un SaaS, contribuer à l'open-source, donner deux conférences et faire du sport.",[35,38207,38208],{},[1707,38209],{"alt":38210,"src":38211},"Ma présentation à StrasbourgJS","\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fstrasbourgjs.jpg",[35,38213,38214],{},"Nous y voilà, et j'écris ces lignes avant mon dernier jour chez Infomaniak.",[51,38216,38218],{"id":38217},"un-nouveau-chapitre","Un Nouveau Chapitre",[35,38220,38221],{},"Mes études sont terminées. Je ferme un chapitre significatif de ma vie. Mais cela signifie aussi le début d'un nouveau chapitre.",[35,38223,38224,38225],{},"J'ai défini quelques objectifs pour les mois à venir et l'année prochaine afin de me guider sur ce nouveau chemin. J'ai fait l'erreur dans le passé de ne pas le faire et je me suis dispersé sur trop de projets et d'activités. Je veux me concentrer sur quelques choses et bien les faire. ",[1216,38226,38227],{},"Ne me demandez pas mes objectifs.",[35,38229,38230,38231,38234,38235,38238,38239,3092,38243,13741,38246,215],{},"Pour commencer ce nouveau chapitre, je cherche un emploi à Paris ou en remote. ",[6862,38232,38233],{},"Je suis ouvert à de nouvelles opportunités",", du backend au frontend. Je m'adapte rapidement, suis avide d'apprendre de nouvelles choses et j'aime rencontrer de nouvelles personnes lors de conférences et de meetups. ",[1216,38236,38237],{},"Je ne veux pas rester assis derrière un bureau tout le temps."," Si vous pensez que je peux contribuer à votre entreprise ou à votre projet, n'hésitez pas à me contacter sur ",[39,38240,38242],{"href":36274,"rel":38241},[77],"X",[39,38244,7255],{"href":7043,"rel":38245},[77],[39,38247,38127],{"href":20761},[35,38249,38250],{},[1216,38251,38252],{},"J'espère que les six prochaines années seront aussi riches que les précédentes.",[35,38254,38255],{},[1707,38256],{"alt":38257,"src":38258},"Montagnes au bord du lac de Salanfe","\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fmountain-salanfe.jpg",{"title":13,"searchDepth":14,"depth":14,"links":38260},[38261],{"id":38217,"depth":14,"text":38218},"2024-08-09","La vie est un voyage. Elle est divisé en plusieurs parties. Aujourd'hui, je termine mes études, je ferme un chapitre important mais j'en ouvre un nouveau.",{},"\u002Ffr\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter","---\ncontentId: b083ef4c-dd4d-4ace-ac09-76b5231c35bc\ntitle: \"La Fin de Mes Études et le Début d'un Nouveau Chapitre\"\ndescription: La vie est un voyage. Elle est divisé en plusieurs parties. Aujourd'hui, je termine mes études, je ferme un chapitre important mais j'en ouvre un nouveau.\ndate: 2024-08-09\n---\n\n> [!IMPORTANT]\n> Je suis ouvert à de nouvelles opportunités, près de Paris ou en remote ! Si vous pensez que je peux contribuer à votre entreprise ou votre projet, n'hésitez pas à me contacter sur [X (Twitter)](https:\u002F\u002Fx.com\u002Fsoubiran_), [LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F) ou [Email](mailto:esteban@soubiran.dev).\n\nIl y a six ans, j'ai débuté mes études à l'INSA Centre Val de Loire pour me spécialiser en cybersécurité. Lorsque j'ai choisi cette école sur Parcoursup, j'étais incertain. À l'époque, ma seule aspiration était de travailler dans l'informatique. _Oui, même le web ne m'avait pas traversé l'esprit._\n\nJ'ai découvert ma passion pour le développement web lors d'un concours appelé la [Nuit de l'info](https:\u002F\u002Fwww.nuitdelinfo.com\u002F) auquel j'ai participé en décembre 2018. La liberté et la créativité que le développement web offre ont été une révélation pour moi. J'ai beaucoup appris, et l'un de mes camarades a ajouté des objets 3D à notre projet. C'était incroyable !\n\nAprès cette expérience qui a profondément transformé mon avenir, j'ai participé chaque année à ce concours. Pendant mon temps libre, je continuais à me former, de HTML à JavaScript en passant par TypeScript. Chaque moment passé sur le web était un délice. J'étais enthousiaste à l'idée d'acquérir de plus en plus de connaissances.\n\nJ'ai rencontré de nombreuses personnes brillantes à l'école. Nous pouvions compter les uns sur les autres pendant les deux années de classes préparatoires. Ensemble, nous avons grandi et appris beaucoup. Je leur en suis reconnaissant et heureux de les avoir rencontrés.\n\nAvec ces amis, nous avons créé de nombreux projets allant de l'IoT à une plateforme de tutorat et le site web du campus de l'école. Nous avons acquis des connaissances précieuses et nous nous sommes beaucoup amusés. Certains de ces projets ont été couronnés de succès, tandis que d'autres ne l'ont pas été. Mais au final, nous avons gagné de l'expérience.\n\nJ'ai investi une quantité significative de temps dans les associations de l'école dès le premier jour. J'ai initié une association pour le développement durable et j'ai été vice-président de l'association nationale des élèves-ingénieurs en France. J'ai beaucoup appris sur la gestion, la communication et le travail d'équipe. Ces expériences ont eu un impact durable sur moi et m'ont vraiment transformé pour le mieux.\n\nAprès ce début intense, j'ai fait un stage de 4 mois dans une entreprise de conseil à Paris. J'ai appris Angular. Au sein de la même entreprise, et pour consolider mon intérêt pour le développement web et mieux me connaître, j'ai pris une année de césure. J'ai continué à travailler dans cette entreprise ainsi qu'à me porter volontaire pour un projet appelé [Le Classement des Associations](https:\u002F\u002Fle-classement.fr\u002F). La gestion de projet, la gestion des réseaux sociaux, l'organisation d'événements et le contact avec des associations, des entreprises et des écoles faisaient partie de mes tâches quotidiennes. _Nous avons privatisé l'Opéra Bastille pendant une journée ! Quelle journée !_\n\n![À l'Opéra Bastille pendant Le Classement](\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Flcda.jpg)\n\nCette année de césure m'a donné beaucoup de temps pour faire de l'open-source et contribuer à des projets comme [Nuxt](https:\u002F\u002Fnuxt.com) et [UnJS](https:\u002F\u002Funjs.io). J'ai participé à de nombreux meetups et donné ma première conférence à Devoxx France. _Une expérience incroyable !_\n\n![Ma présentation à Devoxx France](\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fdevoxxfr.jpeg)\n\nL'année 2023 a été intense avec un tournant dans ma compréhension du développement web.\n\nJe suis retourné à l'école pour terminer mes études et j'ai fait un stage de 6 mois chez [Infomaniak](https:\u002F\u002Fwww.infomaniak.com\u002F). J'ai postulé, sans grand espoir, mais j'ai été accepté. Ma mission était de créer des composants pour le système de design en utilisant Angular. J'ai écrit de nombreux tests, tant unitaires qu'end-to-end, et de la documentation. _J'aime quand les choses sont claires et bien documentées !_ J'ai également mis en place un CI écosystème interne pour tester les projets à chaque demande de fusion. J'ai rencontré beaucoup de personnes incroyables dans une entreprise qui respecte ses valeurs. Une véritable aventure humaine et technique.\n\nJ'ai pris du temps pour marcher en Suisse, construire ce nouveau blog, faire du live coding, écrire des articles, apprendre PHP et Laravel, coder, commencer un SaaS, contribuer à l'open-source, donner deux conférences et faire du sport.\n\n![Ma présentation à StrasbourgJS](\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fstrasbourgjs.jpg)\n\nNous y voilà, et j'écris ces lignes avant mon dernier jour chez Infomaniak.\n\n## Un Nouveau Chapitre\n\nMes études sont terminées. Je ferme un chapitre significatif de ma vie. Mais cela signifie aussi le début d'un nouveau chapitre.\n\nJ'ai défini quelques objectifs pour les mois à venir et l'année prochaine afin de me guider sur ce nouveau chemin. J'ai fait l'erreur dans le passé de ne pas le faire et je me suis dispersé sur trop de projets et d'activités. Je veux me concentrer sur quelques choses et bien les faire. _Ne me demandez pas mes objectifs._\n\nPour commencer ce nouveau chapitre, je cherche un emploi à Paris ou en remote. **Je suis ouvert à de nouvelles opportunités**, du backend au frontend. Je m'adapte rapidement, suis avide d'apprendre de nouvelles choses et j'aime rencontrer de nouvelles personnes lors de conférences et de meetups. _Je ne veux pas rester assis derrière un bureau tout le temps._ Si vous pensez que je peux contribuer à votre entreprise ou à votre projet, n'hésitez pas à me contacter sur [X](https:\u002F\u002Fx.com\u002Fsoubiran_), [LinkedIn](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Festeban25\u002F) ou [Email](mailto:esteban@soubiran.dev).\n\n_J'espère que les six prochaines années seront aussi riches que les précédentes._\n\n![Montagnes au bord du lac de Salanfe](\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter\u002Fmountain-salanfe.jpg)\n",{"title":38109,"description":38263},{"loc":38265,"images":38269},[38270,38271,38272,38273],{"loc":38169},{"loc":38188},{"loc":38211},{"loc":38258},"fr\u002Fposts\u002Fthe-end-of-my-studies-and-the-beginning-of-a-new-chapter","I7XZv6dc7VJQTyXcmo_OMjjgzDBz_XjPyFUoOqEdD2U",{"id":38277,"title":38278,"body":38279,"contentId":38632,"date":38633,"description":38634,"extension":18,"meta":38635,"navigation":20,"path":38636,"rawbody":38637,"robots":23,"schemaOrg":23,"seo":38638,"sitemap":38639,"stem":38646,"__hash__":38647},"posts\u002Ffr\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt.md","Orion, une Collection Communautaire de Modèles pour Nuxt",{"type":7,"value":38280,"toc":38623},[38281,38291,38293,38300,38303,38308,38320,38323,38327,38339,38342,38348,38355,38359,38366,38377,38389,38392,38401,38404,38415,38424,38428,38433,38436,38441,38447,38450,38456,38460,38466,38469,38472,38476,38482,38485,38489,38492,38560,38564,38576,38587,38594,38602,38620],[938,38282,38283],{},[35,38284,38285,38286,215],{},"TL;DR : Je suis ravi d'annoncer Orion, une collection de modèles dirigée par la communauté pour vos prochains projets Nuxt, des pages d'accueil aux applications web complètes. Orion est un endroit où vous pouvez découvrir, rechercher, trouver et apprendre à partir d'une collection de modèles créés par la communauté Nuxt pour l'écosystème Nuxt. Disponible sur ",[39,38287,38290],{"href":38288,"rel":38289},"https:\u002F\u002Forion.barbapapazes.dev",[77],"orion.barbapapazes.dev",[51,38292,17294],{"id":17293},[35,38294,38295,38296,38299],{},"À la fin de février 2024, lors de Vue Amsterdam, Sébastien Chopin a dévoilé ",[39,38297,36579],{"href":37901,"rel":38298},[77],", une plateforme et un module pour créer et déployer des applications Nuxt full-stack sur le cloud.",[35,38301,38302],{},"Cette annonce était énorme car depuis la version 3, le slogan de Nuxt contient le mot 'full-stack', mais jusqu'à présent, c'était plus un rêve qu'une réalité.",[938,38304,38305],{},[35,38306,38307],{},"Créez des applications et des sites web full-stack performants et de qualité production en toute confiance.",[35,38309,38310,38311],{},"Avec cette nouvelle plateforme, le full-stack devient une réalité. ",[1216,38312,38313,38314,38319],{},"Et je suis vraiment enthousiaste à ce sujet ! En décembre 2023, j'ai écrit sur ",[39,38315,38318],{"href":38316,"rel":38317},"https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Fnuxt-3-et-le-full-stack-sur-ledge",[77],"Nuxt 3 et le full-stack sur le cloud"," (Français), et il restait encore beaucoup de travail à faire. 6 mois plus tard, le full-stack est là ! Progrès impressionnant ! Bon travail à l'équipe Nuxt !",[35,38321,38322],{},"Dans cet esprit, je voulais vraiment essayer NuxtHub. Cet outil peut façonner l'avenir de Nuxt, et je pense qu'il est important de le comprendre pour ne pas être dépassé.",[51,38324,38326],{"id":38325},"trouver-une-idée","Trouver une Idée",[35,38328,38329,38330,38338],{},"Pour donner un vrai coup d'essai à NuxtHub, je ne peux pas simplement créer un projet simple comme une liste de tâches. ",[1216,38331,38332,38333,215],{},"Et Sébastien l'a déjà fait avec ",[39,38334,38337],{"href":38335,"rel":38336},"https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-todos-edge",[77],"Atinotes"," Je dois créer quelque chose de plus complexe qui utilise toute la puissance de NuxtHub, de la base de données au stockage d'objets et au magasin clé-valeur.",[35,38340,38341],{},"Au cours des mois de mars et avril, j'ai essayé NuxtHub sur différents projets, et plus je l'utilisais, plus j'aimais. Cela a facilité à la fois le développement local et le déploiement en production. Fini les ajustements entre les deux environnements. C'était un réel plaisir de l'utiliser.",[35,38343,38344,38345],{},"Avec toutes ces expériences, j'ai confiance dans la construction d'un plus grand projet avec NuxtHub. Mais je dois trouver une idée, et ce n'est pas facile. ",[1216,38346,38347],{},"Pas facile est un euphémisme !",[35,38349,38350,38351,38354],{},"Construire quelque chose de faux n'est pas une option. Je sais que je vais passer beaucoup de temps sur ce projet, et cela demandera beaucoup d'énergie. Un projet sans un véritable objectif que personne n'utilisera (ou ne peut utiliser) est, ",[1216,38352,38353],{},"à mon avis",", une perte de temps. En attendant, et parce que le projet n'a pas d'objectif, il est difficile de faire avancer l'écosystème. L'un des objectifs de ce projet est d'être une vitrine, de me permettre d'écrire des articles, de créer de nouveaux modules et, dans l'ensemble, d'aider les gens à construire leurs projets.",[51,38356,38358],{"id":38357},"construire-orion","Construire Orion",[35,38360,38361,38362,38365],{},"Un bon projet pour essayer NuxtHub doit avoir une authentification, utiliser la base de données, le stockage d'objets, le magasin clé-valeur, être compatible avec Cloudflare et être utile, au moins, pour moi. ",[1216,38363,38364],{},"Et si le projet peut être utile à la communauté, je dois admettre que c'est un plus."," Si je ne l'utilise pas chaque semaine, je ne serai pas capable de créer du contenu et de le maintenir.",[35,38367,38368,38369,106,38374],{},"Au début de mai, j'ai trouvé quelques idées intéressantes mais aucune ne m'a convaincu, certaines étaient trop légères et d'autres pas assez utiles. J'étais bloqué. Mais un jour, je suis tombé à nouveau sur l'article d'Astro au sujet du ",[39,38370,38373],{"href":38371,"rel":38372},"https:\u002F\u002Fastro.build\u002Fblog\u002Fdev-portal\u002F",[77],"Portail de Développeurs Astro",[1216,38375,38376],{},"C'est une idée géniale pour un projet !",[35,38378,38379,38380,38385,38386],{},"Un endroit central pour tous les modèles créés par la communauté est l'un des éléments manquants dans l'écosystème Nuxt. Je sais, le site principal de Nuxt a une ",[39,38381,38384],{"href":38382,"rel":38383},"https:\u002F\u002Fnuxt.com\u002Ftemplates",[77],"section pour les modèles",". Mais ce n'est pas suffisant. Les informations disponibles sont limitées et il n'y a aucun moyen de filtrer, rechercher ou trier les modèles. Et, le plus important, ce n'est pas évolutif. ",[1216,38387,38388],{},"Dans les coulisses, c'est un simple fichier YAML qui est mis à jour manuellement.",[35,38390,38391],{},"Depuis la sortie de Nuxt Content et Nuxt Studio, de nombreux modèles ont été créés par la communauté. L'un des moyens de les trouver est de rechercher les mots-clés appropriés sur GitHub. C'est inefficace, cela ne permet pas de découvrir facilement de nouveaux modèles et ne donne pas de visibilité aux auteurs.",[35,38393,38394,38395,106,38398],{},"Avec cette observation, j'ai décidé de construire un projet qui permettra à la communauté de partager ses modèles, de découvrir de nouveaux modèles et d'apprendre d'eux. Ce projet s'appellera ",[6862,38396,38397],{},"Orion",[1216,38399,38400],{},"Il y a une histoire derrière ce nom, mais ce sera pour une autre fois !",[35,38402,38403],{},"Orion est, au moment où j'écris ces lignes, une collection de modèles pour vos projets Nuxt. Des pages d'accueil aux applications web complètes, vous trouverez un modèle qui correspond à vos besoins. Vous pouvez découvrir, rechercher, trouver et apprendre à partir d'une collection de modèles créés par la communauté Nuxt pour l'écosystème Nuxt.",[35,38405,38406,38407,38410,38411],{},"Orion est open-source et disponible sur ",[39,38408,38290],{"href":38288,"rel":38409},[77],". Vous pouvez trouver le code sur ",[1599,38412,38414],{"repo":38413},"barbapapazes\u002Forion"," pour en apprendre davantage ou y contribuer.",[35,38416,38417,38418,38423],{},"Pour présenter votre modèle avec de belles images de couverture, je publie un ",[39,38419,38422],{"href":38420,"rel":38421},"https:\u002F\u002Fwww.figma.com\u002Fcommunity\u002Ffile\u002F1385718462370739769\u002Forion-cover-image-template",[77],"modèle Figma"," que vous pouvez utiliser pour créer votre image de couverture.",[1929,38425,38427],{"id":38426},"fonctionnalités","Fonctionnalités",[35,38429,38430],{},[1707,38431],{"alt":38397,"src":38432},"\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Forion.png",[35,38434,38435],{},"Maintenant que vous connaissez l'histoire derrière Orion et pourquoi je l'ai construit, il est temps de parler des fonctionnalités.",[38437,38438,38440],"h4",{"id":38439},"découvrir-des-modèles","Découvrir des Modèles",[35,38442,38443],{},[1707,38444],{"alt":38445,"src":38446},"Discover Templates","\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fdiscover-templates.png",[35,38448,38449],{},"La page d'accueil contient une section qui répertorie les modèles. Vous pouvez les filtrer par catégorie, prix et modules. Vous pouvez également rechercher un modèle par son nom. Une fois que vous avez trouvé un modèle qui vous intéresse, vous pouvez cliquer dessus pour voir plus de détails comme la démonstration en direct et le code source.",[35,38451,38452],{},[1707,38453],{"alt":38454,"src":38455},"Détails du Modèle","\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Ftemplate-details.png",[38437,38457,38459],{"id":38458},"créer-un-modèle","Créer un Modèle",[35,38461,38462],{},[1707,38463],{"alt":38464,"src":38465},"Create a Template","\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fcreate-a-template.png",[35,38467,38468],{},"Si vous avez un modèle que vous souhaitez partager avec la communauté, vous pouvez créer une nouvelle entrée. Tout d'abord, vous devez vous authentifier avec votre compte GitHub. Ensuite, vous pouvez remplir le formulaire avec les détails de votre modèle et le soumettre.",[35,38470,38471],{},"Avant d'être disponible sur la plateforme, votre modèle sera examiné par l'équipe. Une fois approuvé, il sera visible par tous.",[38437,38473,38475],{"id":38474},"gérer-vos-modèles","Gérer Vos Modèles",[35,38477,38478],{},[1707,38479],{"alt":38480,"src":38481},"Manage Your Templates","\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fmanage-your-templates.png",[35,38483,38484],{},"Si vous avez créé des modèles, vous pouvez les gérer depuis votre tableau de bord. Vous pouvez voir le statut de chaque modèle, les modifier ou les supprimer.",[1929,38486,38488],{"id":38487},"stack-technique","Stack Technique",[35,38490,38491],{},"Orion est construit sur :",[1593,38493,38494,38503,38509,38515,38523,38529,38536,38544,38552],{},[71,38495,38496,8048,38500],{},[39,38497,38499],{"href":23088,"rel":38498},[77],"Nuxt 3",[1216,38501,38502],{},"(avec compatibilité 4 activée)",[71,38504,38505,38508],{},[39,38506,9203],{"href":23093,"rel":38507},[77]," sans personnalisations. Je voulais rester dans l'identité Nuxt car c'est un projet pour la communauté Nuxt.",[71,38510,38511,38514],{},[39,38512,36579],{"href":36577,"rel":38513},[77]," pour la partie full-stack.",[71,38516,38517,38522],{},[39,38518,38521],{"href":38519,"rel":38520},"https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils",[77],"Nuxt Auth"," Utils pour l'authentification.",[71,38524,38525,38528],{},[39,38526,9234],{"href":9232,"rel":38527},[77]," pour l'autorisation.",[71,38530,38531,34220],{},[39,38532,38535],{"href":38533,"rel":38534},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fpages\u002F",[77],"Cloudflare Pages",[71,38537,38538,38543],{},[39,38539,38542],{"href":38540,"rel":38541},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002F",[77],"Cloudflare D1"," pour la base de données.",[71,38545,38546,38551],{},[39,38547,38550],{"href":38548,"rel":38549},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fkv\u002F",[77],"Cloudflare KV"," pour le magasin clé-valeur.",[71,38553,38554,38559],{},[39,38555,38558],{"href":38556,"rel":38557},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002F",[77],"Cloudflare R2"," pour le stockage d'objets.",[51,38561,38563],{"id":38562},"pour-la-communauté","Pour la Communauté",[35,38565,38566,38567,38570,38571,38575],{},"Orion a été construit lors de flux en direct sur ",[39,38568,18581],{"href":18579,"rel":38569},[77],". J'écris également ",[39,38572,38574],{"href":38573},".\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey","un article"," pour partager le processus de construction d'un projet à partir de zéro, pour montrer les difficultés, les solutions et les choix qui ont été faits. Je voulais montrer que construire un projet n'est pas seulement écrire du code, mais aussi beaucoup de réflexion, beaucoup de recherche et beaucoup de tests.",[35,38577,38578,38579,1655,38583,215],{},"Pendant le développement d'Orion, j'ai rencontré quelques problèmes communs que j'ai documentés dans 2 articles : ",[39,38580,38582],{"href":38581},".\u002Fnuxt-going-full-stack-how-to-streamline-form-validation","Nuxt Going Full-Stack : Comment valider les formulaires ?",[39,38584,38586],{"href":38585},".\u002Fnuxt-going-full-stack-how-to-handle-authorization","Nuxt Going Full-Stack : Comment gérer l'autorisation ?",[35,38588,38589,38590,38593],{},"J'ai également créé un module, ",[39,38591,9234],{"href":9232,"rel":38592},[77],", qui vous permet de gérer l'autorisation dans votre projet Nuxt. Ce module est utilisé dans Orion et maintenant disponible pour la communauté. J'espère qu'il vous aidera à construire votre prochain projet.",[35,38595,38596,38597,15917],{},"Tout le processus m'a amené à faire quelques PR sur les dépôts Nuxt et UnJS. L'une d'entre elles est la rédaction du ",[39,38598,38601],{"href":38599,"rel":38600},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fnitro\u002Fpull\u002F2508",[77],"guide de l'auteur du module Nitro",[35,38603,38604,38605,38611,38612],{},"Maintenant que le projet est sorti, vous pouvez ",[39,38606,38608],{"href":38288,"rel":38607},[77],[6862,38609,38610],{},"publier et partager vos modèles"," avec la communauté Nuxt. ",[1216,38613,38614,38615,15917],{},"Les retours sont bienvenus sur ",[39,38616,38619],{"href":38617,"rel":38618},"https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Forion",[77],"le dépôt GitHub",[35,38621,38622],{},"Bon codage !",{"title":13,"searchDepth":14,"depth":14,"links":38624},[38625,38626,38627,38631],{"id":17293,"depth":14,"text":17294},{"id":38325,"depth":14,"text":38326},{"id":38357,"depth":14,"text":38358,"children":38628},[38629,38630],{"id":38426,"depth":255,"text":38427},{"id":38487,"depth":255,"text":38488},{"id":38562,"depth":14,"text":38563},"5a52bbd3-9e97-49d6-ade2-5dea4cd41ce0","2024-06-18","J'ai le plaisir d'annoncer Orion, une collection communautaire de modèles pour vos prochains projets Nuxt, des pages d'atterrissage aux webapp complètes.",{},"\u002Ffr\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt","---\ncontentId: 5a52bbd3-9e97-49d6-ade2-5dea4cd41ce0\ntitle: Orion, une Collection Communautaire de Modèles pour Nuxt\ndescription: J'ai le plaisir d'annoncer Orion, une collection communautaire de modèles pour vos prochains projets Nuxt, des pages d'atterrissage aux webapp complètes.\ndate: 2024-06-18\n---\n\n> TL;DR : Je suis ravi d'annoncer Orion, une collection de modèles dirigée par la communauté pour vos prochains projets Nuxt, des pages d'accueil aux applications web complètes. Orion est un endroit où vous pouvez découvrir, rechercher, trouver et apprendre à partir d'une collection de modèles créés par la communauté Nuxt pour l'écosystème Nuxt. Disponible sur [orion.barbapapazes.dev](https:\u002F\u002Forion.barbapapazes.dev).\n\n## Le Début\n\nÀ la fin de février 2024, lors de Vue Amsterdam, Sébastien Chopin a dévoilé [NuxtHub](https:\u002F\u002Fhub.nuxt.com), une plateforme et un module pour créer et déployer des applications Nuxt full-stack sur le cloud.\n\nCette annonce était énorme car depuis la version 3, le slogan de Nuxt contient le mot 'full-stack', mais jusqu'à présent, c'était plus un rêve qu'une réalité.\n\n> Créez des applications et des sites web full-stack performants et de qualité production en toute confiance.\n\nAvec cette nouvelle plateforme, le full-stack devient une réalité. _Et je suis vraiment enthousiaste à ce sujet ! En décembre 2023, j'ai écrit sur [Nuxt 3 et le full-stack sur le cloud](https:\u002F\u002Festeban-soubiran.site\u002Farticles\u002Fnuxt-3-et-le-full-stack-sur-ledge) (Français), et il restait encore beaucoup de travail à faire. 6 mois plus tard, le full-stack est là ! Progrès impressionnant ! Bon travail à l'équipe Nuxt !_\n\nDans cet esprit, je voulais vraiment essayer NuxtHub. Cet outil peut façonner l'avenir de Nuxt, et je pense qu'il est important de le comprendre pour ne pas être dépassé.\n\n## Trouver une Idée\n\nPour donner un vrai coup d'essai à NuxtHub, je ne peux pas simplement créer un projet simple comme une liste de tâches. _Et Sébastien l'a déjà fait avec [Atinotes](https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-todos-edge)._ Je dois créer quelque chose de plus complexe qui utilise toute la puissance de NuxtHub, de la base de données au stockage d'objets et au magasin clé-valeur.\n\nAu cours des mois de mars et avril, j'ai essayé NuxtHub sur différents projets, et plus je l'utilisais, plus j'aimais. Cela a facilité à la fois le développement local et le déploiement en production. Fini les ajustements entre les deux environnements. C'était un réel plaisir de l'utiliser.\n\nAvec toutes ces expériences, j'ai confiance dans la construction d'un plus grand projet avec NuxtHub. Mais je dois trouver une idée, et ce n'est pas facile. _Pas facile est un euphémisme !_\n\nConstruire quelque chose de faux n'est pas une option. Je sais que je vais passer beaucoup de temps sur ce projet, et cela demandera beaucoup d'énergie. Un projet sans un véritable objectif que personne n'utilisera (ou ne peut utiliser) est, _à mon avis_, une perte de temps. En attendant, et parce que le projet n'a pas d'objectif, il est difficile de faire avancer l'écosystème. L'un des objectifs de ce projet est d'être une vitrine, de me permettre d'écrire des articles, de créer de nouveaux modules et, dans l'ensemble, d'aider les gens à construire leurs projets.\n\n## Construire Orion\n\nUn bon projet pour essayer NuxtHub doit avoir une authentification, utiliser la base de données, le stockage d'objets, le magasin clé-valeur, être compatible avec Cloudflare et être utile, au moins, pour moi. _Et si le projet peut être utile à la communauté, je dois admettre que c'est un plus._ Si je ne l'utilise pas chaque semaine, je ne serai pas capable de créer du contenu et de le maintenir.\n\nAu début de mai, j'ai trouvé quelques idées intéressantes mais aucune ne m'a convaincu, certaines étaient trop légères et d'autres pas assez utiles. J'étais bloqué. Mais un jour, je suis tombé à nouveau sur l'article d'Astro au sujet du [Portail de Développeurs Astro](https:\u002F\u002Fastro.build\u002Fblog\u002Fdev-portal\u002F). _C'est une idée géniale pour un projet !_\n\nUn endroit central pour tous les modèles créés par la communauté est l'un des éléments manquants dans l'écosystème Nuxt. Je sais, le site principal de Nuxt a une [section pour les modèles](https:\u002F\u002Fnuxt.com\u002Ftemplates). Mais ce n'est pas suffisant. Les informations disponibles sont limitées et il n'y a aucun moyen de filtrer, rechercher ou trier les modèles. Et, le plus important, ce n'est pas évolutif. _Dans les coulisses, c'est un simple fichier YAML qui est mis à jour manuellement._\n\nDepuis la sortie de Nuxt Content et Nuxt Studio, de nombreux modèles ont été créés par la communauté. L'un des moyens de les trouver est de rechercher les mots-clés appropriés sur GitHub. C'est inefficace, cela ne permet pas de découvrir facilement de nouveaux modèles et ne donne pas de visibilité aux auteurs.\n\nAvec cette observation, j'ai décidé de construire un projet qui permettra à la communauté de partager ses modèles, de découvrir de nouveaux modèles et d'apprendre d'eux. Ce projet s'appellera **Orion**. _Il y a une histoire derrière ce nom, mais ce sera pour une autre fois !_\n\nOrion est, au moment où j'écris ces lignes, une collection de modèles pour vos projets Nuxt. Des pages d'accueil aux applications web complètes, vous trouverez un modèle qui correspond à vos besoins. Vous pouvez découvrir, rechercher, trouver et apprendre à partir d'une collection de modèles créés par la communauté Nuxt pour l'écosystème Nuxt.\n\nOrion est open-source et disponible sur [orion.barbapapazes.dev](https:\u002F\u002Forion.barbapapazes.dev). Vous pouvez trouver le code sur \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F> pour en apprendre davantage ou y contribuer.\n\nPour présenter votre modèle avec de belles images de couverture, je publie un [modèle Figma](https:\u002F\u002Fwww.figma.com\u002Fcommunity\u002Ffile\u002F1385718462370739769\u002Forion-cover-image-template) que vous pouvez utiliser pour créer votre image de couverture.\n\n### Fonctionnalités\n\n![Orion](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Forion.png)\n\nMaintenant que vous connaissez l'histoire derrière Orion et pourquoi je l'ai construit, il est temps de parler des fonctionnalités.\n\n#### Découvrir des Modèles\n\n![Discover Templates](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fdiscover-templates.png)\n\nLa page d'accueil contient une section qui répertorie les modèles. Vous pouvez les filtrer par catégorie, prix et modules. Vous pouvez également rechercher un modèle par son nom. Une fois que vous avez trouvé un modèle qui vous intéresse, vous pouvez cliquer dessus pour voir plus de détails comme la démonstration en direct et le code source.\n\n![Détails du Modèle](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Ftemplate-details.png)\n\n#### Créer un Modèle\n\n![Create a Template](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fcreate-a-template.png)\n\nSi vous avez un modèle que vous souhaitez partager avec la communauté, vous pouvez créer une nouvelle entrée. Tout d'abord, vous devez vous authentifier avec votre compte GitHub. Ensuite, vous pouvez remplir le formulaire avec les détails de votre modèle et le soumettre.\n\nAvant d'être disponible sur la plateforme, votre modèle sera examiné par l'équipe. Une fois approuvé, il sera visible par tous.\n\n#### Gérer Vos Modèles\n\n![Manage Your Templates](\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt\u002Fmanage-your-templates.png)\n\nSi vous avez créé des modèles, vous pouvez les gérer depuis votre tableau de bord. Vous pouvez voir le statut de chaque modèle, les modifier ou les supprimer.\n\n### Stack Technique\n\nOrion est construit sur :\n\n- [Nuxt 3](https:\u002F\u002Fnuxt.com\u002F) _(avec compatibilité 4 activée)_\n- [Nuxt UI](https:\u002F\u002Fui.nuxt.com\u002F) sans personnalisations. Je voulais rester dans l'identité Nuxt car c'est un projet pour la communauté Nuxt.\n- [NuxtHub](https:\u002F\u002Fhub.nuxt.com\u002F) pour la partie full-stack.\n- [Nuxt Auth](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils) Utils pour l'authentification.\n- [Nuxt Authorization](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fnuxt-authorization) pour l'autorisation.\n- [Cloudflare Pages](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fpages\u002F) pour le déploiement.\n- [Cloudflare D1](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002F) pour la base de données.\n- [Cloudflare KV](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fkv\u002F) pour le magasin clé-valeur.\n- [Cloudflare R2](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fr2\u002F) pour le stockage d'objets.\n\n## Pour la Communauté\n\nOrion a été construit lors de flux en direct sur [Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). J'écris également [un article](.\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey.md) pour partager le processus de construction d'un projet à partir de zéro, pour montrer les difficultés, les solutions et les choix qui ont été faits. Je voulais montrer que construire un projet n'est pas seulement écrire du code, mais aussi beaucoup de réflexion, beaucoup de recherche et beaucoup de tests.\n\nPendant le développement d'Orion, j'ai rencontré quelques problèmes communs que j'ai documentés dans 2 articles : [Nuxt Going Full-Stack : Comment valider les formulaires ?](.\u002Fnuxt-going-full-stack-how-to-streamline-form-validation.md) et [Nuxt Going Full-Stack : Comment gérer l'autorisation ?](.\u002Fnuxt-going-full-stack-how-to-handle-authorization.md).\n\nJ'ai également créé un module, [Nuxt Authorization](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fnuxt-authorization), qui vous permet de gérer l'autorisation dans votre projet Nuxt. Ce module est utilisé dans Orion et maintenant disponible pour la communauté. J'espère qu'il vous aidera à construire votre prochain projet.\n\nTout le processus m'a amené à faire quelques PR sur les dépôts Nuxt et UnJS. L'une d'entre elles est la rédaction du [guide de l'auteur du module Nitro](https:\u002F\u002Fgithub.com\u002Funjs\u002Fnitro\u002Fpull\u002F2508) !\n\nMaintenant que le projet est sorti, vous pouvez [**publier et partager vos modèles**](https:\u002F\u002Forion.barbapapazes.dev) avec la communauté Nuxt. _Les retours sont bienvenus sur [le dépôt GitHub](https:\u002F\u002Fgithub.com\u002FBarbapapazes\u002Forion) !_\n\nBon codage !\n",{"title":38278,"description":38634},{"loc":38636,"images":38640},[38641,38642,38643,38644,38645],{"loc":38432},{"loc":38446},{"loc":38455},{"loc":38465},{"loc":38481},"fr\u002Fposts\u002Forion-a-community-driven-collection-of-templates-for-nuxt","0R03iCb33IE5ar6a_DtxOHx3N9W_d31V46Csj_XzgAI",{"id":38649,"title":38650,"body":38651,"contentId":30604,"date":40565,"description":40566,"extension":18,"meta":40567,"navigation":20,"path":40568,"rawbody":40569,"robots":23,"schemaOrg":23,"seo":40570,"sitemap":40571,"stem":40572,"__hash__":40573},"posts\u002Ffr\u002Fposts\u002Fnuxt-going-full-stack-how-to-handle-authorization.md","Nuxt Devient Full-Stack : Comment Gérer l'Autorisation ?",{"type":7,"value":38652,"toc":40554},[38653,38667,38682,38693,38696,38699,40551],[35,38654,38655,38656,38659,38660,38663,38664,215],{},"Lorsque vous développez une application, il est essentiel de ",[6862,38657,38658],{},"granter"," ou de ",[6862,38661,38662],{},"restrict"," l'accès à des zones ou des données spécifiques en fonction du rôle ou des permissions d'un utilisateur. Ce concept est connu sous le nom d'",[6862,38665,38666],{},"autorisation",[35,38668,38669,38670,38673,38674,38677,38678,38681],{},"L'autorisation est un aspect critique de toute application à des fins de ",[6862,38671,38672],{},"sécurité",". Il est impératif de protéger les ",[6862,38675,38676],{},"données sensibles",", telles que l'adresse e-mail ou le mot de passe d'un utilisateur, et de prévenir la divulgation d'",[6862,38679,38680],{},"informations privées"," qui pourraient compromettre la vie privée des utilisateurs et l'intégrité de votre entreprise.",[35,38683,38684,38685,38688,38689,38692],{},"Pour clarifier : ",[6862,38686,38687],{},"l'authentification"," implique de vérifier l'identité d'un utilisateur, tandis que ",[6862,38690,38691],{},"l'autorisation"," consiste à permettre ou à refuser l'accès à des ressources en fonction du rôle ou des permissions de l'utilisateur.",[35,38694,38695],{},"En construisant Orion, j'ai été confronté à ce défi et j'ai expérimenté plusieurs approches. Dans cet article, je partagerai plusieurs stratégies pour gérer l'autorisation dans une application NuxtHub, allant des méthodes les plus simples aux plus sophistiquées. Il est crucial de comprendre que la méthode la plus complexe n'est pas nécessairement la meilleure pour chaque application.",[35,38697,38698],{},"Orion sert de collection de templates dirigée par la communauté pour vos projets, allant des pages d'atterrissage aux applications web complètes. NuxtHub fonctionne comme une plateforme de déploiement et de gestion pour Nuxt, propulsée par Cloudflare.",[7918,38700,38701,38703,38710,38890,38906,38916,38925,38929,38938,38951,38964,39038,39045,39049,39063,39282,39291,39362,39372,39375,39379,39385,39391,39742,39745,39761,39764,39782,39788,39794,39801,39805,39816,39823,39830,39834,39840,39843,39850,39866,39869,39873,39883,39889,39959,39962,40013,40016,40022,40028,40123,40137,40241,40244,40251,40260,40394,40406,40419,40431,40515,40520,40523,40532,40535,40538,40548],{"id":30589},[51,38702,29078],{"id":29077},[35,38704,38705,38706,38709],{},"Considérez cet ",[6862,38707,38708],{},"endpoint"," dans votre application Nuxt :",[192,38711,38713],{"className":1380,"code":38712,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  const id = getRouteParam(event, 'id')\n\n  const publication = await db.query.publications.findOne({\n    where: { id },\n    columns: {\n      title: true,\n      content: true,\n      status: true,\n      authorId: true,\n    },\n  })\n\n  return publication\n})\n",[156,38714,38715,38738,38763,38767,38799,38812,38821,38831,38841,38852,38863,38867,38873,38877,38884],{"__ignoreMap":13},[200,38716,38717,38719,38721,38724,38726,38728,38730,38732,38734,38736],{"class":202,"line":203},[200,38718,1390],{"class":206},[200,38720,1393],{"class":206},[200,38722,38723],{"class":223}," defineEventHandler",[200,38725,227],{"class":210},[200,38727,3726],{"class":379},[200,38729,1094],{"class":214},[200,38731,25426],{"class":386},[200,38733,2104],{"class":214},[200,38735,393],{"class":379},[200,38737,280],{"class":214},[200,38739,38740,38742,38744,38746,38749,38751,38753,38755,38757,38759,38761],{"class":202,"line":14},[200,38741,3738],{"class":379},[200,38743,3239],{"class":210},[200,38745,509],{"class":214},[200,38747,38748],{"class":223}," getRouteParam",[200,38750,227],{"class":235},[200,38752,25426],{"class":210},[200,38754,549],{"class":214},[200,38756,242],{"class":214},[200,38758,3295],{"class":245},[200,38760,249],{"class":214},[200,38762,416],{"class":235},[200,38764,38765],{"class":202,"line":255},[200,38766,528],{"emptyLinePlaceholder":20},[200,38768,38769,38771,38774,38776,38778,38781,38783,38785,38787,38790,38792,38795,38797],{"class":202,"line":272},[200,38770,3738],{"class":379},[200,38772,38773],{"class":210}," publication",[200,38775,509],{"class":214},[200,38777,452],{"class":206},[200,38779,38780],{"class":210}," db",[200,38782,215],{"class":214},[200,38784,3524],{"class":210},[200,38786,215],{"class":214},[200,38788,38789],{"class":210},"publications",[200,38791,215],{"class":214},[200,38793,38794],{"class":223},"findOne",[200,38796,227],{"class":235},[200,38798,230],{"class":214},[200,38800,38801,38804,38806,38808,38810],{"class":202,"line":283},[200,38802,38803],{"class":235},"    where",[200,38805,239],{"class":214},[200,38807,318],{"class":214},[200,38809,3239],{"class":210},[200,38811,333],{"class":214},[200,38813,38814,38817,38819],{"class":202,"line":300},[200,38815,38816],{"class":235},"    columns",[200,38818,239],{"class":214},[200,38820,280],{"class":214},[200,38822,38823,38825,38827,38829],{"class":202,"line":310},[200,38824,3572],{"class":235},[200,38826,239],{"class":214},[200,38828,2439],{"class":2438},[200,38830,252],{"class":214},[200,38832,38833,38835,38837,38839],{"class":202,"line":336},[200,38834,3604],{"class":235},[200,38836,239],{"class":214},[200,38838,2439],{"class":2438},[200,38840,252],{"class":214},[200,38842,38843,38846,38848,38850],{"class":202,"line":342},[200,38844,38845],{"class":235},"      status",[200,38847,239],{"class":214},[200,38849,2439],{"class":2438},[200,38851,252],{"class":214},[200,38853,38854,38857,38859,38861],{"class":202,"line":365},[200,38855,38856],{"class":235},"      authorId",[200,38858,239],{"class":214},[200,38860,2439],{"class":2438},[200,38862,252],{"class":214},[200,38864,38865],{"class":202,"line":371},[200,38866,339],{"class":214},[200,38868,38869,38871],{"class":202,"line":398},[200,38870,5095],{"class":214},[200,38872,416],{"class":235},[200,38874,38875],{"class":202,"line":405},[200,38876,528],{"emptyLinePlaceholder":20},[200,38878,38879,38881],{"class":202,"line":410},[200,38880,3758],{"class":206},[200,38882,38883],{"class":210}," publication\n",[200,38885,38886,38888],{"class":202,"line":860},[200,38887,413],{"class":214},[200,38889,416],{"class":210},[35,38891,38892,38893,38896,38897,3092,38900,13741,38903,215],{},"Vous pouvez accéder à cet endpoint en effectuant une requête ",[156,38894,38895],{},"GET \u002Fapi\u002Fpublication\u002F\u003Cid>",". Cet endpoint retourne une publication. À première vue, il n'y a pas de problème avec cet endpoint. Cependant, un examen plus attentif révèle qu'une publication a un statut qui peut être ",[156,38898,38899],{},"brouillon",[156,38901,38902],{},"publiée",[156,38904,38905],{},"supprimée",[35,38907,38908,38909,33751,38912,38915],{},"Le problème est que quiconque peut accéder à cet endpoint et consulter une ",[6862,38910,38911],{},"publication brouillon",[6862,38913,38914],{},"publication supprimée",". Certains pourraient être en mesure de voir le contenu d'une publication avant sa publication, ce qui pourrait contenir des informations vitales telles qu'un communiqué de presse ou le lancement d'un produit. Un tel scénario pourrait nuire considérablement à votre entreprise.",[35,38917,38918,38919,38924],{},"Il s'agit d'un problème de sécurité qui nécessite de restreindre l'accès à cet endpoint, en utilisant le ",[39,38920,38923],{"href":38921,"rel":38922},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrinciple_of_least_privilege",[77],"principe du moindre privilège",". La sécurité n'est pas simplement une fonctionnalité mais doit être intégrée dans la conception du système.",[51,38926,38928],{"id":38927},"authentifié-ou-non","Authentifié ou Non",[35,38930,38931,38932,38937],{},"La section suivante repose sur l'utilisation du package Nuxt ",[39,38933,38935],{"href":38519,"rel":38934},[77],[6862,38936,38035],{},". Néanmoins, son utilisation n'est pas essentielle.",[35,38939,38940,38941,38943,38944,38947,38948,215],{},"La première étape pour sécuriser cet ",[6862,38942,38708],{}," consiste à vérifier si l'utilisateur est ",[6862,38945,38946],{},"authentifié",". Si l'utilisateur ne dispose pas d'une authentification, il convient de renvoyer une erreur ",[156,38949,38950],{},"401 Unauthorized",[35,38952,38953,38954,38956,38957,38960,38961,38963],{},"En explorant le package ",[6862,38955,38035],{},", j'ai découvert un utilitaire serveur appelé ",[156,38958,38959],{},"requireUserSession",". Cet utilitaire peut être utilisé au début d'un endpoint pour tenter de récupérer la session de l'utilisateur et déclencher une erreur ",[156,38962,38950],{}," s'il n'y a pas de session, indiquant que l'utilisateur n'est pas authentifié.",[192,38965,38967],{"className":1380,"code":38966,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  await requireUserSession(event)\n\n  const publication = {} \u002F\u002F ...\n\n  return publication\n})\n",[156,38968,38969,38991,39004,39008,39022,39026,39032],{"__ignoreMap":13},[200,38970,38971,38973,38975,38977,38979,38981,38983,38985,38987,38989],{"class":202,"line":203},[200,38972,1390],{"class":206},[200,38974,1393],{"class":206},[200,38976,38723],{"class":223},[200,38978,227],{"class":210},[200,38980,3726],{"class":379},[200,38982,1094],{"class":214},[200,38984,25426],{"class":386},[200,38986,2104],{"class":214},[200,38988,393],{"class":379},[200,38990,280],{"class":214},[200,38992,38993,38995,38998,39000,39002],{"class":202,"line":14},[200,38994,6177],{"class":206},[200,38996,38997],{"class":223}," requireUserSession",[200,38999,227],{"class":235},[200,39001,25426],{"class":210},[200,39003,416],{"class":235},[200,39005,39006],{"class":202,"line":255},[200,39007,528],{"emptyLinePlaceholder":20},[200,39009,39010,39012,39014,39016,39019],{"class":202,"line":272},[200,39011,3738],{"class":379},[200,39013,38773],{"class":210},[200,39015,509],{"class":214},[200,39017,39018],{"class":214}," {}",[200,39020,39021],{"class":401}," \u002F\u002F ...\n",[200,39023,39024],{"class":202,"line":283},[200,39025,528],{"emptyLinePlaceholder":20},[200,39027,39028,39030],{"class":202,"line":300},[200,39029,3758],{"class":206},[200,39031,38883],{"class":210},[200,39033,39034,39036],{"class":202,"line":310},[200,39035,413],{"class":214},[200,39037,416],{"class":210},[35,39039,39040,39041,39044],{},"C'est la première étape pour protéger l'endpoint contre les ",[6862,39042,39043],{},"utilisateurs non authentifiés",". Cependant, si quiconque peut s'inscrire pour créer un compte, il pourrait toujours accéder au contenu d'une publication brouillon. D'autres actions sont donc nécessaires.",[51,39046,39048],{"id":39047},"seulement-pour-les-administrateurs","Seulement pour les Administrateurs",[35,39050,39051,39052,39054,39055,39058,39059,39062],{},"En s'appuyant sur ",[156,39053,38959],{},", je peux facilement concevoir un nouvel utilitaire nommé ",[156,39056,39057],{},"requireAdminSession",". Cette session vérifiera si l'utilisateur est authentifié et dispose de droits d'administrateur. Si l'utilisateur n'est pas un administrateur, une erreur ",[156,39060,39061],{},"403 Forbidden"," doit être renvoyée. Non autorisé se rapporte à l'authentification, tandis que Forbidden concerne l'autorisation.",[192,39064,39066],{"className":1380,"code":39065,"language":1383,"meta":13,"style":13},"export async function requireAdminSession(event: H3Event, opts: { statusCode?: number, message?: string } = {}): Promise\u003CUserSessionRequired> {\n  const userSession = await requireUserSession(event)\n  const { user } = userSession\n\n  if (user.roleType !== 'admin') {\n    throw createError({\n      statusCode: opts.statusCode || 403,\n      message: opts.message || 'Non autorisé',\n    })\n  }\n\n  return userSession as UserSessionRequired\n}\n",[156,39067,39068,39132,39151,39166,39170,39196,39208,39229,39253,39259,39263,39267,39278],{"__ignoreMap":13},[200,39069,39070,39072,39074,39076,39079,39081,39083,39085,39088,39090,39093,39095,39097,39100,39103,39105,39107,39110,39112,39114,39116,39118,39121,39123,39125,39128,39130],{"class":202,"line":203},[200,39071,1390],{"class":206},[200,39073,380],{"class":379},[200,39075,5002],{"class":379},[200,39077,39078],{"class":223}," requireAdminSession",[200,39080,227],{"class":214},[200,39082,25426],{"class":386},[200,39084,239],{"class":214},[200,39086,39087],{"class":1364}," H3Event",[200,39089,549],{"class":214},[200,39091,39092],{"class":386}," opts",[200,39094,239],{"class":214},[200,39096,318],{"class":214},[200,39098,39099],{"class":235}," statusCode",[200,39101,39102],{"class":214},"?:",[200,39104,20155],{"class":1364},[200,39106,549],{"class":214},[200,39108,39109],{"class":235}," message",[200,39111,39102],{"class":214},[200,39113,22752],{"class":1364},[200,39115,980],{"class":214},[200,39117,509],{"class":214},[200,39119,39120],{"class":214}," {}):",[200,39122,5011],{"class":1364},[200,39124,599],{"class":214},[200,39126,39127],{"class":1364},"UserSessionRequired",[200,39129,674],{"class":214},[200,39131,280],{"class":214},[200,39133,39134,39136,39139,39141,39143,39145,39147,39149],{"class":202,"line":14},[200,39135,3738],{"class":379},[200,39137,39138],{"class":210}," userSession",[200,39140,509],{"class":214},[200,39142,452],{"class":206},[200,39144,38997],{"class":223},[200,39146,227],{"class":235},[200,39148,25426],{"class":210},[200,39150,416],{"class":235},[200,39152,39153,39155,39157,39159,39161,39163],{"class":202,"line":255},[200,39154,3738],{"class":379},[200,39156,318],{"class":214},[200,39158,34533],{"class":210},[200,39160,980],{"class":214},[200,39162,509],{"class":214},[200,39164,39165],{"class":210}," userSession\n",[200,39167,39168],{"class":202,"line":272},[200,39169,528],{"emptyLinePlaceholder":20},[200,39171,39172,39174,39176,39178,39180,39183,39185,39187,39190,39192,39194],{"class":202,"line":283},[200,39173,20098],{"class":206},[200,39175,1094],{"class":235},[200,39177,26847],{"class":210},[200,39179,215],{"class":214},[200,39181,39182],{"class":210},"roleType",[200,39184,13398],{"class":214},[200,39186,242],{"class":214},[200,39188,39189],{"class":245},"admin",[200,39191,249],{"class":214},[200,39193,5282],{"class":235},[200,39195,230],{"class":214},[200,39197,39198,39201,39204,39206],{"class":202,"line":300},[200,39199,39200],{"class":206},"    throw",[200,39202,39203],{"class":223}," createError",[200,39205,227],{"class":235},[200,39207,230],{"class":214},[200,39209,39210,39213,39215,39217,39219,39222,39224,39227],{"class":202,"line":310},[200,39211,39212],{"class":235},"      statusCode",[200,39214,239],{"class":214},[200,39216,39092],{"class":210},[200,39218,215],{"class":214},[200,39220,39221],{"class":210},"statusCode",[200,39223,15013],{"class":214},[200,39225,39226],{"class":2060}," 403",[200,39228,252],{"class":214},[200,39230,39231,39234,39236,39238,39240,39242,39244,39246,39249,39251],{"class":202,"line":336},[200,39232,39233],{"class":235},"      message",[200,39235,239],{"class":214},[200,39237,39092],{"class":210},[200,39239,215],{"class":214},[200,39241,29303],{"class":210},[200,39243,15013],{"class":214},[200,39245,242],{"class":214},[200,39247,39248],{"class":245},"Non autorisé",[200,39250,249],{"class":214},[200,39252,252],{"class":214},[200,39254,39255,39257],{"class":202,"line":342},[200,39256,2111],{"class":214},[200,39258,416],{"class":235},[200,39260,39261],{"class":202,"line":365},[200,39262,4505],{"class":214},[200,39264,39265],{"class":202,"line":371},[200,39266,528],{"emptyLinePlaceholder":20},[200,39268,39269,39271,39273,39275],{"class":202,"line":398},[200,39270,3758],{"class":206},[200,39272,39138],{"class":210},[200,39274,14678],{"class":206},[200,39276,39277],{"class":1364}," UserSessionRequired\n",[200,39279,39280],{"class":202,"line":405},[200,39281,1129],{"class":214},[35,39283,39284,39285,39287,39288,39290],{},"Par la suite, ",[156,39286,38959],{}," peut être remplacé par ",[156,39289,39057],{}," dans l'endpoint.",[192,39292,39294],{"className":1380,"code":39293,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  await requireAdminSession(event)\n\n  const publication = {} \u002F\u002F ...\n\n  return publication\n})\n",[156,39295,39296,39318,39330,39334,39346,39350,39356],{"__ignoreMap":13},[200,39297,39298,39300,39302,39304,39306,39308,39310,39312,39314,39316],{"class":202,"line":203},[200,39299,1390],{"class":206},[200,39301,1393],{"class":206},[200,39303,38723],{"class":223},[200,39305,227],{"class":210},[200,39307,3726],{"class":379},[200,39309,1094],{"class":214},[200,39311,25426],{"class":386},[200,39313,2104],{"class":214},[200,39315,393],{"class":379},[200,39317,280],{"class":214},[200,39319,39320,39322,39324,39326,39328],{"class":202,"line":14},[200,39321,6177],{"class":206},[200,39323,39078],{"class":223},[200,39325,227],{"class":235},[200,39327,25426],{"class":210},[200,39329,416],{"class":235},[200,39331,39332],{"class":202,"line":255},[200,39333,528],{"emptyLinePlaceholder":20},[200,39335,39336,39338,39340,39342,39344],{"class":202,"line":272},[200,39337,3738],{"class":379},[200,39339,38773],{"class":210},[200,39341,509],{"class":214},[200,39343,39018],{"class":214},[200,39345,39021],{"class":401},[200,39347,39348],{"class":202,"line":283},[200,39349,528],{"emptyLinePlaceholder":20},[200,39351,39352,39354],{"class":202,"line":300},[200,39353,3758],{"class":206},[200,39355,38883],{"class":210},[200,39357,39358,39360],{"class":202,"line":310},[200,39359,413],{"class":214},[200,39361,416],{"class":210},[35,39363,39364,39365,39368,39369,39371],{},"Maintenant, seuls les ",[6862,39366,39367],{},"admins",", les utilisateurs ayant le rôle ",[156,39370,39189],{},", peuvent accéder au contenu d'une publication.",[35,39373,39374],{},"Cependant, cette approche n'est pas entièrement efficace, car si la publication est publiée, je souhaite permettre à tous les utilisateurs d'y accéder. Actuellement, seuls les admins peuvent voir le contenu d'une publication, quelle que soit le statut de la publication.",[51,39376,39378],{"id":39377},"problèmes-avec-ces-approches","Problèmes avec ces Approches",[35,39380,39381,39382,39384],{},"Les stratégies mentionnées manquent de flexibilité suffisante pour un système d'",[6862,39383,38666],{}," détaillé. Elles ne peuvent pas accommoder les situations où l'auteur de la publication devrait avoir accès, même si la publication est non publiée.",[35,39386,39387,39388,2186],{},"Pour y remédier, je pourrais rédiger un autre utilitaire nommé ",[156,39389,39390],{},"requirePublicationAccess",[192,39392,39394],{"className":1380,"code":39393,"language":1383,"meta":13,"style":13},"export async function requirePublicationAccess(event: H3Event, publication: Publication, opts: { statusCode?: number, message?: string } = {}): Promise\u003CUserSessionRequired> {\n  const userSession = await getUserSession(event)\n  const { user } = userSession\n\n  if (publication.status === 'published')\n    return userSession\n\n  if (!userSession) {\n    throw createError({\n      statusCode: opts.statusCode || 401,\n      message: opts.message || 'Non autorisé',\n    })\n  }\n\n  if (publication.authorId === user.id)\n    return userSession\n\n  if (user.roleType === 'admin')\n    return userSession\n\n  throw createError({\n    statusCode: opts.statusCode || 403,\n    message: opts.message || 'Interdit',\n  })\n}\n",[156,39395,39396,39462,39481,39495,39499,39524,39530,39534,39549,39559,39578,39600,39606,39610,39614,39637,39643,39647,39669,39675,39679,39689,39708,39732,39738],{"__ignoreMap":13},[200,39397,39398,39400,39402,39404,39407,39409,39411,39413,39415,39417,39419,39421,39424,39426,39428,39430,39432,39434,39436,39438,39440,39442,39444,39446,39448,39450,39452,39454,39456,39458,39460],{"class":202,"line":203},[200,39399,1390],{"class":206},[200,39401,380],{"class":379},[200,39403,5002],{"class":379},[200,39405,39406],{"class":223}," requirePublicationAccess",[200,39408,227],{"class":214},[200,39410,25426],{"class":386},[200,39412,239],{"class":214},[200,39414,39087],{"class":1364},[200,39416,549],{"class":214},[200,39418,38773],{"class":386},[200,39420,239],{"class":214},[200,39422,39423],{"class":1364}," Publication",[200,39425,549],{"class":214},[200,39427,39092],{"class":386},[200,39429,239],{"class":214},[200,39431,318],{"class":214},[200,39433,39099],{"class":235},[200,39435,39102],{"class":214},[200,39437,20155],{"class":1364},[200,39439,549],{"class":214},[200,39441,39109],{"class":235},[200,39443,39102],{"class":214},[200,39445,22752],{"class":1364},[200,39447,980],{"class":214},[200,39449,509],{"class":214},[200,39451,39120],{"class":214},[200,39453,5011],{"class":1364},[200,39455,599],{"class":214},[200,39457,39127],{"class":1364},[200,39459,674],{"class":214},[200,39461,280],{"class":214},[200,39463,39464,39466,39468,39470,39472,39475,39477,39479],{"class":202,"line":14},[200,39465,3738],{"class":379},[200,39467,39138],{"class":210},[200,39469,509],{"class":214},[200,39471,452],{"class":206},[200,39473,39474],{"class":223}," getUserSession",[200,39476,227],{"class":235},[200,39478,25426],{"class":210},[200,39480,416],{"class":235},[200,39482,39483,39485,39487,39489,39491,39493],{"class":202,"line":255},[200,39484,3738],{"class":379},[200,39486,318],{"class":214},[200,39488,34533],{"class":210},[200,39490,980],{"class":214},[200,39492,509],{"class":214},[200,39494,39165],{"class":210},[200,39496,39497],{"class":202,"line":272},[200,39498,528],{"emptyLinePlaceholder":20},[200,39500,39501,39503,39505,39508,39510,39513,39515,39517,39520,39522],{"class":202,"line":283},[200,39502,20098],{"class":206},[200,39504,1094],{"class":235},[200,39506,39507],{"class":210},"publication",[200,39509,215],{"class":214},[200,39511,39512],{"class":210},"status",[200,39514,3298],{"class":214},[200,39516,242],{"class":214},[200,39518,39519],{"class":245},"published",[200,39521,249],{"class":214},[200,39523,416],{"class":235},[200,39525,39526,39528],{"class":202,"line":300},[200,39527,3311],{"class":206},[200,39529,39165],{"class":210},[200,39531,39532],{"class":202,"line":310},[200,39533,528],{"emptyLinePlaceholder":20},[200,39535,39536,39538,39540,39542,39545,39547],{"class":202,"line":336},[200,39537,20098],{"class":206},[200,39539,1094],{"class":235},[200,39541,11852],{"class":214},[200,39543,39544],{"class":210},"userSession",[200,39546,5282],{"class":235},[200,39548,230],{"class":214},[200,39550,39551,39553,39555,39557],{"class":202,"line":342},[200,39552,39200],{"class":206},[200,39554,39203],{"class":223},[200,39556,227],{"class":235},[200,39558,230],{"class":214},[200,39560,39561,39563,39565,39567,39569,39571,39573,39576],{"class":202,"line":365},[200,39562,39212],{"class":235},[200,39564,239],{"class":214},[200,39566,39092],{"class":210},[200,39568,215],{"class":214},[200,39570,39221],{"class":210},[200,39572,15013],{"class":214},[200,39574,39575],{"class":2060}," 401",[200,39577,252],{"class":214},[200,39579,39580,39582,39584,39586,39588,39590,39592,39594,39596,39598],{"class":202,"line":371},[200,39581,39233],{"class":235},[200,39583,239],{"class":214},[200,39585,39092],{"class":210},[200,39587,215],{"class":214},[200,39589,29303],{"class":210},[200,39591,15013],{"class":214},[200,39593,242],{"class":214},[200,39595,39248],{"class":245},[200,39597,249],{"class":214},[200,39599,252],{"class":214},[200,39601,39602,39604],{"class":202,"line":398},[200,39603,2111],{"class":214},[200,39605,416],{"class":235},[200,39607,39608],{"class":202,"line":405},[200,39609,4505],{"class":214},[200,39611,39612],{"class":202,"line":410},[200,39613,528],{"emptyLinePlaceholder":20},[200,39615,39616,39618,39620,39622,39624,39627,39629,39631,39633,39635],{"class":202,"line":860},[200,39617,20098],{"class":206},[200,39619,1094],{"class":235},[200,39621,39507],{"class":210},[200,39623,215],{"class":214},[200,39625,39626],{"class":210},"authorId",[200,39628,3298],{"class":214},[200,39630,34533],{"class":210},[200,39632,215],{"class":214},[200,39634,3295],{"class":210},[200,39636,416],{"class":235},[200,39638,39639,39641],{"class":202,"line":889},[200,39640,3311],{"class":206},[200,39642,39165],{"class":210},[200,39644,39645],{"class":202,"line":899},[200,39646,528],{"emptyLinePlaceholder":20},[200,39648,39649,39651,39653,39655,39657,39659,39661,39663,39665,39667],{"class":202,"line":904},[200,39650,20098],{"class":206},[200,39652,1094],{"class":235},[200,39654,26847],{"class":210},[200,39656,215],{"class":214},[200,39658,39182],{"class":210},[200,39660,3298],{"class":214},[200,39662,242],{"class":214},[200,39664,39189],{"class":245},[200,39666,249],{"class":214},[200,39668,416],{"class":235},[200,39670,39671,39673],{"class":202,"line":930},[200,39672,3311],{"class":206},[200,39674,39165],{"class":210},[200,39676,39677],{"class":202,"line":3655},[200,39678,528],{"emptyLinePlaceholder":20},[200,39680,39681,39683,39685,39687],{"class":202,"line":3660},[200,39682,20287],{"class":206},[200,39684,39203],{"class":223},[200,39686,227],{"class":235},[200,39688,230],{"class":214},[200,39690,39691,39694,39696,39698,39700,39702,39704,39706],{"class":202,"line":4035},[200,39692,39693],{"class":235},"    statusCode",[200,39695,239],{"class":214},[200,39697,39092],{"class":210},[200,39699,215],{"class":214},[200,39701,39221],{"class":210},[200,39703,15013],{"class":214},[200,39705,39226],{"class":2060},[200,39707,252],{"class":214},[200,39709,39710,39713,39715,39717,39719,39721,39723,39725,39728,39730],{"class":202,"line":4490},[200,39711,39712],{"class":235},"    message",[200,39714,239],{"class":214},[200,39716,39092],{"class":210},[200,39718,215],{"class":214},[200,39720,29303],{"class":210},[200,39722,15013],{"class":214},[200,39724,242],{"class":214},[200,39726,39727],{"class":245},"Interdit",[200,39729,249],{"class":214},[200,39731,252],{"class":214},[200,39733,39734,39736],{"class":202,"line":4496},[200,39735,5095],{"class":214},[200,39737,416],{"class":235},[200,39739,39740],{"class":202,"line":4502},[200,39741,1129],{"class":214},[35,39743,39744],{},"Cette solution est adéquate tant que je dois gérer d'autres endpoints. Cette approche entraînera une excessive duplication de code concernant le traitement des erreurs. De plus, le code devient plus difficile à tester en raison de la nécessité de gérer un événement H3 entier.",[35,39746,39747,39748,39751,39752,39755,39756,1655,39758,215],{},"Sans une meilleure approche, je continue à développer Orion mais me heurte bientôt à un autre défi : je dois mettre en œuvre la même ",[6862,39749,39750],{},"logique d'autorisation côté client",". Je ne souhaite pas afficher un bouton d'édition à un utilisateur qui n'est ni l'auteur de la publication ni un administrateur, et je ne veux pas cacher ce bouton d'édition à l'administrateur si je décide de permettre l'édition de la publication sur le serveur. Comme pour ",[39,39753,39754],{"href":38581},"la validation des formulaires",", la duplication de la logique peut entraîner des incohérences substantielles entre le client et le serveur, menant à la frustration des clients. Les utilitaires que j'ai créés jusqu'à présent sont uniquement côté serveur en raison de l'utilisation de ",[156,39757,38959],{},[156,39759,39760],{},"getUserSession",[35,39762,39763],{},"Faisons un pas en arrière pour comprendre comment ce problème pourrait être résolu. L'autorisation se compose de trois composants :",[1593,39765,39766,39772,39777],{},[71,39767,39768,39771],{},[6862,39769,39770],{},"permettre"," l'accès à une ressource",[71,39773,39774,39771],{},[6862,39775,39776],{},"refuser",[71,39778,39779,39771],{},[6862,39780,39781],{},"autoriser",[35,39783,39784,39785,39787],{},"Les deux premiers sont des conditions simples produisant une réponse booléenne à des questions comme \"Puis-je accéder à cette publication ? Oui ou Non\". Le développeur doit gérer manuellement cette réponse. Le troisième accorde l'accès à la ressource. Pour la question \"Puis-je accéder à cette publication ?\", il n'y a ni \"oui\" ni \"non\" ; rien ne se passe si la réponse est \"oui\", mais une erreur est levée si la réponse est \"non\". L'",[156,39786,39781],{}," envoie automatiquement une erreur.",[35,39789,39790,39791,215],{},"Ces éléments n'ont aucun lien direct avec le client, le serveur, ou le framework utilisé ou le système d'authentification. Munis de cette connaissance, j'ai commencé à travailler sur un module local pour Orion afin de gérer l'autorisation de manière plus flexible. Après plusieurs jours d'efforts, je suis fier de vous présenter ",[6862,39792,39793],{},"nuxt-authorization",[35,39795,39796,39797,215],{},"Au départ, je visais à développer à la fois un module Nitro et un module Nuxt, mais les modules Nitro ne sont pas encore prêts. Cela dit, j'ai soumis quelques demandes de tirage au dépôt Nitro pour améliorer le système de modules. L'une d'elles est le ",[39,39798,39800],{"href":38599,"rel":39799},[77],"guide de l'auteur du module",[51,39802,39804],{"id":39803},"présentation-de-nuxt-authorization","Présentation de nuxt-authorization",[35,39806,39807,39808,39810,39811,215],{},"Ce module offre une méthode simple mais puissante pour gérer l'",[6862,39809,38666],{}," dans une application Nuxt, applicable tant au client qu'au serveur. Il est indépendant du système d'authentification mais peut être intégré sans effort avec ",[39,39812,39814],{"href":38519,"rel":39813},[77],[6862,39815,38035],{},[35,39817,39818,39819],{},"Pour des instructions sur son utilisation, visitez le dépôt GitHub : ",[1599,39820,39822],{"repo":39821},"barbapapazes\u002Fnuxt-authorization",". Pour explorer la conception et l'implémentation, continuez à lire.",[35,39824,39825,39826,215],{},"Le module est encore à ses débuts de développement, et si vous avez des retours ou des suggestions d'amélioration, n'hésitez pas à ouvrir un problème sur le dépôt GitHub ou à me contacter sur ",[39,39827,38242],{"href":39828,"rel":39829},"https:\u002F\u002Fx.com\u002Fbarbapapazes",[77],[1929,39831,39833],{"id":39832},"problèmes-que-je-souhaite-résoudre","Problèmes que je souhaite résoudre",[35,39835,39836,39837,39839],{},"Avant de décrire ce qui a été accompli avec ",[6862,39838,39793],{},", examinons les problèmes que je souhaitais résoudre.",[35,39841,39842],{},"Tout comme la validation des formulaires, je vise à maintenir une source de vérité unique et une API unique tant pour l'application que pour le serveur. Cela est crucial pour éviter les divergences entre l'application et le serveur.",[35,39844,39845,39846,39849],{},"Je souhaite également avoir la possibilité de définir la ",[6862,39847,39848],{},"logique d'autorisation"," de manière simple et regroupée par ressource. Cette approche améliore la lisibilité, la maintenabilité et la cohérence à travers divers aspects de l'application, tels que la validation des formulaires.",[35,39851,39852,39853,13741,39855,39857,39858,39860,39861,39863,39864,215],{},"La logique d'autorisation doit être suffisamment flexible pour accommoder une gamme variée de cas d'utilisation, des conditions ",[156,39854,39770],{},[156,39856,39776],{}," aux conditions ",[156,39859,39781],{}," plus complexes. Les erreurs produites par la condition ",[156,39862,39781],{}," devraient être personnalisables pour améliorer l'expérience utilisateur. Toutes les erreurs non autorisées ne doivent pas nécessairement être un ",[156,39865,39061],{},[35,39867,39868],{},"Enfin, je désire des composants qui facilitent l'intégration de la logique d'autorisation dans les templates. Par exemple, je souhaite avoir la capacité d'afficher ou de cacher un bouton en fonction de la logique d'autorisation.",[1929,39870,39872],{"id":39871},"conception","Conception",[35,39874,39875,39876,39879,39880,215],{},"Le module se compose de deux composants : l'",[6862,39877,39878],{},"habilité"," et le ",[6862,39881,39882],{},"bouncer",[35,39884,39885,39886,39888],{},"Les habilités constituent les règles qui incarnent la ",[6862,39887,39848],{},". Elles prennent un utilisateur et une ou plusieurs ressources et génèrent une condition de refus ou d'autorisation. Bien qu'elles puissent être regroupées par ressource, elles restent indépendantes les unes des autres.",[192,39890,39892],{"className":1380,"code":39891,"language":1383,"meta":13,"style":13},"export const editBook = defineAbility((user: User, book: Book) => {\n  return user.id === book.authorId\n})\n",[156,39893,39894,39934,39953],{"__ignoreMap":13},[200,39895,39896,39898,39900,39903,39905,39908,39910,39912,39914,39916,39918,39920,39923,39925,39928,39930,39932],{"class":202,"line":203},[200,39897,1390],{"class":206},[200,39899,14305],{"class":379},[200,39901,39902],{"class":210}," editBook ",[200,39904,449],{"class":214},[200,39906,39907],{"class":223}," defineAbility",[200,39909,227],{"class":210},[200,39911,227],{"class":214},[200,39913,26847],{"class":386},[200,39915,239],{"class":214},[200,39917,34635],{"class":1364},[200,39919,549],{"class":214},[200,39921,39922],{"class":386}," book",[200,39924,239],{"class":214},[200,39926,39927],{"class":1364}," Book",[200,39929,2104],{"class":214},[200,39931,393],{"class":379},[200,39933,280],{"class":214},[200,39935,39936,39938,39940,39942,39944,39946,39948,39950],{"class":202,"line":14},[200,39937,3758],{"class":206},[200,39939,34533],{"class":210},[200,39941,215],{"class":214},[200,39943,3295],{"class":210},[200,39945,3298],{"class":214},[200,39947,39922],{"class":210},[200,39949,215],{"class":214},[200,39951,39952],{"class":210},"authorId\n",[200,39954,39955,39957],{"class":202,"line":255},[200,39956,413],{"class":214},[200,39958,416],{"class":210},[35,39960,39961],{},"Dans l'exemple ci-dessus, seul l'auteur d'un livre est autorisé à le modifier. Par défaut, les aptitudes ne sont exécutées que si l'utilisateur est authentifié, mais il est possible de permettre un accès invité à certaines ressources.",[192,39963,39965],{"className":1380,"code":39964,"language":1383,"meta":13,"style":13},"export const listBooks = defineAbility((user: User | null) => {\n  return true\n})\n",[156,39966,39967,40001,40007],{"__ignoreMap":13},[200,39968,39969,39971,39973,39976,39978,39980,39982,39984,39986,39988,39990,39993,39995,39997,39999],{"class":202,"line":203},[200,39970,1390],{"class":206},[200,39972,14305],{"class":379},[200,39974,39975],{"class":210}," listBooks ",[200,39977,449],{"class":214},[200,39979,39907],{"class":223},[200,39981,227],{"class":210},[200,39983,227],{"class":214},[200,39985,26847],{"class":386},[200,39987,239],{"class":214},[200,39989,34635],{"class":1364},[200,39991,39992],{"class":214}," |",[200,39994,12432],{"class":1364},[200,39996,2104],{"class":214},[200,39998,393],{"class":379},[200,40000,280],{"class":214},[200,40002,40003,40005],{"class":202,"line":14},[200,40004,3758],{"class":206},[200,40006,22236],{"class":2438},[200,40008,40009,40011],{"class":202,"line":255},[200,40010,413],{"class":214},[200,40012,416],{"class":210},[35,40014,40015],{},"Dans cet exemple, tout le monde, y compris les utilisateurs non authentifiés, est autorisé à lister les livres.",[35,40017,15736,40018,40021],{},[156,40019,40020],{},"defineAbility"," agit comme une usine qui crée une aptitude mais rien au-delà de cela. Elle doit être utilisée avec des fonctions bouncer.",[35,40023,40024,40025,40027],{},"Dans un endpoint serveur, la fonction ",[156,40026,39781],{}," peut être utilisée pour accorder l'accès à une ressource basée sur les aptitudes.",[192,40029,40031],{"className":1380,"code":40030,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  await authorize(event, listBooks)\n\n  const books = await db.query.books.findMany()\n\n  return books\n})\n",[156,40032,40033,40055,40073,40077,40106,40110,40117],{"__ignoreMap":13},[200,40034,40035,40037,40039,40041,40043,40045,40047,40049,40051,40053],{"class":202,"line":203},[200,40036,1390],{"class":206},[200,40038,1393],{"class":206},[200,40040,38723],{"class":223},[200,40042,227],{"class":210},[200,40044,3726],{"class":379},[200,40046,1094],{"class":214},[200,40048,25426],{"class":386},[200,40050,2104],{"class":214},[200,40052,393],{"class":379},[200,40054,280],{"class":214},[200,40056,40057,40059,40062,40064,40066,40068,40071],{"class":202,"line":14},[200,40058,6177],{"class":206},[200,40060,40061],{"class":223}," authorize",[200,40063,227],{"class":235},[200,40065,25426],{"class":210},[200,40067,549],{"class":214},[200,40069,40070],{"class":210}," listBooks",[200,40072,416],{"class":235},[200,40074,40075],{"class":202,"line":255},[200,40076,528],{"emptyLinePlaceholder":20},[200,40078,40079,40081,40084,40086,40088,40090,40092,40094,40096,40099,40101,40104],{"class":202,"line":272},[200,40080,3738],{"class":379},[200,40082,40083],{"class":210}," books",[200,40085,509],{"class":214},[200,40087,452],{"class":206},[200,40089,38780],{"class":210},[200,40091,215],{"class":214},[200,40093,3524],{"class":210},[200,40095,215],{"class":214},[200,40097,40098],{"class":210},"books",[200,40100,215],{"class":214},[200,40102,40103],{"class":223},"findMany",[200,40105,466],{"class":235},[200,40107,40108],{"class":202,"line":283},[200,40109,528],{"emptyLinePlaceholder":20},[200,40111,40112,40114],{"class":202,"line":300},[200,40113,3758],{"class":206},[200,40115,40116],{"class":210}," books\n",[200,40118,40119,40121],{"class":202,"line":310},[200,40120,413],{"class":214},[200,40122,416],{"class":210},[35,40124,15736,40125,40127,40128,40130,40131,13741,40134,215],{},[156,40126,39781],{}," déclenchera une erreur ",[156,40129,39061],{}," si l'utilisateur n'est pas autorisé à lister les livres. Un contrôle plus granulaire peut être obtenu en utilisant les fonctions ",[156,40132,40133],{},"allows",[156,40135,40136],{},"denies",[192,40138,40140],{"className":1380,"code":40139,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  if (await denies(event, listBooks, book))\n    return []\n\n  const books = await db.query.books.findMany()\n\n  return books\n})\n",[156,40141,40142,40164,40189,40195,40199,40225,40229,40235],{"__ignoreMap":13},[200,40143,40144,40146,40148,40150,40152,40154,40156,40158,40160,40162],{"class":202,"line":203},[200,40145,1390],{"class":206},[200,40147,1393],{"class":206},[200,40149,38723],{"class":223},[200,40151,227],{"class":210},[200,40153,3726],{"class":379},[200,40155,1094],{"class":214},[200,40157,25426],{"class":386},[200,40159,2104],{"class":214},[200,40161,393],{"class":379},[200,40163,280],{"class":214},[200,40165,40166,40168,40170,40172,40175,40177,40179,40181,40183,40185,40187],{"class":202,"line":14},[200,40167,20098],{"class":206},[200,40169,1094],{"class":235},[200,40171,207],{"class":206},[200,40173,40174],{"class":223}," denies",[200,40176,227],{"class":235},[200,40178,25426],{"class":210},[200,40180,549],{"class":214},[200,40182,40070],{"class":210},[200,40184,549],{"class":214},[200,40186,39922],{"class":210},[200,40188,584],{"class":235},[200,40190,40191,40193],{"class":202,"line":255},[200,40192,3311],{"class":206},[200,40194,6433],{"class":235},[200,40196,40197],{"class":202,"line":272},[200,40198,528],{"emptyLinePlaceholder":20},[200,40200,40201,40203,40205,40207,40209,40211,40213,40215,40217,40219,40221,40223],{"class":202,"line":283},[200,40202,3738],{"class":379},[200,40204,40083],{"class":210},[200,40206,509],{"class":214},[200,40208,452],{"class":206},[200,40210,38780],{"class":210},[200,40212,215],{"class":214},[200,40214,3524],{"class":210},[200,40216,215],{"class":214},[200,40218,40098],{"class":210},[200,40220,215],{"class":214},[200,40222,40103],{"class":223},[200,40224,466],{"class":235},[200,40226,40227],{"class":202,"line":300},[200,40228,528],{"emptyLinePlaceholder":20},[200,40230,40231,40233],{"class":202,"line":310},[200,40232,3758],{"class":206},[200,40234,40116],{"class":210},[200,40236,40237,40239],{"class":202,"line":336},[200,40238,413],{"class":214},[200,40240,416],{"class":210},[35,40242,40243],{},"Au lieu de déclencher une erreur, un tableau vide est retourné si l'utilisateur n'est pas autorisé à lister les livres. La méthode appropriée pour gérer l'accès non autorisé dépendra de votre logique commerciale.",[35,40245,40246,40247,40250],{},"Par exemple, retourner une erreur ",[156,40248,40249],{},"404 Not Found"," si l'utilisateur n'est pas autorisé à accéder à une ressource spécifique peut préserver la confidentialité des données. Divulguer l'existence de la ressource pourrait poser un risque pour la sécurité.",[35,40252,40253,40254,1655,40257,215],{},"Cette personnalisation est atteignable dans les aptitudes via les fonctions ",[156,40255,40256],{},"allow",[156,40258,40259],{},"deny",[192,40261,40263],{"className":1380,"code":40262,"language":1383,"meta":13,"style":13},"export const viewBook = defineAbility((user: User, book: Book) => {\n  if (book.status === 'draft') {\n    return deny({\n      statusCode: 404,\n      message: 'Non trouvé',\n    })\n  }\n\n  return allow()\n})\n",[156,40264,40265,40302,40328,40339,40350,40365,40371,40375,40379,40388],{"__ignoreMap":13},[200,40266,40267,40269,40271,40274,40276,40278,40280,40282,40284,40286,40288,40290,40292,40294,40296,40298,40300],{"class":202,"line":203},[200,40268,1390],{"class":206},[200,40270,14305],{"class":379},[200,40272,40273],{"class":210}," viewBook ",[200,40275,449],{"class":214},[200,40277,39907],{"class":223},[200,40279,227],{"class":210},[200,40281,227],{"class":214},[200,40283,26847],{"class":386},[200,40285,239],{"class":214},[200,40287,34635],{"class":1364},[200,40289,549],{"class":214},[200,40291,39922],{"class":386},[200,40293,239],{"class":214},[200,40295,39927],{"class":1364},[200,40297,2104],{"class":214},[200,40299,393],{"class":379},[200,40301,280],{"class":214},[200,40303,40304,40306,40308,40311,40313,40315,40317,40319,40322,40324,40326],{"class":202,"line":14},[200,40305,20098],{"class":206},[200,40307,1094],{"class":235},[200,40309,40310],{"class":210},"book",[200,40312,215],{"class":214},[200,40314,39512],{"class":210},[200,40316,3298],{"class":214},[200,40318,242],{"class":214},[200,40320,40321],{"class":245},"draft",[200,40323,249],{"class":214},[200,40325,5282],{"class":235},[200,40327,230],{"class":214},[200,40329,40330,40332,40335,40337],{"class":202,"line":255},[200,40331,3311],{"class":206},[200,40333,40334],{"class":223}," deny",[200,40336,227],{"class":235},[200,40338,230],{"class":214},[200,40340,40341,40343,40345,40348],{"class":202,"line":272},[200,40342,39212],{"class":235},[200,40344,239],{"class":214},[200,40346,40347],{"class":2060}," 404",[200,40349,252],{"class":214},[200,40351,40352,40354,40356,40358,40361,40363],{"class":202,"line":283},[200,40353,39233],{"class":235},[200,40355,239],{"class":214},[200,40357,242],{"class":214},[200,40359,40360],{"class":245},"Non trouvé",[200,40362,249],{"class":214},[200,40364,252],{"class":214},[200,40366,40367,40369],{"class":202,"line":300},[200,40368,2111],{"class":214},[200,40370,416],{"class":235},[200,40372,40373],{"class":202,"line":310},[200,40374,4505],{"class":214},[200,40376,40377],{"class":202,"line":336},[200,40378,528],{"emptyLinePlaceholder":20},[200,40380,40381,40383,40386],{"class":202,"line":342},[200,40382,3758],{"class":206},[200,40384,40385],{"class":223}," allow",[200,40387,466],{"class":235},[200,40389,40390,40392],{"class":202,"line":365},[200,40391,413],{"class":214},[200,40393,416],{"class":210},[35,40395,40396,40397,40399,40400,40402,40403,40405],{},"Désormais, la fonction ",[156,40398,39781],{}," lancera une erreur ",[156,40401,40249],{}," si l'utilisateur n'est pas autorisé à voir un livre en brouillon, au lieu du ",[156,40404,39061],{}," par défaut.",[35,40407,40408,40409,3092,40411,1655,40413,40416,40417,215],{},"Côté client, les fonctions ",[156,40410,40133],{},[156,40412,40136],{},[156,40414,40415],{},"authorize"," peuvent également être utilisées pour administrer la ",[6862,40418,39848],{},[35,40420,40421,40422,1655,40425,40428,40429,215],{},"De plus, ces fonctions offrent deux composants : ",[156,40423,40424],{},"Can",[156,40426,40427],{},"Cannot",". Ces composants permettent d'afficher ou de cacher des portions d'un template en fonction de la ",[6862,40430,39848],{},[192,40432,40434],{"className":21457,"code":40433,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CCan :ability=\"editBook\" :args=\"[book]\">\n    \u003Cbutton>Modifier\u003C\u002Fbutton>\n  \u003C\u002FCan>\n\u003C\u002Ftemplate>\n",[156,40435,40436,40444,40482,40499,40507],{"__ignoreMap":13},[200,40437,40438,40440,40442],{"class":202,"line":203},[200,40439,599],{"class":214},[200,40441,21468],{"class":235},[200,40443,647],{"class":214},[200,40445,40446,40448,40450,40452,40455,40457,40459,40462,40464,40466,40468,40470,40472,40474,40476,40478,40480],{"class":202,"line":14},[200,40447,656],{"class":214},[200,40449,40424],{"class":235},[200,40451,2186],{"class":214},[200,40453,40454],{"class":379},"ability",[200,40456,449],{"class":214},[200,40458,610],{"class":214},[200,40460,40461],{"class":210},"editBook",[200,40463,610],{"class":214},[200,40465,2186],{"class":214},[200,40467,28348],{"class":379},[200,40469,449],{"class":214},[200,40471,610],{"class":214},[200,40473,9872],{"class":214},[200,40475,40310],{"class":210},[200,40477,360],{"class":214},[200,40479,610],{"class":214},[200,40481,647],{"class":214},[200,40483,40484,40486,40488,40490,40493,40495,40497],{"class":202,"line":255},[200,40485,803],{"class":214},[200,40487,909],{"class":235},[200,40489,674],{"class":214},[200,40491,40492],{"class":210},"Modifier",[200,40494,680],{"class":214},[200,40496,909],{"class":235},[200,40498,647],{"class":214},[200,40500,40501,40503,40505],{"class":202,"line":272},[200,40502,892],{"class":214},[200,40504,40424],{"class":235},[200,40506,647],{"class":214},[200,40508,40509,40511,40513],{"class":202,"line":283},[200,40510,680],{"class":214},[200,40512,21468],{"class":235},[200,40514,647],{"class":214},[35,40516,40517,40518],{},"Consultez le dépôt GitHub pour plus d'informations : ",[1599,40519,215],{"repo":39821},[1929,40521,40522],{"id":28407},"Dernières Pensées",[35,40524,40525,40526,40531],{},"Le code et la conception de ce package s'inspirent fortement du ",[39,40527,40530],{"href":40528,"rel":40529},"https:\u002F\u002Fdocs.adonisjs.com\u002Fguides\u002Fsecurity\u002Fauthorization",[77],"Adonis Bouncer",". C'est un produit de qualité, et réinventer des concepts inutilement est souvent futile.",[35,40533,40534],{},"J'implémente déjà ce package dans Orion, et l'expérience a été très agréable. Je suis convaincu qu'il y a encore un potentiel d'amélioration, mais ce module représente un premier pas pour autonomiser les développeurs Nuxt et faire progresser les capacités full-stack de Nuxt. Les contributions et suggestions pour de nouvelles fonctionnalités ou améliorations sont les bienvenues.",[35,40536,40537],{},"Orion est une collection de templates dirigée par la communauté pour vos projets, s'étendant des pages d'atterrissage aux applications web complètes.",[35,40539,40540,40541,40543,40544,40547],{},"J'espère que cet article et ce module vous aideront à gérer l'",[6862,40542,38666],{}," au sein de votre application Nuxt, empêchant ainsi et restreignant l'",[6862,40545,40546],{},"accès non autorisé"," à vos données. La sécurité est impérative.",[35,40549,40550],{},"Bonne programmation !",[1170,40552,40553],{},"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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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 .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}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":13,"searchDepth":14,"depth":14,"links":40555},[40556,40557,40558,40559,40560],{"id":29077,"depth":14,"text":29078},{"id":38927,"depth":14,"text":38928},{"id":39047,"depth":14,"text":39048},{"id":39377,"depth":14,"text":39378},{"id":39803,"depth":14,"text":39804,"children":40561},[40562,40563,40564],{"id":39832,"depth":255,"text":39833},{"id":39871,"depth":255,"text":39872},{"id":28407,"depth":255,"text":40522},"2024-06-09","Sécurisez votre application Nuxt avec des techniques d'autorisation avancées. Apprenez à restreindre l'accès non autorisé et à protéger les données sensibles.",{},"\u002Ffr\u002Fposts\u002Fnuxt-going-full-stack-how-to-handle-authorization","---\ncontentId: 05da35d9-c21f-4b45-ba86-e88d61339502\ntitle: \"Nuxt Devient Full-Stack : Comment Gérer l'Autorisation ?\"\ndescription: Sécurisez votre application Nuxt avec des techniques d'autorisation avancées. Apprenez à restreindre l'accès non autorisé et à protéger les données sensibles.\ndate: 2024-06-09\n---\n\nLorsque vous développez une application, il est essentiel de **granter** ou de **restrict** l'accès à des zones ou des données spécifiques en fonction du rôle ou des permissions d'un utilisateur. Ce concept est connu sous le nom d'**autorisation**.\n\nL'autorisation est un aspect critique de toute application à des fins de **sécurité**. Il est impératif de protéger les **données sensibles**, telles que l'adresse e-mail ou le mot de passe d'un utilisateur, et de prévenir la divulgation d'**informations privées** qui pourraient compromettre la vie privée des utilisateurs et l'intégrité de votre entreprise.\n\nPour clarifier : **l'authentification** implique de vérifier l'identité d'un utilisateur, tandis que **l'autorisation** consiste à permettre ou à refuser l'accès à des ressources en fonction du rôle ou des permissions de l'utilisateur.\n\nEn construisant Orion, j'ai été confronté à ce défi et j'ai expérimenté plusieurs approches. Dans cet article, je partagerai plusieurs stratégies pour gérer l'autorisation dans une application NuxtHub, allant des méthodes les plus simples aux plus sophistiquées. Il est crucial de comprendre que la méthode la plus complexe n'est pas nécessairement la meilleure pour chaque application.\n\nOrion sert de collection de templates dirigée par la communauté pour vos projets, allant des pages d'atterrissage aux applications web complètes. NuxtHub fonctionne comme une plateforme de déploiement et de gestion pour Nuxt, propulsée par Cloudflare.\n\n\u003CCalloutReadMore id=\"2349bbcd-1535-4f33-8da7-299f46af18b9\" \u002F>\n\n## Le Contexte\n\nConsidérez cet **endpoint** dans votre application Nuxt :\n\n```ts\nexport default defineEventHandler(async (event) => {\n  const id = getRouteParam(event, 'id')\n\n  const publication = await db.query.publications.findOne({\n    where: { id },\n    columns: {\n      title: true,\n      content: true,\n      status: true,\n      authorId: true,\n    },\n  })\n\n  return publication\n})\n```\n\nVous pouvez accéder à cet endpoint en effectuant une requête `GET \u002Fapi\u002Fpublication\u002F\u003Cid>`. Cet endpoint retourne une publication. À première vue, il n'y a pas de problème avec cet endpoint. Cependant, un examen plus attentif révèle qu'une publication a un statut qui peut être `brouillon`, `publiée` ou `supprimée`.\n\nLe problème est que quiconque peut accéder à cet endpoint et consulter une **publication brouillon** ou une **publication supprimée**. Certains pourraient être en mesure de voir le contenu d'une publication avant sa publication, ce qui pourrait contenir des informations vitales telles qu'un communiqué de presse ou le lancement d'un produit. Un tel scénario pourrait nuire considérablement à votre entreprise.\n\nIl s'agit d'un problème de sécurité qui nécessite de restreindre l'accès à cet endpoint, en utilisant le [principe du moindre privilège](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrinciple_of_least_privilege). La sécurité n'est pas simplement une fonctionnalité mais doit être intégrée dans la conception du système.\n\n## Authentifié ou Non\n\nLa section suivante repose sur l'utilisation du package Nuxt [**nuxt-auth-utils**](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils). Néanmoins, son utilisation n'est pas essentielle.\n\nLa première étape pour sécuriser cet **endpoint** consiste à vérifier si l'utilisateur est **authentifié**. Si l'utilisateur ne dispose pas d'une authentification, il convient de renvoyer une erreur `401 Unauthorized`.\n\nEn explorant le package **nuxt-auth-utils**, j'ai découvert un utilitaire serveur appelé `requireUserSession`. Cet utilitaire peut être utilisé au début d'un endpoint pour tenter de récupérer la session de l'utilisateur et déclencher une erreur `401 Unauthorized` s'il n'y a pas de session, indiquant que l'utilisateur n'est pas authentifié.\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await requireUserSession(event)\n\n  const publication = {} \u002F\u002F ...\n\n  return publication\n})\n```\n\nC'est la première étape pour protéger l'endpoint contre les **utilisateurs non authentifiés**. Cependant, si quiconque peut s'inscrire pour créer un compte, il pourrait toujours accéder au contenu d'une publication brouillon. D'autres actions sont donc nécessaires.\n\n## Seulement pour les Administrateurs\n\nEn s'appuyant sur `requireUserSession`, je peux facilement concevoir un nouvel utilitaire nommé `requireAdminSession`. Cette session vérifiera si l'utilisateur est authentifié et dispose de droits d'administrateur. Si l'utilisateur n'est pas un administrateur, une erreur `403 Forbidden` doit être renvoyée. Non autorisé se rapporte à l'authentification, tandis que Forbidden concerne l'autorisation.\n\n```ts\nexport async function requireAdminSession(event: H3Event, opts: { statusCode?: number, message?: string } = {}): Promise\u003CUserSessionRequired> {\n  const userSession = await requireUserSession(event)\n  const { user } = userSession\n\n  if (user.roleType !== 'admin') {\n    throw createError({\n      statusCode: opts.statusCode || 403,\n      message: opts.message || 'Non autorisé',\n    })\n  }\n\n  return userSession as UserSessionRequired\n}\n```\n\nPar la suite, `requireUserSession` peut être remplacé par `requireAdminSession` dans l'endpoint.\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await requireAdminSession(event)\n\n  const publication = {} \u002F\u002F ...\n\n  return publication\n})\n```\n\nMaintenant, seuls les **admins**, les utilisateurs ayant le rôle `admin`, peuvent accéder au contenu d'une publication.\n\nCependant, cette approche n'est pas entièrement efficace, car si la publication est publiée, je souhaite permettre à tous les utilisateurs d'y accéder. Actuellement, seuls les admins peuvent voir le contenu d'une publication, quelle que soit le statut de la publication.\n\n## Problèmes avec ces Approches\n\nLes stratégies mentionnées manquent de flexibilité suffisante pour un système d'**autorisation** détaillé. Elles ne peuvent pas accommoder les situations où l'auteur de la publication devrait avoir accès, même si la publication est non publiée.\n\nPour y remédier, je pourrais rédiger un autre utilitaire nommé `requirePublicationAccess` :\n\n```ts\nexport async function requirePublicationAccess(event: H3Event, publication: Publication, opts: { statusCode?: number, message?: string } = {}): Promise\u003CUserSessionRequired> {\n  const userSession = await getUserSession(event)\n  const { user } = userSession\n\n  if (publication.status === 'published')\n    return userSession\n\n  if (!userSession) {\n    throw createError({\n      statusCode: opts.statusCode || 401,\n      message: opts.message || 'Non autorisé',\n    })\n  }\n\n  if (publication.authorId === user.id)\n    return userSession\n\n  if (user.roleType === 'admin')\n    return userSession\n\n  throw createError({\n    statusCode: opts.statusCode || 403,\n    message: opts.message || 'Interdit',\n  })\n}\n```\n\nCette solution est adéquate tant que je dois gérer d'autres endpoints. Cette approche entraînera une excessive duplication de code concernant le traitement des erreurs. De plus, le code devient plus difficile à tester en raison de la nécessité de gérer un événement H3 entier.\n\nSans une meilleure approche, je continue à développer Orion mais me heurte bientôt à un autre défi : je dois mettre en œuvre la même **logique d'autorisation côté client**. Je ne souhaite pas afficher un bouton d'édition à un utilisateur qui n'est ni l'auteur de la publication ni un administrateur, et je ne veux pas cacher ce bouton d'édition à l'administrateur si je décide de permettre l'édition de la publication sur le serveur. Comme pour [la validation des formulaires](.\u002Fnuxt-going-full-stack-how-to-streamline-form-validation.md), la duplication de la logique peut entraîner des incohérences substantielles entre le client et le serveur, menant à la frustration des clients. Les utilitaires que j'ai créés jusqu'à présent sont uniquement côté serveur en raison de l'utilisation de `requireUserSession` et `getUserSession`.\n\nFaisons un pas en arrière pour comprendre comment ce problème pourrait être résolu. L'autorisation se compose de trois composants :\n\n- **permettre** l'accès à une ressource\n- **refuser** l'accès à une ressource\n- **autoriser** l'accès à une ressource\n\nLes deux premiers sont des conditions simples produisant une réponse booléenne à des questions comme \"Puis-je accéder à cette publication ? Oui ou Non\". Le développeur doit gérer manuellement cette réponse. Le troisième accorde l'accès à la ressource. Pour la question \"Puis-je accéder à cette publication ?\", il n'y a ni \"oui\" ni \"non\" ; rien ne se passe si la réponse est \"oui\", mais une erreur est levée si la réponse est \"non\". L'`autoriser` envoie automatiquement une erreur.\n\nCes éléments n'ont aucun lien direct avec le client, le serveur, ou le framework utilisé ou le système d'authentification. Munis de cette connaissance, j'ai commencé à travailler sur un module local pour Orion afin de gérer l'autorisation de manière plus flexible. Après plusieurs jours d'efforts, je suis fier de vous présenter **nuxt-authorization**.\n\nAu départ, je visais à développer à la fois un module Nitro et un module Nuxt, mais les modules Nitro ne sont pas encore prêts. Cela dit, j'ai soumis quelques demandes de tirage au dépôt Nitro pour améliorer le système de modules. L'une d'elles est le [guide de l'auteur du module](https:\u002F\u002Fgithub.com\u002Funjs\u002Fnitro\u002Fpull\u002F2508).\n\n## Présentation de nuxt-authorization\n\nCe module offre une méthode simple mais puissante pour gérer l'**autorisation** dans une application Nuxt, applicable tant au client qu'au serveur. Il est indépendant du système d'authentification mais peut être intégré sans effort avec [**nuxt-auth-utils**](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils).\n\nPour des instructions sur son utilisation, visitez le dépôt GitHub : \u003CGitHubLink repo=\"barbapapazes\u002Fnuxt-authorization\" \u002F>. Pour explorer la conception et l'implémentation, continuez à lire.\n\nLe module est encore à ses débuts de développement, et si vous avez des retours ou des suggestions d'amélioration, n'hésitez pas à ouvrir un problème sur le dépôt GitHub ou à me contacter sur [X](https:\u002F\u002Fx.com\u002Fbarbapapazes).\n\n### Problèmes que je souhaite résoudre\n\nAvant de décrire ce qui a été accompli avec **nuxt-authorization**, examinons les problèmes que je souhaitais résoudre.\n\nTout comme la validation des formulaires, je vise à maintenir une source de vérité unique et une API unique tant pour l'application que pour le serveur. Cela est crucial pour éviter les divergences entre l'application et le serveur.\n\nJe souhaite également avoir la possibilité de définir la **logique d'autorisation** de manière simple et regroupée par ressource. Cette approche améliore la lisibilité, la maintenabilité et la cohérence à travers divers aspects de l'application, tels que la validation des formulaires.\n\nLa logique d'autorisation doit être suffisamment flexible pour accommoder une gamme variée de cas d'utilisation, des conditions `permettre` ou `refuser` aux conditions `autoriser` plus complexes. Les erreurs produites par la condition `autoriser` devraient être personnalisables pour améliorer l'expérience utilisateur. Toutes les erreurs non autorisées ne doivent pas nécessairement être un `403 Forbidden`.\n\nEnfin, je désire des composants qui facilitent l'intégration de la logique d'autorisation dans les templates. Par exemple, je souhaite avoir la capacité d'afficher ou de cacher un bouton en fonction de la logique d'autorisation.\n\n### Conception\n\nLe module se compose de deux composants : l'**habilité** et le **bouncer**.\n\nLes habilités constituent les règles qui incarnent la **logique d'autorisation**. Elles prennent un utilisateur et une ou plusieurs ressources et génèrent une condition de refus ou d'autorisation. Bien qu'elles puissent être regroupées par ressource, elles restent indépendantes les unes des autres.\n\n```ts\nexport const editBook = defineAbility((user: User, book: Book) => {\n  return user.id === book.authorId\n})\n```\n\nDans l'exemple ci-dessus, seul l'auteur d'un livre est autorisé à le modifier. Par défaut, les aptitudes ne sont exécutées que si l'utilisateur est authentifié, mais il est possible de permettre un accès invité à certaines ressources.\n\n```ts\nexport const listBooks = defineAbility((user: User | null) => {\n  return true\n})\n```\n\nDans cet exemple, tout le monde, y compris les utilisateurs non authentifiés, est autorisé à lister les livres.\n\nLa fonction `defineAbility` agit comme une usine qui crée une aptitude mais rien au-delà de cela. Elle doit être utilisée avec des fonctions bouncer.\n\nDans un endpoint serveur, la fonction `autoriser` peut être utilisée pour accorder l'accès à une ressource basée sur les aptitudes.\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await authorize(event, listBooks)\n\n  const books = await db.query.books.findMany()\n\n  return books\n})\n```\n\nLa fonction `autoriser` déclenchera une erreur `403 Forbidden` si l'utilisateur n'est pas autorisé à lister les livres. Un contrôle plus granulaire peut être obtenu en utilisant les fonctions `allows` ou `denies`.\n\n```ts\nexport default defineEventHandler(async (event) => {\n  if (await denies(event, listBooks, book))\n    return []\n\n  const books = await db.query.books.findMany()\n\n  return books\n})\n```\n\nAu lieu de déclencher une erreur, un tableau vide est retourné si l'utilisateur n'est pas autorisé à lister les livres. La méthode appropriée pour gérer l'accès non autorisé dépendra de votre logique commerciale.\n\nPar exemple, retourner une erreur `404 Not Found` si l'utilisateur n'est pas autorisé à accéder à une ressource spécifique peut préserver la confidentialité des données. Divulguer l'existence de la ressource pourrait poser un risque pour la sécurité.\n\nCette personnalisation est atteignable dans les aptitudes via les fonctions `allow` et `deny`.\n\n```ts\nexport const viewBook = defineAbility((user: User, book: Book) => {\n  if (book.status === 'draft') {\n    return deny({\n      statusCode: 404,\n      message: 'Non trouvé',\n    })\n  }\n\n  return allow()\n})\n```\n\nDésormais, la fonction `autoriser` lancera une erreur `404 Not Found` si l'utilisateur n'est pas autorisé à voir un livre en brouillon, au lieu du `403 Forbidden` par défaut.\n\nCôté client, les fonctions `allows`, `denies` et `authorize` peuvent également être utilisées pour administrer la **logique d'autorisation**.\n\nDe plus, ces fonctions offrent deux composants : `Can` et `Cannot`. Ces composants permettent d'afficher ou de cacher des portions d'un template en fonction de la **logique d'autorisation**.\n\n```vue\n\u003Ctemplate>\n  \u003CCan :ability=\"editBook\" :args=\"[book]\">\n    \u003Cbutton>Modifier\u003C\u002Fbutton>\n  \u003C\u002FCan>\n\u003C\u002Ftemplate>\n```\n\nConsultez le dépôt GitHub pour plus d'informations : \u003CGitHubLink repo=\"barbapapazes\u002Fnuxt-authorization\" \u002F>.\n\n### Dernières Pensées\n\nLe code et la conception de ce package s'inspirent fortement du [Adonis Bouncer](https:\u002F\u002Fdocs.adonisjs.com\u002Fguides\u002Fsecurity\u002Fauthorization). C'est un produit de qualité, et réinventer des concepts inutilement est souvent futile.\n\nJ'implémente déjà ce package dans Orion, et l'expérience a été très agréable. Je suis convaincu qu'il y a encore un potentiel d'amélioration, mais ce module représente un premier pas pour autonomiser les développeurs Nuxt et faire progresser les capacités full-stack de Nuxt. Les contributions et suggestions pour de nouvelles fonctionnalités ou améliorations sont les bienvenues.\n\nOrion est une collection de templates dirigée par la communauté pour vos projets, s'étendant des pages d'atterrissage aux applications web complètes.\n\nJ'espère que cet article et ce module vous aideront à gérer l'**autorisation** au sein de votre application Nuxt, empêchant ainsi et restreignant l'**accès non autorisé** à vos données. La sécurité est impérative.\n\nBonne programmation !\n",{"title":38650,"description":40566},{"loc":40568},"fr\u002Fposts\u002Fnuxt-going-full-stack-how-to-handle-authorization","LhMP1dbeQnx_othzrnPL53z2XcbN_CiWSf0fFfOrRNg",{"id":40575,"title":40576,"body":40577,"contentId":30599,"date":45809,"description":45810,"extension":18,"meta":45811,"navigation":20,"path":45812,"rawbody":45813,"robots":23,"schemaOrg":23,"seo":45814,"sitemap":45815,"stem":45816,"__hash__":45817},"posts\u002Ffr\u002Fposts\u002Fnuxt-going-full-stack-how-to-streamline-form-validation.md","Nuxt Devient Full-Stack : Comment Valider des formulaires ?",{"type":7,"value":40578,"toc":45803},[40579,40590,40595,45800],[35,40580,40581,40582,40585,40586,40589],{},"Depuis des semaines, je construis une application full-stack appelée Orion avec Nuxt et ",[39,40583,36579],{"href":37901,"rel":40584},[77],". Tout au long de ce parcours, j'ai rencontré de nombreux défis, et l'un d'eux est la validation des formulaires. Dans cet article, je vais vous montrer comment j'ai réussi à ",[6862,40587,40588],{},"valider les formulaires à la fois côté client et côté serveur sans duplications"," de la logique de validation, des champs de texte simples aux téléchargements de fichiers complexes avec NuxtHub.",[35,40591,40592],{},[1216,40593,40594],{},"Orion est une collection de modèles basée sur la communauté pour votre prochain projet, des pages de destination aux applications web complètes. NuxtHub est une plateforme de déploiement et d'administration pour Nuxt, propulsée par Cloudflare.",[7918,40596,40597,40606,40612,40614,40632,40643,40647,40650,40773,40776,40860,40866,40871,40875,40890,41031,41037],{"id":30589},[35,40598,40599],{},[1216,40600,40601,40602,40605],{},"Pendant cet article, je vais parler de ",[39,40603,36579],{"href":37901,"rel":40604},[77]," car il fournit les primitives pour construire des applications full-stack, mais même si vous ne l'utilisez pas, vous pouvez facilement suivre cet article. Les formulaires seront gérés sans aucune bibliothèque.",[35,40607,40608,40609],{},"Vous pouvez parcourir le code exemple dans ",[1599,40610,215],{"repo":40611},"barbapapazes\u002Fvalidate-forms-in-nuxt",[51,40613,18647],{"id":18646},[35,40615,40616,40617,40620,40621,40624,40625,1655,40628,40631],{},"Sur le web, les formulaires sont omniprésents. Ils sont le principal moyen d'interaction et de demande d'entrée utilisateur. Mais sur le web, il y a une règle essentielle : ",[6862,40618,40619],{},"ne jamais faire confiance au client"," (absolument jamais et en aucun cas). Cela signifie que ",[6862,40622,40623],{},"toutes les données"," provenant du client doivent être ",[6862,40626,40627],{},"vérifiées",[6862,40629,40630],{},"sanitisées"," sur le serveur avant d'être traitées ou stockées dans une base de données. Un client peut être un navigateur, une application mobile, une requête HTTP ou tout autre objet capable d'envoyer des données à votre serveur, construit par vous ou non.",[35,40633,40634,40635,40638,40639,40642],{},"Lorsque vous construisez une MPA traditionnelle (Multi-Page Application), seul le serveur est responsable de la validation. Pour une SPA (Single-Page Application), à la fois le client et le serveur sont responsables de la validation pour fournir une meilleure expérience utilisateur. ",[1216,40636,40637],{},"Cela est simplifié car il existe un moyen de donner une impression de validation côté client avec une MPA traditionnelle, mais ce n'est pas le sujet de cet article."," MAIS, même si les données sont validées côté client, il est ",[6862,40640,40641],{},"obligatoire"," de les valider côté serveur.",[51,40644,40646],{"id":40645},"le-formulaire-le-plus-simple","Le Formulaire le Plus Simple",[35,40648,40649],{},"Commençons notre parcours avec le formulaire le plus simple : une seule entrée de texte.",[192,40651,40653],{"className":21457,"code":40652,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003Cform method=\"post\" action=\"\u002Ftag\">\n    \u003Cinput type=\"text\" placeholder=\"Nom\" required>\n    \u003Cbutton type=\"submit\">\n      Créer Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,40654,40655,40663,40693,40726,40744,40749,40757,40765],{"__ignoreMap":13},[200,40656,40657,40659,40661],{"class":202,"line":203},[200,40658,599],{"class":214},[200,40660,21468],{"class":235},[200,40662,647],{"class":214},[200,40664,40665,40667,40669,40671,40673,40675,40677,40679,40682,40684,40686,40689,40691],{"class":202,"line":14},[200,40666,656],{"class":214},[200,40668,602],{"class":235},[200,40670,6589],{"class":379},[200,40672,449],{"class":214},[200,40674,610],{"class":214},[200,40676,3793],{"class":245},[200,40678,610],{"class":214},[200,40680,40681],{"class":379}," action",[200,40683,449],{"class":214},[200,40685,610],{"class":214},[200,40687,40688],{"class":245},"\u002Ftag",[200,40690,610],{"class":214},[200,40692,647],{"class":214},[200,40694,40695,40697,40699,40701,40703,40705,40707,40709,40712,40714,40716,40719,40721,40724],{"class":202,"line":255},[200,40696,803],{"class":214},[200,40698,355],{"class":235},[200,40700,321],{"class":379},[200,40702,449],{"class":214},[200,40704,610],{"class":214},[200,40706,697],{"class":245},[200,40708,610],{"class":214},[200,40710,40711],{"class":379}," placeholder",[200,40713,449],{"class":214},[200,40715,610],{"class":214},[200,40717,40718],{"class":245},"Nom",[200,40720,610],{"class":214},[200,40722,40723],{"class":379}," required",[200,40725,647],{"class":214},[200,40727,40728,40730,40732,40734,40736,40738,40740,40742],{"class":202,"line":272},[200,40729,803],{"class":214},[200,40731,909],{"class":235},[200,40733,321],{"class":379},[200,40735,449],{"class":214},[200,40737,610],{"class":214},[200,40739,916],{"class":245},[200,40741,610],{"class":214},[200,40743,647],{"class":214},[200,40745,40746],{"class":202,"line":283},[200,40747,40748],{"class":210},"      Créer Tag\n",[200,40750,40751,40753,40755],{"class":202,"line":300},[200,40752,15078],{"class":214},[200,40754,909],{"class":235},[200,40756,647],{"class":214},[200,40758,40759,40761,40763],{"class":202,"line":310},[200,40760,892],{"class":214},[200,40762,602],{"class":235},[200,40764,647],{"class":214},[200,40766,40767,40769,40771],{"class":202,"line":336},[200,40768,680],{"class":214},[200,40770,21468],{"class":235},[200,40772,647],{"class":214},[35,40774,40775],{},"Ensuite, côté serveur, je peux créer une route pour gérer la soumission du formulaire :",[192,40777,40779],{"className":1380,"code":40778,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n",[156,40780,40781,40803,40823,40827,40832,40836,40854],{"__ignoreMap":13},[200,40782,40783,40785,40787,40789,40791,40793,40795,40797,40799,40801],{"class":202,"line":203},[200,40784,1390],{"class":206},[200,40786,1393],{"class":206},[200,40788,38723],{"class":223},[200,40790,227],{"class":210},[200,40792,3726],{"class":379},[200,40794,1094],{"class":214},[200,40796,25426],{"class":386},[200,40798,2104],{"class":214},[200,40800,393],{"class":379},[200,40802,280],{"class":214},[200,40804,40805,40807,40810,40812,40814,40817,40819,40821],{"class":202,"line":14},[200,40806,3738],{"class":379},[200,40808,40809],{"class":210}," body",[200,40811,509],{"class":214},[200,40813,452],{"class":206},[200,40815,40816],{"class":223}," readBody",[200,40818,227],{"class":235},[200,40820,25426],{"class":210},[200,40822,416],{"class":235},[200,40824,40825],{"class":202,"line":255},[200,40826,528],{"emptyLinePlaceholder":20},[200,40828,40829],{"class":202,"line":272},[200,40830,40831],{"class":401},"  \u002F\u002F Enregistrer dans la base de données...\n",[200,40833,40834],{"class":202,"line":283},[200,40835,528],{"emptyLinePlaceholder":20},[200,40837,40838,40840,40843,40845,40847,40849,40852],{"class":202,"line":300},[200,40839,3758],{"class":206},[200,40841,40842],{"class":223}," sendNoContent",[200,40844,227],{"class":235},[200,40846,25426],{"class":210},[200,40848,549],{"class":214},[200,40850,40851],{"class":2060}," 201",[200,40853,416],{"class":235},[200,40855,40856,40858],{"class":202,"line":310},[200,40857,413],{"class":214},[200,40859,416],{"class":210},[35,40861,40862,40863],{},"Pour l'instant, je n'ai aucune validation. Je peux envoyer n'importe quoi au serveur, et il l'acceptera. ",[1216,40864,40865],{},"C'est un énorme problème de sécurité.",[35,40867,40868],{},[1216,40869,40870],{},"Rappelez-vous que la façon dont je gère la validation est spécifique à une SPA. Une MPA traditionnelle aurait ajouté des erreurs aux messages flash et redirigé la demande.",[1929,40872,40874],{"id":40873},"valider-le-corps","Valider le Corps",[35,40876,40877,40878,106,40883,40886,40887,215],{},"Pour valider le corps côté serveur, j'utilise ",[39,40879,40882],{"href":40880,"rel":40881},"https:\u002F\u002Fzod.dev",[77],"Zod",[1216,40884,40885],{},"Zod est une validation de schéma en TypeScript avec inférence de type statique."," La bonne nouvelle est qu'h3, le routeur et le framework web qui alimentent Nitro, la couche serveur de Nuxt, fournit une utilitaire pour valider le corps : ",[156,40888,40889],{},"readValidatedBody",[192,40891,40893],{"className":1380,"code":40892,"language":1383,"meta":13,"style":13},"import { z } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, z.object({\n    name: z.string(),\n  }).parse)\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n",[156,40894,40895,40913,40917,40939,40968,40984,40997,41001,41005,41009,41025],{"__ignoreMap":13},[200,40896,40897,40899,40901,40903,40905,40907,40909,40911],{"class":202,"line":203},[200,40898,972],{"class":206},[200,40900,318],{"class":214},[200,40902,3180],{"class":210},[200,40904,980],{"class":214},[200,40906,983],{"class":206},[200,40908,242],{"class":214},[200,40910,19813],{"class":245},[200,40912,991],{"class":214},[200,40914,40915],{"class":202,"line":14},[200,40916,528],{"emptyLinePlaceholder":20},[200,40918,40919,40921,40923,40925,40927,40929,40931,40933,40935,40937],{"class":202,"line":255},[200,40920,1390],{"class":206},[200,40922,1393],{"class":206},[200,40924,38723],{"class":223},[200,40926,227],{"class":210},[200,40928,3726],{"class":379},[200,40930,1094],{"class":214},[200,40932,25426],{"class":386},[200,40934,2104],{"class":214},[200,40936,393],{"class":379},[200,40938,280],{"class":214},[200,40940,40941,40943,40945,40947,40949,40952,40954,40956,40958,40960,40962,40964,40966],{"class":202,"line":272},[200,40942,3738],{"class":379},[200,40944,40809],{"class":210},[200,40946,509],{"class":214},[200,40948,452],{"class":206},[200,40950,40951],{"class":223}," readValidatedBody",[200,40953,227],{"class":235},[200,40955,25426],{"class":210},[200,40957,549],{"class":214},[200,40959,3180],{"class":210},[200,40961,215],{"class":214},[200,40963,293],{"class":223},[200,40965,227],{"class":235},[200,40967,230],{"class":214},[200,40969,40970,40972,40974,40976,40978,40980,40982],{"class":202,"line":283},[200,40971,11812],{"class":235},[200,40973,239],{"class":214},[200,40975,3180],{"class":210},[200,40977,215],{"class":214},[200,40979,328],{"class":223},[200,40981,1549],{"class":235},[200,40983,252],{"class":214},[200,40985,40986,40988,40990,40992,40995],{"class":202,"line":300},[200,40987,5095],{"class":214},[200,40989,2104],{"class":235},[200,40991,215],{"class":214},[200,40993,40994],{"class":210},"parse",[200,40996,416],{"class":235},[200,40998,40999],{"class":202,"line":310},[200,41000,528],{"emptyLinePlaceholder":20},[200,41002,41003],{"class":202,"line":336},[200,41004,40831],{"class":401},[200,41006,41007],{"class":202,"line":342},[200,41008,528],{"emptyLinePlaceholder":20},[200,41010,41011,41013,41015,41017,41019,41021,41023],{"class":202,"line":365},[200,41012,3758],{"class":206},[200,41014,40842],{"class":223},[200,41016,227],{"class":235},[200,41018,25426],{"class":210},[200,41020,549],{"class":214},[200,41022,40851],{"class":2060},[200,41024,416],{"class":235},[200,41026,41027,41029],{"class":202,"line":371},[200,41028,413],{"class":214},[200,41030,416],{"class":210},[35,41032,41033,41034,41036],{},"L'utilitaire ",[156,41035,40889],{}," lancera automatiquement une erreur si le corps ne correspond pas au schéma. Par exemple, si le nom est manquant, la requête échouera.",[7151,41038,41042,41045,41052,41307,41314,41593,41601,41605,41608,42093,42113,42117,42120,42123,42133,42219,42226,42229,42232,42325,42328,42439,42442,42446,42453,42458,42462,42465,42598,42609,42624,43119,43122,43128,43402,43405,43832,43839,43842,43934,43938,43947,44128,44132,44135,44141,44144,44322,44325,44625,44628,44634,44985,44992,45254,45264,45609,45614,45777,45779,45794,45797],{"icon":41039,"text":41040,"to":41041},"i-unjs-h3","Valider les Données avec H3","https:\u002F\u002Fh3.unjs.io\u002Fexamples\u002Fvalidate-data",[35,41043,41044],{},"Par défaut, l'erreur sera gérée par le serveur et lancera une erreur 500, mais je peux capturer l'erreur pour renvoyer un message d'erreur plus convivial.",[35,41046,41047,41048,41051],{},"La première solution consiste à utiliser la méthode ",[156,41049,41050],{},"safeParse"," qui ne lancera pas d'erreur mais renverra un résultat avec l'erreur le cas échéant.",[192,41053,41055],{"className":1380,"code":41054,"language":1383,"meta":13,"style":13},"import { z } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const result = await readValidatedBody(event, z.object({\n    name: z.string(),\n  }).safeParse)\n\n  if (!result.success) {\n    throw createError({\n      status: 400,\n      message: 'Données non valides',\n      data: result.error.errors[0].message,\n    })\n  }\n\n  const body = result.data\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n",[156,41056,41057,41075,41079,41101,41129,41145,41157,41161,41180,41190,41201,41216,41245,41251,41255,41259,41273,41277,41281,41285,41301],{"__ignoreMap":13},[200,41058,41059,41061,41063,41065,41067,41069,41071,41073],{"class":202,"line":203},[200,41060,972],{"class":206},[200,41062,318],{"class":214},[200,41064,3180],{"class":210},[200,41066,980],{"class":214},[200,41068,983],{"class":206},[200,41070,242],{"class":214},[200,41072,19813],{"class":245},[200,41074,991],{"class":214},[200,41076,41077],{"class":202,"line":14},[200,41078,528],{"emptyLinePlaceholder":20},[200,41080,41081,41083,41085,41087,41089,41091,41093,41095,41097,41099],{"class":202,"line":255},[200,41082,1390],{"class":206},[200,41084,1393],{"class":206},[200,41086,38723],{"class":223},[200,41088,227],{"class":210},[200,41090,3726],{"class":379},[200,41092,1094],{"class":214},[200,41094,25426],{"class":386},[200,41096,2104],{"class":214},[200,41098,393],{"class":379},[200,41100,280],{"class":214},[200,41102,41103,41105,41107,41109,41111,41113,41115,41117,41119,41121,41123,41125,41127],{"class":202,"line":272},[200,41104,3738],{"class":379},[200,41106,3561],{"class":210},[200,41108,509],{"class":214},[200,41110,452],{"class":206},[200,41112,40951],{"class":223},[200,41114,227],{"class":235},[200,41116,25426],{"class":210},[200,41118,549],{"class":214},[200,41120,3180],{"class":210},[200,41122,215],{"class":214},[200,41124,293],{"class":223},[200,41126,227],{"class":235},[200,41128,230],{"class":214},[200,41130,41131,41133,41135,41137,41139,41141,41143],{"class":202,"line":283},[200,41132,11812],{"class":235},[200,41134,239],{"class":214},[200,41136,3180],{"class":210},[200,41138,215],{"class":214},[200,41140,328],{"class":223},[200,41142,1549],{"class":235},[200,41144,252],{"class":214},[200,41146,41147,41149,41151,41153,41155],{"class":202,"line":300},[200,41148,5095],{"class":214},[200,41150,2104],{"class":235},[200,41152,215],{"class":214},[200,41154,41050],{"class":210},[200,41156,416],{"class":235},[200,41158,41159],{"class":202,"line":310},[200,41160,528],{"emptyLinePlaceholder":20},[200,41162,41163,41165,41167,41169,41171,41173,41176,41178],{"class":202,"line":336},[200,41164,20098],{"class":206},[200,41166,1094],{"class":235},[200,41168,11852],{"class":214},[200,41170,3546],{"class":210},[200,41172,215],{"class":214},[200,41174,41175],{"class":210},"success",[200,41177,5282],{"class":235},[200,41179,230],{"class":214},[200,41181,41182,41184,41186,41188],{"class":202,"line":342},[200,41183,39200],{"class":206},[200,41185,39203],{"class":223},[200,41187,227],{"class":235},[200,41189,230],{"class":214},[200,41191,41192,41194,41196,41199],{"class":202,"line":365},[200,41193,38845],{"class":235},[200,41195,239],{"class":214},[200,41197,41198],{"class":2060}," 400",[200,41200,252],{"class":214},[200,41202,41203,41205,41207,41209,41212,41214],{"class":202,"line":371},[200,41204,39233],{"class":235},[200,41206,239],{"class":214},[200,41208,242],{"class":214},[200,41210,41211],{"class":245},"Données non valides",[200,41213,249],{"class":214},[200,41215,252],{"class":214},[200,41217,41218,41221,41223,41225,41227,41229,41231,41233,41235,41237,41239,41241,41243],{"class":202,"line":398},[200,41219,41220],{"class":235},"      data",[200,41222,239],{"class":214},[200,41224,3561],{"class":210},[200,41226,215],{"class":214},[200,41228,19275],{"class":210},[200,41230,215],{"class":214},[200,41232,26828],{"class":210},[200,41234,9872],{"class":235},[200,41236,3948],{"class":2060},[200,41238,360],{"class":235},[200,41240,215],{"class":214},[200,41242,29303],{"class":210},[200,41244,252],{"class":214},[200,41246,41247,41249],{"class":202,"line":405},[200,41248,2111],{"class":214},[200,41250,416],{"class":235},[200,41252,41253],{"class":202,"line":410},[200,41254,4505],{"class":214},[200,41256,41257],{"class":202,"line":860},[200,41258,528],{"emptyLinePlaceholder":20},[200,41260,41261,41263,41265,41267,41269,41271],{"class":202,"line":889},[200,41262,3738],{"class":379},[200,41264,40809],{"class":210},[200,41266,509],{"class":214},[200,41268,3561],{"class":210},[200,41270,215],{"class":214},[200,41272,3765],{"class":210},[200,41274,41275],{"class":202,"line":899},[200,41276,528],{"emptyLinePlaceholder":20},[200,41278,41279],{"class":202,"line":904},[200,41280,40831],{"class":401},[200,41282,41283],{"class":202,"line":930},[200,41284,528],{"emptyLinePlaceholder":20},[200,41286,41287,41289,41291,41293,41295,41297,41299],{"class":202,"line":3655},[200,41288,3758],{"class":206},[200,41290,40842],{"class":223},[200,41292,227],{"class":235},[200,41294,25426],{"class":210},[200,41296,549],{"class":214},[200,41298,40851],{"class":2060},[200,41300,416],{"class":235},[200,41302,41303,41305],{"class":202,"line":3660},[200,41304,413],{"class":214},[200,41306,416],{"class":210},[35,41308,41309,41310,41313],{},"La seconde solution consiste à utiliser la méthode ",[156,41311,41312],{},"parseAsync"," pour attacher une gestion d'erreur manuelle à la promesse.",[192,41315,41317],{"className":1380,"code":41316,"language":1383,"meta":13,"style":13},"import type { ZodError } from 'zod'\nimport { z } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, data => z.object({\n    name: z.string(),\n  }).parseAsync(data).catch((error) => {\n    if (error instanceof ZodError) {\n      throw createError({\n        status: 400,\n        message: 'Données non valides',\n        data: error.errors[0].message,\n      })\n    }\n  }))\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n",[156,41318,41319,41340,41358,41362,41384,41416,41432,41465,41482,41492,41503,41518,41543,41549,41553,41559,41563,41567,41571,41587],{"__ignoreMap":13},[200,41320,41321,41323,41325,41327,41330,41332,41334,41336,41338],{"class":202,"line":203},[200,41322,972],{"class":206},[200,41324,321],{"class":206},[200,41326,318],{"class":214},[200,41328,41329],{"class":210}," ZodError",[200,41331,980],{"class":214},[200,41333,983],{"class":206},[200,41335,242],{"class":214},[200,41337,19813],{"class":245},[200,41339,991],{"class":214},[200,41341,41342,41344,41346,41348,41350,41352,41354,41356],{"class":202,"line":14},[200,41343,972],{"class":206},[200,41345,318],{"class":214},[200,41347,3180],{"class":210},[200,41349,980],{"class":214},[200,41351,983],{"class":206},[200,41353,242],{"class":214},[200,41355,19813],{"class":245},[200,41357,991],{"class":214},[200,41359,41360],{"class":202,"line":255},[200,41361,528],{"emptyLinePlaceholder":20},[200,41363,41364,41366,41368,41370,41372,41374,41376,41378,41380,41382],{"class":202,"line":272},[200,41365,1390],{"class":206},[200,41367,1393],{"class":206},[200,41369,38723],{"class":223},[200,41371,227],{"class":210},[200,41373,3726],{"class":379},[200,41375,1094],{"class":214},[200,41377,25426],{"class":386},[200,41379,2104],{"class":214},[200,41381,393],{"class":379},[200,41383,280],{"class":214},[200,41385,41386,41388,41390,41392,41394,41396,41398,41400,41402,41404,41406,41408,41410,41412,41414],{"class":202,"line":283},[200,41387,3738],{"class":379},[200,41389,40809],{"class":210},[200,41391,509],{"class":214},[200,41393,452],{"class":206},[200,41395,40951],{"class":223},[200,41397,227],{"class":235},[200,41399,25426],{"class":210},[200,41401,549],{"class":214},[200,41403,5056],{"class":386},[200,41405,393],{"class":379},[200,41407,3180],{"class":210},[200,41409,215],{"class":214},[200,41411,293],{"class":223},[200,41413,227],{"class":235},[200,41415,230],{"class":214},[200,41417,41418,41420,41422,41424,41426,41428,41430],{"class":202,"line":300},[200,41419,11812],{"class":235},[200,41421,239],{"class":214},[200,41423,3180],{"class":210},[200,41425,215],{"class":214},[200,41427,328],{"class":223},[200,41429,1549],{"class":235},[200,41431,252],{"class":214},[200,41433,41434,41436,41438,41440,41442,41444,41446,41448,41450,41453,41455,41457,41459,41461,41463],{"class":202,"line":310},[200,41435,5095],{"class":214},[200,41437,2104],{"class":235},[200,41439,215],{"class":214},[200,41441,41312],{"class":223},[200,41443,227],{"class":235},[200,41445,12736],{"class":210},[200,41447,2104],{"class":235},[200,41449,215],{"class":214},[200,41451,41452],{"class":223},"catch",[200,41454,227],{"class":235},[200,41456,227],{"class":214},[200,41458,19275],{"class":386},[200,41460,2104],{"class":214},[200,41462,393],{"class":379},[200,41464,280],{"class":214},[200,41466,41467,41469,41471,41473,41476,41478,41480],{"class":202,"line":336},[200,41468,5267],{"class":206},[200,41470,1094],{"class":235},[200,41472,19275],{"class":210},[200,41474,41475],{"class":214}," instanceof",[200,41477,41329],{"class":1364},[200,41479,5282],{"class":235},[200,41481,230],{"class":214},[200,41483,41484,41486,41488,41490],{"class":202,"line":342},[200,41485,20250],{"class":206},[200,41487,39203],{"class":223},[200,41489,227],{"class":235},[200,41491,230],{"class":214},[200,41493,41494,41497,41499,41501],{"class":202,"line":365},[200,41495,41496],{"class":235},"        status",[200,41498,239],{"class":214},[200,41500,41198],{"class":2060},[200,41502,252],{"class":214},[200,41504,41505,41508,41510,41512,41514,41516],{"class":202,"line":371},[200,41506,41507],{"class":235},"        message",[200,41509,239],{"class":214},[200,41511,242],{"class":214},[200,41513,41211],{"class":245},[200,41515,249],{"class":214},[200,41517,252],{"class":214},[200,41519,41520,41523,41525,41527,41529,41531,41533,41535,41537,41539,41541],{"class":202,"line":398},[200,41521,41522],{"class":235},"        data",[200,41524,239],{"class":214},[200,41526,19220],{"class":210},[200,41528,215],{"class":214},[200,41530,26828],{"class":210},[200,41532,9872],{"class":235},[200,41534,3948],{"class":2060},[200,41536,360],{"class":235},[200,41538,215],{"class":214},[200,41540,29303],{"class":210},[200,41542,252],{"class":214},[200,41544,41545,41547],{"class":202,"line":405},[200,41546,2647],{"class":214},[200,41548,416],{"class":235},[200,41550,41551],{"class":202,"line":410},[200,41552,4977],{"class":214},[200,41554,41555,41557],{"class":202,"line":860},[200,41556,5095],{"class":214},[200,41558,584],{"class":235},[200,41560,41561],{"class":202,"line":889},[200,41562,528],{"emptyLinePlaceholder":20},[200,41564,41565],{"class":202,"line":899},[200,41566,40831],{"class":401},[200,41568,41569],{"class":202,"line":904},[200,41570,528],{"emptyLinePlaceholder":20},[200,41572,41573,41575,41577,41579,41581,41583,41585],{"class":202,"line":930},[200,41574,3758],{"class":206},[200,41576,40842],{"class":223},[200,41578,227],{"class":235},[200,41580,25426],{"class":210},[200,41582,549],{"class":214},[200,41584,40851],{"class":2060},[200,41586,416],{"class":235},[200,41588,41589,41591],{"class":202,"line":3655},[200,41590,413],{"class":214},[200,41592,416],{"class":210},[35,41594,41595],{},[1216,41596,41597,41598,41600],{},"Vous pourriez envelopper le validateur avec un ",[156,41599,26285],{},", mais je trouve cela moins élégant et lisible.",[1929,41602,41604],{"id":41603},"valider-le-frontend","Valider le Frontend",[35,41606,41607],{},"Côté frontend, je peux également utiliser Zod pour valider le formulaire avant de l'envoyer au serveur.",[192,41609,41611],{"className":21457,"code":41610,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport type { ZodError } from 'zod'\nimport { z } from 'zod'\n\nconst schema = z.object({\n  name: z.string({ message: 'Obligatoire' }), \u002F\u002F Je peux personnaliser le message d'erreur affiché à l'utilisateur\n})\n\nconst state = reactive({\n  name: '',\n})\n\nasync function onSubmit() {\n  try {\n    const data = schema.parse(state)\n    await $fetch('\u002Ftag', {\n      method: 'post',\n      body: data,\n    })\n  }\n  catch (error) {\n    if (error instanceof ZodError) {\n      throw createError({\n        status: 400,\n        message: 'Données non valides',\n        data: error.errors,\n      })\n    }\n  }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cform @submit.prevent=\"onSubmit()\">\n    \u003Cinput v-model=\"state.name\" type=\"text\" placeholder=\"Nom\" required>\n    \u003Cbutton type=\"submit\">\n      Créer Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,41612,41613,41633,41653,41671,41675,41694,41730,41736,41740,41755,41765,41771,41775,41788,41795,41816,41834,41849,41860,41866,41870,41883,41899,41909,41919,41933,41947,41953,41957,41961,41965,41973,41977,41985,42005,42047,42065,42069,42077,42085],{"__ignoreMap":13},[200,41614,41615,41617,41619,41621,41623,41625,41627,41629,41631],{"class":202,"line":203},[200,41616,599],{"class":214},[200,41618,14896],{"class":235},[200,41620,14594],{"class":379},[200,41622,449],{"class":214},[200,41624,610],{"class":214},[200,41626,1383],{"class":245},[200,41628,610],{"class":214},[200,41630,21825],{"class":379},[200,41632,647],{"class":214},[200,41634,41635,41637,41639,41641,41643,41645,41647,41649,41651],{"class":202,"line":14},[200,41636,972],{"class":206},[200,41638,321],{"class":206},[200,41640,318],{"class":214},[200,41642,41329],{"class":210},[200,41644,980],{"class":214},[200,41646,983],{"class":206},[200,41648,242],{"class":214},[200,41650,19813],{"class":245},[200,41652,991],{"class":214},[200,41654,41655,41657,41659,41661,41663,41665,41667,41669],{"class":202,"line":255},[200,41656,972],{"class":206},[200,41658,318],{"class":214},[200,41660,3180],{"class":210},[200,41662,980],{"class":214},[200,41664,983],{"class":206},[200,41666,242],{"class":214},[200,41668,19813],{"class":245},[200,41670,991],{"class":214},[200,41672,41673],{"class":202,"line":272},[200,41674,528],{"emptyLinePlaceholder":20},[200,41676,41677,41679,41682,41684,41686,41688,41690,41692],{"class":202,"line":283},[200,41678,443],{"class":379},[200,41680,41681],{"class":210}," schema ",[200,41683,449],{"class":214},[200,41685,3180],{"class":210},[200,41687,215],{"class":214},[200,41689,293],{"class":223},[200,41691,227],{"class":210},[200,41693,230],{"class":214},[200,41695,41696,41698,41700,41702,41704,41706,41708,41710,41712,41714,41716,41719,41721,41723,41725,41727],{"class":202,"line":300},[200,41697,236],{"class":235},[200,41699,239],{"class":214},[200,41701,3180],{"class":210},[200,41703,215],{"class":214},[200,41705,328],{"class":223},[200,41707,227],{"class":210},[200,41709,2767],{"class":214},[200,41711,39109],{"class":235},[200,41713,239],{"class":214},[200,41715,242],{"class":214},[200,41717,41718],{"class":245},"Obligatoire",[200,41720,249],{"class":214},[200,41722,980],{"class":214},[200,41724,2104],{"class":210},[200,41726,549],{"class":214},[200,41728,41729],{"class":401}," \u002F\u002F Je peux personnaliser le message d'erreur affiché à l'utilisateur\n",[200,41731,41732,41734],{"class":202,"line":310},[200,41733,413],{"class":214},[200,41735,416],{"class":210},[200,41737,41738],{"class":202,"line":336},[200,41739,528],{"emptyLinePlaceholder":20},[200,41741,41742,41744,41746,41748,41751,41753],{"class":202,"line":342},[200,41743,443],{"class":379},[200,41745,18960],{"class":210},[200,41747,449],{"class":214},[200,41749,41750],{"class":223}," reactive",[200,41752,227],{"class":210},[200,41754,230],{"class":214},[200,41756,41757,41759,41761,41763],{"class":202,"line":365},[200,41758,236],{"class":235},[200,41760,239],{"class":214},[200,41762,3909],{"class":214},[200,41764,252],{"class":214},[200,41766,41767,41769],{"class":202,"line":371},[200,41768,413],{"class":214},[200,41770,416],{"class":210},[200,41772,41773],{"class":202,"line":398},[200,41774,528],{"emptyLinePlaceholder":20},[200,41776,41777,41779,41781,41784,41786],{"class":202,"line":405},[200,41778,3726],{"class":379},[200,41780,5002],{"class":379},[200,41782,41783],{"class":223}," onSubmit",[200,41785,1549],{"class":214},[200,41787,280],{"class":214},[200,41789,41790,41793],{"class":202,"line":410},[200,41791,41792],{"class":206},"  try",[200,41794,280],{"class":214},[200,41796,41797,41799,41801,41803,41806,41808,41810,41812,41814],{"class":202,"line":860},[200,41798,2014],{"class":379},[200,41800,5056],{"class":210},[200,41802,509],{"class":214},[200,41804,41805],{"class":210}," schema",[200,41807,215],{"class":214},[200,41809,40994],{"class":223},[200,41811,227],{"class":235},[200,41813,26875],{"class":210},[200,41815,416],{"class":235},[200,41817,41818,41820,41822,41824,41826,41828,41830,41832],{"class":202,"line":889},[200,41819,19231],{"class":206},[200,41821,18754],{"class":223},[200,41823,227],{"class":235},[200,41825,249],{"class":214},[200,41827,40688],{"class":245},[200,41829,249],{"class":214},[200,41831,549],{"class":214},[200,41833,280],{"class":214},[200,41835,41836,41839,41841,41843,41845,41847],{"class":202,"line":899},[200,41837,41838],{"class":235},"      method",[200,41840,239],{"class":214},[200,41842,242],{"class":214},[200,41844,3793],{"class":245},[200,41846,249],{"class":214},[200,41848,252],{"class":214},[200,41850,41851,41854,41856,41858],{"class":202,"line":904},[200,41852,41853],{"class":235},"      body",[200,41855,239],{"class":214},[200,41857,5056],{"class":210},[200,41859,252],{"class":214},[200,41861,41862,41864],{"class":202,"line":930},[200,41863,2111],{"class":214},[200,41865,416],{"class":235},[200,41867,41868],{"class":202,"line":3655},[200,41869,4505],{"class":214},[200,41871,41872,41875,41877,41879,41881],{"class":202,"line":3660},[200,41873,41874],{"class":206},"  catch",[200,41876,1094],{"class":235},[200,41878,19275],{"class":210},[200,41880,5282],{"class":235},[200,41882,230],{"class":214},[200,41884,41885,41887,41889,41891,41893,41895,41897],{"class":202,"line":4035},[200,41886,5267],{"class":206},[200,41888,1094],{"class":235},[200,41890,19275],{"class":210},[200,41892,41475],{"class":214},[200,41894,41329],{"class":1364},[200,41896,5282],{"class":235},[200,41898,230],{"class":214},[200,41900,41901,41903,41905,41907],{"class":202,"line":4490},[200,41902,20250],{"class":206},[200,41904,39203],{"class":223},[200,41906,227],{"class":235},[200,41908,230],{"class":214},[200,41910,41911,41913,41915,41917],{"class":202,"line":4496},[200,41912,41496],{"class":235},[200,41914,239],{"class":214},[200,41916,41198],{"class":2060},[200,41918,252],{"class":214},[200,41920,41921,41923,41925,41927,41929,41931],{"class":202,"line":4502},[200,41922,41507],{"class":235},[200,41924,239],{"class":214},[200,41926,242],{"class":214},[200,41928,41211],{"class":245},[200,41930,249],{"class":214},[200,41932,252],{"class":214},[200,41934,41935,41937,41939,41941,41943,41945],{"class":202,"line":4508},[200,41936,41522],{"class":235},[200,41938,239],{"class":214},[200,41940,19220],{"class":210},[200,41942,215],{"class":214},[200,41944,26828],{"class":210},[200,41946,252],{"class":214},[200,41948,41949,41951],{"class":202,"line":5469},[200,41950,2647],{"class":214},[200,41952,416],{"class":235},[200,41954,41955],{"class":202,"line":5474},[200,41956,4977],{"class":214},[200,41958,41959],{"class":202,"line":13237},[200,41960,4505],{"class":214},[200,41962,41963],{"class":202,"line":13263},[200,41964,1129],{"class":214},[200,41966,41967,41969,41971],{"class":202,"line":13279},[200,41968,680],{"class":214},[200,41970,14896],{"class":235},[200,41972,647],{"class":214},[200,41974,41975],{"class":202,"line":13323},[200,41976,528],{"emptyLinePlaceholder":20},[200,41978,41979,41981,41983],{"class":202,"line":13329},[200,41980,599],{"class":214},[200,41982,21468],{"class":235},[200,41984,647],{"class":214},[200,41986,41987,41989,41991,41994,41996,41998,42001,42003],{"class":202,"line":13334},[200,41988,656],{"class":214},[200,41990,602],{"class":235},[200,41992,41993],{"class":379}," @submit.prevent",[200,41995,449],{"class":214},[200,41997,610],{"class":214},[200,41999,42000],{"class":245},"onSubmit()",[200,42002,610],{"class":214},[200,42004,647],{"class":214},[200,42006,42007,42009,42011,42014,42016,42018,42021,42023,42025,42027,42029,42031,42033,42035,42037,42039,42041,42043,42045],{"class":202,"line":13360},[200,42008,803],{"class":214},[200,42010,355],{"class":235},[200,42012,42013],{"class":379}," v-model",[200,42015,449],{"class":214},[200,42017,610],{"class":214},[200,42019,42020],{"class":245},"state.name",[200,42022,610],{"class":214},[200,42024,321],{"class":379},[200,42026,449],{"class":214},[200,42028,610],{"class":214},[200,42030,697],{"class":245},[200,42032,610],{"class":214},[200,42034,40711],{"class":379},[200,42036,449],{"class":214},[200,42038,610],{"class":214},[200,42040,40718],{"class":245},[200,42042,610],{"class":214},[200,42044,40723],{"class":379},[200,42046,647],{"class":214},[200,42048,42049,42051,42053,42055,42057,42059,42061,42063],{"class":202,"line":13365},[200,42050,803],{"class":214},[200,42052,909],{"class":235},[200,42054,321],{"class":379},[200,42056,449],{"class":214},[200,42058,610],{"class":214},[200,42060,916],{"class":245},[200,42062,610],{"class":214},[200,42064,647],{"class":214},[200,42066,42067],{"class":202,"line":13385},[200,42068,40748],{"class":210},[200,42070,42071,42073,42075],{"class":202,"line":13405},[200,42072,15078],{"class":214},[200,42074,909],{"class":235},[200,42076,647],{"class":214},[200,42078,42079,42081,42083],{"class":202,"line":13410},[200,42080,892],{"class":214},[200,42082,602],{"class":235},[200,42084,647],{"class":214},[200,42086,42087,42089,42091],{"class":202,"line":13418},[200,42088,680],{"class":214},[200,42090,21468],{"class":235},[200,42092,647],{"class":214},[35,42094,42095,42096,17891,42099,42102,42103,13741,42108,215],{},"L'implémentation est très simple et directe. Je pourrais enregistrer les erreurs dans une variable pour les afficher à l'utilisateur. Si vous souhaitez aller plus loin, je vous recommande d'essayer le composant ",[156,42097,42098],{},"Form",[39,42100,9203],{"href":23093,"rel":42101},[77]," ou une bibliothèque de validation comme ",[39,42104,42107],{"href":42105,"rel":42106},"https:\u002F\u002Fvee-validate.logaretm.com\u002Fv4\u002F",[77],"VeeValidate",[39,42109,42112],{"href":42110,"rel":42111},"https:\u002F\u002Fformkit.com\u002F",[77],"FormKit",[51,42114,42116],{"id":42115},"refactorisons","Refactorisons",[35,42118,42119],{},"Maintenant que j'ai la logique de validation à la fois côté client et côté serveur, voyons s'il y a un code intéressant à refactoriser.",[35,42121,42122],{},"La première chose que je remarque est que la logique de validation, côté client et serveur, est la même, et c'est totalement normal. Mais que se passe-t-il si je décide de changer la logique de validation côté serveur parce que mon API est utilisée par une autre application ? Mon frontend pourrait être cassé car la logique de validation sera différente. Pour éviter cela, il serait peut-être mieux d'écrire le validateur une seule fois et de l'utiliser des deux côtés. Qu'en pensez-vous ?",[35,42124,42125,42126,35146,42129,42132],{},"Je peux créer un fichier ",[156,42127,42128],{},"validators.ts",[156,42130,42131],{},"utils"," de la partie client (à la racine) d'une application Nuxt. Ce fichier contiendra tous les validateurs pour mon application et sera partagé entre le client et le serveur.",[192,42134,42136],{"className":1380,"code":42135,"language":1383,"meta":13,"style":13},"import { z } from 'zod'\n\nexport const createTagValidator = z.object({\n  name: z.string({ message: 'Obligatoire' }),\n})\n",[156,42137,42138,42156,42160,42181,42213],{"__ignoreMap":13},[200,42139,42140,42142,42144,42146,42148,42150,42152,42154],{"class":202,"line":203},[200,42141,972],{"class":206},[200,42143,318],{"class":214},[200,42145,3180],{"class":210},[200,42147,980],{"class":214},[200,42149,983],{"class":206},[200,42151,242],{"class":214},[200,42153,19813],{"class":245},[200,42155,991],{"class":214},[200,42157,42158],{"class":202,"line":14},[200,42159,528],{"emptyLinePlaceholder":20},[200,42161,42162,42164,42166,42169,42171,42173,42175,42177,42179],{"class":202,"line":255},[200,42163,1390],{"class":206},[200,42165,14305],{"class":379},[200,42167,42168],{"class":210}," createTagValidator ",[200,42170,449],{"class":214},[200,42172,3180],{"class":210},[200,42174,215],{"class":214},[200,42176,293],{"class":223},[200,42178,227],{"class":210},[200,42180,230],{"class":214},[200,42182,42183,42185,42187,42189,42191,42193,42195,42197,42199,42201,42203,42205,42207,42209,42211],{"class":202,"line":272},[200,42184,236],{"class":235},[200,42186,239],{"class":214},[200,42188,3180],{"class":210},[200,42190,215],{"class":214},[200,42192,328],{"class":223},[200,42194,227],{"class":210},[200,42196,2767],{"class":214},[200,42198,39109],{"class":235},[200,42200,239],{"class":214},[200,42202,242],{"class":214},[200,42204,41718],{"class":245},[200,42206,249],{"class":214},[200,42208,980],{"class":214},[200,42210,2104],{"class":210},[200,42212,252],{"class":214},[200,42214,42215,42217],{"class":202,"line":283},[200,42216,413],{"class":214},[200,42218,416],{"class":210},[938,42220,42221],{},[35,42222,42223,42225],{},[200,42224,31019],{},"\nCe fichier peut être utilisé côté serveur mais a été créé côté client, donc vous devez explicitement importer tout ce dont vous avez besoin pour éviter des problèmes de rendu.",[35,42227,42228],{},"Ce validateur contient le message pour l'erreur pour aider l'utilisateur à comprendre ce qui ne va pas.",[35,42230,42231],{},"Grâce à la fonction d'auto-import de Nuxt, je peux facilement l'utiliser côté client :",[192,42233,42235],{"className":21457,"code":42234,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst schema = createTagValidator\n\n\u002F\u002F ...\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cform>\n  \u003C!-- ... -->\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,42236,42237,42257,42268,42272,42276,42284,42288,42296,42304,42309,42317],{"__ignoreMap":13},[200,42238,42239,42241,42243,42245,42247,42249,42251,42253,42255],{"class":202,"line":203},[200,42240,599],{"class":214},[200,42242,14896],{"class":235},[200,42244,14594],{"class":379},[200,42246,449],{"class":214},[200,42248,610],{"class":214},[200,42250,1383],{"class":245},[200,42252,610],{"class":214},[200,42254,21825],{"class":379},[200,42256,647],{"class":214},[200,42258,42259,42261,42263,42265],{"class":202,"line":14},[200,42260,443],{"class":379},[200,42262,41681],{"class":210},[200,42264,449],{"class":214},[200,42266,42267],{"class":210}," createTagValidator\n",[200,42269,42270],{"class":202,"line":255},[200,42271,528],{"emptyLinePlaceholder":20},[200,42273,42274],{"class":202,"line":272},[200,42275,32897],{"class":401},[200,42277,42278,42280,42282],{"class":202,"line":283},[200,42279,680],{"class":214},[200,42281,14896],{"class":235},[200,42283,647],{"class":214},[200,42285,42286],{"class":202,"line":300},[200,42287,528],{"emptyLinePlaceholder":20},[200,42289,42290,42292,42294],{"class":202,"line":310},[200,42291,599],{"class":214},[200,42293,21468],{"class":235},[200,42295,647],{"class":214},[200,42297,42298,42300,42302],{"class":202,"line":336},[200,42299,656],{"class":214},[200,42301,602],{"class":235},[200,42303,647],{"class":214},[200,42305,42306],{"class":202,"line":342},[200,42307,42308],{"class":401},"  \u003C!-- ... -->\n",[200,42310,42311,42313,42315],{"class":202,"line":365},[200,42312,892],{"class":214},[200,42314,602],{"class":235},[200,42316,647],{"class":214},[200,42318,42319,42321,42323],{"class":202,"line":371},[200,42320,680],{"class":214},[200,42322,21468],{"class":235},[200,42324,647],{"class":214},[35,42326,42327],{},"Ensuite, vous pouvez également l'utiliser côté serveur en l'importation :",[192,42329,42331],{"className":1380,"code":42330,"language":1383,"meta":13,"style":13},"import { createTagValidator } from '~\u002Futils\u002Fvalidators'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, createTagValidator.parse)\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n",[156,42332,42333,42353,42357,42379,42405,42409,42413,42417,42433],{"__ignoreMap":13},[200,42334,42335,42337,42339,42342,42344,42346,42348,42351],{"class":202,"line":203},[200,42336,972],{"class":206},[200,42338,318],{"class":214},[200,42340,42341],{"class":210}," createTagValidator",[200,42343,980],{"class":214},[200,42345,983],{"class":206},[200,42347,242],{"class":214},[200,42349,42350],{"class":245},"~\u002Futils\u002Fvalidators",[200,42352,991],{"class":214},[200,42354,42355],{"class":202,"line":14},[200,42356,528],{"emptyLinePlaceholder":20},[200,42358,42359,42361,42363,42365,42367,42369,42371,42373,42375,42377],{"class":202,"line":255},[200,42360,1390],{"class":206},[200,42362,1393],{"class":206},[200,42364,38723],{"class":223},[200,42366,227],{"class":210},[200,42368,3726],{"class":379},[200,42370,1094],{"class":214},[200,42372,25426],{"class":386},[200,42374,2104],{"class":214},[200,42376,393],{"class":379},[200,42378,280],{"class":214},[200,42380,42381,42383,42385,42387,42389,42391,42393,42395,42397,42399,42401,42403],{"class":202,"line":272},[200,42382,3738],{"class":379},[200,42384,40809],{"class":210},[200,42386,509],{"class":214},[200,42388,452],{"class":206},[200,42390,40951],{"class":223},[200,42392,227],{"class":235},[200,42394,25426],{"class":210},[200,42396,549],{"class":214},[200,42398,42341],{"class":210},[200,42400,215],{"class":214},[200,42402,40994],{"class":210},[200,42404,416],{"class":235},[200,42406,42407],{"class":202,"line":283},[200,42408,528],{"emptyLinePlaceholder":20},[200,42410,42411],{"class":202,"line":300},[200,42412,40831],{"class":401},[200,42414,42415],{"class":202,"line":310},[200,42416,528],{"emptyLinePlaceholder":20},[200,42418,42419,42421,42423,42425,42427,42429,42431],{"class":202,"line":336},[200,42420,3758],{"class":206},[200,42422,40842],{"class":223},[200,42424,227],{"class":235},[200,42426,25426],{"class":210},[200,42428,549],{"class":214},[200,42430,40851],{"class":2060},[200,42432,416],{"class":235},[200,42434,42435,42437],{"class":202,"line":342},[200,42436,413],{"class":214},[200,42438,416],{"class":210},[35,42440,42441],{},"Maintenant, je peux changer en toute sécurité la logique de validation sans casser le frontend, car ils suivent les mêmes règles, et je n'ai pas à dupliquer le code.",[51,42443,42445],{"id":42444},"gestion-des-fichiers","Gestion des Fichiers",[35,42447,42448,42449,42452],{},"Pour cette partie, je vais vous montrer deux façons différentes de valider les fichiers côté serveur. La première utilisera Zod et quelques raffinements, et la seconde utilisera l'utilitaire ",[156,42450,42451],{},"ensureBlob"," de NuxtHub.",[35,42454,42455],{},[1216,42456,42457],{},"Cette validation n'est pas complète et devrait être améliorée pour gérer plus de cas.",[1929,42459,42461],{"id":42460},"avec-zod","Avec Zod",[35,42463,42464],{},"Pour cette seconde partie, je vais vous montrer comment télécharger un fichier sur le serveur. D'abord, ajoutons notre entrée dans le formulaire :",[192,42466,42468],{"className":21457,"code":42467,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003Cform action=\"\u002Fapi\u002Fimage\" enctype=\"multipart\u002Fform-data\" method=\"post\">\n    \u003Cinput type=\"file\" accept=\"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg\" required>\n    \u003Cbutton type=\"submit\">\n      Créer Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,42469,42470,42478,42519,42552,42570,42574,42582,42590],{"__ignoreMap":13},[200,42471,42472,42474,42476],{"class":202,"line":203},[200,42473,599],{"class":214},[200,42475,21468],{"class":235},[200,42477,647],{"class":214},[200,42479,42480,42482,42484,42486,42488,42490,42493,42495,42498,42500,42502,42505,42507,42509,42511,42513,42515,42517],{"class":202,"line":14},[200,42481,656],{"class":214},[200,42483,602],{"class":235},[200,42485,40681],{"class":379},[200,42487,449],{"class":214},[200,42489,610],{"class":214},[200,42491,42492],{"class":245},"\u002Fapi\u002Fimage",[200,42494,610],{"class":214},[200,42496,42497],{"class":379}," enctype",[200,42499,449],{"class":214},[200,42501,610],{"class":214},[200,42503,42504],{"class":245},"multipart\u002Fform-data",[200,42506,610],{"class":214},[200,42508,6589],{"class":379},[200,42510,449],{"class":214},[200,42512,610],{"class":214},[200,42514,3793],{"class":245},[200,42516,610],{"class":214},[200,42518,647],{"class":214},[200,42520,42521,42523,42525,42527,42529,42531,42534,42536,42539,42541,42543,42546,42548,42550],{"class":202,"line":255},[200,42522,803],{"class":214},[200,42524,355],{"class":235},[200,42526,321],{"class":379},[200,42528,449],{"class":214},[200,42530,610],{"class":214},[200,42532,42533],{"class":245},"file",[200,42535,610],{"class":214},[200,42537,42538],{"class":379}," accept",[200,42540,449],{"class":214},[200,42542,610],{"class":214},[200,42544,42545],{"class":245},"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg",[200,42547,610],{"class":214},[200,42549,40723],{"class":379},[200,42551,647],{"class":214},[200,42553,42554,42556,42558,42560,42562,42564,42566,42568],{"class":202,"line":272},[200,42555,803],{"class":214},[200,42557,909],{"class":235},[200,42559,321],{"class":379},[200,42561,449],{"class":214},[200,42563,610],{"class":214},[200,42565,916],{"class":245},[200,42567,610],{"class":214},[200,42569,647],{"class":214},[200,42571,42572],{"class":202,"line":283},[200,42573,40748],{"class":210},[200,42575,42576,42578,42580],{"class":202,"line":300},[200,42577,15078],{"class":214},[200,42579,909],{"class":235},[200,42581,647],{"class":214},[200,42583,42584,42586,42588],{"class":202,"line":310},[200,42585,892],{"class":214},[200,42587,602],{"class":235},[200,42589,647],{"class":214},[200,42591,42592,42594,42596],{"class":202,"line":336},[200,42593,680],{"class":214},[200,42595,21468],{"class":235},[200,42597,647],{"class":214},[35,42599,42600,42601,42604,42605,42608],{},"Ensuite, je peux gérer le fichier côté serveur, mais c'est un peu plus complexe car j'envoie un ",[156,42602,42603],{},"formData"," et h3 ne fournit pas d'utilitaire comme ",[156,42606,42607],{},"readValidatedFormData",". Donc, je dois le gérer manuellement.",[35,42610,42611,42612,42614,42615,42618,42619,26733,42621,42623],{},"D'abord, je vais lire le ",[156,42613,42603],{}," en utilisant l'utilitaire ",[156,42616,42617],{},"readFormData",", puis j'extraire l'",[156,42620,9752],{},[156,42622,42603],{}," et la valider en utilisant Zod.",[192,42625,42627],{"className":1380,"code":42626,"language":1383,"meta":13,"style":13},"import { any, object } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const formData = await readFormData(event)\n\n  const image = formData.get('image')\n  await object({\n    image: any()\n      .refine(image => image instanceof File, { message: 'L\\'image doit être un fichier' })\n      .refine(image => image.size \u003C 1024 * 1024, { message: 'L\\'image doit faire moins de 1 Mo' })\n      .refine(image => image.type.startsWith('image\u002F'), { message: 'L\\'image doit être une image' })\n      .refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'L\\'image doit être un JPEG ou un PNG' })\n  }).parseAsync({ image }).catch((error) => {\n    throw createError({\n      status: 400,\n      message: 'Image non valide',\n      data: error.errors[0].message\n    })\n  })\n\n  \u002F\u002F Enregistrer l'image dans une base de données ou un système de fichiers\n\n  return sendNoContent(event, 201)\n})\n",[156,42628,42629,42653,42657,42679,42699,42703,42729,42739,42750,42794,42844,42900,42978,43014,43024,43034,43049,43072,43078,43084,43088,43093,43097,43113],{"__ignoreMap":13},[200,42630,42631,42633,42635,42638,42640,42643,42645,42647,42649,42651],{"class":202,"line":203},[200,42632,972],{"class":206},[200,42634,318],{"class":214},[200,42636,42637],{"class":210}," any",[200,42639,549],{"class":214},[200,42641,42642],{"class":210}," object",[200,42644,980],{"class":214},[200,42646,983],{"class":206},[200,42648,242],{"class":214},[200,42650,19813],{"class":245},[200,42652,991],{"class":214},[200,42654,42655],{"class":202,"line":14},[200,42656,528],{"emptyLinePlaceholder":20},[200,42658,42659,42661,42663,42665,42667,42669,42671,42673,42675,42677],{"class":202,"line":255},[200,42660,1390],{"class":206},[200,42662,1393],{"class":206},[200,42664,38723],{"class":223},[200,42666,227],{"class":210},[200,42668,3726],{"class":379},[200,42670,1094],{"class":214},[200,42672,25426],{"class":386},[200,42674,2104],{"class":214},[200,42676,393],{"class":379},[200,42678,280],{"class":214},[200,42680,42681,42683,42686,42688,42690,42693,42695,42697],{"class":202,"line":272},[200,42682,3738],{"class":379},[200,42684,42685],{"class":210}," formData",[200,42687,509],{"class":214},[200,42689,452],{"class":206},[200,42691,42692],{"class":223}," readFormData",[200,42694,227],{"class":235},[200,42696,25426],{"class":210},[200,42698,416],{"class":235},[200,42700,42701],{"class":202,"line":283},[200,42702,528],{"emptyLinePlaceholder":20},[200,42704,42705,42707,42710,42712,42714,42716,42719,42721,42723,42725,42727],{"class":202,"line":300},[200,42706,3738],{"class":379},[200,42708,42709],{"class":210}," image",[200,42711,509],{"class":214},[200,42713,42685],{"class":210},[200,42715,215],{"class":214},[200,42717,42718],{"class":223},"get",[200,42720,227],{"class":235},[200,42722,249],{"class":214},[200,42724,9752],{"class":245},[200,42726,249],{"class":214},[200,42728,416],{"class":235},[200,42730,42731,42733,42735,42737],{"class":202,"line":310},[200,42732,6177],{"class":206},[200,42734,42642],{"class":223},[200,42736,227],{"class":235},[200,42738,230],{"class":214},[200,42740,42741,42744,42746,42748],{"class":202,"line":336},[200,42742,42743],{"class":235},"    image",[200,42745,239],{"class":214},[200,42747,42637],{"class":223},[200,42749,466],{"class":235},[200,42751,42752,42754,42757,42759,42761,42763,42765,42767,42770,42772,42774,42776,42778,42780,42783,42785,42788,42790,42792],{"class":202,"line":342},[200,42753,5066],{"class":214},[200,42755,42756],{"class":223},"refine",[200,42758,227],{"class":235},[200,42760,9752],{"class":386},[200,42762,393],{"class":379},[200,42764,42709],{"class":210},[200,42766,41475],{"class":214},[200,42768,42769],{"class":1364}," File",[200,42771,549],{"class":214},[200,42773,318],{"class":214},[200,42775,39109],{"class":235},[200,42777,239],{"class":214},[200,42779,242],{"class":214},[200,42781,42782],{"class":245},"L",[200,42784,16056],{"class":210},[200,42786,42787],{"class":245},"image doit être un fichier",[200,42789,249],{"class":214},[200,42791,980],{"class":214},[200,42793,416],{"class":235},[200,42795,42796,42798,42800,42802,42804,42806,42808,42810,42812,42814,42817,42819,42821,42823,42825,42827,42829,42831,42833,42835,42838,42840,42842],{"class":202,"line":365},[200,42797,5066],{"class":214},[200,42799,42756],{"class":223},[200,42801,227],{"class":235},[200,42803,9752],{"class":386},[200,42805,393],{"class":379},[200,42807,42709],{"class":210},[200,42809,215],{"class":214},[200,42811,35121],{"class":210},[200,42813,31004],{"class":214},[200,42815,42816],{"class":2060}," 1024",[200,42818,29668],{"class":214},[200,42820,42816],{"class":2060},[200,42822,549],{"class":214},[200,42824,318],{"class":214},[200,42826,39109],{"class":235},[200,42828,239],{"class":214},[200,42830,242],{"class":214},[200,42832,42782],{"class":245},[200,42834,16056],{"class":210},[200,42836,42837],{"class":245},"image doit faire moins de 1 Mo",[200,42839,249],{"class":214},[200,42841,980],{"class":214},[200,42843,416],{"class":235},[200,42845,42846,42848,42850,42852,42854,42856,42858,42860,42862,42864,42866,42868,42870,42873,42875,42877,42879,42881,42883,42885,42887,42889,42891,42894,42896,42898],{"class":202,"line":371},[200,42847,5066],{"class":214},[200,42849,42756],{"class":223},[200,42851,227],{"class":235},[200,42853,9752],{"class":386},[200,42855,393],{"class":379},[200,42857,42709],{"class":210},[200,42859,215],{"class":214},[200,42861,3786],{"class":210},[200,42863,215],{"class":214},[200,42865,6120],{"class":223},[200,42867,227],{"class":235},[200,42869,249],{"class":214},[200,42871,42872],{"class":245},"image\u002F",[200,42874,249],{"class":214},[200,42876,2104],{"class":235},[200,42878,549],{"class":214},[200,42880,318],{"class":214},[200,42882,39109],{"class":235},[200,42884,239],{"class":214},[200,42886,242],{"class":214},[200,42888,42782],{"class":245},[200,42890,16056],{"class":210},[200,42892,42893],{"class":245},"image doit être une image",[200,42895,249],{"class":214},[200,42897,980],{"class":214},[200,42899,416],{"class":235},[200,42901,42902,42904,42906,42908,42910,42912,42914,42916,42919,42921,42923,42925,42928,42930,42932,42934,42937,42939,42941,42943,42945,42947,42949,42951,42953,42955,42957,42959,42961,42963,42965,42967,42969,42972,42974,42976],{"class":202,"line":398},[200,42903,5066],{"class":214},[200,42905,42756],{"class":223},[200,42907,227],{"class":235},[200,42909,9752],{"class":386},[200,42911,393],{"class":379},[200,42913,350],{"class":235},[200,42915,249],{"class":214},[200,42917,42918],{"class":245},"image\u002Fpng",[200,42920,249],{"class":214},[200,42922,549],{"class":214},[200,42924,242],{"class":214},[200,42926,42927],{"class":245},"image\u002Fjpeg",[200,42929,249],{"class":214},[200,42931,549],{"class":214},[200,42933,242],{"class":214},[200,42935,42936],{"class":245},"image\u002Fjpg",[200,42938,249],{"class":214},[200,42940,360],{"class":235},[200,42942,215],{"class":214},[200,42944,3856],{"class":223},[200,42946,227],{"class":235},[200,42948,9752],{"class":210},[200,42950,215],{"class":214},[200,42952,3786],{"class":210},[200,42954,2104],{"class":235},[200,42956,549],{"class":214},[200,42958,318],{"class":214},[200,42960,39109],{"class":235},[200,42962,239],{"class":214},[200,42964,242],{"class":214},[200,42966,42782],{"class":245},[200,42968,16056],{"class":210},[200,42970,42971],{"class":245},"image doit être un JPEG ou un PNG",[200,42973,249],{"class":214},[200,42975,980],{"class":214},[200,42977,416],{"class":235},[200,42979,42980,42982,42984,42986,42988,42990,42992,42994,42996,42998,43000,43002,43004,43006,43008,43010,43012],{"class":202,"line":405},[200,42981,5095],{"class":214},[200,42983,2104],{"class":235},[200,42985,215],{"class":214},[200,42987,41312],{"class":223},[200,42989,227],{"class":235},[200,42991,2767],{"class":214},[200,42993,42709],{"class":210},[200,42995,980],{"class":214},[200,42997,2104],{"class":235},[200,42999,215],{"class":214},[200,43001,41452],{"class":223},[200,43003,227],{"class":235},[200,43005,227],{"class":214},[200,43007,19275],{"class":386},[200,43009,2104],{"class":214},[200,43011,393],{"class":379},[200,43013,280],{"class":214},[200,43015,43016,43018,43020,43022],{"class":202,"line":410},[200,43017,39200],{"class":206},[200,43019,39203],{"class":223},[200,43021,227],{"class":235},[200,43023,230],{"class":214},[200,43025,43026,43028,43030,43032],{"class":202,"line":860},[200,43027,38845],{"class":235},[200,43029,239],{"class":214},[200,43031,41198],{"class":2060},[200,43033,252],{"class":214},[200,43035,43036,43038,43040,43042,43045,43047],{"class":202,"line":889},[200,43037,39233],{"class":235},[200,43039,239],{"class":214},[200,43041,242],{"class":214},[200,43043,43044],{"class":245},"Image non valide",[200,43046,249],{"class":214},[200,43048,252],{"class":214},[200,43050,43051,43053,43055,43057,43059,43061,43063,43065,43067,43069],{"class":202,"line":899},[200,43052,41220],{"class":235},[200,43054,239],{"class":214},[200,43056,19220],{"class":210},[200,43058,215],{"class":214},[200,43060,26828],{"class":210},[200,43062,9872],{"class":235},[200,43064,3948],{"class":2060},[200,43066,360],{"class":235},[200,43068,215],{"class":214},[200,43070,43071],{"class":210},"message\n",[200,43073,43074,43076],{"class":202,"line":904},[200,43075,2111],{"class":214},[200,43077,416],{"class":235},[200,43079,43080,43082],{"class":202,"line":930},[200,43081,5095],{"class":214},[200,43083,416],{"class":235},[200,43085,43086],{"class":202,"line":3655},[200,43087,528],{"emptyLinePlaceholder":20},[200,43089,43090],{"class":202,"line":3660},[200,43091,43092],{"class":401},"  \u002F\u002F Enregistrer l'image dans une base de données ou un système de fichiers\n",[200,43094,43095],{"class":202,"line":4035},[200,43096,528],{"emptyLinePlaceholder":20},[200,43098,43099,43101,43103,43105,43107,43109,43111],{"class":202,"line":4490},[200,43100,3758],{"class":206},[200,43102,40842],{"class":223},[200,43104,227],{"class":235},[200,43106,25426],{"class":210},[200,43108,549],{"class":214},[200,43110,40851],{"class":2060},[200,43112,416],{"class":235},[200,43114,43115,43117],{"class":202,"line":4496},[200,43116,413],{"class":214},[200,43118,416],{"class":210},[35,43120,43121],{},"Côté client, je peux également valider le fichier en utilisant la même technique qu'auparavant et en partageant le validateur entre le client et le serveur.",[35,43123,43124,43125,2186],{},"D'abord, je dois déplacer le validateur dans le fichier ",[156,43126,43127],{},"utils\u002Fvalidators.ts",[192,43129,43131],{"className":1380,"code":43130,"language":1383,"meta":13,"style":13},"import { any, object } from 'zod'\n\nexport const createImageValidator = object({\n  image: any()\n    .refine(image => image instanceof File, { message: 'L\\'image doit être un fichier' })\n    .refine(image => image.size \u003C 1024 * 1024, { message: 'L\\'image doit faire moins de 1 Mo' })\n    .refine(image => image.type.startsWith('image\u002F'), { message: 'L\\'image doit être une image' })\n    .refine(image => ['image\u002Fjpeg', 'image\u002Fpng'].includes(image.type), { message: 'L\\'image doit être un JPEG ou un PNG' })\n})\n",[156,43132,43133,43155,43159,43176,43187,43229,43278,43332,43396],{"__ignoreMap":13},[200,43134,43135,43137,43139,43141,43143,43145,43147,43149,43151,43153],{"class":202,"line":203},[200,43136,972],{"class":206},[200,43138,318],{"class":214},[200,43140,42637],{"class":210},[200,43142,549],{"class":214},[200,43144,42642],{"class":210},[200,43146,980],{"class":214},[200,43148,983],{"class":206},[200,43150,242],{"class":214},[200,43152,19813],{"class":245},[200,43154,991],{"class":214},[200,43156,43157],{"class":202,"line":14},[200,43158,528],{"emptyLinePlaceholder":20},[200,43160,43161,43163,43165,43168,43170,43172,43174],{"class":202,"line":255},[200,43162,1390],{"class":206},[200,43164,14305],{"class":379},[200,43166,43167],{"class":210}," createImageValidator ",[200,43169,449],{"class":214},[200,43171,42642],{"class":223},[200,43173,227],{"class":210},[200,43175,230],{"class":214},[200,43177,43178,43181,43183,43185],{"class":202,"line":272},[200,43179,43180],{"class":235},"  image",[200,43182,239],{"class":214},[200,43184,42637],{"class":223},[200,43186,466],{"class":210},[200,43188,43189,43191,43193,43195,43197,43199,43202,43205,43207,43209,43211,43213,43215,43217,43219,43221,43223,43225,43227],{"class":202,"line":283},[200,43190,3770],{"class":214},[200,43192,42756],{"class":223},[200,43194,227],{"class":210},[200,43196,9752],{"class":386},[200,43198,393],{"class":379},[200,43200,43201],{"class":210}," image ",[200,43203,43204],{"class":214},"instanceof",[200,43206,42769],{"class":1364},[200,43208,549],{"class":214},[200,43210,318],{"class":214},[200,43212,39109],{"class":235},[200,43214,239],{"class":214},[200,43216,242],{"class":214},[200,43218,42782],{"class":245},[200,43220,16056],{"class":210},[200,43222,42787],{"class":245},[200,43224,249],{"class":214},[200,43226,980],{"class":214},[200,43228,416],{"class":210},[200,43230,43231,43233,43235,43237,43239,43241,43243,43245,43248,43250,43252,43254,43256,43258,43260,43262,43264,43266,43268,43270,43272,43274,43276],{"class":202,"line":300},[200,43232,3770],{"class":214},[200,43234,42756],{"class":223},[200,43236,227],{"class":210},[200,43238,9752],{"class":386},[200,43240,393],{"class":379},[200,43242,42709],{"class":210},[200,43244,215],{"class":214},[200,43246,43247],{"class":210},"size ",[200,43249,599],{"class":214},[200,43251,42816],{"class":2060},[200,43253,29668],{"class":214},[200,43255,42816],{"class":2060},[200,43257,549],{"class":214},[200,43259,318],{"class":214},[200,43261,39109],{"class":235},[200,43263,239],{"class":214},[200,43265,242],{"class":214},[200,43267,42782],{"class":245},[200,43269,16056],{"class":210},[200,43271,42837],{"class":245},[200,43273,249],{"class":214},[200,43275,980],{"class":214},[200,43277,416],{"class":210},[200,43279,43280,43282,43284,43286,43288,43290,43292,43294,43296,43298,43300,43302,43304,43306,43308,43310,43312,43314,43316,43318,43320,43322,43324,43326,43328,43330],{"class":202,"line":310},[200,43281,3770],{"class":214},[200,43283,42756],{"class":223},[200,43285,227],{"class":210},[200,43287,9752],{"class":386},[200,43289,393],{"class":379},[200,43291,42709],{"class":210},[200,43293,215],{"class":214},[200,43295,3786],{"class":210},[200,43297,215],{"class":214},[200,43299,6120],{"class":223},[200,43301,227],{"class":210},[200,43303,249],{"class":214},[200,43305,42872],{"class":245},[200,43307,249],{"class":214},[200,43309,2104],{"class":210},[200,43311,549],{"class":214},[200,43313,318],{"class":214},[200,43315,39109],{"class":235},[200,43317,239],{"class":214},[200,43319,242],{"class":214},[200,43321,42782],{"class":245},[200,43323,16056],{"class":210},[200,43325,42893],{"class":245},[200,43327,249],{"class":214},[200,43329,980],{"class":214},[200,43331,416],{"class":210},[200,43333,43334,43336,43338,43340,43342,43344,43346,43348,43350,43352,43354,43356,43358,43360,43362,43364,43366,43369,43371,43374,43376,43378,43380,43382,43384,43386,43388,43390,43392,43394],{"class":202,"line":336},[200,43335,3770],{"class":214},[200,43337,42756],{"class":223},[200,43339,227],{"class":210},[200,43341,9752],{"class":386},[200,43343,393],{"class":379},[200,43345,350],{"class":210},[200,43347,249],{"class":214},[200,43349,42927],{"class":245},[200,43351,249],{"class":214},[200,43353,549],{"class":214},[200,43355,242],{"class":214},[200,43357,42918],{"class":245},[200,43359,249],{"class":214},[200,43361,360],{"class":210},[200,43363,215],{"class":214},[200,43365,3856],{"class":223},[200,43367,43368],{"class":210},"(image",[200,43370,215],{"class":214},[200,43372,43373],{"class":210},"type)",[200,43375,549],{"class":214},[200,43377,318],{"class":214},[200,43379,39109],{"class":235},[200,43381,239],{"class":214},[200,43383,242],{"class":214},[200,43385,42782],{"class":245},[200,43387,16056],{"class":210},[200,43389,42971],{"class":245},[200,43391,249],{"class":214},[200,43393,980],{"class":214},[200,43395,416],{"class":210},[200,43397,43398,43400],{"class":202,"line":342},[200,43399,413],{"class":214},[200,43401,416],{"class":210},[35,43403,43404],{},"Ensuite, je peux l'utiliser lors de la soumission du formulaire pour éviter d'envoyer des données non valides au serveur :",[192,43406,43408],{"className":21457,"code":43407,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nasync function onImageSubmit(event: Event) {\n  const target = event.target as HTMLFormElement\n  const formData = new FormData(target)\n\n  const image = formData.get('image')\n  try {\n    await createImageValidator.parseAsync({ image })\n\n    await $fetch('\u002Fapi\u002Fimage', {\n      method: 'POST',\n      body: formData,\n    })\n  }\n  catch (error) {\n    console.error(error)\n  }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Ch1>Valider les Formulaires avec Nuxt\u003C\u002Fh1>\n\n    \u003Ch2>\n      Image\n    \u003C\u002Fh2>\n    \u003Cform enctype=\"multipart\u002Fform-data\" @submit.prevent=\"onImageSubmit($event)\">\n      \u003Cinput type=\"file\" name=\"image\" accept=\"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg\">\n      \u003Cbutton type=\"submit\">\n        Soumettre\n      \u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[156,43409,43410,43430,43452,43474,43493,43497,43521,43527,43548,43552,43570,43584,43594,43600,43604,43616,43631,43635,43639,43647,43651,43659,43667,43684,43688,43696,43701,43709,43738,43776,43794,43799,43808,43816,43824],{"__ignoreMap":13},[200,43411,43412,43414,43416,43418,43420,43422,43424,43426,43428],{"class":202,"line":203},[200,43413,599],{"class":214},[200,43415,14896],{"class":235},[200,43417,14594],{"class":379},[200,43419,449],{"class":214},[200,43421,610],{"class":214},[200,43423,1383],{"class":245},[200,43425,610],{"class":214},[200,43427,21825],{"class":379},[200,43429,647],{"class":214},[200,43431,43432,43434,43436,43439,43441,43443,43445,43448,43450],{"class":202,"line":14},[200,43433,3726],{"class":379},[200,43435,5002],{"class":379},[200,43437,43438],{"class":223}," onImageSubmit",[200,43440,227],{"class":214},[200,43442,25426],{"class":386},[200,43444,239],{"class":214},[200,43446,43447],{"class":1364}," Event",[200,43449,2104],{"class":214},[200,43451,280],{"class":214},[200,43453,43454,43456,43459,43461,43464,43466,43469,43471],{"class":202,"line":255},[200,43455,3738],{"class":379},[200,43457,43458],{"class":210}," target",[200,43460,509],{"class":214},[200,43462,43463],{"class":210}," event",[200,43465,215],{"class":214},[200,43467,43468],{"class":210},"target",[200,43470,14678],{"class":206},[200,43472,43473],{"class":1364}," HTMLFormElement\n",[200,43475,43476,43478,43480,43482,43484,43487,43489,43491],{"class":202,"line":272},[200,43477,3738],{"class":379},[200,43479,42685],{"class":210},[200,43481,509],{"class":214},[200,43483,13007],{"class":214},[200,43485,43486],{"class":223}," FormData",[200,43488,227],{"class":235},[200,43490,43468],{"class":210},[200,43492,416],{"class":235},[200,43494,43495],{"class":202,"line":283},[200,43496,528],{"emptyLinePlaceholder":20},[200,43498,43499,43501,43503,43505,43507,43509,43511,43513,43515,43517,43519],{"class":202,"line":300},[200,43500,3738],{"class":379},[200,43502,42709],{"class":210},[200,43504,509],{"class":214},[200,43506,42685],{"class":210},[200,43508,215],{"class":214},[200,43510,42718],{"class":223},[200,43512,227],{"class":235},[200,43514,249],{"class":214},[200,43516,9752],{"class":245},[200,43518,249],{"class":214},[200,43520,416],{"class":235},[200,43522,43523,43525],{"class":202,"line":310},[200,43524,41792],{"class":206},[200,43526,280],{"class":214},[200,43528,43529,43531,43534,43536,43538,43540,43542,43544,43546],{"class":202,"line":336},[200,43530,19231],{"class":206},[200,43532,43533],{"class":210}," createImageValidator",[200,43535,215],{"class":214},[200,43537,41312],{"class":223},[200,43539,227],{"class":235},[200,43541,2767],{"class":214},[200,43543,42709],{"class":210},[200,43545,980],{"class":214},[200,43547,416],{"class":235},[200,43549,43550],{"class":202,"line":342},[200,43551,528],{"emptyLinePlaceholder":20},[200,43553,43554,43556,43558,43560,43562,43564,43566,43568],{"class":202,"line":365},[200,43555,19231],{"class":206},[200,43557,18754],{"class":223},[200,43559,227],{"class":235},[200,43561,249],{"class":214},[200,43563,42492],{"class":245},[200,43565,249],{"class":214},[200,43567,549],{"class":214},[200,43569,280],{"class":214},[200,43571,43572,43574,43576,43578,43580,43582],{"class":202,"line":371},[200,43573,41838],{"class":235},[200,43575,239],{"class":214},[200,43577,242],{"class":214},[200,43579,18779],{"class":245},[200,43581,249],{"class":214},[200,43583,252],{"class":214},[200,43585,43586,43588,43590,43592],{"class":202,"line":398},[200,43587,41853],{"class":235},[200,43589,239],{"class":214},[200,43591,42685],{"class":210},[200,43593,252],{"class":214},[200,43595,43596,43598],{"class":202,"line":405},[200,43597,2111],{"class":214},[200,43599,416],{"class":235},[200,43601,43602],{"class":202,"line":410},[200,43603,4505],{"class":214},[200,43605,43606,43608,43610,43612,43614],{"class":202,"line":860},[200,43607,41874],{"class":206},[200,43609,1094],{"class":235},[200,43611,19275],{"class":210},[200,43613,5282],{"class":235},[200,43615,230],{"class":214},[200,43617,43618,43621,43623,43625,43627,43629],{"class":202,"line":889},[200,43619,43620],{"class":210},"    console",[200,43622,215],{"class":214},[200,43624,19275],{"class":223},[200,43626,227],{"class":235},[200,43628,19275],{"class":210},[200,43630,416],{"class":235},[200,43632,43633],{"class":202,"line":899},[200,43634,4505],{"class":214},[200,43636,43637],{"class":202,"line":904},[200,43638,1129],{"class":214},[200,43640,43641,43643,43645],{"class":202,"line":930},[200,43642,680],{"class":214},[200,43644,14896],{"class":235},[200,43646,647],{"class":214},[200,43648,43649],{"class":202,"line":3655},[200,43650,528],{"emptyLinePlaceholder":20},[200,43652,43653,43655,43657],{"class":202,"line":3660},[200,43654,599],{"class":214},[200,43656,21468],{"class":235},[200,43658,647],{"class":214},[200,43660,43661,43663,43665],{"class":202,"line":4035},[200,43662,656],{"class":214},[200,43664,11400],{"class":235},[200,43666,647],{"class":214},[200,43668,43669,43671,43673,43675,43678,43680,43682],{"class":202,"line":4490},[200,43670,803],{"class":214},[200,43672,10095],{"class":235},[200,43674,674],{"class":214},[200,43676,43677],{"class":210},"Valider les Formulaires avec Nuxt",[200,43679,680],{"class":214},[200,43681,10095],{"class":235},[200,43683,647],{"class":214},[200,43685,43686],{"class":202,"line":4496},[200,43687,528],{"emptyLinePlaceholder":20},[200,43689,43690,43692,43694],{"class":202,"line":4502},[200,43691,803],{"class":214},[200,43693,51],{"class":235},[200,43695,647],{"class":214},[200,43697,43698],{"class":202,"line":4508},[200,43699,43700],{"class":210},"      Image\n",[200,43702,43703,43705,43707],{"class":202,"line":5469},[200,43704,15078],{"class":214},[200,43706,51],{"class":235},[200,43708,647],{"class":214},[200,43710,43711,43713,43715,43717,43719,43721,43723,43725,43727,43729,43731,43734,43736],{"class":202,"line":5474},[200,43712,803],{"class":214},[200,43714,602],{"class":235},[200,43716,42497],{"class":379},[200,43718,449],{"class":214},[200,43720,610],{"class":214},[200,43722,42504],{"class":245},[200,43724,610],{"class":214},[200,43726,41993],{"class":379},[200,43728,449],{"class":214},[200,43730,610],{"class":214},[200,43732,43733],{"class":245},"onImageSubmit($event)",[200,43735,610],{"class":214},[200,43737,647],{"class":214},[200,43739,43740,43742,43744,43746,43748,43750,43752,43754,43756,43758,43760,43762,43764,43766,43768,43770,43772,43774],{"class":202,"line":13237},[200,43741,22071],{"class":214},[200,43743,355],{"class":235},[200,43745,321],{"class":379},[200,43747,449],{"class":214},[200,43749,610],{"class":214},[200,43751,42533],{"class":245},[200,43753,610],{"class":214},[200,43755,700],{"class":379},[200,43757,449],{"class":214},[200,43759,610],{"class":214},[200,43761,9752],{"class":245},[200,43763,610],{"class":214},[200,43765,42538],{"class":379},[200,43767,449],{"class":214},[200,43769,610],{"class":214},[200,43771,42545],{"class":245},[200,43773,610],{"class":214},[200,43775,647],{"class":214},[200,43777,43778,43780,43782,43784,43786,43788,43790,43792],{"class":202,"line":13263},[200,43779,22071],{"class":214},[200,43781,909],{"class":235},[200,43783,321],{"class":379},[200,43785,449],{"class":214},[200,43787,610],{"class":214},[200,43789,916],{"class":245},[200,43791,610],{"class":214},[200,43793,647],{"class":214},[200,43795,43796],{"class":202,"line":13279},[200,43797,43798],{"class":210},"        Soumettre\n",[200,43800,43801,43804,43806],{"class":202,"line":13323},[200,43802,43803],{"class":214},"      \u003C\u002F",[200,43805,909],{"class":235},[200,43807,647],{"class":214},[200,43809,43810,43812,43814],{"class":202,"line":13329},[200,43811,15078],{"class":214},[200,43813,602],{"class":235},[200,43815,647],{"class":214},[200,43817,43818,43820,43822],{"class":202,"line":13334},[200,43819,892],{"class":214},[200,43821,11400],{"class":235},[200,43823,647],{"class":214},[200,43825,43826,43828,43830],{"class":202,"line":13360},[200,43827,680],{"class":214},[200,43829,21468],{"class":235},[200,43831,647],{"class":214},[35,43833,43834,43835,43838],{},"Si la validation échoue, la requête ne sera pas envoyée au serveur et l'erreur sera affichée dans la console. Bien sûr, vous pouvez gérer l'erreur comme vous le souhaitez, comme utiliser un ",[156,43836,43837],{},"toast"," ou afficher un message d'erreur sous l'entrée concernée.",[35,43840,43841],{},"Côté serveur, je peux remplacer la logique de validation par le validateur :",[192,43843,43845],{"className":1380,"code":43844,"language":1383,"meta":13,"style":13},"import { saveImageValidator } from '~\u002Futils\u002Fvalidators'\n\nexport default defineEventHandler(async (event) => {\n  \u002F\u002F ...\n\n  await saveImageValidator.parseAsync({ image })\n\n  \u002F\u002F ...\n})\n",[156,43846,43847,43866,43870,43892,43896,43900,43920,43924,43928],{"__ignoreMap":13},[200,43848,43849,43851,43853,43856,43858,43860,43862,43864],{"class":202,"line":203},[200,43850,972],{"class":206},[200,43852,318],{"class":214},[200,43854,43855],{"class":210}," saveImageValidator",[200,43857,980],{"class":214},[200,43859,983],{"class":206},[200,43861,242],{"class":214},[200,43863,42350],{"class":245},[200,43865,991],{"class":214},[200,43867,43868],{"class":202,"line":14},[200,43869,528],{"emptyLinePlaceholder":20},[200,43871,43872,43874,43876,43878,43880,43882,43884,43886,43888,43890],{"class":202,"line":255},[200,43873,1390],{"class":206},[200,43875,1393],{"class":206},[200,43877,38723],{"class":223},[200,43879,227],{"class":210},[200,43881,3726],{"class":379},[200,43883,1094],{"class":214},[200,43885,25426],{"class":386},[200,43887,2104],{"class":214},[200,43889,393],{"class":379},[200,43891,280],{"class":214},[200,43893,43894],{"class":202,"line":272},[200,43895,10648],{"class":401},[200,43897,43898],{"class":202,"line":283},[200,43899,528],{"emptyLinePlaceholder":20},[200,43901,43902,43904,43906,43908,43910,43912,43914,43916,43918],{"class":202,"line":300},[200,43903,6177],{"class":206},[200,43905,43855],{"class":210},[200,43907,215],{"class":214},[200,43909,41312],{"class":223},[200,43911,227],{"class":235},[200,43913,2767],{"class":214},[200,43915,42709],{"class":210},[200,43917,980],{"class":214},[200,43919,416],{"class":235},[200,43921,43922],{"class":202,"line":310},[200,43923,528],{"emptyLinePlaceholder":20},[200,43925,43926],{"class":202,"line":336},[200,43927,10648],{"class":401},[200,43929,43930,43932],{"class":202,"line":342},[200,43931,413],{"class":214},[200,43933,416],{"class":210},[1929,43935,43937],{"id":43936},"avec-nuxthub","Avec NuxtHub",[35,43939,43940,43941,43943,43944],{},"NuxtHub est livré avec un utilitaire nommé ",[156,43942,42451],{}," qui peut être utilisé pour vérifier la taille et le type du fichier. Cet utilitaire est très utile car il lancera automatiquement une erreur si le fichier n'est pas valide, mais ne peut être utilisé que côté serveur. Cela signifie que j'aurai un schéma Zod côté client et un utilitaire NuxtHub côté serveur. ",[1216,43945,43946],{},"Utilisez des constantes pour partager les limites de validation entre le schéma Zod et l'utilitaire NuxtHub.",[192,43948,43950],{"className":1380,"code":43949,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  const formData = await readFormData(event)\n\n  const image = formData.get('image') as Blob\n  ensureBlob(image, {\n    maxSize: '1Mo',\n    types: ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg']\n  })\n\n  \u002F\u002F Enregistrer l'image dans une base de données ou un système de fichiers\n\n  return sendNoContent(event, 201)\n})\n",[156,43951,43952,43974,43992,43996,44026,44039,44055,44088,44094,44098,44102,44106,44122],{"__ignoreMap":13},[200,43953,43954,43956,43958,43960,43962,43964,43966,43968,43970,43972],{"class":202,"line":203},[200,43955,1390],{"class":206},[200,43957,1393],{"class":206},[200,43959,38723],{"class":223},[200,43961,227],{"class":210},[200,43963,3726],{"class":379},[200,43965,1094],{"class":214},[200,43967,25426],{"class":386},[200,43969,2104],{"class":214},[200,43971,393],{"class":379},[200,43973,280],{"class":214},[200,43975,43976,43978,43980,43982,43984,43986,43988,43990],{"class":202,"line":14},[200,43977,3738],{"class":379},[200,43979,42685],{"class":210},[200,43981,509],{"class":214},[200,43983,452],{"class":206},[200,43985,42692],{"class":223},[200,43987,227],{"class":235},[200,43989,25426],{"class":210},[200,43991,416],{"class":235},[200,43993,43994],{"class":202,"line":255},[200,43995,528],{"emptyLinePlaceholder":20},[200,43997,43998,44000,44002,44004,44006,44008,44010,44012,44014,44016,44018,44020,44023],{"class":202,"line":272},[200,43999,3738],{"class":379},[200,44001,42709],{"class":210},[200,44003,509],{"class":214},[200,44005,42685],{"class":210},[200,44007,215],{"class":214},[200,44009,42718],{"class":223},[200,44011,227],{"class":235},[200,44013,249],{"class":214},[200,44015,9752],{"class":245},[200,44017,249],{"class":214},[200,44019,5282],{"class":235},[200,44021,44022],{"class":206},"as",[200,44024,44025],{"class":1364}," Blob\n",[200,44027,44028,44031,44033,44035,44037],{"class":202,"line":283},[200,44029,44030],{"class":223},"  ensureBlob",[200,44032,227],{"class":235},[200,44034,9752],{"class":210},[200,44036,549],{"class":214},[200,44038,280],{"class":214},[200,44040,44041,44044,44046,44048,44051,44053],{"class":202,"line":300},[200,44042,44043],{"class":235},"    maxSize",[200,44045,239],{"class":214},[200,44047,242],{"class":214},[200,44049,44050],{"class":245},"1Mo",[200,44052,249],{"class":214},[200,44054,252],{"class":214},[200,44056,44057,44060,44062,44064,44066,44068,44070,44072,44074,44076,44078,44080,44082,44084,44086],{"class":202,"line":310},[200,44058,44059],{"class":235},"    types",[200,44061,239],{"class":214},[200,44063,350],{"class":235},[200,44065,249],{"class":214},[200,44067,42918],{"class":245},[200,44069,249],{"class":214},[200,44071,549],{"class":214},[200,44073,242],{"class":214},[200,44075,42927],{"class":245},[200,44077,249],{"class":214},[200,44079,549],{"class":214},[200,44081,242],{"class":214},[200,44083,42936],{"class":245},[200,44085,249],{"class":214},[200,44087,4511],{"class":235},[200,44089,44090,44092],{"class":202,"line":336},[200,44091,5095],{"class":214},[200,44093,416],{"class":235},[200,44095,44096],{"class":202,"line":342},[200,44097,528],{"emptyLinePlaceholder":20},[200,44099,44100],{"class":202,"line":365},[200,44101,43092],{"class":401},[200,44103,44104],{"class":202,"line":371},[200,44105,528],{"emptyLinePlaceholder":20},[200,44107,44108,44110,44112,44114,44116,44118,44120],{"class":202,"line":398},[200,44109,3758],{"class":206},[200,44111,40842],{"class":223},[200,44113,227],{"class":235},[200,44115,25426],{"class":210},[200,44117,549],{"class":214},[200,44119,40851],{"class":2060},[200,44121,416],{"class":235},[200,44123,44124,44126],{"class":202,"line":405},[200,44125,413],{"class":214},[200,44127,416],{"class":210},[51,44129,44131],{"id":44130},"texte-et-blob-dans-le-même-formulaire","Texte et Blob dans le Même Formulaire",[35,44133,44134],{},"Jusqu'à présent, j'ai seulement un type d'entrée dans mon formulaire, texte ou fichier. Mais que se passe-t-il si je veux avoir les deux dans le même formulaire ?",[35,44136,44137,44138,44140],{},"Vous devez savoir que vous devez absolument utiliser un ",[156,44139,42603],{}," pour envoyer le formulaire au serveur en raison de l'entrée de fichier.",[35,44142,44143],{},"Le formulaire est toujours très simple :",[192,44145,44147],{"className":21457,"code":44146,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003Cform action=\"\u002Fapi\u002Ftext-image\" enctype=\"multipart\u002Fform-data\" method=\"post\">\n    \u003Cinput type=\"text\" name=\"name\" placeholder=\"Nom\" required>\n    \u003Cinput type=\"file\" name=\"image\" accept=\"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg\" required>\n    \u003Cbutton type=\"submit\">\n      Créer Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n",[156,44148,44149,44157,44196,44236,44276,44294,44298,44306,44314],{"__ignoreMap":13},[200,44150,44151,44153,44155],{"class":202,"line":203},[200,44152,599],{"class":214},[200,44154,21468],{"class":235},[200,44156,647],{"class":214},[200,44158,44159,44161,44163,44165,44167,44169,44172,44174,44176,44178,44180,44182,44184,44186,44188,44190,44192,44194],{"class":202,"line":14},[200,44160,656],{"class":214},[200,44162,602],{"class":235},[200,44164,40681],{"class":379},[200,44166,449],{"class":214},[200,44168,610],{"class":214},[200,44170,44171],{"class":245},"\u002Fapi\u002Ftext-image",[200,44173,610],{"class":214},[200,44175,42497],{"class":379},[200,44177,449],{"class":214},[200,44179,610],{"class":214},[200,44181,42504],{"class":245},[200,44183,610],{"class":214},[200,44185,6589],{"class":379},[200,44187,449],{"class":214},[200,44189,610],{"class":214},[200,44191,3793],{"class":245},[200,44193,610],{"class":214},[200,44195,647],{"class":214},[200,44197,44198,44200,44202,44204,44206,44208,44210,44212,44214,44216,44218,44220,44222,44224,44226,44228,44230,44232,44234],{"class":202,"line":255},[200,44199,803],{"class":214},[200,44201,355],{"class":235},[200,44203,321],{"class":379},[200,44205,449],{"class":214},[200,44207,610],{"class":214},[200,44209,697],{"class":245},[200,44211,610],{"class":214},[200,44213,700],{"class":379},[200,44215,449],{"class":214},[200,44217,610],{"class":214},[200,44219,20112],{"class":245},[200,44221,610],{"class":214},[200,44223,40711],{"class":379},[200,44225,449],{"class":214},[200,44227,610],{"class":214},[200,44229,40718],{"class":245},[200,44231,610],{"class":214},[200,44233,40723],{"class":379},[200,44235,647],{"class":214},[200,44237,44238,44240,44242,44244,44246,44248,44250,44252,44254,44256,44258,44260,44262,44264,44266,44268,44270,44272,44274],{"class":202,"line":272},[200,44239,803],{"class":214},[200,44241,355],{"class":235},[200,44243,321],{"class":379},[200,44245,449],{"class":214},[200,44247,610],{"class":214},[200,44249,42533],{"class":245},[200,44251,610],{"class":214},[200,44253,700],{"class":379},[200,44255,449],{"class":214},[200,44257,610],{"class":214},[200,44259,9752],{"class":245},[200,44261,610],{"class":214},[200,44263,42538],{"class":379},[200,44265,449],{"class":214},[200,44267,610],{"class":214},[200,44269,42545],{"class":245},[200,44271,610],{"class":214},[200,44273,40723],{"class":379},[200,44275,647],{"class":214},[200,44277,44278,44280,44282,44284,44286,44288,44290,44292],{"class":202,"line":283},[200,44279,803],{"class":214},[200,44281,909],{"class":235},[200,44283,321],{"class":379},[200,44285,449],{"class":214},[200,44287,610],{"class":214},[200,44289,916],{"class":245},[200,44291,610],{"class":214},[200,44293,647],{"class":214},[200,44295,44296],{"class":202,"line":300},[200,44297,40748],{"class":210},[200,44299,44300,44302,44304],{"class":202,"line":310},[200,44301,15078],{"class":214},[200,44303,909],{"class":235},[200,44305,647],{"class":214},[200,44307,44308,44310,44312],{"class":202,"line":336},[200,44309,892],{"class":214},[200,44311,602],{"class":235},[200,44313,647],{"class":214},[200,44315,44316,44318,44320],{"class":202,"line":342},[200,44317,680],{"class":214},[200,44319,21468],{"class":235},[200,44321,647],{"class":214},[35,44323,44324],{},"Maintenant, je peux créer un validateur en utilisant Zod :",[192,44326,44328],{"className":1380,"code":44327,"language":1383,"meta":13,"style":13},"import { any, object, string } from 'zod'\n\nexport const createValidator = object({\n  name: string({ message: 'Obligatoire' }),\n  image: any().refine(image => image instanceof File, { message: 'L\\'image doit être un fichier' }).refine(image => image.size \u003C 1024 * 1024, { message: 'L\\'image doit faire moins de 1 Mo' }).refine(image => image.type.startsWith('image\u002F'), { message: 'L\\'image doit être une image' }).refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'L\\'image doit être un JPEG ou un PNG' })\n})\n",[156,44329,44330,44356,44360,44377,44405,44619],{"__ignoreMap":13},[200,44331,44332,44334,44336,44338,44340,44342,44344,44346,44348,44350,44352,44354],{"class":202,"line":203},[200,44333,972],{"class":206},[200,44335,318],{"class":214},[200,44337,42637],{"class":210},[200,44339,549],{"class":214},[200,44341,42642],{"class":210},[200,44343,549],{"class":214},[200,44345,22752],{"class":210},[200,44347,980],{"class":214},[200,44349,983],{"class":206},[200,44351,242],{"class":214},[200,44353,19813],{"class":245},[200,44355,991],{"class":214},[200,44357,44358],{"class":202,"line":14},[200,44359,528],{"emptyLinePlaceholder":20},[200,44361,44362,44364,44366,44369,44371,44373,44375],{"class":202,"line":255},[200,44363,1390],{"class":206},[200,44365,14305],{"class":379},[200,44367,44368],{"class":210}," createValidator ",[200,44370,449],{"class":214},[200,44372,42642],{"class":223},[200,44374,227],{"class":210},[200,44376,230],{"class":214},[200,44378,44379,44381,44383,44385,44387,44389,44391,44393,44395,44397,44399,44401,44403],{"class":202,"line":272},[200,44380,236],{"class":235},[200,44382,239],{"class":214},[200,44384,22752],{"class":223},[200,44386,227],{"class":210},[200,44388,2767],{"class":214},[200,44390,39109],{"class":235},[200,44392,239],{"class":214},[200,44394,242],{"class":214},[200,44396,41718],{"class":245},[200,44398,249],{"class":214},[200,44400,980],{"class":214},[200,44402,2104],{"class":210},[200,44404,252],{"class":214},[200,44406,44407,44409,44411,44413,44415,44417,44419,44421,44423,44425,44427,44429,44431,44433,44435,44437,44439,44441,44443,44445,44447,44449,44451,44453,44455,44457,44459,44461,44463,44465,44467,44469,44471,44473,44475,44477,44479,44481,44483,44485,44487,44489,44491,44493,44495,44497,44499,44501,44503,44505,44507,44509,44511,44513,44515,44517,44519,44521,44523,44525,44527,44529,44531,44533,44535,44537,44539,44541,44543,44545,44547,44549,44551,44553,44555,44557,44559,44561,44563,44565,44567,44569,44571,44573,44575,44577,44579,44581,44583,44585,44587,44589,44591,44593,44595,44597,44599,44601,44603,44605,44607,44609,44611,44613,44615,44617],{"class":202,"line":283},[200,44408,43180],{"class":235},[200,44410,239],{"class":214},[200,44412,42637],{"class":223},[200,44414,1549],{"class":210},[200,44416,215],{"class":214},[200,44418,42756],{"class":223},[200,44420,227],{"class":210},[200,44422,9752],{"class":386},[200,44424,393],{"class":379},[200,44426,43201],{"class":210},[200,44428,43204],{"class":214},[200,44430,42769],{"class":1364},[200,44432,549],{"class":214},[200,44434,318],{"class":214},[200,44436,39109],{"class":235},[200,44438,239],{"class":214},[200,44440,242],{"class":214},[200,44442,42782],{"class":245},[200,44444,16056],{"class":210},[200,44446,42787],{"class":245},[200,44448,249],{"class":214},[200,44450,980],{"class":214},[200,44452,2104],{"class":210},[200,44454,215],{"class":214},[200,44456,42756],{"class":223},[200,44458,227],{"class":210},[200,44460,9752],{"class":386},[200,44462,393],{"class":379},[200,44464,42709],{"class":210},[200,44466,215],{"class":214},[200,44468,43247],{"class":210},[200,44470,599],{"class":214},[200,44472,42816],{"class":2060},[200,44474,29668],{"class":214},[200,44476,42816],{"class":2060},[200,44478,549],{"class":214},[200,44480,318],{"class":214},[200,44482,39109],{"class":235},[200,44484,239],{"class":214},[200,44486,242],{"class":214},[200,44488,42782],{"class":245},[200,44490,16056],{"class":210},[200,44492,42837],{"class":245},[200,44494,249],{"class":214},[200,44496,980],{"class":214},[200,44498,2104],{"class":210},[200,44500,215],{"class":214},[200,44502,42756],{"class":223},[200,44504,227],{"class":210},[200,44506,9752],{"class":386},[200,44508,393],{"class":379},[200,44510,42709],{"class":210},[200,44512,215],{"class":214},[200,44514,3786],{"class":210},[200,44516,215],{"class":214},[200,44518,6120],{"class":223},[200,44520,227],{"class":210},[200,44522,249],{"class":214},[200,44524,42872],{"class":245},[200,44526,249],{"class":214},[200,44528,2104],{"class":210},[200,44530,549],{"class":214},[200,44532,318],{"class":214},[200,44534,39109],{"class":235},[200,44536,239],{"class":214},[200,44538,242],{"class":214},[200,44540,42782],{"class":245},[200,44542,16056],{"class":210},[200,44544,42893],{"class":245},[200,44546,249],{"class":214},[200,44548,980],{"class":214},[200,44550,2104],{"class":210},[200,44552,215],{"class":214},[200,44554,42756],{"class":223},[200,44556,227],{"class":210},[200,44558,9752],{"class":386},[200,44560,393],{"class":379},[200,44562,350],{"class":210},[200,44564,249],{"class":214},[200,44566,42918],{"class":245},[200,44568,249],{"class":214},[200,44570,549],{"class":214},[200,44572,242],{"class":214},[200,44574,42927],{"class":245},[200,44576,249],{"class":214},[200,44578,549],{"class":214},[200,44580,242],{"class":214},[200,44582,42936],{"class":245},[200,44584,249],{"class":214},[200,44586,360],{"class":210},[200,44588,215],{"class":214},[200,44590,3856],{"class":223},[200,44592,43368],{"class":210},[200,44594,215],{"class":214},[200,44596,43373],{"class":210},[200,44598,549],{"class":214},[200,44600,318],{"class":214},[200,44602,39109],{"class":235},[200,44604,239],{"class":214},[200,44606,242],{"class":214},[200,44608,42782],{"class":245},[200,44610,16056],{"class":210},[200,44612,42971],{"class":245},[200,44614,249],{"class":214},[200,44616,980],{"class":214},[200,44618,416],{"class":210},[200,44620,44621,44623],{"class":202,"line":300},[200,44622,413],{"class":214},[200,44624,416],{"class":210},[35,44626,44627],{},"Ce validateur peut être utilisé comme auparavant, sur le serveur et le client.",[35,44629,44630,44631,44633],{},"Si vous préférez utiliser l'utilitaire ",[156,44632,42451],{}," de NuxtHub, je vous recommande de diviser le validateur en deux parties : une pour le texte et une pour l'image. Cela signifie que le texte et l'image seront utilisés côté client en les fusionnant en un seul objet, et uniquement le texte sera utilisé côté serveur.",[192,44635,44637],{"className":1380,"code":44636,"language":1383,"meta":13,"style":13},"import { any, object, string } from 'zod'\n\nexport const createTextValidator = object({\n  name: string({ message: 'Obligatoire' }),\n})\n\nexport const createImageValidator = object({\n  image: any().refine(image => image instanceof File, { message: 'L\\'image doit être un fichier' }).refine(image => image.size \u003C 1024 * 1024, { message: 'L\\'image doit faire moins de 1 Mo' }).refine(image => image.type.startsWith('image\u002F'), { message: 'L\\'image doit être une image' }).refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'L\\'image doit être un JPEG ou un PNG' })\n})\n\nexport const createValidator = createTextValidator.merge(createImageValidator)\n",[156,44638,44639,44665,44669,44686,44714,44720,44724,44740,44954,44960,44964],{"__ignoreMap":13},[200,44640,44641,44643,44645,44647,44649,44651,44653,44655,44657,44659,44661,44663],{"class":202,"line":203},[200,44642,972],{"class":206},[200,44644,318],{"class":214},[200,44646,42637],{"class":210},[200,44648,549],{"class":214},[200,44650,42642],{"class":210},[200,44652,549],{"class":214},[200,44654,22752],{"class":210},[200,44656,980],{"class":214},[200,44658,983],{"class":206},[200,44660,242],{"class":214},[200,44662,19813],{"class":245},[200,44664,991],{"class":214},[200,44666,44667],{"class":202,"line":14},[200,44668,528],{"emptyLinePlaceholder":20},[200,44670,44671,44673,44675,44678,44680,44682,44684],{"class":202,"line":255},[200,44672,1390],{"class":206},[200,44674,14305],{"class":379},[200,44676,44677],{"class":210}," createTextValidator ",[200,44679,449],{"class":214},[200,44681,42642],{"class":223},[200,44683,227],{"class":210},[200,44685,230],{"class":214},[200,44687,44688,44690,44692,44694,44696,44698,44700,44702,44704,44706,44708,44710,44712],{"class":202,"line":272},[200,44689,236],{"class":235},[200,44691,239],{"class":214},[200,44693,22752],{"class":223},[200,44695,227],{"class":210},[200,44697,2767],{"class":214},[200,44699,39109],{"class":235},[200,44701,239],{"class":214},[200,44703,242],{"class":214},[200,44705,41718],{"class":245},[200,44707,249],{"class":214},[200,44709,980],{"class":214},[200,44711,2104],{"class":210},[200,44713,252],{"class":214},[200,44715,44716,44718],{"class":202,"line":283},[200,44717,413],{"class":214},[200,44719,416],{"class":210},[200,44721,44722],{"class":202,"line":300},[200,44723,528],{"emptyLinePlaceholder":20},[200,44725,44726,44728,44730,44732,44734,44736,44738],{"class":202,"line":310},[200,44727,1390],{"class":206},[200,44729,14305],{"class":379},[200,44731,43167],{"class":210},[200,44733,449],{"class":214},[200,44735,42642],{"class":223},[200,44737,227],{"class":210},[200,44739,230],{"class":214},[200,44741,44742,44744,44746,44748,44750,44752,44754,44756,44758,44760,44762,44764,44766,44768,44770,44772,44774,44776,44778,44780,44782,44784,44786,44788,44790,44792,44794,44796,44798,44800,44802,44804,44806,44808,44810,44812,44814,44816,44818,44820,44822,44824,44826,44828,44830,44832,44834,44836,44838,44840,44842,44844,44846,44848,44850,44852,44854,44856,44858,44860,44862,44864,44866,44868,44870,44872,44874,44876,44878,44880,44882,44884,44886,44888,44890,44892,44894,44896,44898,44900,44902,44904,44906,44908,44910,44912,44914,44916,44918,44920,44922,44924,44926,44928,44930,44932,44934,44936,44938,44940,44942,44944,44946,44948,44950,44952],{"class":202,"line":336},[200,44743,43180],{"class":235},[200,44745,239],{"class":214},[200,44747,42637],{"class":223},[200,44749,1549],{"class":210},[200,44751,215],{"class":214},[200,44753,42756],{"class":223},[200,44755,227],{"class":210},[200,44757,9752],{"class":386},[200,44759,393],{"class":379},[200,44761,43201],{"class":210},[200,44763,43204],{"class":214},[200,44765,42769],{"class":1364},[200,44767,549],{"class":214},[200,44769,318],{"class":214},[200,44771,39109],{"class":235},[200,44773,239],{"class":214},[200,44775,242],{"class":214},[200,44777,42782],{"class":245},[200,44779,16056],{"class":210},[200,44781,42787],{"class":245},[200,44783,249],{"class":214},[200,44785,980],{"class":214},[200,44787,2104],{"class":210},[200,44789,215],{"class":214},[200,44791,42756],{"class":223},[200,44793,227],{"class":210},[200,44795,9752],{"class":386},[200,44797,393],{"class":379},[200,44799,42709],{"class":210},[200,44801,215],{"class":214},[200,44803,43247],{"class":210},[200,44805,599],{"class":214},[200,44807,42816],{"class":2060},[200,44809,29668],{"class":214},[200,44811,42816],{"class":2060},[200,44813,549],{"class":214},[200,44815,318],{"class":214},[200,44817,39109],{"class":235},[200,44819,239],{"class":214},[200,44821,242],{"class":214},[200,44823,42782],{"class":245},[200,44825,16056],{"class":210},[200,44827,42837],{"class":245},[200,44829,249],{"class":214},[200,44831,980],{"class":214},[200,44833,2104],{"class":210},[200,44835,215],{"class":214},[200,44837,42756],{"class":223},[200,44839,227],{"class":210},[200,44841,9752],{"class":386},[200,44843,393],{"class":379},[200,44845,42709],{"class":210},[200,44847,215],{"class":214},[200,44849,3786],{"class":210},[200,44851,215],{"class":214},[200,44853,6120],{"class":223},[200,44855,227],{"class":210},[200,44857,249],{"class":214},[200,44859,42872],{"class":245},[200,44861,249],{"class":214},[200,44863,2104],{"class":210},[200,44865,549],{"class":214},[200,44867,318],{"class":214},[200,44869,39109],{"class":235},[200,44871,239],{"class":214},[200,44873,242],{"class":214},[200,44875,42782],{"class":245},[200,44877,16056],{"class":210},[200,44879,42893],{"class":245},[200,44881,249],{"class":214},[200,44883,980],{"class":214},[200,44885,2104],{"class":210},[200,44887,215],{"class":214},[200,44889,42756],{"class":223},[200,44891,227],{"class":210},[200,44893,9752],{"class":386},[200,44895,393],{"class":379},[200,44897,350],{"class":210},[200,44899,249],{"class":214},[200,44901,42918],{"class":245},[200,44903,249],{"class":214},[200,44905,549],{"class":214},[200,44907,242],{"class":214},[200,44909,42927],{"class":245},[200,44911,249],{"class":214},[200,44913,549],{"class":214},[200,44915,242],{"class":214},[200,44917,42936],{"class":245},[200,44919,249],{"class":214},[200,44921,360],{"class":210},[200,44923,215],{"class":214},[200,44925,3856],{"class":223},[200,44927,43368],{"class":210},[200,44929,215],{"class":214},[200,44931,43373],{"class":210},[200,44933,549],{"class":214},[200,44935,318],{"class":214},[200,44937,39109],{"class":235},[200,44939,239],{"class":214},[200,44941,242],{"class":214},[200,44943,42782],{"class":245},[200,44945,16056],{"class":210},[200,44947,42971],{"class":245},[200,44949,249],{"class":214},[200,44951,980],{"class":214},[200,44953,416],{"class":210},[200,44955,44956,44958],{"class":202,"line":342},[200,44957,413],{"class":214},[200,44959,416],{"class":210},[200,44961,44962],{"class":202,"line":365},[200,44963,528],{"emptyLinePlaceholder":20},[200,44965,44966,44968,44970,44972,44974,44977,44979,44982],{"class":202,"line":371},[200,44967,1390],{"class":206},[200,44969,14305],{"class":379},[200,44971,44368],{"class":210},[200,44973,449],{"class":214},[200,44975,44976],{"class":210}," createTextValidator",[200,44978,215],{"class":214},[200,44980,44981],{"class":223},"merge",[200,44983,44984],{"class":210},"(createImageValidator)\n",[35,44986,44987,44988,44991],{},"Côté client, j'utiliserai le ",[156,44989,44990],{},"createValidator"," pour valider le formulaire avant de l'envoyer au serveur :",[192,44993,44995],{"className":21457,"code":44994,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nasync function onSubmit(event: Event) {\n  const target = event.target as HTMLFormElement\n  const formData = new FormData(target)\n\n  const name = formData.get('name')\n  const image = formData.get('image')\n  try {\n    await createValidator.parseAsync({ name, image })\n\n    await $fetch('\u002Fapi\u002Ftext-image', {\n      method: 'POST',\n      body: formData,\n    })\n  }\n  catch (error) {\n    console.error(error)\n  }\n}\n\u003C\u002Fscript>\n",[156,44996,44997,45017,45037,45055,45073,45077,45101,45125,45131,45156,45160,45178,45192,45202,45208,45212,45224,45238,45242,45246],{"__ignoreMap":13},[200,44998,44999,45001,45003,45005,45007,45009,45011,45013,45015],{"class":202,"line":203},[200,45000,599],{"class":214},[200,45002,14896],{"class":235},[200,45004,14594],{"class":379},[200,45006,449],{"class":214},[200,45008,610],{"class":214},[200,45010,1383],{"class":245},[200,45012,610],{"class":214},[200,45014,21825],{"class":379},[200,45016,647],{"class":214},[200,45018,45019,45021,45023,45025,45027,45029,45031,45033,45035],{"class":202,"line":14},[200,45020,3726],{"class":379},[200,45022,5002],{"class":379},[200,45024,41783],{"class":223},[200,45026,227],{"class":214},[200,45028,25426],{"class":386},[200,45030,239],{"class":214},[200,45032,43447],{"class":1364},[200,45034,2104],{"class":214},[200,45036,280],{"class":214},[200,45038,45039,45041,45043,45045,45047,45049,45051,45053],{"class":202,"line":255},[200,45040,3738],{"class":379},[200,45042,43458],{"class":210},[200,45044,509],{"class":214},[200,45046,43463],{"class":210},[200,45048,215],{"class":214},[200,45050,43468],{"class":210},[200,45052,14678],{"class":206},[200,45054,43473],{"class":1364},[200,45056,45057,45059,45061,45063,45065,45067,45069,45071],{"class":202,"line":272},[200,45058,3738],{"class":379},[200,45060,42685],{"class":210},[200,45062,509],{"class":214},[200,45064,13007],{"class":214},[200,45066,43486],{"class":223},[200,45068,227],{"class":235},[200,45070,43468],{"class":210},[200,45072,416],{"class":235},[200,45074,45075],{"class":202,"line":283},[200,45076,528],{"emptyLinePlaceholder":20},[200,45078,45079,45081,45083,45085,45087,45089,45091,45093,45095,45097,45099],{"class":202,"line":300},[200,45080,3738],{"class":379},[200,45082,700],{"class":210},[200,45084,509],{"class":214},[200,45086,42685],{"class":210},[200,45088,215],{"class":214},[200,45090,42718],{"class":223},[200,45092,227],{"class":235},[200,45094,249],{"class":214},[200,45096,20112],{"class":245},[200,45098,249],{"class":214},[200,45100,416],{"class":235},[200,45102,45103,45105,45107,45109,45111,45113,45115,45117,45119,45121,45123],{"class":202,"line":310},[200,45104,3738],{"class":379},[200,45106,42709],{"class":210},[200,45108,509],{"class":214},[200,45110,42685],{"class":210},[200,45112,215],{"class":214},[200,45114,42718],{"class":223},[200,45116,227],{"class":235},[200,45118,249],{"class":214},[200,45120,9752],{"class":245},[200,45122,249],{"class":214},[200,45124,416],{"class":235},[200,45126,45127,45129],{"class":202,"line":336},[200,45128,41792],{"class":206},[200,45130,280],{"class":214},[200,45132,45133,45135,45138,45140,45142,45144,45146,45148,45150,45152,45154],{"class":202,"line":342},[200,45134,19231],{"class":206},[200,45136,45137],{"class":210}," createValidator",[200,45139,215],{"class":214},[200,45141,41312],{"class":223},[200,45143,227],{"class":235},[200,45145,2767],{"class":214},[200,45147,700],{"class":210},[200,45149,549],{"class":214},[200,45151,42709],{"class":210},[200,45153,980],{"class":214},[200,45155,416],{"class":235},[200,45157,45158],{"class":202,"line":365},[200,45159,528],{"emptyLinePlaceholder":20},[200,45161,45162,45164,45166,45168,45170,45172,45174,45176],{"class":202,"line":371},[200,45163,19231],{"class":206},[200,45165,18754],{"class":223},[200,45167,227],{"class":235},[200,45169,249],{"class":214},[200,45171,44171],{"class":245},[200,45173,249],{"class":214},[200,45175,549],{"class":214},[200,45177,280],{"class":214},[200,45179,45180,45182,45184,45186,45188,45190],{"class":202,"line":398},[200,45181,41838],{"class":235},[200,45183,239],{"class":214},[200,45185,242],{"class":214},[200,45187,18779],{"class":245},[200,45189,249],{"class":214},[200,45191,252],{"class":214},[200,45193,45194,45196,45198,45200],{"class":202,"line":405},[200,45195,41853],{"class":235},[200,45197,239],{"class":214},[200,45199,42685],{"class":210},[200,45201,252],{"class":214},[200,45203,45204,45206],{"class":202,"line":410},[200,45205,2111],{"class":214},[200,45207,416],{"class":235},[200,45209,45210],{"class":202,"line":860},[200,45211,4505],{"class":214},[200,45213,45214,45216,45218,45220,45222],{"class":202,"line":889},[200,45215,41874],{"class":206},[200,45217,1094],{"class":235},[200,45219,19275],{"class":210},[200,45221,5282],{"class":235},[200,45223,230],{"class":214},[200,45225,45226,45228,45230,45232,45234,45236],{"class":202,"line":899},[200,45227,43620],{"class":210},[200,45229,215],{"class":214},[200,45231,19275],{"class":223},[200,45233,227],{"class":235},[200,45235,19275],{"class":210},[200,45237,416],{"class":235},[200,45239,45240],{"class":202,"line":904},[200,45241,4505],{"class":214},[200,45243,45244],{"class":202,"line":930},[200,45245,1129],{"class":214},[200,45247,45248,45250,45252],{"class":202,"line":3655},[200,45249,680],{"class":214},[200,45251,14896],{"class":235},[200,45253,647],{"class":214},[35,45255,45256,45257,45260,45261,45263],{},"Côté serveur, j'utiliserai le ",[156,45258,45259],{},"createTextValidator"," pour valider le texte et l'utilitaire ",[156,45262,42451],{}," pour valider l'image :",[192,45265,45267],{"className":1380,"code":45266,"language":1383,"meta":13,"style":13},"import { createTextValidator } from '~\u002Futils\u002Fvalidators'\n\nexport default defineEventHandler(async (event) => {\n  const formData = await readFormData(event)\n\n  const name = formData.get('name')\n  const data = await createTextValidator.parseAsync({ name }).catch((error) => {\n    throw createError({\n      status: 400,\n      message: 'Données non valides',\n      data: error.errors[0].message\n    })\n  })\n\n  \u002F\u002F Enregistrer le texte dans la base de données...\n\n  const image = formData.get('image') as Blob\n  ensureBlob(image, {\n    maxSize: '1 Mo',\n    types: ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg']\n  })\n\n  \u002F\u002F Enregistrer l'image dans une base de données ou un système de fichiers\n\n  return sendNoContent(event, 201)\n})\n",[156,45268,45269,45287,45291,45313,45331,45335,45359,45401,45411,45421,45435,45457,45463,45469,45473,45478,45482,45510,45522,45537,45569,45575,45579,45583,45587,45603],{"__ignoreMap":13},[200,45270,45271,45273,45275,45277,45279,45281,45283,45285],{"class":202,"line":203},[200,45272,972],{"class":206},[200,45274,318],{"class":214},[200,45276,44976],{"class":210},[200,45278,980],{"class":214},[200,45280,983],{"class":206},[200,45282,242],{"class":214},[200,45284,42350],{"class":245},[200,45286,991],{"class":214},[200,45288,45289],{"class":202,"line":14},[200,45290,528],{"emptyLinePlaceholder":20},[200,45292,45293,45295,45297,45299,45301,45303,45305,45307,45309,45311],{"class":202,"line":255},[200,45294,1390],{"class":206},[200,45296,1393],{"class":206},[200,45298,38723],{"class":223},[200,45300,227],{"class":210},[200,45302,3726],{"class":379},[200,45304,1094],{"class":214},[200,45306,25426],{"class":386},[200,45308,2104],{"class":214},[200,45310,393],{"class":379},[200,45312,280],{"class":214},[200,45314,45315,45317,45319,45321,45323,45325,45327,45329],{"class":202,"line":272},[200,45316,3738],{"class":379},[200,45318,42685],{"class":210},[200,45320,509],{"class":214},[200,45322,452],{"class":206},[200,45324,42692],{"class":223},[200,45326,227],{"class":235},[200,45328,25426],{"class":210},[200,45330,416],{"class":235},[200,45332,45333],{"class":202,"line":283},[200,45334,528],{"emptyLinePlaceholder":20},[200,45336,45337,45339,45341,45343,45345,45347,45349,45351,45353,45355,45357],{"class":202,"line":300},[200,45338,3738],{"class":379},[200,45340,700],{"class":210},[200,45342,509],{"class":214},[200,45344,42685],{"class":210},[200,45346,215],{"class":214},[200,45348,42718],{"class":223},[200,45350,227],{"class":235},[200,45352,249],{"class":214},[200,45354,20112],{"class":245},[200,45356,249],{"class":214},[200,45358,416],{"class":235},[200,45360,45361,45363,45365,45367,45369,45371,45373,45375,45377,45379,45381,45383,45385,45387,45389,45391,45393,45395,45397,45399],{"class":202,"line":310},[200,45362,3738],{"class":379},[200,45364,5056],{"class":210},[200,45366,509],{"class":214},[200,45368,452],{"class":206},[200,45370,44976],{"class":210},[200,45372,215],{"class":214},[200,45374,41312],{"class":223},[200,45376,227],{"class":235},[200,45378,2767],{"class":214},[200,45380,700],{"class":210},[200,45382,980],{"class":214},[200,45384,2104],{"class":235},[200,45386,215],{"class":214},[200,45388,41452],{"class":223},[200,45390,227],{"class":235},[200,45392,227],{"class":214},[200,45394,19275],{"class":386},[200,45396,2104],{"class":214},[200,45398,393],{"class":379},[200,45400,280],{"class":214},[200,45402,45403,45405,45407,45409],{"class":202,"line":336},[200,45404,39200],{"class":206},[200,45406,39203],{"class":223},[200,45408,227],{"class":235},[200,45410,230],{"class":214},[200,45412,45413,45415,45417,45419],{"class":202,"line":342},[200,45414,38845],{"class":235},[200,45416,239],{"class":214},[200,45418,41198],{"class":2060},[200,45420,252],{"class":214},[200,45422,45423,45425,45427,45429,45431,45433],{"class":202,"line":365},[200,45424,39233],{"class":235},[200,45426,239],{"class":214},[200,45428,242],{"class":214},[200,45430,41211],{"class":245},[200,45432,249],{"class":214},[200,45434,252],{"class":214},[200,45436,45437,45439,45441,45443,45445,45447,45449,45451,45453,45455],{"class":202,"line":371},[200,45438,41220],{"class":235},[200,45440,239],{"class":214},[200,45442,19220],{"class":210},[200,45444,215],{"class":214},[200,45446,26828],{"class":210},[200,45448,9872],{"class":235},[200,45450,3948],{"class":2060},[200,45452,360],{"class":235},[200,45454,215],{"class":214},[200,45456,43071],{"class":210},[200,45458,45459,45461],{"class":202,"line":398},[200,45460,2111],{"class":214},[200,45462,416],{"class":235},[200,45464,45465,45467],{"class":202,"line":405},[200,45466,5095],{"class":214},[200,45468,416],{"class":235},[200,45470,45471],{"class":202,"line":410},[200,45472,528],{"emptyLinePlaceholder":20},[200,45474,45475],{"class":202,"line":860},[200,45476,45477],{"class":401},"  \u002F\u002F Enregistrer le texte dans la base de données...\n",[200,45479,45480],{"class":202,"line":889},[200,45481,528],{"emptyLinePlaceholder":20},[200,45483,45484,45486,45488,45490,45492,45494,45496,45498,45500,45502,45504,45506,45508],{"class":202,"line":899},[200,45485,3738],{"class":379},[200,45487,42709],{"class":210},[200,45489,509],{"class":214},[200,45491,42685],{"class":210},[200,45493,215],{"class":214},[200,45495,42718],{"class":223},[200,45497,227],{"class":235},[200,45499,249],{"class":214},[200,45501,9752],{"class":245},[200,45503,249],{"class":214},[200,45505,5282],{"class":235},[200,45507,44022],{"class":206},[200,45509,44025],{"class":1364},[200,45511,45512,45514,45516,45518,45520],{"class":202,"line":904},[200,45513,44030],{"class":223},[200,45515,227],{"class":235},[200,45517,9752],{"class":210},[200,45519,549],{"class":214},[200,45521,280],{"class":214},[200,45523,45524,45526,45528,45530,45533,45535],{"class":202,"line":930},[200,45525,44043],{"class":235},[200,45527,239],{"class":214},[200,45529,242],{"class":214},[200,45531,45532],{"class":245},"1 Mo",[200,45534,249],{"class":214},[200,45536,252],{"class":214},[200,45538,45539,45541,45543,45545,45547,45549,45551,45553,45555,45557,45559,45561,45563,45565,45567],{"class":202,"line":3655},[200,45540,44059],{"class":235},[200,45542,239],{"class":214},[200,45544,350],{"class":235},[200,45546,249],{"class":214},[200,45548,42918],{"class":245},[200,45550,249],{"class":214},[200,45552,549],{"class":214},[200,45554,242],{"class":214},[200,45556,42927],{"class":245},[200,45558,249],{"class":214},[200,45560,549],{"class":214},[200,45562,242],{"class":214},[200,45564,42936],{"class":245},[200,45566,249],{"class":214},[200,45568,4511],{"class":235},[200,45570,45571,45573],{"class":202,"line":3660},[200,45572,5095],{"class":214},[200,45574,416],{"class":235},[200,45576,45577],{"class":202,"line":4035},[200,45578,528],{"emptyLinePlaceholder":20},[200,45580,45581],{"class":202,"line":4490},[200,45582,43092],{"class":401},[200,45584,45585],{"class":202,"line":4496},[200,45586,528],{"emptyLinePlaceholder":20},[200,45588,45589,45591,45593,45595,45597,45599,45601],{"class":202,"line":4502},[200,45590,3758],{"class":206},[200,45592,40842],{"class":223},[200,45594,227],{"class":235},[200,45596,25426],{"class":210},[200,45598,549],{"class":214},[200,45600,40851],{"class":2060},[200,45602,416],{"class":235},[200,45604,45605,45607],{"class":202,"line":4508},[200,45606,413],{"class":214},[200,45608,416],{"class":210},[35,45610,45611,45612,1569],{},"Pour des formulaires plus longs, je pourrais utiliser une boucle pour extraire chaque champ du formulaire et les valider. Dans cette boucle, je peux gérer des cas spécifiques comme des tableaux de champs multi-sélection ou des fichiers ou ignorer certains champs (qui pourraient être validés en utilisant ",[156,45613,42451],{},[192,45615,45617],{"className":1380,"code":45616,"language":1383,"meta":13,"style":13},"const data = {}\nfor (const [key, value] of formData.entries()) {\n  if (key === 'moduleId[]') {\n    data[key] = formData.getAll(key)\n    continue\n  }\n  else if (key === 'image') {\n    continue\n  }\n  else {\n    data[key] = value\n  }\n}\n",[156,45618,45619,45629,45660,45681,45707,45712,45716,45740,45744,45748,45754,45769,45773],{"__ignoreMap":13},[200,45620,45621,45623,45625,45627],{"class":202,"line":203},[200,45622,443],{"class":379},[200,45624,10969],{"class":210},[200,45626,449],{"class":214},[200,45628,19914],{"class":214},[200,45630,45631,45633,45635,45637,45639,45641,45643,45645,45647,45649,45651,45653,45655,45658],{"class":202,"line":14},[200,45632,1089],{"class":206},[200,45634,1094],{"class":210},[200,45636,443],{"class":379},[200,45638,350],{"class":214},[200,45640,24627],{"class":210},[200,45642,549],{"class":214},[200,45644,809],{"class":210},[200,45646,360],{"class":214},[200,45648,6457],{"class":214},[200,45650,42685],{"class":210},[200,45652,215],{"class":214},[200,45654,6465],{"class":223},[200,45656,45657],{"class":210},"()) ",[200,45659,230],{"class":214},[200,45661,45662,45664,45666,45668,45670,45672,45675,45677,45679],{"class":202,"line":255},[200,45663,20098],{"class":206},[200,45665,1094],{"class":235},[200,45667,24627],{"class":210},[200,45669,3298],{"class":214},[200,45671,242],{"class":214},[200,45673,45674],{"class":245},"moduleId[]",[200,45676,249],{"class":214},[200,45678,5282],{"class":235},[200,45680,230],{"class":214},[200,45682,45683,45686,45688,45690,45692,45694,45696,45698,45701,45703,45705],{"class":202,"line":272},[200,45684,45685],{"class":210},"    data",[200,45687,9872],{"class":235},[200,45689,24627],{"class":210},[200,45691,18849],{"class":235},[200,45693,449],{"class":214},[200,45695,42685],{"class":210},[200,45697,215],{"class":214},[200,45699,45700],{"class":223},"getAll",[200,45702,227],{"class":235},[200,45704,24627],{"class":210},[200,45706,416],{"class":235},[200,45708,45709],{"class":202,"line":283},[200,45710,45711],{"class":206},"    continue\n",[200,45713,45714],{"class":202,"line":300},[200,45715,4505],{"class":214},[200,45717,45718,45721,45724,45726,45728,45730,45732,45734,45736,45738],{"class":202,"line":310},[200,45719,45720],{"class":206},"  else",[200,45722,45723],{"class":206}," if",[200,45725,1094],{"class":235},[200,45727,24627],{"class":210},[200,45729,3298],{"class":214},[200,45731,242],{"class":214},[200,45733,9752],{"class":245},[200,45735,249],{"class":214},[200,45737,5282],{"class":235},[200,45739,230],{"class":214},[200,45741,45742],{"class":202,"line":336},[200,45743,45711],{"class":206},[200,45745,45746],{"class":202,"line":342},[200,45747,4505],{"class":214},[200,45749,45750,45752],{"class":202,"line":365},[200,45751,45720],{"class":206},[200,45753,280],{"class":214},[200,45755,45756,45758,45760,45762,45764,45766],{"class":202,"line":371},[200,45757,45685],{"class":210},[200,45759,9872],{"class":235},[200,45761,24627],{"class":210},[200,45763,18849],{"class":235},[200,45765,449],{"class":214},[200,45767,45768],{"class":210}," value\n",[200,45770,45771],{"class":202,"line":398},[200,45772,4505],{"class":214},[200,45774,45775],{"class":202,"line":405},[200,45776,1129],{"class":214},[51,45778,24146],{"id":24145},[35,45780,28985,45781,45784,45785],{},[1216,45782,45783],{},"voilà"," ! Vous êtes maintenant capables de valider des formulaires à la fois côté client et côté serveur sans dupliquer la logique de validation. C'est un grand pas en avant dans la construction d'une application full-stack avec Nuxt. J'espère que cet article vous aidera à construire votre prochaine application en toute confiance. ",[1216,45786,45787,45788,45793],{},"Vous devriez absolument consulter la ",[39,45789,45792],{"href":45790,"rel":45791},"https:\u002F\u002Fhub.nuxt.com\u002Fdocs",[77],"documentation de NuxtHub"," pour en savoir plus sur la plateforme et le pouvoir qu'elle offre pour construire des applications full-stack avec Nuxt.",[35,45795,45796],{},"N'hésitez pas à me faire savoir si vous avez des questions ou des commentaires. Je pourrais faire quelques erreurs, alors n'hésitez pas à me corriger. Je suis toujours à la recherche de moyens pour améliorer mon code et mes articles.",[35,45798,45799],{},"Merci de votre lecture !",[1170,45801,45802],{},"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 pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .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 .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 .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}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}",{"title":13,"searchDepth":14,"depth":14,"links":45804},[45805,45806],{"id":18646,"depth":14,"text":18647},{"id":40645,"depth":14,"text":40646,"children":45807},[45808],{"id":40873,"depth":255,"text":40874},"2024-06-07","Découvrez comment valider les formulaires à la fois côté client et côté serveur sans dupliquer la logique de validation.",{},"\u002Ffr\u002Fposts\u002Fnuxt-going-full-stack-how-to-streamline-form-validation","---\ncontentId: 8c077ba8-63ca-4784-b693-0e1fa8623bf9\ntitle: \"Nuxt Devient Full-Stack : Comment Valider des formulaires ?\"\ndescription: Découvrez comment valider les formulaires à la fois côté client et côté serveur sans dupliquer la logique de validation.\ndate: 2024-06-07\n---\n\nDepuis des semaines, je construis une application full-stack appelée Orion avec Nuxt et [NuxtHub](https:\u002F\u002Fhub.nuxt.com). Tout au long de ce parcours, j'ai rencontré de nombreux défis, et l'un d'eux est la validation des formulaires. Dans cet article, je vais vous montrer comment j'ai réussi à **valider les formulaires à la fois côté client et côté serveur sans duplications** de la logique de validation, des champs de texte simples aux téléchargements de fichiers complexes avec NuxtHub.\n\n_Orion est une collection de modèles basée sur la communauté pour votre prochain projet, des pages de destination aux applications web complètes. NuxtHub est une plateforme de déploiement et d'administration pour Nuxt, propulsée par Cloudflare._\n\n\u003CCalloutReadMore id=\"2349bbcd-1535-4f33-8da7-299f46af18b9\" \u002F>\n\n_Pendant cet article, je vais parler de [NuxtHub](https:\u002F\u002Fhub.nuxt.com) car il fournit les primitives pour construire des applications full-stack, mais même si vous ne l'utilisez pas, vous pouvez facilement suivre cet article. Les formulaires seront gérés sans aucune bibliothèque._\n\nVous pouvez parcourir le code exemple dans \u003CGitHubLink repo=\"barbapapazes\u002Fvalidate-forms-in-nuxt\" \u002F>.\n\n## Le Problème\n\nSur le web, les formulaires sont omniprésents. Ils sont le principal moyen d'interaction et de demande d'entrée utilisateur. Mais sur le web, il y a une règle essentielle : **ne jamais faire confiance au client** (absolument jamais et en aucun cas). Cela signifie que **toutes les données** provenant du client doivent être **vérifiées** et **sanitisées** sur le serveur avant d'être traitées ou stockées dans une base de données. Un client peut être un navigateur, une application mobile, une requête HTTP ou tout autre objet capable d'envoyer des données à votre serveur, construit par vous ou non.\n\nLorsque vous construisez une MPA traditionnelle (Multi-Page Application), seul le serveur est responsable de la validation. Pour une SPA (Single-Page Application), à la fois le client et le serveur sont responsables de la validation pour fournir une meilleure expérience utilisateur. _Cela est simplifié car il existe un moyen de donner une impression de validation côté client avec une MPA traditionnelle, mais ce n'est pas le sujet de cet article._ MAIS, même si les données sont validées côté client, il est **obligatoire** de les valider côté serveur.\n\n## Le Formulaire le Plus Simple\n\nCommençons notre parcours avec le formulaire le plus simple : une seule entrée de texte.\n\n```vue\n\u003Ctemplate>\n  \u003Cform method=\"post\" action=\"\u002Ftag\">\n    \u003Cinput type=\"text\" placeholder=\"Nom\" required>\n    \u003Cbutton type=\"submit\">\n      Créer Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nEnsuite, côté serveur, je peux créer une route pour gérer la soumission du formulaire :\n\n```ts\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n```\n\nPour l'instant, je n'ai aucune validation. Je peux envoyer n'importe quoi au serveur, et il l'acceptera. _C'est un énorme problème de sécurité._\n\n_Rappelez-vous que la façon dont je gère la validation est spécifique à une SPA. Une MPA traditionnelle aurait ajouté des erreurs aux messages flash et redirigé la demande._\n\n### Valider le Corps\n\nPour valider le corps côté serveur, j'utilise [Zod](https:\u002F\u002Fzod.dev). _Zod est une validation de schéma en TypeScript avec inférence de type statique._ La bonne nouvelle est qu'h3, le routeur et le framework web qui alimentent Nitro, la couche serveur de Nuxt, fournit une utilitaire pour valider le corps : `readValidatedBody`.\n\n```ts\nimport { z } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, z.object({\n    name: z.string(),\n  }).parse)\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n```\n\nL'utilitaire `readValidatedBody` lancera automatiquement une erreur si le corps ne correspond pas au schéma. Par exemple, si le nom est manquant, la requête échouera.\n\n\u003CCallout icon=\"i-unjs-h3\" text=\"Valider les Données avec H3\" to=\"https:\u002F\u002Fh3.unjs.io\u002Fexamples\u002Fvalidate-data\" \u002F>\n\nPar défaut, l'erreur sera gérée par le serveur et lancera une erreur 500, mais je peux capturer l'erreur pour renvoyer un message d'erreur plus convivial.\n\nLa première solution consiste à utiliser la méthode `safeParse` qui ne lancera pas d'erreur mais renverra un résultat avec l'erreur le cas échéant.\n\n```ts\nimport { z } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const result = await readValidatedBody(event, z.object({\n    name: z.string(),\n  }).safeParse)\n\n  if (!result.success) {\n    throw createError({\n      status: 400,\n      message: 'Données non valides',\n      data: result.error.errors[0].message,\n    })\n  }\n\n  const body = result.data\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n```\n\nLa seconde solution consiste à utiliser la méthode `parseAsync` pour attacher une gestion d'erreur manuelle à la promesse.\n\n```ts\nimport type { ZodError } from 'zod'\nimport { z } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, data => z.object({\n    name: z.string(),\n  }).parseAsync(data).catch((error) => {\n    if (error instanceof ZodError) {\n      throw createError({\n        status: 400,\n        message: 'Données non valides',\n        data: error.errors[0].message,\n      })\n    }\n  }))\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n```\n\n_Vous pourriez envelopper le validateur avec un `try\u002Fcatch`, mais je trouve cela moins élégant et lisible._\n\n### Valider le Frontend\n\nCôté frontend, je peux également utiliser Zod pour valider le formulaire avant de l'envoyer au serveur.\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nimport type { ZodError } from 'zod'\nimport { z } from 'zod'\n\nconst schema = z.object({\n  name: z.string({ message: 'Obligatoire' }), \u002F\u002F Je peux personnaliser le message d'erreur affiché à l'utilisateur\n})\n\nconst state = reactive({\n  name: '',\n})\n\nasync function onSubmit() {\n  try {\n    const data = schema.parse(state)\n    await $fetch('\u002Ftag', {\n      method: 'post',\n      body: data,\n    })\n  }\n  catch (error) {\n    if (error instanceof ZodError) {\n      throw createError({\n        status: 400,\n        message: 'Données non valides',\n        data: error.errors,\n      })\n    }\n  }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cform @submit.prevent=\"onSubmit()\">\n    \u003Cinput v-model=\"state.name\" type=\"text\" placeholder=\"Nom\" required>\n    \u003Cbutton type=\"submit\">\n      Créer Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nL'implémentation est très simple et directe. Je pourrais enregistrer les erreurs dans une variable pour les afficher à l'utilisateur. Si vous souhaitez aller plus loin, je vous recommande d'essayer le composant `Form` de [Nuxt UI](https:\u002F\u002Fui.nuxt.com\u002F) ou une bibliothèque de validation comme [VeeValidate](https:\u002F\u002Fvee-validate.logaretm.com\u002Fv4\u002F) ou [FormKit](https:\u002F\u002Fformkit.com\u002F).\n\n## Refactorisons\n\nMaintenant que j'ai la logique de validation à la fois côté client et côté serveur, voyons s'il y a un code intéressant à refactoriser.\n\nLa première chose que je remarque est que la logique de validation, côté client et serveur, est la même, et c'est totalement normal. Mais que se passe-t-il si je décide de changer la logique de validation côté serveur parce que mon API est utilisée par une autre application ? Mon frontend pourrait être cassé car la logique de validation sera différente. Pour éviter cela, il serait peut-être mieux d'écrire le validateur une seule fois et de l'utiliser des deux côtés. Qu'en pensez-vous ?\n\nJe peux créer un fichier `validators.ts` dans le dossier `utils` de la partie client (à la racine) d'une application Nuxt. Ce fichier contiendra tous les validateurs pour mon application et sera partagé entre le client et le serveur.\n\n```ts\nimport { z } from 'zod'\n\nexport const createTagValidator = z.object({\n  name: z.string({ message: 'Obligatoire' }),\n})\n```\n\n> [!TIP]\n> Ce fichier peut être utilisé côté serveur mais a été créé côté client, donc vous devez explicitement importer tout ce dont vous avez besoin pour éviter des problèmes de rendu.\n\nCe validateur contient le message pour l'erreur pour aider l'utilisateur à comprendre ce qui ne va pas.\n\nGrâce à la fonction d'auto-import de Nuxt, je peux facilement l'utiliser côté client :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst schema = createTagValidator\n\n\u002F\u002F ...\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cform>\n  \u003C!-- ... -->\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nEnsuite, vous pouvez également l'utiliser côté serveur en l'importation :\n\n```ts\nimport { createTagValidator } from '~\u002Futils\u002Fvalidators'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, createTagValidator.parse)\n\n  \u002F\u002F Enregistrer dans la base de données...\n\n  return sendNoContent(event, 201)\n})\n```\n\nMaintenant, je peux changer en toute sécurité la logique de validation sans casser le frontend, car ils suivent les mêmes règles, et je n'ai pas à dupliquer le code.\n\n## Gestion des Fichiers\n\nPour cette partie, je vais vous montrer deux façons différentes de valider les fichiers côté serveur. La première utilisera Zod et quelques raffinements, et la seconde utilisera l'utilitaire `ensureBlob` de NuxtHub.\n\n_Cette validation n'est pas complète et devrait être améliorée pour gérer plus de cas._\n\n### Avec Zod\n\nPour cette seconde partie, je vais vous montrer comment télécharger un fichier sur le serveur. D'abord, ajoutons notre entrée dans le formulaire :\n\n```vue\n\u003Ctemplate>\n  \u003Cform action=\"\u002Fapi\u002Fimage\" enctype=\"multipart\u002Fform-data\" method=\"post\">\n    \u003Cinput type=\"file\" accept=\"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg\" required>\n    \u003Cbutton type=\"submit\">\n      Créer Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nEnsuite, je peux gérer le fichier côté serveur, mais c'est un peu plus complexe car j'envoie un `formData` et h3 ne fournit pas d'utilitaire comme `readValidatedFormData`. Donc, je dois le gérer manuellement.\n\nD'abord, je vais lire le `formData` en utilisant l'utilitaire `readFormData`, puis j'extraire l'`image` du `formData` et la valider en utilisant Zod.\n\n```ts\nimport { any, object } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const formData = await readFormData(event)\n\n  const image = formData.get('image')\n  await object({\n    image: any()\n      .refine(image => image instanceof File, { message: 'L\\'image doit être un fichier' })\n      .refine(image => image.size \u003C 1024 * 1024, { message: 'L\\'image doit faire moins de 1 Mo' })\n      .refine(image => image.type.startsWith('image\u002F'), { message: 'L\\'image doit être une image' })\n      .refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'L\\'image doit être un JPEG ou un PNG' })\n  }).parseAsync({ image }).catch((error) => {\n    throw createError({\n      status: 400,\n      message: 'Image non valide',\n      data: error.errors[0].message\n    })\n  })\n\n  \u002F\u002F Enregistrer l'image dans une base de données ou un système de fichiers\n\n  return sendNoContent(event, 201)\n})\n```\n\nCôté client, je peux également valider le fichier en utilisant la même technique qu'auparavant et en partageant le validateur entre le client et le serveur.\n\nD'abord, je dois déplacer le validateur dans le fichier `utils\u002Fvalidators.ts` :\n\n```ts\nimport { any, object } from 'zod'\n\nexport const createImageValidator = object({\n  image: any()\n    .refine(image => image instanceof File, { message: 'L\\'image doit être un fichier' })\n    .refine(image => image.size \u003C 1024 * 1024, { message: 'L\\'image doit faire moins de 1 Mo' })\n    .refine(image => image.type.startsWith('image\u002F'), { message: 'L\\'image doit être une image' })\n    .refine(image => ['image\u002Fjpeg', 'image\u002Fpng'].includes(image.type), { message: 'L\\'image doit être un JPEG ou un PNG' })\n})\n```\n\nEnsuite, je peux l'utiliser lors de la soumission du formulaire pour éviter d'envoyer des données non valides au serveur :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nasync function onImageSubmit(event: Event) {\n  const target = event.target as HTMLFormElement\n  const formData = new FormData(target)\n\n  const image = formData.get('image')\n  try {\n    await createImageValidator.parseAsync({ image })\n\n    await $fetch('\u002Fapi\u002Fimage', {\n      method: 'POST',\n      body: formData,\n    })\n  }\n  catch (error) {\n    console.error(error)\n  }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Ch1>Valider les Formulaires avec Nuxt\u003C\u002Fh1>\n\n    \u003Ch2>\n      Image\n    \u003C\u002Fh2>\n    \u003Cform enctype=\"multipart\u002Fform-data\" @submit.prevent=\"onImageSubmit($event)\">\n      \u003Cinput type=\"file\" name=\"image\" accept=\"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg\">\n      \u003Cbutton type=\"submit\">\n        Soumettre\n      \u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\nSi la validation échoue, la requête ne sera pas envoyée au serveur et l'erreur sera affichée dans la console. Bien sûr, vous pouvez gérer l'erreur comme vous le souhaitez, comme utiliser un `toast` ou afficher un message d'erreur sous l'entrée concernée.\n\nCôté serveur, je peux remplacer la logique de validation par le validateur :\n\n```ts\nimport { saveImageValidator } from '~\u002Futils\u002Fvalidators'\n\nexport default defineEventHandler(async (event) => {\n  \u002F\u002F ...\n\n  await saveImageValidator.parseAsync({ image })\n\n  \u002F\u002F ...\n})\n```\n\n### Avec NuxtHub\n\nNuxtHub est livré avec un utilitaire nommé `ensureBlob` qui peut être utilisé pour vérifier la taille et le type du fichier. Cet utilitaire est très utile car il lancera automatiquement une erreur si le fichier n'est pas valide, mais ne peut être utilisé que côté serveur. Cela signifie que j'aurai un schéma Zod côté client et un utilitaire NuxtHub côté serveur. _Utilisez des constantes pour partager les limites de validation entre le schéma Zod et l'utilitaire NuxtHub._\n\n```ts\nexport default defineEventHandler(async (event) => {\n  const formData = await readFormData(event)\n\n  const image = formData.get('image') as Blob\n  ensureBlob(image, {\n    maxSize: '1Mo',\n    types: ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg']\n  })\n\n  \u002F\u002F Enregistrer l'image dans une base de données ou un système de fichiers\n\n  return sendNoContent(event, 201)\n})\n```\n\n## Texte et Blob dans le Même Formulaire\n\nJusqu'à présent, j'ai seulement un type d'entrée dans mon formulaire, texte ou fichier. Mais que se passe-t-il si je veux avoir les deux dans le même formulaire ?\n\nVous devez savoir que vous devez absolument utiliser un `formData` pour envoyer le formulaire au serveur en raison de l'entrée de fichier.\n\nLe formulaire est toujours très simple :\n\n```vue\n\u003Ctemplate>\n  \u003Cform action=\"\u002Fapi\u002Ftext-image\" enctype=\"multipart\u002Fform-data\" method=\"post\">\n    \u003Cinput type=\"text\" name=\"name\" placeholder=\"Nom\" required>\n    \u003Cinput type=\"file\" name=\"image\" accept=\"image\u002Fpng, image\u002Fjpeg, image\u002Fjpg\" required>\n    \u003Cbutton type=\"submit\">\n      Créer Tag\n    \u003C\u002Fbutton>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>\n```\n\nMaintenant, je peux créer un validateur en utilisant Zod :\n\n```ts\nimport { any, object, string } from 'zod'\n\nexport const createValidator = object({\n  name: string({ message: 'Obligatoire' }),\n  image: any().refine(image => image instanceof File, { message: 'L\\'image doit être un fichier' }).refine(image => image.size \u003C 1024 * 1024, { message: 'L\\'image doit faire moins de 1 Mo' }).refine(image => image.type.startsWith('image\u002F'), { message: 'L\\'image doit être une image' }).refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'L\\'image doit être un JPEG ou un PNG' })\n})\n```\n\nCe validateur peut être utilisé comme auparavant, sur le serveur et le client.\n\nSi vous préférez utiliser l'utilitaire `ensureBlob` de NuxtHub, je vous recommande de diviser le validateur en deux parties : une pour le texte et une pour l'image. Cela signifie que le texte et l'image seront utilisés côté client en les fusionnant en un seul objet, et uniquement le texte sera utilisé côté serveur.\n\n```ts\nimport { any, object, string } from 'zod'\n\nexport const createTextValidator = object({\n  name: string({ message: 'Obligatoire' }),\n})\n\nexport const createImageValidator = object({\n  image: any().refine(image => image instanceof File, { message: 'L\\'image doit être un fichier' }).refine(image => image.size \u003C 1024 * 1024, { message: 'L\\'image doit faire moins de 1 Mo' }).refine(image => image.type.startsWith('image\u002F'), { message: 'L\\'image doit être une image' }).refine(image => ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg'].includes(image.type), { message: 'L\\'image doit être un JPEG ou un PNG' })\n})\n\nexport const createValidator = createTextValidator.merge(createImageValidator)\n```\n\nCôté client, j'utiliserai le `createValidator` pour valider le formulaire avant de l'envoyer au serveur :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nasync function onSubmit(event: Event) {\n  const target = event.target as HTMLFormElement\n  const formData = new FormData(target)\n\n  const name = formData.get('name')\n  const image = formData.get('image')\n  try {\n    await createValidator.parseAsync({ name, image })\n\n    await $fetch('\u002Fapi\u002Ftext-image', {\n      method: 'POST',\n      body: formData,\n    })\n  }\n  catch (error) {\n    console.error(error)\n  }\n}\n\u003C\u002Fscript>\n```\n\nCôté serveur, j'utiliserai le `createTextValidator` pour valider le texte et l'utilitaire `ensureBlob` pour valider l'image :\n\n```ts\nimport { createTextValidator } from '~\u002Futils\u002Fvalidators'\n\nexport default defineEventHandler(async (event) => {\n  const formData = await readFormData(event)\n\n  const name = formData.get('name')\n  const data = await createTextValidator.parseAsync({ name }).catch((error) => {\n    throw createError({\n      status: 400,\n      message: 'Données non valides',\n      data: error.errors[0].message\n    })\n  })\n\n  \u002F\u002F Enregistrer le texte dans la base de données...\n\n  const image = formData.get('image') as Blob\n  ensureBlob(image, {\n    maxSize: '1 Mo',\n    types: ['image\u002Fpng', 'image\u002Fjpeg', 'image\u002Fjpg']\n  })\n\n  \u002F\u002F Enregistrer l'image dans une base de données ou un système de fichiers\n\n  return sendNoContent(event, 201)\n})\n```\n\nPour des formulaires plus longs, je pourrais utiliser une boucle pour extraire chaque champ du formulaire et les valider. Dans cette boucle, je peux gérer des cas spécifiques comme des tableaux de champs multi-sélection ou des fichiers ou ignorer certains champs (qui pourraient être validés en utilisant `ensureBlob`).\n\n```ts\nconst data = {}\nfor (const [key, value] of formData.entries()) {\n  if (key === 'moduleId[]') {\n    data[key] = formData.getAll(key)\n    continue\n  }\n  else if (key === 'image') {\n    continue\n  }\n  else {\n    data[key] = value\n  }\n}\n```\n\n## Conclusion\n\nEt _voilà_ ! Vous êtes maintenant capables de valider des formulaires à la fois côté client et côté serveur sans dupliquer la logique de validation. C'est un grand pas en avant dans la construction d'une application full-stack avec Nuxt. J'espère que cet article vous aidera à construire votre prochaine application en toute confiance. _Vous devriez absolument consulter la [documentation de NuxtHub](https:\u002F\u002Fhub.nuxt.com\u002Fdocs) pour en savoir plus sur la plateforme et le pouvoir qu'elle offre pour construire des applications full-stack avec Nuxt._\n\nN'hésitez pas à me faire savoir si vous avez des questions ou des commentaires. Je pourrais faire quelques erreurs, alors n'hésitez pas à me corriger. Je suis toujours à la recherche de moyens pour améliorer mon code et mes articles.\n\nMerci de votre lecture !\n",{"title":40576,"description":45810},{"loc":45812},"fr\u002Fposts\u002Fnuxt-going-full-stack-how-to-streamline-form-validation","UnvxnnkvgTeO4duH9JEcZsC_VwGrInWRErZmVACOu-U",{"id":45819,"title":45820,"body":45821,"contentId":30589,"date":60222,"description":60223,"extension":18,"meta":60224,"navigation":20,"path":60225,"rawbody":60226,"robots":23,"schemaOrg":23,"seo":60227,"sitemap":60228,"stem":60254,"__hash__":60255},"posts\u002Ffr\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey.md","Créer une App Nuxt Full-Stack : une Aventure sur Twitch",{"type":7,"value":45822,"toc":60218},[45823,45828,45831,45854,45857,45862,45865,45868,45872,45878,45898,45904,45910,45917,45930,45943,46100,46103,46113,46136,46139,46144,46150,46154,46160,46176,46179,46182,60215],[35,45824,45825],{},[6862,45826,45827],{},"Je l'ai enfin fait !",[35,45829,45830],{},"Après des mois à y penser, j'ai enfin lancé mon premier stream sur Twitch, le 28 avril 2024. C'est une étape très importante pour moi car j'adore partager mes connaissances et parler de ce que je fais.",[11400,45832,132,45838],{"className":45833},[45834,45835,45836,45837],"grid","grid-cols-1","md:grid-cols-2","gap-4",[45839,45840,132,45841],"calloutfollowmeontwitch",{},[7151,45842,132,45845],{"icon":7153,"text":45843,"href":45844},"Voir les rediffusions","https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes",[7151,45846,132,45849],{"icon":45847,"text":45848,"href":36274},"i-simple-icons-x","Ne manquez rien",[7151,45850],{"icon":45851,"text":45852,"href":45853},"i-simple-icons-github","L'application Full-Stack","https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion",[35,45855,45856],{},"Quand j'étais à l'école, j'organisais des cours pour expliquer la programmation à mes camarades. J'ai aussi rejoint une communauté pour expliquer les choses sur les ordinateurs, j'ai donné quelques conférences, et récemment, j'ai commencé à écrire des articles sur mon blog.",[35,45858,45859],{},[6862,45860,45861],{},"Twitch est juste une nouvelle façon de continuer à faire ce que j'aime !",[35,45863,45864],{},"Dans les semaines à venir, je partagerai avec vous mon parcours pour construire une application Full-Stack Nuxt, déployée sur Cloudflare, appelée Orion. J'expliquerai comment je l'ai construite, les choix que j'ai faits, et les problèmes que j'ai rencontrés. Mon objectif à travers cet article est de vous donner une vue honnête et réaliste des avantages et des inconvénients de la création d'une application Full-Stack en edge avec Nuxt en mai 2024, à travers le processus de construction d'une application réelle.",[35,45866,45867],{},"Dans cet article, je partagerai avec vous chaque étape de cette première série de streams. Si vous ne parlez pas français (car les streams sont en français), si vous avez manqué un stream, ou si vous préférez simplement lire, cet article est fait pour vous !",[51,45869,45871],{"id":45870},"découverte-de-nuxthub-et-début-de-lapplication-full-stack-nuxt","Découverte de NuxtHub et Début de l'Application Full-Stack Nuxt",[35,45873,45874],{},[1707,45875],{"alt":45876,"src":45877},"Vignette de mon premier live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-04-28.png",[35,45879,45880],{},[1216,45881,45882,45885,45886,45890,45891,45894,45895,215],{},[39,45883,38397],{"href":45853,"rel":45884},[77]," est une application Full-Stack construite avec Nuxt et développée en direct ",[39,45887,45889],{"href":18579,"rel":45888},[77],"sur Twitch",". Vous pouvez voir la rediffusion sur ",[39,45892,19427],{"href":45844,"rel":45893},[77]," et le code source est open-source sur ",[39,45896,11636],{"href":45853,"rel":45897},[77],[35,45899,45900,45901],{},"C'était mon premier stream, et pour être honnête, j'étais un peu stressé. Le moment où vous devez passer de la scène \"Starting Soon\" à la caméra est difficile. Mais une fois que j'ai commencé à parler, tout s'est bien passé. ",[1216,45902,45903],{},"Cependant, cela ne veut pas dire que c'est le meilleur stream que vous verrez jamais. Nous allons nous améliorer !",[35,45905,45906],{},[1707,45907],{"alt":45908,"src":45909},"Moi, pas très à l'aise devant la caméra.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-04-28-me.png",[35,45911,45912,45913,45916],{},"Tout d'abord, je présente ",[39,45914,36579],{"href":37901,"rel":45915},[77],". C'est une plateforme développée par l'équipe Nuxt pour faciliter le déploiement et la gestion des applications Nuxt déployées sur Cloudflare. En quelques clics, vous pouvez déployer votre application Nuxt sur Cloudflare. C'est vraiment magique ! Grâce à un module Nuxt, vous pourrez développer votre application localement et ensuite la déployer sans modifier votre code. Tout, de la base de données au stockage du blog, fonctionne parfaitement dans les deux environnements.",[35,45918,45919,45920,45929],{},"J'ai essayé de déployer un modèle depuis NuxtHub pour montrer à quel point il est facile d'utiliser la plateforme. J'ai choisi le modèle Atinotes. Malheureusement, cette application produit un bundle de plus de 1 Mo, la limite pour le niveau gratuit, donc le déploiement échoue. Dommage pour une démonstration. ",[1216,45921,45922,45923,45928],{},"Après cet échec, j'ai pensé à un moyen de prévenir cela et d'informer les utilisateurs si un modèle nécessite un plan payant ou non. ",[39,45924,45927],{"href":45925,"rel":45926},"https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F13",[77],"J'ai soulevé un problème"," qui a été rapidement corrigé !",". La démo attendra plus tard.",[35,45931,45932,45933,17891,45938,45942],{},"Après cet effet de démonstration, nous avons commencé à coder l'application Full-Stack Nuxt en ajoutant l'authentification. Pour ce faire, nous utilisons le module ",[39,45934,45936],{"href":38033,"rel":45935},[77],[156,45937,38035],{},[39,45939,37897],{"href":45940,"rel":45941},"https:\u002F\u002Fgithub.com\u002FAtinux",[77],". Avec quelques lignes de code, nous ajoutons l'authentification. Voici le code que nous avons utilisé :",[192,45944,45946],{"className":1380,"code":45945,"language":1383,"meta":13,"style":13},"export default oauth.githubEventHandler({\n  config: {\n    emailRequired: true,\n  },\n  async onSuccess(event, { user, tokens }) {\n    await setUserSession(event, {\n      user: {\n        githubId: user.githubId,\n      },\n    })\n\n    return sendRedirect(event, '\u002F')\n  },\n})\n",[156,45947,45948,45966,45975,45986,45990,46015,46030,46039,46055,46059,46065,46069,46090,46094],{"__ignoreMap":13},[200,45949,45950,45952,45954,45957,45959,45962,45964],{"class":202,"line":203},[200,45951,1390],{"class":206},[200,45953,1393],{"class":206},[200,45955,45956],{"class":210}," oauth",[200,45958,215],{"class":214},[200,45960,45961],{"class":223},"githubEventHandler",[200,45963,227],{"class":210},[200,45965,230],{"class":214},[200,45967,45968,45971,45973],{"class":202,"line":14},[200,45969,45970],{"class":235},"  config",[200,45972,239],{"class":214},[200,45974,280],{"class":214},[200,45976,45977,45980,45982,45984],{"class":202,"line":255},[200,45978,45979],{"class":235},"    emailRequired",[200,45981,239],{"class":214},[200,45983,2439],{"class":2438},[200,45985,252],{"class":214},[200,45987,45988],{"class":202,"line":272},[200,45989,368],{"class":214},[200,45991,45992,45994,45997,45999,46001,46003,46005,46007,46009,46011,46013],{"class":202,"line":283},[200,45993,3234],{"class":379},[200,45995,45996],{"class":235}," onSuccess",[200,45998,227],{"class":214},[200,46000,25426],{"class":386},[200,46002,549],{"class":214},[200,46004,318],{"class":214},[200,46006,34533],{"class":386},[200,46008,549],{"class":214},[200,46010,9869],{"class":386},[200,46012,390],{"class":214},[200,46014,280],{"class":214},[200,46016,46017,46019,46022,46024,46026,46028],{"class":202,"line":300},[200,46018,19231],{"class":206},[200,46020,46021],{"class":223}," setUserSession",[200,46023,227],{"class":235},[200,46025,25426],{"class":210},[200,46027,549],{"class":214},[200,46029,280],{"class":214},[200,46031,46032,46035,46037],{"class":202,"line":310},[200,46033,46034],{"class":235},"      user",[200,46036,239],{"class":214},[200,46038,280],{"class":214},[200,46040,46041,46044,46046,46048,46050,46053],{"class":202,"line":336},[200,46042,46043],{"class":235},"        githubId",[200,46045,239],{"class":214},[200,46047,34533],{"class":210},[200,46049,215],{"class":214},[200,46051,46052],{"class":210},"githubId",[200,46054,252],{"class":214},[200,46056,46057],{"class":202,"line":342},[200,46058,12098],{"class":214},[200,46060,46061,46063],{"class":202,"line":365},[200,46062,2111],{"class":214},[200,46064,416],{"class":235},[200,46066,46067],{"class":202,"line":371},[200,46068,528],{"emptyLinePlaceholder":20},[200,46070,46071,46073,46076,46078,46080,46082,46084,46086,46088],{"class":202,"line":398},[200,46072,3311],{"class":206},[200,46074,46075],{"class":223}," sendRedirect",[200,46077,227],{"class":235},[200,46079,25426],{"class":210},[200,46081,549],{"class":214},[200,46083,242],{"class":214},[200,46085,2898],{"class":245},[200,46087,249],{"class":214},[200,46089,416],{"class":235},[200,46091,46092],{"class":202,"line":405},[200,46093,368],{"class":214},[200,46095,46096,46098],{"class":202,"line":410},[200,46097,413],{"class":214},[200,46099,416],{"class":210},[35,46101,46102],{},"C'est vraiment simple et ça fonctionne à merveille, bien intégré dans une application Nuxt.",[35,46104,46105,46106,46112],{},"Pour terminer le stream, j'ai rapidement configuré NuxtHub dans mon application pour montrer à quel point il est facile de jouer avec une base de données. J'ai essayé d'utiliser SQL brut, mais la recette pour migrer la base de données ne fonctionnait pas. ",[1216,46107,46108],{},[39,46109,45927],{"href":46110,"rel":46111},"https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F15",[77]," à ce sujet et il est maintenant corrigé ! Après ce deuxième échec, j'ai décidé de commencer à utiliser Drizzle ORM, un ORM simple mais puissant compatible avec les workers de Cloudflare. Dans tous les cas, j'aurais dû utiliser Drizzle car gérer tout avec SQL brut est trop de travail.",[35,46114,46115,46116,46122,46123,46126,46127,46132,46133],{},"Pendant le live, quelqu'un a demandé comment appliquer les migrations à la base de données de production. Je dois admettre que je ne connaissais pas la réponse et rien dans la documentation n'était clair à ce sujet. ",[1216,46117,46118],{},[39,46119,45927],{"href":46120,"rel":46121},"https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F14",[77]," à ce sujet, qui a reçu une réponse. ",[1216,46124,46125],{},"Il faut déployer votre application puis démarrer le développement local avec une connexion distante pour exécuter les migrations"," Clairement, c'est loin d'être idéal pour le moment, mais avec les prochaines ",[39,46128,46131],{"href":46129,"rel":46130},"https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Ftasks",[77],"tâches Nitro",", cela devrait être plus facile dans un avenir proche. ",[1216,46134,46135],{},"Je l'espère vraiment.",[35,46137,46138],{},"Globalement, je suis tellement heureux d'avoir commencé ce stream. Même si ce n'est pas encore parfait, commencer est déjà un grand pas en avant, et je sais que les futurs streams ne feront que devenir plus faciles à commencer.",[35,46140,46141],{},[1216,46142,46143],{},"J'étais tellement stressé que je suis allé très vite dans mes explications, donc j'ai décidé que le prochain stream serait plus bavard pour réexpliquer différents concepts.",[35,46145,46146,46147],{},"Le projet est open-source et disponible sur GitHub à ",[1599,46148,46149],{"repo":38413}," ! Donnez-lui une étoile si vous l'aimez ! ⭐",[51,46151,46153],{"id":46152},"discussion-sur-cloudflare-nuxthub-et-github-oauth","Discussion sur Cloudflare, NuxtHub et GitHub OAuth",[35,46155,46156],{},[1707,46157],{"alt":46158,"src":46159},"Vignette de mon deuxième live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-02.png",[35,46161,46162],{},[1216,46163,46164,45885,46167,45890,46170,45894,46173,215],{},[39,46165,38397],{"href":45853,"rel":46166},[77],[39,46168,45889],{"href":18579,"rel":46169},[77],[39,46171,19427],{"href":45844,"rel":46172},[77],[39,46174,11636],{"href":45853,"rel":46175},[77],[35,46177,46178],{},"J'étais frustré par mon premier live stream. Avec le stress, je n'ai pas expliqué des concepts comme ce qu'est Cloudflare, ce qu'est l'edge et comment NuxtHub est lié à Cloudflare. Mais je sais qu'il est important d'expliquer ces concepts puisque ce sont des technologies nouvelles et que tout le monde ne les connaît pas.",[35,46180,46181],{},"À StrasbourgJS ou Devoxx, j'ai parlé d'UnJS et des problèmes résolus par l'écosystème. Beaucoup de personnes, pendant ou après, m'ont demandé ce qu'est l'edge. Qu'est-ce que c'est ? Pourquoi en avons-nous besoin ? D'où cela vient-il ?",[7918,46183,46185],{"id":46184},"a1291894-3de4-444e-b49b-a5dec98f3e27",[7918,46186,46187,46190,46194,46197,46200,46206,46209,46218,46228,46231,46234,46271,46274,46277,46283,46286,46293,46299,46309,46315,46318,46321,46343,46347,46353,46356,46362,46391,46394,46398,46404,46410,47072,47075,47077,47080,47084,47088,47094,47112,47115,47119,47122,47128,47131,47136,47145,47148,47180,47183,47185,47203,47210,47213,47217,47227,47274,47278,47281,47287,47291,47302,47442,47457,47464,47610,47616,47620,47623,47746,47752,47757,48042,48048,48051,48178,48181,48643,48656,48663,48668,48671],{"id":20859},[35,46188,46189],{},"Le but de mes streams est de tester cette nouvelle technologie, et avec ces retours, il était nécessaire d'expliquer ces concepts.",[1929,46191,46193],{"id":46192},"cloudflare-en-un-coup-dœil","Cloudflare en un coup d'œil",[35,46195,46196],{},"Cloudflare est une entreprise qui propose de nombreux services pour rendre le web plus rapide et plus sécurisé, comme une solution zéro-trust, un WAF, un CDN, un DNS, une protection contre les DDoS, et plus encore, pour les entreprises comme pour les particuliers.",[35,46198,46199],{},"Leur produit le plus connu est le proxy inverse. Cloudflare agit comme le point d'entrée principal et unique pour votre trafic. Ensuite, il dirigera, en interne, le trafic vers le bon serveur en utilisant le DNS. Cela permet de cacher votre IP serveur, de mettre en cache les ressources statiques et de protéger votre serveur contre les attaques DDoS.",[35,46201,46202],{},[1707,46203],{"alt":46204,"src":46205},"Schéma du proxy inverse.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Freverse-proxy.png",[35,46207,46208],{},"Pour fournir ces services, ils ont construit un réseau de centres de données à travers le monde pour être le plus proche possible des utilisateurs afin de servir le contenu mis en cache plus rapidement et de pouvoir atténuer les attaques. Cela s'appelle le réseau edge. Il est à la frontière entre les utilisateurs et le web, pour lequel il constitue la passerelle.",[35,46210,46211,46212,46217],{},"En septembre 2017, ",[39,46213,46216],{"href":46214,"rel":46215},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-cloudflare-workers",[77],"ils ont annoncé Cloudflare Workers",". Pour comprendre l'objectif à l'époque, citons l'article :",[938,46219,46220,46223],{},[35,46221,46222],{},"Que faire si vous souhaitez équilibrer la charge avec un algorithme d'affinité personnalisé ? Que faire si les règles de mise en cache HTTP standard ne sont pas tout à fait adaptées et que vous avez besoin de logique personnalisée pour améliorer votre taux de cache ? Que faire si vous voulez écrire des règles WAF personnalisées adaptées à votre application ?",[35,46224,46225],{},[6862,46226,46227],{},"Vous voulez écrire du code",[35,46229,46230],{},"Les Workers ont été lancés pour permettre aux développeurs de personnaliser l'infrastructure selon leurs besoins. La solution Cloudflare Workers est construite sur l'API Service Worker, une norme dans le navigateur pour intercepter et modifier les requêtes et les réponses.",[35,46232,46233],{},"Depuis lors, ils ont construit une plateforme complète pour les développeurs autour des workers :",[1593,46235,46236,46243,46250,46257,46264],{},[71,46237,46238],{},[39,46239,46242],{"href":46240,"rel":46241},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-workers-kv",[77],"Workers KV, magasin de clés-valeurs sans serveur",[71,46244,46245],{},[39,46246,46249],{"href":46247,"rel":46248},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-d1",[77],"D1, base de données SQL sans serveur",[71,46251,46252],{},[39,46253,46256],{"href":46254,"rel":46255},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-r2-object-storage",[77],"R2, stockage d'objets sans serveur",[71,46258,46259],{},[39,46260,46263],{"href":46261,"rel":46262},"https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-cloudflare-queues",[77],"Queues, files d'attente de tâches sans serveur",[71,46265,46266],{},[39,46267,46270],{"href":46268,"rel":46269},"https:\u002F\u002Fblog.cloudflare.com\u002Fworkers-ai",[77],"Workers AI, inférence GPU sans serveur",[35,46272,46273],{},"Avec tous ces services, il est maintenant (théoriquement, et c'est le but de cette série de répondre à la question) possible de construire une application entièrement sur le réseau mondial de Cloudflare.",[1929,46275,36579],{"id":46276},"nuxthub",[35,46278,46279],{},[1707,46280],{"alt":46281,"src":46282},"Page d'accueil de NuxtHub.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-home-page.png",[35,46284,46285],{},"Depuis la version 3 de Nuxt, l'équipe pousse l'edge. Nitro, la partie serveur de Nuxt, peut être déployée partout, sur les plateformes actuelles et futures, et est optimisée pour l'edge. H3, le serveur HTTP sous-jacent, se réveille en moins de 2 ms, et la sortie de build de Nitro est inférieure à 1 Mo, modules Node inclus.",[35,46287,46288,46289,46292],{},"NuxtHub est la continuité de cet effort. D'abord, c'est une plateforme pour déployer des applications Nuxt sur Cloudflare. C'est un déploiement en deux clics. Vous connectez votre compte Cloudflare car c'est construit sur Cloudflare, vous choisissez un modèle ou un dépôt, vous cliquez sur déployer, et c'est tout. ",[1216,46290,46291],{},"Votre application est en direct sur l'edge."," On a l'impression que c'est de la magie.",[35,46294,46295],{},[1707,46296],{"alt":46297,"src":46298},"Modèles NuxtHub pouvant être déployés en deux clics.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-deploy.png",[35,46300,46301,46302,215],{},"NuxtHub est également un module pour votre application Nuxt afin d'ajouter des fonctionnalités d'accès aux données. Localement, vous pourrez jouer instantanément avec une base de données, un magasin KV, et même un stockage d'objets. En production, vous n'avez pas besoin de changer votre code, il fonctionnera de la même manière. C'est clairement la véritable magie de NuxtHub, alimentée par ",[39,46303,46306],{"href":46304,"rel":46305},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fworker",[77],[156,46307,46308],{},"workerd",[35,46310,46311],{},[1707,46312],{"alt":46313,"src":46314},"Schéma de NuxtHub (de hub.nuxt.com).","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-schema.png",[35,46316,46317],{},"Il y a aussi une fonctionnalité permettant à un environnement de développement local d'interagir avec les données distantes. Pas sûr du cas d'utilisation, mais c'est définitivement intéressant.",[35,46319,46320],{},"Même si la plateforme est encore nouvelle, annoncée lors de Vue Amsterdam 2024, elle est déjà très prometteuse. L'équipe est très réactive, et la plateforme évolue rapidement. Je suis très impatient de voir ce que l'avenir réserve à NuxtHub, mais il y a quelques indices dans la documentation :",[938,46322,46323,46326],{},[35,46324,46325],{},"Nous prévoyons de fournir une expérience backend complète pour les applications Nuxt à travers divers packages @nuxthub.",[1593,46327,46328,46331,46334,46337,46340],{},[71,46329,46330],{},"@nuxthub\u002Fcore : Package principal pour fournir des fonctionnalités de stockage",[71,46332,46333],{},"@nuxthub\u002Fauth : Ajouter l'authentification pour la gestion des utilisateurs (bientôt)",[71,46335,46336],{},"@nuxthub\u002Femail : Envoyer des e-mails transactionnels à vos utilisateurs (bientôt)",[71,46338,46339],{},"@nuxthub\u002Fanalytics : Comprendre votre trafic et suivre les événements au sein de votre application et API (bientôt)",[71,46341,46342],{},"@nuxthub\u002F... : Vous le nommez !",[1929,46344,46346],{"id":46345},"github-oauth","GitHub OAuth",[35,46348,46349,46350,46352],{},"Lors du précédent live stream, j'ai ajouté l'authentification, en utilisant ",[156,46351,38035],{}," à Orion, l'application Full-Stack Nuxt. J'ai utilisé GitHub OAuth, et je pense qu'il est important de voir comment cela fonctionne en coulisses.",[35,46354,46355],{},"Pendant le live, nous avons plongé dans le code, et j'ai expliqué comment cela fonctionne. Voici un schéma pour vous aider à comprendre le processus :",[35,46357,46358],{},[1707,46359],{"alt":46360,"src":46361},"Schéma GitHub OAuth.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Foauth-github.png",[68,46363,46364,46370,46373,46376,46379,46382,46385,46388],{},[71,46365,46366,46367,215],{},"L'utilisateur clique sur le bouton de connexion, qui pointe vers ",[156,46368,46369],{},"\u002Fauth\u002Fgithub",[71,46371,46372],{},"Le serveur envoie une redirection vers la page OAuth de GitHub.",[71,46374,46375],{},"L'utilisateur accepte l'autorisation de GitHub.",[71,46377,46378],{},"GitHub redirige l'utilisateur vers l'URL de rappel de l'application avec un code.",[71,46380,46381],{},"Le serveur échange le code contre des tokens.",[71,46383,46384],{},"Le serveur utilise les tokens pour obtenir les informations de l'utilisateur.",[71,46386,46387],{},"Le serveur stocke l'utilisateur dans une session.",[71,46389,46390],{},"Le serveur redirige l'utilisateur vers la page d'accueil.",[35,46392,46393],{},"Dans ces étapes, le serveur est Nitro, et nous ne parlons pas des erreurs possibles. Il y a de nombreuses erreurs possibles comme le refus de l'autorisation par l'utilisateur, un code invalide, des tokens invalides, l'utilisateur non trouvé, etc. Chaque erreur doit être gérée pour offrir une bonne expérience utilisateur.",[38437,46395,46397],{"id":46396},"code-du-gestionnaire-oauth-github","Code du gestionnaire OAuth GitHub",[35,46399,46400,46401,46403],{},"Voyons maintenant le code de ",[156,46402,38035],{}," pour comprendre comment il fonctionne.",[35,46405,46406,46407,215],{},"Pour simplifier, j'ai retiré certaines parties du code qui ne sont pas nécessaires à la compréhension du processus, et je me suis concentré sur les étapes principales. Vous pouvez trouver le code complet dans le ",[39,46408,2160],{"href":38519,"rel":46409},[77],[192,46411,46413],{"className":1380,"code":46412,"language":1383,"meta":13,"style":13},"\u002F**\n * Gestionnaire d'événements OAuth GitHub.\n *\n * _Le code est simplifié pour l'article et ne fonctionne pas tel quel._\n *\u002F\nexport function githubEventHandler({ config, onSuccess }: OAuthConfig\u003COAuthGitHubConfig>) {\n  return eventHandler(async (event: H3Event) => {\n    config = { \u002F** ... *\u002F}\n\n    \u002F**\n     * Récupérer la requête depuis l'événement.\n     *\u002F\n    const query = getQuery(event)\n\n    if (query.error) {\n      \u002F\u002F Gérer l'erreur\n      return\n    }\n\n    if (!query.code) {\n      \u002F**\n       * Rediriger vers la page OAuth GitHub s'il n'y a pas de code dans la requête.\n       * Si l'utilisateur accepte l'autorisation, GitHub redirigera l'utilisateur vers l'application avec le code.\n       * Le code est un code unique à échanger contre les tokens.\n       *\u002F\n      const redirectUrl = getRequestURL(event).href\n      return sendRedirect(\n        event,\n        withQuery(config.authorizationURL as string, {\n          client_id: config.clientId,\n          redirect_uri: redirectUrl,\n          scope: config.scope.join(' '),\n          ...config.authorizationParams,\n        }),\n      )\n    }\n\n    \u002F**\n     * Utiliser le code pour obtenir les tokens.\n     *\u002F\n    const tokens: any = await $fetch(\n      config.tokenURL as string,\n      {\n        method: 'POST',\n        body: {\n          code: query.code,\n        },\n      },\n    )\n\n    const accessToken = tokens.access_token\n\n    \u002F**\n     * Utiliser le token d'accès pour obtenir les informations de l'utilisateur.\n     *\u002F\n    const user: any = await ofetch('https:\u002F\u002Fapi.github.com\u002Fuser', {\n      headers: {\n        Authorization: `token ${accessToken}`,\n      },\n    })\n\n    \u002F**\n     * Appeler le callback onSuccess avec l'utilisateur et les tokens.\n     *\n     * L'OAuth est terminé, et l'utilisateur peut maintenant être authentifié avec notre système.\n     * Avec Nitro, nous stockons l'utilisateur dans une session au sein d'un cookie.\n     *\u002F\n    return onSuccess(event, {\n      user,\n      tokens,\n    })\n  })\n}\n",[156,46414,46415,46419,46424,46429,46434,46438,46472,46497,46510,46514,46518,46523,46527,46544,46548,46564,46569,46574,46578,46582,46600,46605,46610,46615,46620,46625,46648,46656,46663,46685,46701,46712,46740,46754,46762,46767,46771,46775,46779,46784,46788,46806,46822,46826,46841,46850,46864,46868,46872,46876,46880,46896,46900,46904,46909,46913,46942,46951,46972,46977,46984,46989,46994,47000,47006,47012,47018,47023,47038,47045,47053,47060,47067],{"__ignoreMap":13},[200,46416,46417],{"class":202,"line":203},[200,46418,26119],{"class":401},[200,46420,46421],{"class":202,"line":14},[200,46422,46423],{"class":401}," * Gestionnaire d'événements OAuth GitHub.\n",[200,46425,46426],{"class":202,"line":255},[200,46427,46428],{"class":401}," *\n",[200,46430,46431],{"class":202,"line":272},[200,46432,46433],{"class":401}," * _Le code est simplifié pour l'article et ne fonctionne pas tel quel._\n",[200,46435,46436],{"class":202,"line":283},[200,46437,26134],{"class":401},[200,46439,46440,46442,46444,46447,46449,46452,46454,46456,46459,46462,46464,46467,46470],{"class":202,"line":300},[200,46441,1390],{"class":206},[200,46443,5002],{"class":379},[200,46445,46446],{"class":223}," githubEventHandler",[200,46448,3966],{"class":214},[200,46450,46451],{"class":386}," config",[200,46453,549],{"class":214},[200,46455,45996],{"class":386},[200,46457,46458],{"class":214}," }:",[200,46460,46461],{"class":1364}," OAuthConfig",[200,46463,599],{"class":214},[200,46465,46466],{"class":1364},"OAuthGitHubConfig",[200,46468,46469],{"class":214},">)",[200,46471,280],{"class":214},[200,46473,46474,46476,46479,46481,46483,46485,46487,46489,46491,46493,46495],{"class":202,"line":310},[200,46475,3758],{"class":206},[200,46477,46478],{"class":223}," eventHandler",[200,46480,227],{"class":235},[200,46482,3726],{"class":379},[200,46484,1094],{"class":214},[200,46486,25426],{"class":386},[200,46488,239],{"class":214},[200,46490,39087],{"class":1364},[200,46492,2104],{"class":214},[200,46494,393],{"class":379},[200,46496,280],{"class":214},[200,46498,46499,46501,46503,46505,46508],{"class":202,"line":336},[200,46500,24973],{"class":210},[200,46502,509],{"class":214},[200,46504,318],{"class":214},[200,46506,46507],{"class":401}," \u002F** ... *\u002F",[200,46509,1129],{"class":214},[200,46511,46512],{"class":202,"line":342},[200,46513,528],{"emptyLinePlaceholder":20},[200,46515,46516],{"class":202,"line":365},[200,46517,19641],{"class":401},[200,46519,46520],{"class":202,"line":371},[200,46521,46522],{"class":401},"     * Récupérer la requête depuis l'événement.\n",[200,46524,46525],{"class":202,"line":398},[200,46526,19651],{"class":401},[200,46528,46529,46531,46533,46535,46538,46540,46542],{"class":202,"line":405},[200,46530,2014],{"class":379},[200,46532,3499],{"class":210},[200,46534,509],{"class":214},[200,46536,46537],{"class":223}," getQuery",[200,46539,227],{"class":235},[200,46541,25426],{"class":210},[200,46543,416],{"class":235},[200,46545,46546],{"class":202,"line":410},[200,46547,528],{"emptyLinePlaceholder":20},[200,46549,46550,46552,46554,46556,46558,46560,46562],{"class":202,"line":860},[200,46551,5267],{"class":206},[200,46553,1094],{"class":235},[200,46555,3524],{"class":210},[200,46557,215],{"class":214},[200,46559,19275],{"class":210},[200,46561,5282],{"class":235},[200,46563,230],{"class":214},[200,46565,46566],{"class":202,"line":889},[200,46567,46568],{"class":401},"      \u002F\u002F Gérer l'erreur\n",[200,46570,46571],{"class":202,"line":899},[200,46572,46573],{"class":206},"      return\n",[200,46575,46576],{"class":202,"line":904},[200,46577,4977],{"class":214},[200,46579,46580],{"class":202,"line":930},[200,46581,528],{"emptyLinePlaceholder":20},[200,46583,46584,46586,46588,46590,46592,46594,46596,46598],{"class":202,"line":3655},[200,46585,5267],{"class":206},[200,46587,1094],{"class":235},[200,46589,11852],{"class":214},[200,46591,3524],{"class":210},[200,46593,215],{"class":214},[200,46595,156],{"class":210},[200,46597,5282],{"class":235},[200,46599,230],{"class":214},[200,46601,46602],{"class":202,"line":3660},[200,46603,46604],{"class":401},"      \u002F**\n",[200,46606,46607],{"class":202,"line":4035},[200,46608,46609],{"class":401},"       * Rediriger vers la page OAuth GitHub s'il n'y a pas de code dans la requête.\n",[200,46611,46612],{"class":202,"line":4490},[200,46613,46614],{"class":401},"       * Si l'utilisateur accepte l'autorisation, GitHub redirigera l'utilisateur vers l'application avec le code.\n",[200,46616,46617],{"class":202,"line":4496},[200,46618,46619],{"class":401},"       * Le code est un code unique à échanger contre les tokens.\n",[200,46621,46622],{"class":202,"line":4502},[200,46623,46624],{"class":401},"       *\u002F\n",[200,46626,46627,46629,46632,46634,46637,46639,46641,46643,46645],{"class":202,"line":4508},[200,46628,20136],{"class":379},[200,46630,46631],{"class":210}," redirectUrl",[200,46633,509],{"class":214},[200,46635,46636],{"class":223}," getRequestURL",[200,46638,227],{"class":235},[200,46640,25426],{"class":210},[200,46642,2104],{"class":235},[200,46644,215],{"class":214},[200,46646,46647],{"class":210},"href\n",[200,46649,46650,46652,46654],{"class":202,"line":5469},[200,46651,11730],{"class":206},[200,46653,46075],{"class":223},[200,46655,3130],{"class":235},[200,46657,46658,46661],{"class":202,"line":5474},[200,46659,46660],{"class":210},"        event",[200,46662,252],{"class":214},[200,46664,46665,46668,46670,46672,46674,46677,46679,46681,46683],{"class":202,"line":13237},[200,46666,46667],{"class":223},"        withQuery",[200,46669,227],{"class":235},[200,46671,13490],{"class":210},[200,46673,215],{"class":214},[200,46675,46676],{"class":210},"authorizationURL",[200,46678,14678],{"class":206},[200,46680,22752],{"class":1364},[200,46682,549],{"class":214},[200,46684,280],{"class":214},[200,46686,46687,46690,46692,46694,46696,46699],{"class":202,"line":13263},[200,46688,46689],{"class":235},"          client_id",[200,46691,239],{"class":214},[200,46693,46451],{"class":210},[200,46695,215],{"class":214},[200,46697,46698],{"class":210},"clientId",[200,46700,252],{"class":214},[200,46702,46703,46706,46708,46710],{"class":202,"line":13279},[200,46704,46705],{"class":235},"          redirect_uri",[200,46707,239],{"class":214},[200,46709,46631],{"class":210},[200,46711,252],{"class":214},[200,46713,46714,46717,46719,46721,46723,46726,46728,46730,46732,46734,46736,46738],{"class":202,"line":13323},[200,46715,46716],{"class":235},"          scope",[200,46718,239],{"class":214},[200,46720,46451],{"class":210},[200,46722,215],{"class":214},[200,46724,46725],{"class":210},"scope",[200,46727,215],{"class":214},[200,46729,6202],{"class":223},[200,46731,227],{"class":235},[200,46733,249],{"class":214},[200,46735,242],{"class":214},[200,46737,2104],{"class":235},[200,46739,252],{"class":214},[200,46741,46742,46745,46747,46749,46752],{"class":202,"line":13329},[200,46743,46744],{"class":214},"          ...",[200,46746,13490],{"class":210},[200,46748,215],{"class":214},[200,46750,46751],{"class":210},"authorizationParams",[200,46753,252],{"class":214},[200,46755,46756,46758,46760],{"class":202,"line":13334},[200,46757,13388],{"class":214},[200,46759,2104],{"class":235},[200,46761,252],{"class":214},[200,46763,46764],{"class":202,"line":13360},[200,46765,46766],{"class":235},"      )\n",[200,46768,46769],{"class":202,"line":13365},[200,46770,4977],{"class":214},[200,46772,46773],{"class":202,"line":13385},[200,46774,528],{"emptyLinePlaceholder":20},[200,46776,46777],{"class":202,"line":13405},[200,46778,19641],{"class":401},[200,46780,46781],{"class":202,"line":13410},[200,46782,46783],{"class":401},"     * Utiliser le code pour obtenir les tokens.\n",[200,46785,46786],{"class":202,"line":13418},[200,46787,19651],{"class":401},[200,46789,46790,46792,46794,46796,46798,46800,46802,46804],{"class":202,"line":13436},[200,46791,2014],{"class":379},[200,46793,9869],{"class":210},[200,46795,239],{"class":214},[200,46797,42637],{"class":1364},[200,46799,509],{"class":214},[200,46801,452],{"class":206},[200,46803,18754],{"class":223},[200,46805,3130],{"class":235},[200,46807,46808,46811,46813,46816,46818,46820],{"class":202,"line":13466},[200,46809,46810],{"class":210},"      config",[200,46812,215],{"class":214},[200,46814,46815],{"class":210},"tokenURL",[200,46817,14678],{"class":206},[200,46819,22752],{"class":1364},[200,46821,252],{"class":214},[200,46823,46824],{"class":202,"line":13472},[200,46825,4457],{"class":214},[200,46827,46828,46831,46833,46835,46837,46839],{"class":202,"line":13477},[200,46829,46830],{"class":235},"        method",[200,46832,239],{"class":214},[200,46834,242],{"class":214},[200,46836,18779],{"class":245},[200,46838,249],{"class":214},[200,46840,252],{"class":214},[200,46842,46843,46846,46848],{"class":202,"line":13482},[200,46844,46845],{"class":235},"        body",[200,46847,239],{"class":214},[200,46849,280],{"class":214},[200,46851,46852,46854,46856,46858,46860,46862],{"class":202,"line":13497},[200,46853,13421],{"class":235},[200,46855,239],{"class":214},[200,46857,3499],{"class":210},[200,46859,215],{"class":214},[200,46861,156],{"class":210},[200,46863,252],{"class":214},[200,46865,46866],{"class":202,"line":13508},[200,46867,4965],{"class":214},[200,46869,46870],{"class":202,"line":13513},[200,46871,12098],{"class":214},[200,46873,46874],{"class":202,"line":13518},[200,46875,3869],{"class":235},[200,46877,46878],{"class":202,"line":20247},[200,46879,528],{"emptyLinePlaceholder":20},[200,46881,46882,46884,46887,46889,46891,46893],{"class":202,"line":20269},[200,46883,2014],{"class":379},[200,46885,46886],{"class":210}," accessToken",[200,46888,509],{"class":214},[200,46890,9869],{"class":210},[200,46892,215],{"class":214},[200,46894,46895],{"class":210},"access_token\n",[200,46897,46898],{"class":202,"line":20274},[200,46899,528],{"emptyLinePlaceholder":20},[200,46901,46902],{"class":202,"line":20279},[200,46903,19641],{"class":401},[200,46905,46906],{"class":202,"line":20284},[200,46907,46908],{"class":401},"     * Utiliser le token d'accès pour obtenir les informations de l'utilisateur.\n",[200,46910,46911],{"class":202,"line":20305},[200,46912,19651],{"class":401},[200,46914,46915,46917,46919,46921,46923,46925,46927,46929,46931,46933,46936,46938,46940],{"class":202,"line":20312},[200,46916,2014],{"class":379},[200,46918,34533],{"class":210},[200,46920,239],{"class":214},[200,46922,42637],{"class":1364},[200,46924,509],{"class":214},[200,46926,452],{"class":206},[200,46928,4858],{"class":223},[200,46930,227],{"class":235},[200,46932,249],{"class":214},[200,46934,46935],{"class":245},"https:\u002F\u002Fapi.github.com\u002Fuser",[200,46937,249],{"class":214},[200,46939,549],{"class":214},[200,46941,280],{"class":214},[200,46943,46944,46947,46949],{"class":202,"line":20317},[200,46945,46946],{"class":235},"      headers",[200,46948,239],{"class":214},[200,46950,280],{"class":214},[200,46952,46953,46956,46958,46960,46963,46965,46968,46970],{"class":202,"line":20333},[200,46954,46955],{"class":235},"        Authorization",[200,46957,239],{"class":214},[200,46959,11154],{"class":214},[200,46961,46962],{"class":245},"token ",[200,46964,4868],{"class":214},[200,46966,46967],{"class":210},"accessToken",[200,46969,23045],{"class":214},[200,46971,252],{"class":214},[200,46973,46975],{"class":202,"line":46974},59,[200,46976,12098],{"class":214},[200,46978,46980,46982],{"class":202,"line":46979},60,[200,46981,2111],{"class":214},[200,46983,416],{"class":235},[200,46985,46987],{"class":202,"line":46986},61,[200,46988,528],{"emptyLinePlaceholder":20},[200,46990,46992],{"class":202,"line":46991},62,[200,46993,19641],{"class":401},[200,46995,46997],{"class":202,"line":46996},63,[200,46998,46999],{"class":401},"     * Appeler le callback onSuccess avec l'utilisateur et les tokens.\n",[200,47001,47003],{"class":202,"line":47002},64,[200,47004,47005],{"class":401},"     *\n",[200,47007,47009],{"class":202,"line":47008},65,[200,47010,47011],{"class":401},"     * L'OAuth est terminé, et l'utilisateur peut maintenant être authentifié avec notre système.\n",[200,47013,47015],{"class":202,"line":47014},66,[200,47016,47017],{"class":401},"     * Avec Nitro, nous stockons l'utilisateur dans une session au sein d'un cookie.\n",[200,47019,47021],{"class":202,"line":47020},67,[200,47022,19651],{"class":401},[200,47024,47026,47028,47030,47032,47034,47036],{"class":202,"line":47025},68,[200,47027,3311],{"class":206},[200,47029,45996],{"class":223},[200,47031,227],{"class":235},[200,47033,25426],{"class":210},[200,47035,549],{"class":214},[200,47037,280],{"class":214},[200,47039,47041,47043],{"class":202,"line":47040},69,[200,47042,46034],{"class":210},[200,47044,252],{"class":214},[200,47046,47048,47051],{"class":202,"line":47047},70,[200,47049,47050],{"class":210},"      tokens",[200,47052,252],{"class":214},[200,47054,47056,47058],{"class":202,"line":47055},71,[200,47057,2111],{"class":214},[200,47059,416],{"class":235},[200,47061,47063,47065],{"class":202,"line":47062},72,[200,47064,5095],{"class":214},[200,47066,416],{"class":235},[200,47068,47070],{"class":202,"line":47069},73,[200,47071,1129],{"class":214},[35,47073,47074],{},"Comme on peut s'y attendre, le code est assez simple, et nous reconnaissons facilement les étapes que nous avons vues dans le schéma.",[1929,47076,24146],{"id":24145},[35,47078,47079],{},"Je n'ai pas codé durant ce live. Je voulais expliquer de nombreux concepts importants pour aider chacun à comprendre le contexte du projet Orion. J'espère que c'était clair et que vous avez appris quelque chose de nouveau.",[35,47081,46146,47082],{},[1599,47083,46149],{"repo":38413},[51,47085,47087],{"id":47086},"schéma-sql-et-premiers-éléments-de-linterface-admin-dorion","Schéma SQL et Premiers Éléments de l'Interface Admin d'Orion",[35,47089,47090],{},[1707,47091],{"alt":47092,"src":47093},"Vignette de mon troisième live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-03.png",[35,47095,47096],{},[1216,47097,47098,47101,47102,47105,47106,45894,47109,215],{},[39,47099,38397],{"href":45853,"rel":47100},[77]," est une application full-stack construite avec Nuxt et développée en direct ",[39,47103,45889],{"href":18579,"rel":47104},[77],". Vous pouvez voir le replay sur ",[39,47107,19427],{"href":45844,"rel":47108},[77],[39,47110,11636],{"href":45853,"rel":47111},[77],[35,47113,47114],{},"Dans ce troisième stream, j'ai créé le schéma SQL pour la base de données. J'ai ensuite configuré Nuxt UI et Nuxt UI Pro pour créer la mise en page de l'administration et le premier formulaire pour les catégories. Cet article est un résumé du stream et du travail réalisé.",[1929,47116,47118],{"id":47117},"schéma-sql","Schéma SQL",[35,47120,47121],{},"Qu'est-ce qu'un schéma SQL et pourquoi ai-je créé ce schéma SQL ? Ce sont deux bonnes questions.",[35,47123,47124,47125],{},"Un schéma SQL est un moyen d'organiser la base de données. C'est une feuille de route sur la manière dont la base de données sera structurée. Il est donc très utile de réfléchir à cela avant de commencer à coder. Il n'est pas nécessaire qu'il soit parfait, mais il aide à itérer sur la structure de la base de données, à éviter les erreurs et à avoir une vision claire du projet. ",[1216,47126,47127],{},"Je pense personnellement que c'est un indispensable lorsqu'on commence un nouveau projet.",[35,47129,47130],{},"Avant d'écrire quoi que ce soit pour le projet Orion, j'ai donc créé le schéma SQL.",[35,47132,47133],{},[1707,47134],{"alt":47118,"src":47135},"\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fsql-schema.png",[35,47137,47138,47139,47144],{},"Pour construire ce schéma, j'ai utilisé ",[39,47140,47143],{"href":47141,"rel":47142},"https:\u002F\u002Fdrawsql.app\u002F",[77],"drawSQL",". C'est une application web pour créer des schémas SQL avec une interface utilisateur agréable. Elle est très facile à utiliser et gratuite pour les petits projets. Je la recommande.",[35,47146,47147],{},"La base de données pour le projet Orion, l'application full-stack Nuxt, est simple et courante pour ce type de projet.",[1593,47149,47150,47157,47163,47168,47174],{},[71,47151,47152,47153,47156],{},"Une table ",[156,47154,47155],{},"users"," pour stocker les utilisateurs de l'application. Stocker les utilisateurs est utile pour l'interface admin, pour bannir un utilisateur ou pour donner des droits d'administrateur.",[71,47158,47152,47159,47162],{},[156,47160,47161],{},"categories"," pour stocker les catégories du thème comme blog, e-commerce, documentation, etc. C'est une table simple avec un nom et un slug. Le slug est utilisé pour l'URL de la catégorie.",[71,47164,47152,47165,47167],{},[156,47166,2476],{}," pour stocker les modules du thème. La table aura un nom, un slug et un type (officiel, communauté). Cela sera utile pour filtrer les thèmes par modules.",[71,47169,47152,47170,47173],{},[156,47171,47172],{},"themes"," pour stocker le contenu principal de l'application. Elle aura de nombreuses colonnes, mais nous verrons cela dans un autre stream.",[71,47175,47152,47176,47179],{},[156,47177,47178],{},"feedbacks"," pour stocker les retours des administrateurs sur un thème. En effet, un thème doit être évalué avant d'être publié et les retours seront utilisés pour indiquer au créateur ce qu'il doit améliorer.",[35,47181,47182],{},"Ce schéma est un bon point de départ pour clarifier les choses et savoir où commencer, mais je sais déjà qu'il évoluera.",[1929,47184,31458],{"id":31457},[35,47186,47187,47188,47193,47194,47199,47200],{},"Nuxt UI est une bibliothèque de composants créée par l'équipe de Nuxt. Initialement, c'était un outil interne pour leurs propres projets comme ",[39,47189,47192],{"href":47190,"rel":47191},"https:\u002F\u002Fvolta.net",[77],"Volta",". En mai 2023, ils ont décidé de ",[39,47195,47198],{"href":47196,"rel":47197},"https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Freleases\u002Ftag\u002Fv2.0.0",[77],"le rendre open-source"," et de le rendre disponible gratuitement pour tout le monde. ",[1216,47201,47202],{},"Aujourd'hui, Nuxt UI est mon choix de prédilection pour construire des interfaces utilisateur avec Nuxt car il est simple, beau et pourtant très personnalisable.",[35,47204,47205,47206,47209],{},"Nuxt UI Pro est construit sur Nuxt UI et fournit des composants liés aux mises en page, comme une page, un hero, un en-tête et des mises en page complètes comme un tableau de bord, un blog, une documentation, etc. C'est un produit payant, mais il en vaut la peine. ",[1216,47207,47208],{},"Je l'utilise pour Orion, l'application full-stack, car il fait gagner du temps et est magnifique."," Le mieux, c'est qu'il est gratuit pendant le développement (le processus de build nécessitera une clé), donc vous pouvez l'essayer avant de l'acheter.",[35,47211,47212],{},"Pendant le stream, j'ai commencé par installer Nuxt UI et Nuxt UI Pro. J'ai ensuite créé la mise en page pour l'interface d'administration et la page pour les catégories. Voyons le code.",[1929,47214,47216],{"id":47215},"mise-en-page-admin","Mise en Page Admin",[35,47218,47219,47220,47223,47224,215],{},"Avec Nuxt UI Pro, cette partie a été très facile. Il n'y a pas encore de navbar ni de sidebar. ",[1216,47221,47222],{},"L'objectif est d'ajouter des fonctionnalités et des composants étape par étape, selon les besoins, et d'éviter de trop anticiper."," Voici le code pour la mise en page ",[156,47225,47226],{},"layouts\u002Fadmin.vue",[192,47228,47230],{"className":21457,"code":47229,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CUDashboardLayout>\n    \u003Cslot \u002F>\n  \u003C\u002FUDashboardLayout>\n\u003C\u002Ftemplate>\n",[156,47231,47232,47240,47249,47258,47266],{"__ignoreMap":13},[200,47233,47234,47236,47238],{"class":202,"line":203},[200,47235,599],{"class":214},[200,47237,21468],{"class":235},[200,47239,647],{"class":214},[200,47241,47242,47244,47247],{"class":202,"line":14},[200,47243,656],{"class":214},[200,47245,47246],{"class":235},"UDashboardLayout",[200,47248,647],{"class":214},[200,47250,47251,47253,47256],{"class":202,"line":255},[200,47252,803],{"class":214},[200,47254,47255],{"class":235},"slot",[200,47257,21527],{"class":214},[200,47259,47260,47262,47264],{"class":202,"line":272},[200,47261,892],{"class":214},[200,47263,47246],{"class":235},[200,47265,647],{"class":214},[200,47267,47268,47270,47272],{"class":202,"line":283},[200,47269,680],{"class":214},[200,47271,21468],{"class":235},[200,47273,647],{"class":214},[1929,47275,47277],{"id":47276},"page-des-catégories","Page des Catégories",[35,47279,47280],{},"Avant de développer la vue, j'ai créé l'endpoint API pour obtenir et mettre à jour les catégories.",[35,47282,47283],{},[1707,47284],{"alt":47285,"src":47286},"Résultat de la mise en page et de la page pour les catégories.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Flayout-and-page-for-categories.png",[38437,47288,47290],{"id":47289},"api-des-catégories","API des Catégories",[35,47292,47293,47294,47297,47298,47301],{},"Grâce à Nitro et Drizzle (configuré dans le premier stream), c'est très simple. Dans le fichier ",[156,47295,47296],{},"server\u002Fapi\u002Fcategories\u002Findex.get.ts"," (le ",[156,47299,47300],{},".get."," est utilisé pour spécifier le verbe HTTP), j'ai écrit le code suivant :",[192,47303,47305],{"className":1380,"code":47304,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async () => {\n  const categories = await useDrizzle().select({\n    id: tables.categories.id,\n    slug: tables.categories.slug,\n    name: tables.categories.name\n  }).from(tables.categories)\n\n  return categories\n})\n",[156,47306,47307,47325,47349,47368,47388,47404,47425,47429,47436],{"__ignoreMap":13},[200,47308,47309,47311,47313,47315,47317,47319,47321,47323],{"class":202,"line":203},[200,47310,1390],{"class":206},[200,47312,1393],{"class":206},[200,47314,38723],{"class":223},[200,47316,227],{"class":210},[200,47318,3726],{"class":379},[200,47320,3729],{"class":214},[200,47322,393],{"class":379},[200,47324,280],{"class":214},[200,47326,47327,47329,47332,47334,47336,47339,47341,47343,47345,47347],{"class":202,"line":14},[200,47328,3738],{"class":379},[200,47330,47331],{"class":210}," categories",[200,47333,509],{"class":214},[200,47335,452],{"class":206},[200,47337,47338],{"class":223}," useDrizzle",[200,47340,1549],{"class":235},[200,47342,215],{"class":214},[200,47344,769],{"class":223},[200,47346,227],{"class":235},[200,47348,230],{"class":214},[200,47350,47351,47353,47355,47358,47360,47362,47364,47366],{"class":202,"line":255},[200,47352,15861],{"class":235},[200,47354,239],{"class":214},[200,47356,47357],{"class":210}," tables",[200,47359,215],{"class":214},[200,47361,47161],{"class":210},[200,47363,215],{"class":214},[200,47365,3295],{"class":210},[200,47367,252],{"class":214},[200,47369,47370,47373,47375,47377,47379,47381,47383,47386],{"class":202,"line":272},[200,47371,47372],{"class":235},"    slug",[200,47374,239],{"class":214},[200,47376,47357],{"class":210},[200,47378,215],{"class":214},[200,47380,47161],{"class":210},[200,47382,215],{"class":214},[200,47384,47385],{"class":210},"slug",[200,47387,252],{"class":214},[200,47389,47390,47392,47394,47396,47398,47400,47402],{"class":202,"line":283},[200,47391,11812],{"class":235},[200,47393,239],{"class":214},[200,47395,47357],{"class":210},[200,47397,215],{"class":214},[200,47399,47161],{"class":210},[200,47401,215],{"class":214},[200,47403,29957],{"class":210},[200,47405,47406,47408,47410,47412,47414,47416,47419,47421,47423],{"class":202,"line":300},[200,47407,5095],{"class":214},[200,47409,2104],{"class":235},[200,47411,215],{"class":214},[200,47413,1489],{"class":223},[200,47415,227],{"class":235},[200,47417,47418],{"class":210},"tables",[200,47420,215],{"class":214},[200,47422,47161],{"class":210},[200,47424,416],{"class":235},[200,47426,47427],{"class":202,"line":310},[200,47428,528],{"emptyLinePlaceholder":20},[200,47430,47431,47433],{"class":202,"line":336},[200,47432,3758],{"class":206},[200,47434,47435],{"class":210}," categories\n",[200,47437,47438,47440],{"class":202,"line":342},[200,47439,413],{"class":214},[200,47441,416],{"class":210},[35,47443,41033,47444,47447,47448,3092,47450,9502,47452,106,47454],{},[156,47445,47446],{},"useDrizzle"," est un wrapper autour de l'ORM Drizzle. Ce code renverra toutes les catégories avec seulement les champs ",[156,47449,3295],{},[156,47451,47385],{},[156,47453,20112],{},[1216,47455,47456],{},"C'est une bonne pratique de ne renvoyer que les champs dont vous avez besoin pour éviter d'envoyer trop de données.",[35,47458,47459,47460,47463],{},"Ensuite, j'ai créé l'endpoint pour créer une nouvelle catégorie, et c'est le même processus. J'ai créé le fichier ",[156,47461,47462],{},"server\u002Fapi\u002Fcategories\u002Findex.post.ts"," avec le code suivant :",[192,47465,47467],{"className":1380,"code":47466,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  await useDrizzle().insert(tables.categories).values({\n    slug: useSlugify(body.name),\n    name: body.name,\n  }).execute()\n\n  return body\n})\n",[156,47468,47469,47491,47509,47513,47545,47566,47580,47593,47597,47604],{"__ignoreMap":13},[200,47470,47471,47473,47475,47477,47479,47481,47483,47485,47487,47489],{"class":202,"line":203},[200,47472,1390],{"class":206},[200,47474,1393],{"class":206},[200,47476,38723],{"class":223},[200,47478,227],{"class":210},[200,47480,3726],{"class":379},[200,47482,1094],{"class":214},[200,47484,25426],{"class":386},[200,47486,2104],{"class":214},[200,47488,393],{"class":379},[200,47490,280],{"class":214},[200,47492,47493,47495,47497,47499,47501,47503,47505,47507],{"class":202,"line":14},[200,47494,3738],{"class":379},[200,47496,40809],{"class":210},[200,47498,509],{"class":214},[200,47500,452],{"class":206},[200,47502,40816],{"class":223},[200,47504,227],{"class":235},[200,47506,25426],{"class":210},[200,47508,416],{"class":235},[200,47510,47511],{"class":202,"line":255},[200,47512,528],{"emptyLinePlaceholder":20},[200,47514,47515,47517,47519,47521,47523,47526,47528,47530,47532,47534,47536,47538,47541,47543],{"class":202,"line":272},[200,47516,6177],{"class":206},[200,47518,47338],{"class":223},[200,47520,1549],{"class":235},[200,47522,215],{"class":214},[200,47524,47525],{"class":223},"insert",[200,47527,227],{"class":235},[200,47529,47418],{"class":210},[200,47531,215],{"class":214},[200,47533,47161],{"class":210},[200,47535,2104],{"class":235},[200,47537,215],{"class":214},[200,47539,47540],{"class":223},"values",[200,47542,227],{"class":235},[200,47544,230],{"class":214},[200,47546,47547,47549,47551,47554,47556,47558,47560,47562,47564],{"class":202,"line":283},[200,47548,47372],{"class":235},[200,47550,239],{"class":214},[200,47552,47553],{"class":223}," useSlugify",[200,47555,227],{"class":235},[200,47557,23914],{"class":210},[200,47559,215],{"class":214},[200,47561,20112],{"class":210},[200,47563,2104],{"class":235},[200,47565,252],{"class":214},[200,47567,47568,47570,47572,47574,47576,47578],{"class":202,"line":300},[200,47569,11812],{"class":235},[200,47571,239],{"class":214},[200,47573,40809],{"class":210},[200,47575,215],{"class":214},[200,47577,20112],{"class":210},[200,47579,252],{"class":214},[200,47581,47582,47584,47586,47588,47591],{"class":202,"line":310},[200,47583,5095],{"class":214},[200,47585,2104],{"class":235},[200,47587,215],{"class":214},[200,47589,47590],{"class":223},"execute",[200,47592,466],{"class":235},[200,47594,47595],{"class":202,"line":336},[200,47596,528],{"emptyLinePlaceholder":20},[200,47598,47599,47601],{"class":202,"line":342},[200,47600,3758],{"class":206},[200,47602,47603],{"class":210}," body\n",[200,47605,47606,47608],{"class":202,"line":365},[200,47607,413],{"class":214},[200,47609,416],{"class":210},[35,47611,47612,47613,215],{},"Dans ce code, il n'y a pas de vérification de sécurité, et chaque utilisateur peut créer une catégorie. Ce n'est clairement pas un cas d'utilisation pour notre application, mais la vérification sera ajoutée plus tard. En même temps, je n'ai pas vérifié le corps de la requête. Il est important de valider les données reçues d'un client pour éviter les problèmes de sécurité. ",[6862,47614,47615],{},"Ce sera le sujet du prochain stream",[51,47617,47619],{"id":47618},"contenu-des-catégories","Contenu des Catégories",[35,47621,47622],{},"La page des catégories est construite avec Nuxt UI Pro, ce qui rend le code très simple.",[192,47624,47626],{"className":21457,"code":47625,"language":12075,"meta":13,"style":13},"\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n  layout: 'admin'\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUDashboardPage>\n    \u003CUDashboardPanel grow>\n      \u003C!-- Contenu -->\n    \u003C\u002FUDashboardPanel>\n  \u003C\u002FUDashboardPage>\n\u003C\u002Ftemplate>\n",[156,47627,47628,47648,47657,47670,47676,47684,47688,47696,47705,47717,47722,47730,47738],{"__ignoreMap":13},[200,47629,47630,47632,47634,47636,47638,47640,47642,47644,47646],{"class":202,"line":203},[200,47631,599],{"class":214},[200,47633,14896],{"class":235},[200,47635,21825],{"class":379},[200,47637,14594],{"class":379},[200,47639,449],{"class":214},[200,47641,610],{"class":214},[200,47643,1383],{"class":245},[200,47645,610],{"class":214},[200,47647,647],{"class":214},[200,47649,47650,47653,47655],{"class":202,"line":14},[200,47651,47652],{"class":223},"definePageMeta",[200,47654,227],{"class":210},[200,47656,230],{"class":214},[200,47658,47659,47662,47664,47666,47668],{"class":202,"line":255},[200,47660,47661],{"class":235},"  layout",[200,47663,239],{"class":214},[200,47665,242],{"class":214},[200,47667,39189],{"class":245},[200,47669,991],{"class":214},[200,47671,47672,47674],{"class":202,"line":272},[200,47673,413],{"class":214},[200,47675,416],{"class":210},[200,47677,47678,47680,47682],{"class":202,"line":283},[200,47679,680],{"class":214},[200,47681,14896],{"class":235},[200,47683,647],{"class":214},[200,47685,47686],{"class":202,"line":300},[200,47687,528],{"emptyLinePlaceholder":20},[200,47689,47690,47692,47694],{"class":202,"line":310},[200,47691,599],{"class":214},[200,47693,21468],{"class":235},[200,47695,647],{"class":214},[200,47697,47698,47700,47703],{"class":202,"line":336},[200,47699,656],{"class":214},[200,47701,47702],{"class":235},"UDashboardPage",[200,47704,647],{"class":214},[200,47706,47707,47709,47712,47715],{"class":202,"line":342},[200,47708,803],{"class":214},[200,47710,47711],{"class":235},"UDashboardPanel",[200,47713,47714],{"class":379}," grow",[200,47716,647],{"class":214},[200,47718,47719],{"class":202,"line":365},[200,47720,47721],{"class":401},"      \u003C!-- Contenu -->\n",[200,47723,47724,47726,47728],{"class":202,"line":371},[200,47725,15078],{"class":214},[200,47727,47711],{"class":235},[200,47729,647],{"class":214},[200,47731,47732,47734,47736],{"class":202,"line":398},[200,47733,892],{"class":214},[200,47735,47702],{"class":235},[200,47737,647],{"class":214},[200,47739,47740,47742,47744],{"class":202,"line":405},[200,47741,680],{"class":214},[200,47743,21468],{"class":235},[200,47745,647],{"class":214},[35,47747,47748,47749,47751],{},"Dans le ",[156,47750,47711],{},", nous pouvons ajouter un en-tête avec le titre de la page, un bouton pour créer une nouvelle catégorie, et un tableau pour afficher les catégories.",[35,47753,47754,47755,2186],{},"Commençons par ajouter le tableau dans le ",[156,47756,47711],{},[192,47758,47760],{"className":21457,"code":47759,"language":12075,"meta":13,"style":13},"\u003Cscript setup lang=\"ts\">\nconst columns = [{\n  key: 'id',\n  label: '#'\n}, {\n  key: 'name',\n  label: 'Nom',\n  sortable: true\n}, {\n  key: 'slug',\n  label: 'Slug',\n}]\n\nconst { data: categories, pending } = await useFetch('\u002Fapi\u002Fcategories', {\n  deep: false,\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUTable :columns=\"columns\" :rows=\"categories\" :loading=\"pending\" \u002F>\n\u003C\u002Ftemplate>\n",[156,47761,47762,47782,47795,47809,47822,47828,47842,47856,47865,47871,47885,47900,47906,47910,47949,47960,47966,47974,47978,47986,48034],{"__ignoreMap":13},[200,47763,47764,47766,47768,47770,47772,47774,47776,47778,47780],{"class":202,"line":203},[200,47765,599],{"class":214},[200,47767,14896],{"class":235},[200,47769,21825],{"class":379},[200,47771,14594],{"class":379},[200,47773,449],{"class":214},[200,47775,610],{"class":214},[200,47777,1383],{"class":245},[200,47779,610],{"class":214},[200,47781,647],{"class":214},[200,47783,47784,47786,47789,47791,47793],{"class":202,"line":14},[200,47785,443],{"class":379},[200,47787,47788],{"class":210}," columns ",[200,47790,449],{"class":214},[200,47792,350],{"class":210},[200,47794,230],{"class":214},[200,47796,47797,47799,47801,47803,47805,47807],{"class":202,"line":255},[200,47798,18976],{"class":235},[200,47800,239],{"class":214},[200,47802,242],{"class":214},[200,47804,3295],{"class":245},[200,47806,249],{"class":214},[200,47808,252],{"class":214},[200,47810,47811,47814,47816,47818,47820],{"class":202,"line":272},[200,47812,47813],{"class":235},"  label",[200,47815,239],{"class":214},[200,47817,242],{"class":214},[200,47819,2902],{"class":245},[200,47821,991],{"class":214},[200,47823,47824,47826],{"class":202,"line":283},[200,47825,26897],{"class":214},[200,47827,280],{"class":214},[200,47829,47830,47832,47834,47836,47838,47840],{"class":202,"line":300},[200,47831,18976],{"class":235},[200,47833,239],{"class":214},[200,47835,242],{"class":214},[200,47837,20112],{"class":245},[200,47839,249],{"class":214},[200,47841,252],{"class":214},[200,47843,47844,47846,47848,47850,47852,47854],{"class":202,"line":310},[200,47845,47813],{"class":235},[200,47847,239],{"class":214},[200,47849,242],{"class":214},[200,47851,40718],{"class":245},[200,47853,249],{"class":214},[200,47855,252],{"class":214},[200,47857,47858,47861,47863],{"class":202,"line":336},[200,47859,47860],{"class":235},"  sortable",[200,47862,239],{"class":214},[200,47864,22236],{"class":2438},[200,47866,47867,47869],{"class":202,"line":342},[200,47868,26897],{"class":214},[200,47870,280],{"class":214},[200,47872,47873,47875,47877,47879,47881,47883],{"class":202,"line":365},[200,47874,18976],{"class":235},[200,47876,239],{"class":214},[200,47878,242],{"class":214},[200,47880,47385],{"class":245},[200,47882,249],{"class":214},[200,47884,252],{"class":214},[200,47886,47887,47889,47891,47893,47896,47898],{"class":202,"line":371},[200,47888,47813],{"class":235},[200,47890,239],{"class":214},[200,47892,242],{"class":214},[200,47894,47895],{"class":245},"Slug",[200,47897,249],{"class":214},[200,47899,252],{"class":214},[200,47901,47902,47904],{"class":202,"line":398},[200,47903,413],{"class":214},[200,47905,4511],{"class":210},[200,47907,47908],{"class":202,"line":405},[200,47909,528],{"emptyLinePlaceholder":20},[200,47911,47912,47914,47916,47918,47920,47922,47924,47927,47929,47931,47933,47936,47938,47940,47943,47945,47947],{"class":202,"line":410},[200,47913,443],{"class":379},[200,47915,318],{"class":214},[200,47917,5056],{"class":235},[200,47919,239],{"class":214},[200,47921,47331],{"class":210},[200,47923,549],{"class":214},[200,47925,47926],{"class":210}," pending ",[200,47928,413],{"class":214},[200,47930,509],{"class":214},[200,47932,452],{"class":206},[200,47934,47935],{"class":223}," useFetch",[200,47937,227],{"class":210},[200,47939,249],{"class":214},[200,47941,47942],{"class":245},"\u002Fapi\u002Fcategories",[200,47944,249],{"class":214},[200,47946,549],{"class":214},[200,47948,280],{"class":214},[200,47950,47951,47954,47956,47958],{"class":202,"line":860},[200,47952,47953],{"class":235},"  deep",[200,47955,239],{"class":214},[200,47957,16169],{"class":2438},[200,47959,252],{"class":214},[200,47961,47962,47964],{"class":202,"line":889},[200,47963,413],{"class":214},[200,47965,416],{"class":210},[200,47967,47968,47970,47972],{"class":202,"line":899},[200,47969,680],{"class":214},[200,47971,14896],{"class":235},[200,47973,647],{"class":214},[200,47975,47976],{"class":202,"line":904},[200,47977,528],{"emptyLinePlaceholder":20},[200,47979,47980,47982,47984],{"class":202,"line":930},[200,47981,599],{"class":214},[200,47983,21468],{"class":235},[200,47985,647],{"class":214},[200,47987,47988,47990,47993,47995,47998,48000,48002,48004,48006,48008,48011,48013,48015,48017,48019,48021,48023,48025,48027,48030,48032],{"class":202,"line":3655},[200,47989,656],{"class":214},[200,47991,47992],{"class":235},"UTable",[200,47994,2186],{"class":214},[200,47996,47997],{"class":379},"columns",[200,47999,449],{"class":214},[200,48001,610],{"class":214},[200,48003,47997],{"class":210},[200,48005,610],{"class":214},[200,48007,2186],{"class":214},[200,48009,48010],{"class":379},"rows",[200,48012,449],{"class":214},[200,48014,610],{"class":214},[200,48016,47161],{"class":210},[200,48018,610],{"class":214},[200,48020,2186],{"class":214},[200,48022,9900],{"class":379},[200,48024,449],{"class":214},[200,48026,610],{"class":214},[200,48028,48029],{"class":210},"pending",[200,48031,610],{"class":214},[200,48033,21527],{"class":214},[200,48035,48036,48038,48040],{"class":202,"line":3660},[200,48037,680],{"class":214},[200,48039,21468],{"class":235},[200,48041,647],{"class":214},[35,48043,48044,48045],{},"C'est difficile d'être plus simple ! ",[1216,48046,48047],{},"Merci à Nuxt UI pour ce travail !",[35,48049,48050],{},"Ensuite, ajoutons l'en-tête avec le titre et le bouton pour créer une nouvelle catégorie :",[192,48052,48054],{"className":21457,"code":48053,"language":12075,"meta":13,"style":13},"\u003Ctemplate>\n  \u003CUDashboardNavbar\n    title=\"Catégories\"\n  >\n    \u003Ctemplate #right>\n      \u003CUButton\n        label=\"Nouvelle catégorie\"\n        trailing-icon=\"i-heroicons-plus\"\n        color=\"gray\"\n      \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FUDashboardNavbar>\n\u003C\u002Ftemplate>\n",[156,48055,48056,48064,48071,48084,48088,48101,48108,48121,48135,48149,48153,48161,48170],{"__ignoreMap":13},[200,48057,48058,48060,48062],{"class":202,"line":203},[200,48059,599],{"class":214},[200,48061,21468],{"class":235},[200,48063,647],{"class":214},[200,48065,48066,48068],{"class":202,"line":14},[200,48067,656],{"class":214},[200,48069,48070],{"class":235},"UDashboardNavbar\n",[200,48072,48073,48075,48077,48079,48082],{"class":202,"line":255},[200,48074,21482],{"class":379},[200,48076,449],{"class":214},[200,48078,610],{"class":214},[200,48080,48081],{"class":245},"Catégories",[200,48083,616],{"class":214},[200,48085,48086],{"class":202,"line":272},[200,48087,11550],{"class":214},[200,48089,48090,48092,48094,48096,48099],{"class":202,"line":283},[200,48091,803],{"class":214},[200,48093,21468],{"class":235},[200,48095,23911],{"class":214},[200,48097,48098],{"class":379},"right",[200,48100,647],{"class":214},[200,48102,48103,48105],{"class":202,"line":300},[200,48104,22071],{"class":214},[200,48106,48107],{"class":235},"UButton\n",[200,48109,48110,48112,48114,48116,48119],{"class":202,"line":310},[200,48111,23928],{"class":379},[200,48113,449],{"class":214},[200,48115,610],{"class":214},[200,48117,48118],{"class":245},"Nouvelle catégorie",[200,48120,616],{"class":214},[200,48122,48123,48126,48128,48130,48133],{"class":202,"line":336},[200,48124,48125],{"class":379},"        trailing-icon",[200,48127,449],{"class":214},[200,48129,610],{"class":214},[200,48131,48132],{"class":245},"i-heroicons-plus",[200,48134,616],{"class":214},[200,48136,48137,48140,48142,48144,48147],{"class":202,"line":342},[200,48138,48139],{"class":379},"        color",[200,48141,449],{"class":214},[200,48143,610],{"class":214},[200,48145,48146],{"class":245},"gray",[200,48148,616],{"class":214},[200,48150,48151],{"class":202,"line":365},[200,48152,23957],{"class":214},[200,48154,48155,48157,48159],{"class":202,"line":371},[200,48156,15078],{"class":214},[200,48158,21468],{"class":235},[200,48160,647],{"class":214},[200,48162,48163,48165,48168],{"class":202,"line":398},[200,48164,892],{"class":214},[200,48166,48167],{"class":235},"UDashboardNavbar",[200,48169,647],{"class":214},[200,48171,48172,48174,48176],{"class":202,"line":405},[200,48173,680],{"class":214},[200,48175,21468],{"class":235},[200,48177,647],{"class":214},[35,48179,48180],{},"Toujours très facile. Le bouton sera utilisé pour ouvrir un modal avec un formulaire pour créer une nouvelle catégorie. J'ai commencé à créer le formulaire mais de manière très simple :",[192,48182,48184],{"className":21457,"code":48183,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport type { FormSubmitEvent } from '#ui\u002Ftypes'\nimport type { output } from 'zod'\nimport { object, string } from 'zod'\n\nconst schema = object({\n  name: string({ message: 'Requis' }),\n})\n\ntype Schema = output\u003Ctypeof schema>\n\nconst state = reactive({\n  name: undefined,\n})\n\nasync function onSubmit(event: FormSubmitEvent\u003CSchema>) {\n  await $fetch('\u002Fapi\u002Fcategories', {\n    method: 'POST',\n    body: event.data,\n  })\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUForm :schema=\"schema\" :state=\"state\" @submit=\"onSubmit\">\n    \u003CUFormGroup label=\"Nom\" name=\"name\">\n      \u003CUInput v-model=\"state.name\" \u002F>\n    \u003C\u002FUFormGroup>\n\n    \u003CUButton type=\"submit\">\n      Soumettre\n    \u003C\u002FUButton>\n  \u003C\u002FUForm>\n\u003C\u002Ftemplate>\n",[156,48185,48186,48206,48228,48249,48271,48275,48289,48318,48324,48328,48346,48350,48364,48373,48379,48383,48408,48426,48440,48454,48460,48464,48472,48476,48484,48531,48560,48583,48591,48595,48614,48619,48627,48635],{"__ignoreMap":13},[200,48187,48188,48190,48192,48194,48196,48198,48200,48202,48204],{"class":202,"line":203},[200,48189,599],{"class":214},[200,48191,14896],{"class":235},[200,48193,14594],{"class":379},[200,48195,449],{"class":214},[200,48197,610],{"class":214},[200,48199,1383],{"class":245},[200,48201,610],{"class":214},[200,48203,21825],{"class":379},[200,48205,647],{"class":214},[200,48207,48208,48210,48212,48214,48217,48219,48221,48223,48226],{"class":202,"line":14},[200,48209,972],{"class":206},[200,48211,321],{"class":206},[200,48213,318],{"class":214},[200,48215,48216],{"class":210}," FormSubmitEvent",[200,48218,980],{"class":214},[200,48220,983],{"class":206},[200,48222,242],{"class":214},[200,48224,48225],{"class":245},"#ui\u002Ftypes",[200,48227,991],{"class":214},[200,48229,48230,48232,48234,48236,48239,48241,48243,48245,48247],{"class":202,"line":255},[200,48231,972],{"class":206},[200,48233,321],{"class":206},[200,48235,318],{"class":214},[200,48237,48238],{"class":210}," output",[200,48240,980],{"class":214},[200,48242,983],{"class":206},[200,48244,242],{"class":214},[200,48246,19813],{"class":245},[200,48248,991],{"class":214},[200,48250,48251,48253,48255,48257,48259,48261,48263,48265,48267,48269],{"class":202,"line":272},[200,48252,972],{"class":206},[200,48254,318],{"class":214},[200,48256,42642],{"class":210},[200,48258,549],{"class":214},[200,48260,22752],{"class":210},[200,48262,980],{"class":214},[200,48264,983],{"class":206},[200,48266,242],{"class":214},[200,48268,19813],{"class":245},[200,48270,991],{"class":214},[200,48272,48273],{"class":202,"line":283},[200,48274,528],{"emptyLinePlaceholder":20},[200,48276,48277,48279,48281,48283,48285,48287],{"class":202,"line":300},[200,48278,443],{"class":379},[200,48280,41681],{"class":210},[200,48282,449],{"class":214},[200,48284,42642],{"class":223},[200,48286,227],{"class":210},[200,48288,230],{"class":214},[200,48290,48291,48293,48295,48297,48299,48301,48303,48305,48307,48310,48312,48314,48316],{"class":202,"line":310},[200,48292,236],{"class":235},[200,48294,239],{"class":214},[200,48296,22752],{"class":223},[200,48298,227],{"class":210},[200,48300,2767],{"class":214},[200,48302,39109],{"class":235},[200,48304,239],{"class":214},[200,48306,242],{"class":214},[200,48308,48309],{"class":245},"Requis",[200,48311,249],{"class":214},[200,48313,980],{"class":214},[200,48315,2104],{"class":210},[200,48317,252],{"class":214},[200,48319,48320,48322],{"class":202,"line":336},[200,48321,413],{"class":214},[200,48323,416],{"class":210},[200,48325,48326],{"class":202,"line":342},[200,48327,528],{"emptyLinePlaceholder":20},[200,48329,48330,48332,48335,48337,48339,48342,48344],{"class":202,"line":365},[200,48331,3786],{"class":379},[200,48333,48334],{"class":1364}," Schema",[200,48336,509],{"class":214},[200,48338,48238],{"class":1364},[200,48340,48341],{"class":214},"\u003Ctypeof",[200,48343,41805],{"class":210},[200,48345,647],{"class":214},[200,48347,48348],{"class":202,"line":371},[200,48349,528],{"emptyLinePlaceholder":20},[200,48351,48352,48354,48356,48358,48360,48362],{"class":202,"line":398},[200,48353,443],{"class":379},[200,48355,18960],{"class":210},[200,48357,449],{"class":214},[200,48359,41750],{"class":223},[200,48361,227],{"class":210},[200,48363,230],{"class":214},[200,48365,48366,48368,48370],{"class":202,"line":405},[200,48367,236],{"class":235},[200,48369,239],{"class":214},[200,48371,48372],{"class":214}," undefined,\n",[200,48374,48375,48377],{"class":202,"line":410},[200,48376,413],{"class":214},[200,48378,416],{"class":210},[200,48380,48381],{"class":202,"line":860},[200,48382,528],{"emptyLinePlaceholder":20},[200,48384,48385,48387,48389,48391,48393,48395,48397,48399,48401,48404,48406],{"class":202,"line":889},[200,48386,3726],{"class":379},[200,48388,5002],{"class":379},[200,48390,41783],{"class":223},[200,48392,227],{"class":214},[200,48394,25426],{"class":386},[200,48396,239],{"class":214},[200,48398,48216],{"class":1364},[200,48400,599],{"class":214},[200,48402,48403],{"class":1364},"Schema",[200,48405,46469],{"class":214},[200,48407,280],{"class":214},[200,48409,48410,48412,48414,48416,48418,48420,48422,48424],{"class":202,"line":899},[200,48411,6177],{"class":206},[200,48413,18754],{"class":223},[200,48415,227],{"class":235},[200,48417,249],{"class":214},[200,48419,47942],{"class":245},[200,48421,249],{"class":214},[200,48423,549],{"class":214},[200,48425,280],{"class":214},[200,48427,48428,48430,48432,48434,48436,48438],{"class":202,"line":904},[200,48429,18772],{"class":235},[200,48431,239],{"class":214},[200,48433,242],{"class":214},[200,48435,18779],{"class":245},[200,48437,249],{"class":214},[200,48439,252],{"class":214},[200,48441,48442,48444,48446,48448,48450,48452],{"class":202,"line":930},[200,48443,18788],{"class":235},[200,48445,239],{"class":214},[200,48447,43463],{"class":210},[200,48449,215],{"class":214},[200,48451,12736],{"class":210},[200,48453,252],{"class":214},[200,48455,48456,48458],{"class":202,"line":3655},[200,48457,5095],{"class":214},[200,48459,416],{"class":235},[200,48461,48462],{"class":202,"line":3660},[200,48463,1129],{"class":214},[200,48465,48466,48468,48470],{"class":202,"line":4035},[200,48467,680],{"class":214},[200,48469,14896],{"class":235},[200,48471,647],{"class":214},[200,48473,48474],{"class":202,"line":4490},[200,48475,528],{"emptyLinePlaceholder":20},[200,48477,48478,48480,48482],{"class":202,"line":4496},[200,48479,599],{"class":214},[200,48481,21468],{"class":235},[200,48483,647],{"class":214},[200,48485,48486,48488,48491,48493,48496,48498,48500,48502,48504,48506,48508,48510,48512,48514,48516,48518,48520,48522,48524,48527,48529],{"class":202,"line":4502},[200,48487,656],{"class":214},[200,48489,48490],{"class":235},"UForm",[200,48492,2186],{"class":214},[200,48494,48495],{"class":379},"schema",[200,48497,449],{"class":214},[200,48499,610],{"class":214},[200,48501,48495],{"class":210},[200,48503,610],{"class":214},[200,48505,2186],{"class":214},[200,48507,26875],{"class":379},[200,48509,449],{"class":214},[200,48511,610],{"class":214},[200,48513,26875],{"class":210},[200,48515,610],{"class":214},[200,48517,22417],{"class":214},[200,48519,916],{"class":379},[200,48521,449],{"class":214},[200,48523,610],{"class":214},[200,48525,48526],{"class":210},"onSubmit",[200,48528,610],{"class":214},[200,48530,647],{"class":214},[200,48532,48533,48535,48538,48540,48542,48544,48546,48548,48550,48552,48554,48556,48558],{"class":202,"line":4508},[200,48534,803],{"class":214},[200,48536,48537],{"class":235},"UFormGroup",[200,48539,21516],{"class":379},[200,48541,449],{"class":214},[200,48543,610],{"class":214},[200,48545,40718],{"class":245},[200,48547,610],{"class":214},[200,48549,700],{"class":379},[200,48551,449],{"class":214},[200,48553,610],{"class":214},[200,48555,20112],{"class":245},[200,48557,610],{"class":214},[200,48559,647],{"class":214},[200,48561,48562,48564,48567,48569,48571,48573,48575,48577,48579,48581],{"class":202,"line":5469},[200,48563,22071],{"class":214},[200,48565,48566],{"class":235},"UInput",[200,48568,42013],{"class":379},[200,48570,449],{"class":214},[200,48572,610],{"class":214},[200,48574,26875],{"class":210},[200,48576,215],{"class":214},[200,48578,20112],{"class":210},[200,48580,610],{"class":214},[200,48582,21527],{"class":214},[200,48584,48585,48587,48589],{"class":202,"line":5474},[200,48586,15078],{"class":214},[200,48588,48537],{"class":235},[200,48590,647],{"class":214},[200,48592,48593],{"class":202,"line":13237},[200,48594,528],{"emptyLinePlaceholder":20},[200,48596,48597,48599,48602,48604,48606,48608,48610,48612],{"class":202,"line":13263},[200,48598,803],{"class":214},[200,48600,48601],{"class":235},"UButton",[200,48603,321],{"class":379},[200,48605,449],{"class":214},[200,48607,610],{"class":214},[200,48609,916],{"class":245},[200,48611,610],{"class":214},[200,48613,647],{"class":214},[200,48615,48616],{"class":202,"line":13279},[200,48617,48618],{"class":210},"      Soumettre\n",[200,48620,48621,48623,48625],{"class":202,"line":13323},[200,48622,15078],{"class":214},[200,48624,48601],{"class":235},[200,48626,647],{"class":214},[200,48628,48629,48631,48633],{"class":202,"line":13329},[200,48630,892],{"class":214},[200,48632,48490],{"class":235},[200,48634,647],{"class":214},[200,48636,48637,48639,48641],{"class":202,"line":13334},[200,48638,680],{"class":214},[200,48640,21468],{"class":235},[200,48642,647],{"class":214},[35,48644,48645,48646,48649,48650,48652,48653,48655],{},"J'utilise le formulaire tel qu'expliqué dans la documentation de Nuxt UI. Je n'ai pas vraiment choisi Yup pour la validation mais c'était la première option proposée. ",[1216,48647,48648],{},"Nous verrons si c'est un bon choix à l'avenir."," Dans la méthode ",[156,48651,48526],{},", je fais simplement une requête ",[156,48654,33165],{}," à l'API pour créer une nouvelle catégorie. Dans le prochain stream, nous l'améliorerons en gérant les états de succès et d'erreur.",[35,48657,48658,48659,48662],{},"Ce composant est placé dans un modal en utilisant le composant ",[156,48660,48661],{},"UModal"," et en écoutant l'événement de clic sur le bouton pour ouvrir le modal.",[35,48664,48665],{},[1216,48666,48667],{},"Un point sur lequel je ne suis pas sûr est la gestion de la réponse de l'API. Devrais-je retourner la nouvelle catégorie et l'ajouter dans le tableau, ou devrais-je rafraîchir le tableau ? Pour le moment, j'ai opté pour la seconde solution.",[35,48669,48670],{},"Utiliser Nuxt UI m'aide à construire l'interface admin rapidement, sans avoir besoin de composants personnalisés.",[7151,48672,48674,48678,48681,48685,48689,48695,48711,48714,48718,48721,48739,48926,48935,48947,48951,48958,48964,49126,49132,49586,49589,49596,49930,49934,49937,49944,50054,50061,50068,50078,50284,50287,50341,50344,50348,50351,50355,50359,50365,50381,50396,50476,50490,50512],{"icon":45851,"text":48673,"to":45853,"target":7156},"Voir le code source",[1929,48675,48677],{"id":48676},"fin-du-stream","Fin du Stream",[35,48679,48680],{},"C'était définitivement un bon stream. Le projet avance sans trop de difficultés et c'est un plaisir de travailler dessus. J'attends avec impatience le prochain stream pour continuer à travailler sur l'interface admin et les formulaires. Nuxt UI et Nuxt UI Pro sont des changeurs de jeu : code simple, facile à utiliser, et je peux me concentrer sur les fonctionnalités de l'application.",[35,48682,46146,48683],{},[1599,48684,46149],{"repo":38413},[51,48686,48688],{"id":48687},"continuons-linterface-admin","Continuons l'Interface Admin",[35,48690,48691],{},[1707,48692],{"alt":48693,"src":48694},"Thumbnail de mon quatrième live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-04.png",[35,48696,48697],{},[1216,48698,48699,47101,48702,45890,48705,45894,48708,215],{},[39,48700,38397],{"href":45853,"rel":48701},[77],[39,48703,45889],{"href":18579,"rel":48704},[77],[39,48706,19427],{"href":45844,"rel":48707},[77],[39,48709,11636],{"href":45853,"rel":48710},[77],[35,48712,48713],{},"Lors du stream précédent, j'ai commencé à créer la page des catégories. J'ai mis en place les endpoints API pour obtenir et créer des catégories, et j'ai construit l'interface utilisateur pour afficher les catégories et le formulaire pour en créer une nouvelle. Dans ce stream, j'ai continué à travailler sur l'interface admin en ajoutant le formulaire de modification de catégorie et en créant une nouvelle page pour gérer les utilisateurs. J'ai également ajouté le bouncer pour sécuriser l'accès à la partie admin de l'application, tant pour le frontend que pour le backend. Voyons les travaux réalisés en détail.",[1929,48715,48717],{"id":48716},"formulaire-de-modification-de-catégorie","Formulaire de Modification de Catégorie",[35,48719,48720],{},"Cette partie était très similaire au formulaire de création de catégorie. Au lieu de soumettre les données du formulaire à l'API pour créer une nouvelle catégorie, j'ai changé l'endpoint pour mettre à jour une catégorie existante.",[35,48722,48723,48724,48727,48728,48731,48732,48735,48736,48738],{},"J'ai créé le formulaire dans un nouveau composant ",[156,48725,48726],{},"components\u002Fcategories\u002FCategoryEditForm.vue"," (et renommé le précédent formulaire en ",[156,48729,48730],{},"CategoryCreateForm.vue","). J'ai également créé un nouvel endpoint ",[156,48733,48734],{},"server\u002Fapi\u002Fcategories\u002F[id].put.ts"," pour mettre à jour une catégorie. Cet endpoint met à jour une catégorie en utilisant l'",[156,48737,3295],{}," dans l'URL et le corps de la requête :",[192,48740,48742],{"className":1380,"code":48741,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  await useDrizzle().update(tables.categories).set({\n    slug: useSlugify(body.name),\n    name: body.name,\n  }).where(eq(tables.categories.id, params.id)).execute()\n\n  return sendNoContent(event, 200)\n})\n",[156,48743,48744,48766,48784,48788,48820,48840,48854,48899,48903,48920],{"__ignoreMap":13},[200,48745,48746,48748,48750,48752,48754,48756,48758,48760,48762,48764],{"class":202,"line":203},[200,48747,1390],{"class":206},[200,48749,1393],{"class":206},[200,48751,38723],{"class":223},[200,48753,227],{"class":210},[200,48755,3726],{"class":379},[200,48757,1094],{"class":214},[200,48759,25426],{"class":386},[200,48761,2104],{"class":214},[200,48763,393],{"class":379},[200,48765,280],{"class":214},[200,48767,48768,48770,48772,48774,48776,48778,48780,48782],{"class":202,"line":14},[200,48769,3738],{"class":379},[200,48771,40809],{"class":210},[200,48773,509],{"class":214},[200,48775,452],{"class":206},[200,48777,40816],{"class":223},[200,48779,227],{"class":235},[200,48781,25426],{"class":210},[200,48783,416],{"class":235},[200,48785,48786],{"class":202,"line":255},[200,48787,528],{"emptyLinePlaceholder":20},[200,48789,48790,48792,48794,48796,48798,48801,48803,48805,48807,48809,48811,48813,48816,48818],{"class":202,"line":272},[200,48791,6177],{"class":206},[200,48793,47338],{"class":223},[200,48795,1549],{"class":235},[200,48797,215],{"class":214},[200,48799,48800],{"class":223},"update",[200,48802,227],{"class":235},[200,48804,47418],{"class":210},[200,48806,215],{"class":214},[200,48808,47161],{"class":210},[200,48810,2104],{"class":235},[200,48812,215],{"class":214},[200,48814,48815],{"class":223},"set",[200,48817,227],{"class":235},[200,48819,230],{"class":214},[200,48821,48822,48824,48826,48828,48830,48832,48834,48836,48838],{"class":202,"line":283},[200,48823,47372],{"class":235},[200,48825,239],{"class":214},[200,48827,47553],{"class":223},[200,48829,227],{"class":235},[200,48831,23914],{"class":210},[200,48833,215],{"class":214},[200,48835,20112],{"class":210},[200,48837,2104],{"class":235},[200,48839,252],{"class":214},[200,48841,48842,48844,48846,48848,48850,48852],{"class":202,"line":300},[200,48843,11812],{"class":235},[200,48845,239],{"class":214},[200,48847,40809],{"class":210},[200,48849,215],{"class":214},[200,48851,20112],{"class":210},[200,48853,252],{"class":214},[200,48855,48856,48858,48860,48862,48865,48867,48870,48872,48874,48876,48878,48880,48882,48884,48887,48889,48891,48893,48895,48897],{"class":202,"line":310},[200,48857,5095],{"class":214},[200,48859,2104],{"class":235},[200,48861,215],{"class":214},[200,48863,48864],{"class":223},"where",[200,48866,227],{"class":235},[200,48868,48869],{"class":223},"eq",[200,48871,227],{"class":235},[200,48873,47418],{"class":210},[200,48875,215],{"class":214},[200,48877,47161],{"class":210},[200,48879,215],{"class":214},[200,48881,3295],{"class":210},[200,48883,549],{"class":214},[200,48885,48886],{"class":210}," params",[200,48888,215],{"class":214},[200,48890,3295],{"class":210},[200,48892,6131],{"class":235},[200,48894,215],{"class":214},[200,48896,47590],{"class":223},[200,48898,466],{"class":235},[200,48900,48901],{"class":202,"line":336},[200,48902,528],{"emptyLinePlaceholder":20},[200,48904,48905,48907,48909,48911,48913,48915,48918],{"class":202,"line":342},[200,48906,3758],{"class":206},[200,48908,40842],{"class":223},[200,48910,227],{"class":235},[200,48912,25426],{"class":210},[200,48914,549],{"class":214},[200,48916,48917],{"class":2060}," 200",[200,48919,416],{"class":235},[200,48921,48922,48924],{"class":202,"line":365},[200,48923,413],{"class":214},[200,48925,416],{"class":210},[35,48927,48928],{},[1216,48929,48930,48931,48934],{},"Le code de statut 200 pourrait ne pas être le meilleur choix (204 est mieux pour un ",[156,48932,48933],{},"PUT","), mais je réfléchis encore à savoir si je devrais retourner la catégorie mise à jour ou non.",[35,48936,48937,48938,48943,48944],{},"J'ai également tenté de valider le corps de la requête en utilisant Yup, car c'était l'outil utilisé dans le frontend. Cependant, j'ai rapidement réalisé qu'il n'était pas possible de caster une chaîne en nombre. Je sais que myzod peut le faire, alors je l'ai essayé. J'ai rencontré ",[39,48939,48942],{"href":48940,"rel":48941},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fh3\u002Fissues\u002F745",[77],"un problème"," et j'ai donc utilisé Zod. ",[1216,48945,48946],{},"J'ai déplacé le frontend vers Zod en dehors du stream.",[1929,48948,48950],{"id":48949},"page-des-utilisateurs","Page des Utilisateurs",[35,48952,48953,48954,48957],{},"En réalité, lorsqu'un utilisateur se connecte, les données associées de GitHub sont stockées dans la base de données (",[1216,48955,48956],{},"en écrivant cette partie, je me demande si cela est nécessaire car cela signifie qu'une connexion est une opération d'écriture, ce qui est plus coûteux qu'une lecture",") et dans la session. Ce comportement est utile pour promouvoir l'utilisateur au statut d'admin ou pour bannir un utilisateur. Mais pour le moment, il n'y a pas de moyen de gérer les utilisateurs. Donc, j'ai créé une nouvelle page pour gérer les utilisateurs. Comme la page des catégories, la page affichera les utilisateurs dans un tableau. Cependant, il ne sera pas possible de créer ou de mettre à jour un utilisateur pour le moment.",[35,48959,48960,48961,2186],{},"Pour récupérer les utilisateurs depuis la base de données, j'ai créé l'endpoint API ",[156,48962,48963],{},"server\u002Fapi\u002Fusers\u002Findex.get.ts",[192,48965,48967],{"className":1380,"code":48966,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  const users = await useDrizzle().select({\n    id: tables.users.id,\n    githubId: tables.users.githubId,\n    username: tables.users.username,\n    roleType: tables.users.roleType\n  }).from(tables.users)\n\n  return users\n})\n",[156,48968,48969,48991,49014,49032,49051,49071,49089,49109,49113,49120],{"__ignoreMap":13},[200,48970,48971,48973,48975,48977,48979,48981,48983,48985,48987,48989],{"class":202,"line":203},[200,48972,1390],{"class":206},[200,48974,1393],{"class":206},[200,48976,38723],{"class":223},[200,48978,227],{"class":210},[200,48980,3726],{"class":379},[200,48982,1094],{"class":214},[200,48984,25426],{"class":386},[200,48986,2104],{"class":214},[200,48988,393],{"class":379},[200,48990,280],{"class":214},[200,48992,48993,48995,48998,49000,49002,49004,49006,49008,49010,49012],{"class":202,"line":14},[200,48994,3738],{"class":379},[200,48996,48997],{"class":210}," users",[200,48999,509],{"class":214},[200,49001,452],{"class":206},[200,49003,47338],{"class":223},[200,49005,1549],{"class":235},[200,49007,215],{"class":214},[200,49009,769],{"class":223},[200,49011,227],{"class":235},[200,49013,230],{"class":214},[200,49015,49016,49018,49020,49022,49024,49026,49028,49030],{"class":202,"line":255},[200,49017,15861],{"class":235},[200,49019,239],{"class":214},[200,49021,47357],{"class":210},[200,49023,215],{"class":214},[200,49025,47155],{"class":210},[200,49027,215],{"class":214},[200,49029,3295],{"class":210},[200,49031,252],{"class":214},[200,49033,49034,49037,49039,49041,49043,49045,49047,49049],{"class":202,"line":272},[200,49035,49036],{"class":235},"    githubId",[200,49038,239],{"class":214},[200,49040,47357],{"class":210},[200,49042,215],{"class":214},[200,49044,47155],{"class":210},[200,49046,215],{"class":214},[200,49048,46052],{"class":210},[200,49050,252],{"class":214},[200,49052,49053,49056,49058,49060,49062,49064,49066,49069],{"class":202,"line":283},[200,49054,49055],{"class":235},"    username",[200,49057,239],{"class":214},[200,49059,47357],{"class":210},[200,49061,215],{"class":214},[200,49063,47155],{"class":210},[200,49065,215],{"class":214},[200,49067,49068],{"class":210},"username",[200,49070,252],{"class":214},[200,49072,49073,49076,49078,49080,49082,49084,49086],{"class":202,"line":300},[200,49074,49075],{"class":235},"    roleType",[200,49077,239],{"class":214},[200,49079,47357],{"class":210},[200,49081,215],{"class":214},[200,49083,47155],{"class":210},[200,49085,215],{"class":214},[200,49087,49088],{"class":210},"roleType\n",[200,49090,49091,49093,49095,49097,49099,49101,49103,49105,49107],{"class":202,"line":310},[200,49092,5095],{"class":214},[200,49094,2104],{"class":235},[200,49096,215],{"class":214},[200,49098,1489],{"class":223},[200,49100,227],{"class":235},[200,49102,47418],{"class":210},[200,49104,215],{"class":214},[200,49106,47155],{"class":210},[200,49108,416],{"class":235},[200,49110,49111],{"class":202,"line":336},[200,49112,528],{"emptyLinePlaceholder":20},[200,49114,49115,49117],{"class":202,"line":342},[200,49116,3758],{"class":206},[200,49118,49119],{"class":210}," users\n",[200,49121,49122,49124],{"class":202,"line":365},[200,49123,413],{"class":214},[200,49125,416],{"class":210},[35,49127,49128,49129,49131],{},"Pour les afficher, j'utilise le composant ",[156,49130,47992],{}," de Nuxt UI Pro. Le code est très similaire à celui de la page des catégories :",[192,49133,49135],{"className":21457,"code":49134,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst columns = [{\n  key: 'id',\n  label: '#'\n}, {\n  key: 'githubId',\n  label: 'GitHub ID',\n}, {\n  key: 'username',\n  label: 'Nom d\\'utilisateur',\n  sortable: true\n}, {\n  key: 'roleType',\n  label: 'Type de rôle'\n}]\nconst { data: users } = await useFetch('\u002Fapi\u002Fusers', {\n  deep: false,\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUDashboardPage>\n    \u003CUDashboardPanel grow>\n      \u003CUTable :columns=\"columns\" :rows=\"users\">\n        \u003Ctemplate #roleType-data=\"{ row }\">\n          \u003CUBadge variant=\"subtle\" :color=\"row.roleType === 'admin' ? 'amber' : 'primary'\">\n            {{ row.roleType }}\n          \u003C\u002FUBadge>\n        \u003C\u002Ftemplate>\n      \u003C\u002FUTable>\n    \u003C\u002FUDashboardPanel>\n  \u003C\u002FUDashboardPage>\n\u003C\u002Ftemplate>\n",[156,49136,49137,49157,49169,49183,49195,49201,49215,49230,49236,49250,49270,49278,49284,49298,49311,49317,49351,49361,49367,49375,49379,49387,49395,49405,49437,49464,49531,49536,49545,49554,49562,49570,49578],{"__ignoreMap":13},[200,49138,49139,49141,49143,49145,49147,49149,49151,49153,49155],{"class":202,"line":203},[200,49140,599],{"class":214},[200,49142,14896],{"class":235},[200,49144,14594],{"class":379},[200,49146,449],{"class":214},[200,49148,610],{"class":214},[200,49150,1383],{"class":245},[200,49152,610],{"class":214},[200,49154,21825],{"class":379},[200,49156,647],{"class":214},[200,49158,49159,49161,49163,49165,49167],{"class":202,"line":14},[200,49160,443],{"class":379},[200,49162,47788],{"class":210},[200,49164,449],{"class":214},[200,49166,350],{"class":210},[200,49168,230],{"class":214},[200,49170,49171,49173,49175,49177,49179,49181],{"class":202,"line":255},[200,49172,18976],{"class":235},[200,49174,239],{"class":214},[200,49176,242],{"class":214},[200,49178,3295],{"class":245},[200,49180,249],{"class":214},[200,49182,252],{"class":214},[200,49184,49185,49187,49189,49191,49193],{"class":202,"line":272},[200,49186,47813],{"class":235},[200,49188,239],{"class":214},[200,49190,242],{"class":214},[200,49192,2902],{"class":245},[200,49194,991],{"class":214},[200,49196,49197,49199],{"class":202,"line":283},[200,49198,26897],{"class":214},[200,49200,280],{"class":214},[200,49202,49203,49205,49207,49209,49211,49213],{"class":202,"line":300},[200,49204,18976],{"class":235},[200,49206,239],{"class":214},[200,49208,242],{"class":214},[200,49210,46052],{"class":245},[200,49212,249],{"class":214},[200,49214,252],{"class":214},[200,49216,49217,49219,49221,49223,49226,49228],{"class":202,"line":310},[200,49218,47813],{"class":235},[200,49220,239],{"class":214},[200,49222,242],{"class":214},[200,49224,49225],{"class":245},"GitHub ID",[200,49227,249],{"class":214},[200,49229,252],{"class":214},[200,49231,49232,49234],{"class":202,"line":336},[200,49233,26897],{"class":214},[200,49235,280],{"class":214},[200,49237,49238,49240,49242,49244,49246,49248],{"class":202,"line":342},[200,49239,18976],{"class":235},[200,49241,239],{"class":214},[200,49243,242],{"class":214},[200,49245,49068],{"class":245},[200,49247,249],{"class":214},[200,49249,252],{"class":214},[200,49251,49252,49254,49256,49258,49261,49263,49266,49268],{"class":202,"line":365},[200,49253,47813],{"class":235},[200,49255,239],{"class":214},[200,49257,242],{"class":214},[200,49259,49260],{"class":245},"Nom d",[200,49262,16056],{"class":210},[200,49264,49265],{"class":245},"utilisateur",[200,49267,249],{"class":214},[200,49269,252],{"class":214},[200,49271,49272,49274,49276],{"class":202,"line":371},[200,49273,47860],{"class":235},[200,49275,239],{"class":214},[200,49277,22236],{"class":2438},[200,49279,49280,49282],{"class":202,"line":398},[200,49281,26897],{"class":214},[200,49283,280],{"class":214},[200,49285,49286,49288,49290,49292,49294,49296],{"class":202,"line":405},[200,49287,18976],{"class":235},[200,49289,239],{"class":214},[200,49291,242],{"class":214},[200,49293,39182],{"class":245},[200,49295,249],{"class":214},[200,49297,252],{"class":214},[200,49299,49300,49302,49304,49306,49309],{"class":202,"line":410},[200,49301,47813],{"class":235},[200,49303,239],{"class":214},[200,49305,242],{"class":214},[200,49307,49308],{"class":245},"Type de rôle",[200,49310,991],{"class":214},[200,49312,49313,49315],{"class":202,"line":860},[200,49314,413],{"class":214},[200,49316,4511],{"class":210},[200,49318,49319,49321,49323,49325,49327,49330,49332,49334,49336,49338,49340,49342,49345,49347,49349],{"class":202,"line":889},[200,49320,443],{"class":379},[200,49322,318],{"class":214},[200,49324,5056],{"class":235},[200,49326,239],{"class":214},[200,49328,49329],{"class":210}," users ",[200,49331,413],{"class":214},[200,49333,509],{"class":214},[200,49335,452],{"class":206},[200,49337,47935],{"class":223},[200,49339,227],{"class":210},[200,49341,249],{"class":214},[200,49343,49344],{"class":245},"\u002Fapi\u002Fusers",[200,49346,249],{"class":214},[200,49348,549],{"class":214},[200,49350,280],{"class":214},[200,49352,49353,49355,49357,49359],{"class":202,"line":899},[200,49354,47953],{"class":235},[200,49356,239],{"class":214},[200,49358,16169],{"class":2438},[200,49360,252],{"class":214},[200,49362,49363,49365],{"class":202,"line":904},[200,49364,413],{"class":214},[200,49366,416],{"class":210},[200,49368,49369,49371,49373],{"class":202,"line":930},[200,49370,680],{"class":214},[200,49372,14896],{"class":235},[200,49374,647],{"class":214},[200,49376,49377],{"class":202,"line":3655},[200,49378,528],{"emptyLinePlaceholder":20},[200,49380,49381,49383,49385],{"class":202,"line":3660},[200,49382,599],{"class":214},[200,49384,21468],{"class":235},[200,49386,647],{"class":214},[200,49388,49389,49391,49393],{"class":202,"line":4035},[200,49390,656],{"class":214},[200,49392,47702],{"class":235},[200,49394,647],{"class":214},[200,49396,49397,49399,49401,49403],{"class":202,"line":4490},[200,49398,803],{"class":214},[200,49400,47711],{"class":235},[200,49402,47714],{"class":379},[200,49404,647],{"class":214},[200,49406,49407,49409,49411,49413,49415,49417,49419,49421,49423,49425,49427,49429,49431,49433,49435],{"class":202,"line":4496},[200,49408,22071],{"class":214},[200,49410,47992],{"class":235},[200,49412,2186],{"class":214},[200,49414,47997],{"class":379},[200,49416,449],{"class":214},[200,49418,610],{"class":214},[200,49420,47997],{"class":210},[200,49422,610],{"class":214},[200,49424,2186],{"class":214},[200,49426,48010],{"class":379},[200,49428,449],{"class":214},[200,49430,610],{"class":214},[200,49432,47155],{"class":210},[200,49434,610],{"class":214},[200,49436,647],{"class":214},[200,49438,49439,49442,49444,49446,49449,49451,49453,49455,49458,49460,49462],{"class":202,"line":4502},[200,49440,49441],{"class":214},"        \u003C",[200,49443,21468],{"class":235},[200,49445,23911],{"class":214},[200,49447,49448],{"class":379},"roleType-data",[200,49450,449],{"class":214},[200,49452,610],{"class":214},[200,49454,2767],{"class":214},[200,49456,49457],{"class":210}," row ",[200,49459,413],{"class":214},[200,49461,610],{"class":214},[200,49463,647],{"class":214},[200,49465,49466,49469,49472,49475,49477,49479,49482,49484,49486,49489,49491,49493,49496,49498,49500,49503,49505,49507,49509,49512,49514,49517,49519,49522,49524,49527,49529],{"class":202,"line":4508},[200,49467,49468],{"class":214},"          \u003C",[200,49470,49471],{"class":235},"UBadge",[200,49473,49474],{"class":379}," variant",[200,49476,449],{"class":214},[200,49478,610],{"class":214},[200,49480,49481],{"class":245},"subtle",[200,49483,610],{"class":214},[200,49485,2186],{"class":214},[200,49487,49488],{"class":379},"color",[200,49490,449],{"class":214},[200,49492,610],{"class":214},[200,49494,49495],{"class":210},"row",[200,49497,215],{"class":214},[200,49499,39182],{"class":210},[200,49501,49502],{"class":214}," === ",[200,49504,249],{"class":214},[200,49506,39189],{"class":245},[200,49508,249],{"class":214},[200,49510,49511],{"class":214}," ? ",[200,49513,249],{"class":214},[200,49515,49516],{"class":245},"amber",[200,49518,249],{"class":214},[200,49520,49521],{"class":214}," : ",[200,49523,249],{"class":214},[200,49525,49526],{"class":245},"primary",[200,49528,14706],{"class":214},[200,49530,647],{"class":214},[200,49532,49533],{"class":202,"line":5469},[200,49534,49535],{"class":210},"            {{ row.roleType }}\n",[200,49537,49538,49541,49543],{"class":202,"line":5474},[200,49539,49540],{"class":214},"          \u003C\u002F",[200,49542,49471],{"class":235},[200,49544,647],{"class":214},[200,49546,49547,49550,49552],{"class":202,"line":13237},[200,49548,49549],{"class":214},"        \u003C\u002F",[200,49551,21468],{"class":235},[200,49553,647],{"class":214},[200,49555,49556,49558,49560],{"class":202,"line":13263},[200,49557,43803],{"class":214},[200,49559,47992],{"class":235},[200,49561,647],{"class":214},[200,49563,49564,49566,49568],{"class":202,"line":13279},[200,49565,15078],{"class":214},[200,49567,47711],{"class":235},[200,49569,647],{"class":214},[200,49571,49572,49574,49576],{"class":202,"line":13323},[200,49573,892],{"class":214},[200,49575,47702],{"class":235},[200,49577,647],{"class":214},[200,49579,49580,49582,49584],{"class":202,"line":13329},[200,49581,680],{"class":214},[200,49583,21468],{"class":235},[200,49585,647],{"class":214},[35,49587,49588],{},"Comme prévu, le code est très similaire à celui de la page des catégories.",[35,49590,49591,49592,49595],{},"Maintenant que nous avons deux pages, une pour les catégories et une pour les utilisateurs, j'ai ajouté une barre latérale pour naviguer entre elles. Rien de compliqué ici, j'ai simplement utilisé le composant ",[156,49593,49594],{},"UDashboardSidebar"," de Nuxt UI Pro et ajouté les liens vers les pages des catégories et des utilisateurs :",[192,49597,49599],{"className":21457,"code":49598,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst links = [{\n  id: 'categories',\n  label: 'Catégories',\n  icon: 'i-heroicons-tag',\n  to: '\u002Fadmin\u002Fcategories',\n  tooltip: {\n    text: 'Catégories',\n  }\n}, {\n  id: 'users',\n  label: 'Utilisateurs',\n  icon: 'i-heroicons-user-group',\n  to: '\u002Fadmin\u002Fusers',\n  tooltip: {\n    text: 'Utilisateurs',\n  }\n}]\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUDashboardLayout>\n    \u003CUDashboardPanel>\n      \u003CUDashboardSidebar>\n        \u003CUDashboardSidebarLinks :links=\"links\" \u002F>\n      \u003C\u002FUDashboardSidebar>\n    \u003C\u002FUDashboardPanel>\n\n    \u003Cslot \u002F>\n  \u003C\u002FUDashboardLayout>\n\u003C\u002Ftemplate>\n",[156,49600,49601,49621,49634,49649,49663,49679,49695,49704,49719,49723,49729,49743,49758,49773,49788,49796,49810,49814,49820,49828,49832,49840,49848,49856,49864,49886,49894,49902,49906,49914,49922],{"__ignoreMap":13},[200,49602,49603,49605,49607,49609,49611,49613,49615,49617,49619],{"class":202,"line":203},[200,49604,599],{"class":214},[200,49606,14896],{"class":235},[200,49608,14594],{"class":379},[200,49610,449],{"class":214},[200,49612,610],{"class":214},[200,49614,1383],{"class":245},[200,49616,610],{"class":214},[200,49618,21825],{"class":379},[200,49620,647],{"class":214},[200,49622,49623,49625,49628,49630,49632],{"class":202,"line":14},[200,49624,443],{"class":379},[200,49626,49627],{"class":210}," links ",[200,49629,449],{"class":214},[200,49631,350],{"class":210},[200,49633,230],{"class":214},[200,49635,49636,49639,49641,49643,49645,49647],{"class":202,"line":255},[200,49637,49638],{"class":235},"  id",[200,49640,239],{"class":214},[200,49642,242],{"class":214},[200,49644,47161],{"class":245},[200,49646,249],{"class":214},[200,49648,252],{"class":214},[200,49650,49651,49653,49655,49657,49659,49661],{"class":202,"line":272},[200,49652,47813],{"class":235},[200,49654,239],{"class":214},[200,49656,242],{"class":214},[200,49658,48081],{"class":245},[200,49660,249],{"class":214},[200,49662,252],{"class":214},[200,49664,49665,49668,49670,49672,49675,49677],{"class":202,"line":283},[200,49666,49667],{"class":235},"  icon",[200,49669,239],{"class":214},[200,49671,242],{"class":214},[200,49673,49674],{"class":245},"i-heroicons-tag",[200,49676,249],{"class":214},[200,49678,252],{"class":214},[200,49680,49681,49684,49686,49688,49691,49693],{"class":202,"line":300},[200,49682,49683],{"class":235},"  to",[200,49685,239],{"class":214},[200,49687,242],{"class":214},[200,49689,49690],{"class":245},"\u002Fadmin\u002Fcategories",[200,49692,249],{"class":214},[200,49694,252],{"class":214},[200,49696,49697,49700,49702],{"class":202,"line":310},[200,49698,49699],{"class":235},"  tooltip",[200,49701,239],{"class":214},[200,49703,280],{"class":214},[200,49705,49706,49709,49711,49713,49715,49717],{"class":202,"line":336},[200,49707,49708],{"class":235},"    text",[200,49710,239],{"class":214},[200,49712,242],{"class":214},[200,49714,48081],{"class":245},[200,49716,249],{"class":214},[200,49718,252],{"class":214},[200,49720,49721],{"class":202,"line":342},[200,49722,4505],{"class":214},[200,49724,49725,49727],{"class":202,"line":365},[200,49726,26897],{"class":214},[200,49728,280],{"class":214},[200,49730,49731,49733,49735,49737,49739,49741],{"class":202,"line":371},[200,49732,49638],{"class":235},[200,49734,239],{"class":214},[200,49736,242],{"class":214},[200,49738,47155],{"class":245},[200,49740,249],{"class":214},[200,49742,252],{"class":214},[200,49744,49745,49747,49749,49751,49754,49756],{"class":202,"line":398},[200,49746,47813],{"class":235},[200,49748,239],{"class":214},[200,49750,242],{"class":214},[200,49752,49753],{"class":245},"Utilisateurs",[200,49755,249],{"class":214},[200,49757,252],{"class":214},[200,49759,49760,49762,49764,49766,49769,49771],{"class":202,"line":405},[200,49761,49667],{"class":235},[200,49763,239],{"class":214},[200,49765,242],{"class":214},[200,49767,49768],{"class":245},"i-heroicons-user-group",[200,49770,249],{"class":214},[200,49772,252],{"class":214},[200,49774,49775,49777,49779,49781,49784,49786],{"class":202,"line":410},[200,49776,49683],{"class":235},[200,49778,239],{"class":214},[200,49780,242],{"class":214},[200,49782,49783],{"class":245},"\u002Fadmin\u002Fusers",[200,49785,249],{"class":214},[200,49787,252],{"class":214},[200,49789,49790,49792,49794],{"class":202,"line":860},[200,49791,49699],{"class":235},[200,49793,239],{"class":214},[200,49795,280],{"class":214},[200,49797,49798,49800,49802,49804,49806,49808],{"class":202,"line":889},[200,49799,49708],{"class":235},[200,49801,239],{"class":214},[200,49803,242],{"class":214},[200,49805,49753],{"class":245},[200,49807,249],{"class":214},[200,49809,252],{"class":214},[200,49811,49812],{"class":202,"line":899},[200,49813,4505],{"class":214},[200,49815,49816,49818],{"class":202,"line":904},[200,49817,413],{"class":214},[200,49819,4511],{"class":210},[200,49821,49822,49824,49826],{"class":202,"line":930},[200,49823,680],{"class":214},[200,49825,14896],{"class":235},[200,49827,647],{"class":214},[200,49829,49830],{"class":202,"line":3655},[200,49831,528],{"emptyLinePlaceholder":20},[200,49833,49834,49836,49838],{"class":202,"line":3660},[200,49835,599],{"class":214},[200,49837,21468],{"class":235},[200,49839,647],{"class":214},[200,49841,49842,49844,49846],{"class":202,"line":4035},[200,49843,656],{"class":214},[200,49845,47246],{"class":235},[200,49847,647],{"class":214},[200,49849,49850,49852,49854],{"class":202,"line":4490},[200,49851,803],{"class":214},[200,49853,47711],{"class":235},[200,49855,647],{"class":214},[200,49857,49858,49860,49862],{"class":202,"line":4496},[200,49859,22071],{"class":214},[200,49861,49594],{"class":235},[200,49863,647],{"class":214},[200,49865,49866,49868,49871,49873,49876,49878,49880,49882,49884],{"class":202,"line":4502},[200,49867,49441],{"class":214},[200,49869,49870],{"class":235},"UDashboardSidebarLinks",[200,49872,2186],{"class":214},[200,49874,49875],{"class":379},"links",[200,49877,449],{"class":214},[200,49879,610],{"class":214},[200,49881,49875],{"class":210},[200,49883,610],{"class":214},[200,49885,21527],{"class":214},[200,49887,49888,49890,49892],{"class":202,"line":4508},[200,49889,43803],{"class":214},[200,49891,49594],{"class":235},[200,49893,647],{"class":214},[200,49895,49896,49898,49900],{"class":202,"line":5469},[200,49897,15078],{"class":214},[200,49899,47711],{"class":235},[200,49901,647],{"class":214},[200,49903,49904],{"class":202,"line":5474},[200,49905,528],{"emptyLinePlaceholder":20},[200,49907,49908,49910,49912],{"class":202,"line":13237},[200,49909,803],{"class":214},[200,49911,47255],{"class":235},[200,49913,21527],{"class":214},[200,49915,49916,49918,49920],{"class":202,"line":13263},[200,49917,892],{"class":214},[200,49919,47246],{"class":235},[200,49921,647],{"class":214},[200,49923,49924,49926,49928],{"class":202,"line":13279},[200,49925,680],{"class":214},[200,49927,21468],{"class":235},[200,49929,647],{"class":214},[51,49931,49933],{"id":49932},"sécuriser-la-partie-administration","Sécuriser la Partie Administration",[35,49935,49936],{},"Lors de la construction de la partie administration d'une application, il est crucial de sécuriser et de restreindre l'accès, tant pour le frontend que pour le backend. Cela signifie que seuls les utilisateurs autorisés peuvent visualiser et manipuler les données.",[35,49938,49939,49940,49943],{},"Pour restreindre l'accès à la partie UI de l'administration, c'est-à-dire le frontend, j'ai utilisé un middleware. Il s'agit d'une fonction qui s'exécute avant la navigation vers la page et peut être utilisée pour vérifier si l'utilisateur est authentifié et dispose des bonnes permissions. Dans ce cas, j'ai créé un middleware ",[156,49941,49942],{},"middleware\u002Fadmin.ts"," pour vérifier si l'utilisateur est authentifié et a le rôle d'admin :",[192,49945,49947],{"className":1380,"code":49946,"language":1383,"meta":13,"style":13},"export default defineNuxtRouteMiddleware(() => {\n  const { user } = useUserSession()\n\n  const isAdmin = user.value?.roleType === 'admin'\n\n  if (!isAdmin)\n    return redirectTo('\u002F')\n})\n",[156,49948,49949,49966,49983,49987,50014,50018,50031,50048],{"__ignoreMap":13},[200,49950,49951,49953,49955,49958,49960,49962,49964],{"class":202,"line":203},[200,49952,1390],{"class":206},[200,49954,1393],{"class":206},[200,49956,49957],{"class":223}," defineNuxtRouteMiddleware",[200,49959,227],{"class":210},[200,49961,1549],{"class":214},[200,49963,393],{"class":379},[200,49965,280],{"class":214},[200,49967,49968,49970,49972,49974,49976,49978,49981],{"class":202,"line":14},[200,49969,3738],{"class":379},[200,49971,318],{"class":214},[200,49973,34533],{"class":210},[200,49975,980],{"class":214},[200,49977,509],{"class":214},[200,49979,49980],{"class":223}," useUserSession",[200,49982,466],{"class":235},[200,49984,49985],{"class":202,"line":255},[200,49986,528],{"emptyLinePlaceholder":20},[200,49988,49989,49991,49994,49996,49998,50000,50002,50004,50006,50008,50010,50012],{"class":202,"line":272},[200,49990,3738],{"class":379},[200,49992,49993],{"class":210}," isAdmin",[200,49995,509],{"class":214},[200,49997,34533],{"class":210},[200,49999,215],{"class":214},[200,50001,15840],{"class":210},[200,50003,6536],{"class":214},[200,50005,39182],{"class":210},[200,50007,3298],{"class":214},[200,50009,242],{"class":214},[200,50011,39189],{"class":245},[200,50013,991],{"class":214},[200,50015,50016],{"class":202,"line":283},[200,50017,528],{"emptyLinePlaceholder":20},[200,50019,50020,50022,50024,50026,50029],{"class":202,"line":300},[200,50021,20098],{"class":206},[200,50023,1094],{"class":235},[200,50025,11852],{"class":214},[200,50027,50028],{"class":210},"isAdmin",[200,50030,416],{"class":235},[200,50032,50033,50035,50038,50040,50042,50044,50046],{"class":202,"line":310},[200,50034,3311],{"class":206},[200,50036,50037],{"class":223}," redirectTo",[200,50039,227],{"class":235},[200,50041,249],{"class":214},[200,50043,2898],{"class":245},[200,50045,249],{"class":214},[200,50047,416],{"class":235},[200,50049,50050,50052],{"class":202,"line":336},[200,50051,413],{"class":214},[200,50053,416],{"class":210},[35,50055,50056],{},[1216,50057,50058,50059,215],{},"Le middleware doit être utilisé sur les pages nécessitant une protection en utilisant ",[156,50060,47652],{},[35,50062,50063,50064,50067],{},"Je récupère l'utilisateur depuis la session en utilisant ",[156,50065,50066],{},"useUserSession"," et vérifie si l'utilisateur a le rôle d'admin. Sinon, je redirige l'utilisateur vers la page d'accueil.",[35,50069,50070,50071,50074,50075,2186],{},"Le backend est beaucoup plus critique que le frontend, mais il n'est pas plus difficile à sécuriser. Pour ce faire, j'ai créé un utilitaire nommé ",[156,50072,50073],{},"requireAdminUser"," dans le fichier ",[156,50076,50077],{},"server\u002Futils\u002Fsession.ts",[192,50079,50081],{"className":1380,"code":50080,"language":1383,"meta":13,"style":13},"import type { UserSessionRequired } from '#auth-utils'\nimport type { H3Event } from 'h3'\n\nexport async function requireAdminUser(event: H3Event): Promise\u003CUserSessionRequired> {\n  const userSession = await getUserSession(event)\n\n  if (!userSession.user || userSession.user.roleType !== 'admin') {\n    throw createError({\n      statusCode: 401,\n      message: 'Unauthorized',\n    })\n  }\n\n  return userSession as UserSessionRequired\n}\n",[156,50082,50083,50105,50125,50129,50161,50179,50183,50221,50231,50241,50256,50262,50266,50270,50280],{"__ignoreMap":13},[200,50084,50085,50087,50089,50091,50094,50096,50098,50100,50103],{"class":202,"line":203},[200,50086,972],{"class":206},[200,50088,321],{"class":206},[200,50090,318],{"class":214},[200,50092,50093],{"class":210}," UserSessionRequired",[200,50095,980],{"class":214},[200,50097,983],{"class":206},[200,50099,242],{"class":214},[200,50101,50102],{"class":245},"#auth-utils",[200,50104,991],{"class":214},[200,50106,50107,50109,50111,50113,50115,50117,50119,50121,50123],{"class":202,"line":14},[200,50108,972],{"class":206},[200,50110,321],{"class":206},[200,50112,318],{"class":214},[200,50114,39087],{"class":210},[200,50116,980],{"class":214},[200,50118,983],{"class":206},[200,50120,242],{"class":214},[200,50122,1929],{"class":245},[200,50124,991],{"class":214},[200,50126,50127],{"class":202,"line":255},[200,50128,528],{"emptyLinePlaceholder":20},[200,50130,50131,50133,50135,50137,50140,50142,50144,50146,50148,50151,50153,50155,50157,50159],{"class":202,"line":272},[200,50132,1390],{"class":206},[200,50134,380],{"class":379},[200,50136,5002],{"class":379},[200,50138,50139],{"class":223}," requireAdminUser",[200,50141,227],{"class":214},[200,50143,25426],{"class":386},[200,50145,239],{"class":214},[200,50147,39087],{"class":1364},[200,50149,50150],{"class":214},"):",[200,50152,5011],{"class":1364},[200,50154,599],{"class":214},[200,50156,39127],{"class":1364},[200,50158,674],{"class":214},[200,50160,280],{"class":214},[200,50162,50163,50165,50167,50169,50171,50173,50175,50177],{"class":202,"line":283},[200,50164,3738],{"class":379},[200,50166,39138],{"class":210},[200,50168,509],{"class":214},[200,50170,452],{"class":206},[200,50172,39474],{"class":223},[200,50174,227],{"class":235},[200,50176,25426],{"class":210},[200,50178,416],{"class":235},[200,50180,50181],{"class":202,"line":300},[200,50182,528],{"emptyLinePlaceholder":20},[200,50184,50185,50187,50189,50191,50193,50195,50197,50199,50201,50203,50205,50207,50209,50211,50213,50215,50217,50219],{"class":202,"line":310},[200,50186,20098],{"class":206},[200,50188,1094],{"class":235},[200,50190,11852],{"class":214},[200,50192,39544],{"class":210},[200,50194,215],{"class":214},[200,50196,26847],{"class":210},[200,50198,15013],{"class":214},[200,50200,39138],{"class":210},[200,50202,215],{"class":214},[200,50204,26847],{"class":210},[200,50206,215],{"class":214},[200,50208,39182],{"class":210},[200,50210,13398],{"class":214},[200,50212,242],{"class":214},[200,50214,39189],{"class":245},[200,50216,249],{"class":214},[200,50218,5282],{"class":235},[200,50220,230],{"class":214},[200,50222,50223,50225,50227,50229],{"class":202,"line":336},[200,50224,39200],{"class":206},[200,50226,39203],{"class":223},[200,50228,227],{"class":235},[200,50230,230],{"class":214},[200,50232,50233,50235,50237,50239],{"class":202,"line":342},[200,50234,39212],{"class":235},[200,50236,239],{"class":214},[200,50238,39575],{"class":2060},[200,50240,252],{"class":214},[200,50242,50243,50245,50247,50249,50252,50254],{"class":202,"line":365},[200,50244,39233],{"class":235},[200,50246,239],{"class":214},[200,50248,242],{"class":214},[200,50250,50251],{"class":245},"Unauthorized",[200,50253,249],{"class":214},[200,50255,252],{"class":214},[200,50257,50258,50260],{"class":202,"line":371},[200,50259,2111],{"class":214},[200,50261,416],{"class":235},[200,50263,50264],{"class":202,"line":398},[200,50265,4505],{"class":214},[200,50267,50268],{"class":202,"line":405},[200,50269,528],{"emptyLinePlaceholder":20},[200,50271,50272,50274,50276,50278],{"class":202,"line":410},[200,50273,3758],{"class":206},[200,50275,39138],{"class":210},[200,50277,14678],{"class":206},[200,50279,39277],{"class":1364},[200,50281,50282],{"class":202,"line":860},[200,50283,1129],{"class":214},[35,50285,50286],{},"Cette fonction vérifie si l'utilisateur est authentifié et possède le rôle d'admin. Sinon, elle renvoie une erreur avec un code de statut 401. Cette fonction est utilisée dans les endpoints API pour les sécuriser :",[192,50288,50290],{"className":1380,"code":50289,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  await requireAdminUser(event)\n\n  \u002F\u002F Code pour gérer la requête\n})\n",[156,50291,50292,50314,50326,50330,50335],{"__ignoreMap":13},[200,50293,50294,50296,50298,50300,50302,50304,50306,50308,50310,50312],{"class":202,"line":203},[200,50295,1390],{"class":206},[200,50297,1393],{"class":206},[200,50299,38723],{"class":223},[200,50301,227],{"class":210},[200,50303,3726],{"class":379},[200,50305,1094],{"class":214},[200,50307,25426],{"class":386},[200,50309,2104],{"class":214},[200,50311,393],{"class":379},[200,50313,280],{"class":214},[200,50315,50316,50318,50320,50322,50324],{"class":202,"line":14},[200,50317,6177],{"class":206},[200,50319,50139],{"class":223},[200,50321,227],{"class":235},[200,50323,25426],{"class":210},[200,50325,416],{"class":235},[200,50327,50328],{"class":202,"line":255},[200,50329,528],{"emptyLinePlaceholder":20},[200,50331,50332],{"class":202,"line":272},[200,50333,50334],{"class":401},"  \u002F\u002F Code pour gérer la requête\n",[200,50336,50337,50339],{"class":202,"line":283},[200,50338,413],{"class":214},[200,50340,416],{"class":210},[35,50342,50343],{},"Avec ces deux fonctions, le frontend et le backend de la partie admin sont sécurisés. Seuls les utilisateurs authentifiés avec le rôle d'admin peuvent accéder à la partie administration de l'application.",[1929,50345,50347],{"id":50346},"à-venir","À Venir",[35,50349,50350],{},"Ce stream a été très productif et a mis en place des éléments importants pour l'avenir. Le prochain stream sera consacré aux modules et au créateur de thèmes. J'espère vraiment pouvoir mettre cela en production bientôt. Je suis vraiment impatient de voir les retours de la communauté.",[35,50352,46146,50353],{},[1599,50354,46149],{"repo":38413},[51,50356,50358],{"id":50357},"récupérer-une-api-externe-pour-les-modules","Récupérer une API externe pour les modules",[35,50360,50361],{},[1707,50362],{"alt":50363,"src":50364},"Vignette de mon cinquième direct.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-18.png",[35,50366,50367],{},[1216,50368,50369,47101,50372,47105,50375,45894,50378,215],{},[39,50370,38397],{"href":45853,"rel":50371},[77],[39,50373,45889],{"href":18579,"rel":50374},[77],[39,50376,19427],{"href":45844,"rel":50377},[77],[39,50379,11636],{"href":45853,"rel":50380},[77],[35,50382,50383,50384,50386,50387,50389,50390,50392,50393,50395],{},"Avant de continuer et d'implémenter la page des modules, j'ai décidé de modifier le comportement de ",[156,50385,33157],{}," sur les pages actuelles des Catégories et des Utilisateurs. En effet, le bloc composable ",[156,50388,33157],{}," bloque la navigation côté client jusqu'à ce que les données soient récupérées. Cela donne la sensation d'une application lente. Pour éviter cela, je peux utiliser le drapeau ",[156,50391,9909],{},". Côté serveur, cela ne change rien et la page sera toujours servie avec les données. Côté client, la navigation sera instantanée et j'afficherai un squelette pendant que les données sont en cours de récupération en utilisant le ",[156,50394,48029],{}," ref.",[192,50397,50399],{"className":1380,"code":50398,"language":1383,"meta":13,"style":13},"const { data, pending } = await useFetch('\u002Fapi\u002Fcategories', {\n  deep: false,\n  lazy: true, \u002F\u002F \u003C= Ne bloque pas la navigation\n  default: () => []\n})\n",[156,50400,50401,50433,50443,50457,50470],{"__ignoreMap":13},[200,50402,50403,50405,50407,50409,50411,50413,50415,50417,50419,50421,50423,50425,50427,50429,50431],{"class":202,"line":203},[200,50404,443],{"class":379},[200,50406,318],{"class":214},[200,50408,5056],{"class":210},[200,50410,549],{"class":214},[200,50412,47926],{"class":210},[200,50414,413],{"class":214},[200,50416,509],{"class":214},[200,50418,452],{"class":206},[200,50420,47935],{"class":223},[200,50422,227],{"class":210},[200,50424,249],{"class":214},[200,50426,47942],{"class":245},[200,50428,249],{"class":214},[200,50430,549],{"class":214},[200,50432,280],{"class":214},[200,50434,50435,50437,50439,50441],{"class":202,"line":14},[200,50436,47953],{"class":235},[200,50438,239],{"class":214},[200,50440,16169],{"class":2438},[200,50442,252],{"class":214},[200,50444,50445,50448,50450,50452,50454],{"class":202,"line":255},[200,50446,50447],{"class":235},"  lazy",[200,50449,239],{"class":214},[200,50451,2439],{"class":2438},[200,50453,549],{"class":214},[200,50455,50456],{"class":401}," \u002F\u002F \u003C= Ne bloque pas la navigation\n",[200,50458,50459,50462,50464,50466,50468],{"class":202,"line":272},[200,50460,50461],{"class":223},"  default",[200,50463,239],{"class":214},[200,50465,3729],{"class":214},[200,50467,393],{"class":379},[200,50469,6433],{"class":210},[200,50471,50472,50474],{"class":202,"line":283},[200,50473,413],{"class":214},[200,50475,416],{"class":210},[35,50477,19439,50478,50480,50481,50483,50484,50486,50487,50489],{},[156,50479,48029],{}," ref est un booléen qui indique si les données sont en cours de récupération ou non. Je le passe simplement aux props ",[156,50482,9900],{}," du composant ",[156,50485,47992],{}," de Nuxt UI Pro et ",[1216,50488,45783],{}," ! L'utilisateur verra un loader pendant que les données sont en cours de récupération et la navigation sera instantanée.",[11400,50491,132,50494,132,50503],{"flex":50492,"md":50493,"gap-x-2":13},"~ col","flex-row",[130,50495,136,50496,136,50500,132],{},[1707,50497],{"src":50498,"alt":50499,"loading":9909},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fblocking-fetch.gif","Sans drapeau",[143,50501,50502],{},"Sans drapeau lazy (blocage de la navigation)",[130,50504,136,50505,136,50509,132],{},[1707,50506],{"src":50507,"alt":50508,"loading":9909},"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Flazy-fetch.gif","Avec drapeau lazy",[143,50510,50511],{},"Avec drapeau lazy (navigation instantanée)",[7151,50513,50516,50520,50528,50535,50539,50550,50553,50557,50570,50578,50649,50663,50781,50791,50800,51021,51028,51031,51069,51082,51099,51102,51344,51350,51355,51358,51362,51369,51524,51527,51530,51533,51538,51541,51544,51548,51553,51556,51568,51571,51582,51585,51591,51608,51611,51640,51643,52049,52063,52070,52715,52718,52722,52728,52744,52747,52751,52756,52759,53237,53247,53265,53276,53279,53388,53392,53395,53402,53848,53863,53872,53876,53881,53891,53897,54066,54069,54094,54100,54106,55403,55409,55604,55607,55734,55740,55854,55860,55877,56248,56264,56267,56270,56273,56277,56283,56299,56302,56313,56319,56323,56348,56351,56394,56412,56435,56438,56660,56665,56880,56883,56887,56890,56893,56947,56954,56964,56977,57167,57176,57180,57185,57190,57197,57221,57224,57230,57242,57326,57329,57335,57338,57382,57388,57391,57394,57579,57582,57586,57589,57592,57596,57601,57604,57610,57618,57622,57625,57628,57681,57691,57698,57719,57722,57787,57796,57800,57809,57888,57892,57895,58173,58176,58179,58476,58479,58485,58488,58491,58495,58501,58517,58520,58523,58526,58530,58536,58542,58551,58878,58882,58885,58889,58896,59068,59072,59079,59223,59226,59230,59233,59554,59559,59563,59568,59583,59595,59598,59728,59735,59746,59755,60043,60052,60055,60199,60206,60209,60212],{"icon":7153,"text":50514,"to":50515,"target":7156},"En savoir plus sur useFetch","https:\u002F\u002Fyoutu.be\u002Fb1S5os65Urs",[1929,50517,50519],{"id":50518},"page-des-modules","Page des Modules",[35,50521,50522,50523,50527],{},"Les modules sont ",[39,50524,50526],{"href":2467,"rel":50525},[77],"des modules Nuxt",". Ils seront liés aux thèmes et seront utilisés pour filtrer les thèmes. Imaginez que vous recherchez un thème avec un bon SEO et précisément un sitemap, vous pourrez filtrer les thèmes en fonction des modules qu'ils utilisent. C'est l'objectif de la fonctionnalité des modules. Cela aidera également l'utilisateur à comprendre comment le thème est construit et quelles sont les fonctionnalités.",[35,50529,50530,50531,50534],{},"La première chose que je fais est d'ajouter une nouvelle page ",[156,50532,50533],{},"pages\u002Fadmin\u002Fmodules.vue",". Ensuite, j'ajoute le tableau pour afficher les modules et les métadonnées de la page pour utiliser la mise en page admin et le middleware admin. C'est similaire aux pages des catégories et des utilisateurs, donc je ne vais pas le détailler ici.",[38437,50536,50538],{"id":50537},"remplir-la-base-de-données","Remplir la Base de Données",[35,50540,50541,50542,50549],{},"La question principale est : comment remplir la base de données avec les modules ? Pour les catégories, je les crée manuellement. Cela convient car il n'y a pas beaucoup de catégories et il n'y a pas de moyen d'obtenir une liste prédéfinie, mais pour les modules, c'est différent. Les modules sont créés par l'équipe principale ou la communauté et il y en a beaucoup. De plus, la liste n'est pas statique et peut changer au fil du temps. Donc, je dois trouver une API pour récupérer les modules. Heureusement, Nuxt a une API pour obtenir la liste des modules : ",[39,50543,50546],{"href":50544,"rel":50545},"https:\u002F\u002Fapi.nuxt.com\u002Fmodules",[77],[156,50547,50548],{},"api.nuxt.com\u002Fmodules",". Cette API n'est pas documentée mais elle est publique et je peux l'utiliser.",[35,50551,50552],{},"Grâce à cette API, je pourrai récupérer les données, les assainir pour ne garder que les informations utiles et les stocker dans la base de données. Je vais créer un point de terminaison, accessible uniquement à l'admin, pour récupérer les modules et les stocker dans la base de données. Un deuxième point de terminaison sera disponible pour récupérer les modules depuis la base de données. Cela sera utile pour le frontend afin d'afficher les modules.",[38437,50554,50556],{"id":50555},"récupération-de-lapi-externe","Récupération de l'API Externe",[35,50558,50559,50560,1094,50563,50569],{},"Je crée un point de terminaison ",[156,50561,50562],{},"server\u002Fapi\u002Fmodules\u002Ffetch.post.ts",[1216,50564,50565,50566,50568],{},"hors stream, j'ai trouvé que nommer ce point de terminaison ",[156,50567,24477],{}," était une meilleure idée",") pour récupérer les modules depuis l'API externe et les stocker dans la base de données.",[35,50571,50572,50573,50575,50576,2186],{},"Tout d'abord, j'ajoute l'utilitaire ",[156,50574,50073],{}," pour sécuriser le point de terminaison. Ensuite, je récupère les modules depuis l'API externe en utilisant la fonction globale ",[156,50577,33160],{},[192,50579,50581],{"className":1380,"code":50580,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  await requireAdminUser(event)\n\n  const data = await $fetch('https:\u002F\u002Fapi.nuxt.com\u002Fmodules')\n})\n",[156,50582,50583,50605,50617,50621,50643],{"__ignoreMap":13},[200,50584,50585,50587,50589,50591,50593,50595,50597,50599,50601,50603],{"class":202,"line":203},[200,50586,1390],{"class":206},[200,50588,1393],{"class":206},[200,50590,38723],{"class":223},[200,50592,227],{"class":210},[200,50594,3726],{"class":379},[200,50596,1094],{"class":214},[200,50598,25426],{"class":386},[200,50600,2104],{"class":214},[200,50602,393],{"class":379},[200,50604,280],{"class":214},[200,50606,50607,50609,50611,50613,50615],{"class":202,"line":14},[200,50608,6177],{"class":206},[200,50610,50139],{"class":223},[200,50612,227],{"class":235},[200,50614,25426],{"class":210},[200,50616,416],{"class":235},[200,50618,50619],{"class":202,"line":255},[200,50620,528],{"emptyLinePlaceholder":20},[200,50622,50623,50625,50627,50629,50631,50633,50635,50637,50639,50641],{"class":202,"line":272},[200,50624,3738],{"class":379},[200,50626,5056],{"class":210},[200,50628,509],{"class":214},[200,50630,452],{"class":206},[200,50632,18754],{"class":223},[200,50634,227],{"class":235},[200,50636,249],{"class":214},[200,50638,50544],{"class":245},[200,50640,249],{"class":214},[200,50642,416],{"class":235},[200,50644,50645,50647],{"class":202,"line":283},[200,50646,413],{"class":214},[200,50648,416],{"class":210},[35,50650,50651,50652,3092,50654,3092,50657,9502,50659,50662],{},"Les données retournées par l'API contiennent beaucoup de données dont je n'ai pas besoin. Je n'ai besoin que du ",[156,50653,20112],{},[156,50655,50656],{},"repo",[156,50658,3786],{},[156,50660,50661],{},"icon"," des modules. Donc, j'assainis les données pour ne garder que les informations utiles :",[192,50664,50666],{"className":1380,"code":50665,"language":1383,"meta":13,"style":13},"const modules = data.modules\n  .map(({ name, type, icon, repo }) => ({ name, type, icon, repo }))\n  .filter(({ type }) => type === 'official' || type === 'community')\n",[156,50667,50668,50684,50738],{"__ignoreMap":13},[200,50669,50670,50672,50675,50677,50679,50681],{"class":202,"line":203},[200,50671,443],{"class":379},[200,50673,50674],{"class":210}," modules ",[200,50676,449],{"class":214},[200,50678,5056],{"class":210},[200,50680,215],{"class":214},[200,50682,50683],{"class":210},"modules\n",[200,50685,50686,50689,50691,50693,50695,50697,50699,50701,50703,50706,50708,50711,50713,50715,50717,50719,50721,50723,50725,50727,50729,50731,50734,50736],{"class":202,"line":14},[200,50687,50688],{"class":214},"  .",[200,50690,3541],{"class":223},[200,50692,227],{"class":210},[200,50694,3966],{"class":214},[200,50696,700],{"class":386},[200,50698,549],{"class":214},[200,50700,321],{"class":386},[200,50702,549],{"class":214},[200,50704,50705],{"class":386}," icon",[200,50707,549],{"class":214},[200,50709,50710],{"class":386}," repo",[200,50712,390],{"class":214},[200,50714,393],{"class":379},[200,50716,1094],{"class":210},[200,50718,2767],{"class":214},[200,50720,700],{"class":210},[200,50722,549],{"class":214},[200,50724,321],{"class":210},[200,50726,549],{"class":214},[200,50728,50705],{"class":210},[200,50730,549],{"class":214},[200,50732,50733],{"class":210}," repo ",[200,50735,413],{"class":214},[200,50737,584],{"class":210},[200,50739,50740,50742,50744,50746,50748,50750,50752,50754,50757,50759,50761,50764,50766,50768,50770,50772,50774,50777,50779],{"class":202,"line":255},[200,50741,50688],{"class":214},[200,50743,3773],{"class":223},[200,50745,227],{"class":210},[200,50747,3966],{"class":214},[200,50749,321],{"class":386},[200,50751,390],{"class":214},[200,50753,393],{"class":379},[200,50755,50756],{"class":210}," type ",[200,50758,6558],{"class":214},[200,50760,242],{"class":214},[200,50762,50763],{"class":245},"official",[200,50765,249],{"class":214},[200,50767,15013],{"class":214},[200,50769,50756],{"class":210},[200,50771,6558],{"class":214},[200,50773,242],{"class":214},[200,50775,50776],{"class":245},"community",[200,50778,249],{"class":214},[200,50780,416],{"class":210},[35,50782,50783,50784,50787,50788,50790],{},"Je choisis de ne pas garder le type ",[156,50785,50786],{},"third party"," car il peut s'agir d'un module de n'importe qui et je ne connais pas la qualité de ces modules. La clé ",[156,50789,50656],{}," n'était pas prévue mais je pense que c'est une bonne idée de l'avoir. Je pourrais l'utiliser pour ajouter un lien vers le dépôt GitHub du module sur une page de thème.",[35,50792,50793,50794,50797,50798,2186],{},"Maintenant que j'ai mes modules, je dois les stocker dans la base de données, et je dois également créer la table pour les stocker. Dans le fichier ",[156,50795,50796],{},"server\u002Fdatabase\u002Fschema.ts",", je crée la table ",[156,50799,2476],{},[192,50801,50803],{"className":1380,"code":50802,"language":1383,"meta":13,"style":13},"export const modules = sqliteTable('modules', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  name: text('name').notNull().unique(),\n  repo: text('repo').notNull().unique(),\n  type: text('type', { enum: ['official', 'community'] }).notNull(),\n  icon: text('icon'),\n})\n",[156,50804,50805,50830,50871,50906,50939,50995,51015],{"__ignoreMap":13},[200,50806,50807,50809,50811,50813,50815,50818,50820,50822,50824,50826,50828],{"class":202,"line":203},[200,50808,1390],{"class":206},[200,50810,14305],{"class":379},[200,50812,50674],{"class":210},[200,50814,449],{"class":214},[200,50816,50817],{"class":223}," sqliteTable",[200,50819,227],{"class":210},[200,50821,249],{"class":214},[200,50823,2476],{"class":245},[200,50825,249],{"class":214},[200,50827,549],{"class":214},[200,50829,280],{"class":214},[200,50831,50832,50834,50836,50839,50841,50843,50845,50847,50849,50851,50854,50856,50858,50861,50863,50865,50867,50869],{"class":202,"line":14},[200,50833,49638],{"class":235},[200,50835,239],{"class":214},[200,50837,50838],{"class":223}," integer",[200,50840,227],{"class":210},[200,50842,249],{"class":214},[200,50844,3295],{"class":245},[200,50846,249],{"class":214},[200,50848,2104],{"class":210},[200,50850,215],{"class":214},[200,50852,50853],{"class":223},"primaryKey",[200,50855,227],{"class":210},[200,50857,2767],{"class":214},[200,50859,50860],{"class":235}," autoIncrement",[200,50862,239],{"class":214},[200,50864,2439],{"class":2438},[200,50866,980],{"class":214},[200,50868,2104],{"class":210},[200,50870,252],{"class":214},[200,50872,50873,50875,50877,50880,50882,50884,50886,50888,50890,50892,50895,50897,50899,50902,50904],{"class":202,"line":255},[200,50874,236],{"class":235},[200,50876,239],{"class":214},[200,50878,50879],{"class":223}," text",[200,50881,227],{"class":210},[200,50883,249],{"class":214},[200,50885,20112],{"class":245},[200,50887,249],{"class":214},[200,50889,2104],{"class":210},[200,50891,215],{"class":214},[200,50893,50894],{"class":223},"notNull",[200,50896,1549],{"class":210},[200,50898,215],{"class":214},[200,50900,50901],{"class":223},"unique",[200,50903,1549],{"class":210},[200,50905,252],{"class":214},[200,50907,50908,50911,50913,50915,50917,50919,50921,50923,50925,50927,50929,50931,50933,50935,50937],{"class":202,"line":272},[200,50909,50910],{"class":235},"  repo",[200,50912,239],{"class":214},[200,50914,50879],{"class":223},[200,50916,227],{"class":210},[200,50918,249],{"class":214},[200,50920,50656],{"class":245},[200,50922,249],{"class":214},[200,50924,2104],{"class":210},[200,50926,215],{"class":214},[200,50928,50894],{"class":223},[200,50930,1549],{"class":210},[200,50932,215],{"class":214},[200,50934,50901],{"class":223},[200,50936,1549],{"class":210},[200,50938,252],{"class":214},[200,50940,50941,50944,50946,50948,50950,50952,50954,50956,50958,50960,50963,50965,50967,50969,50971,50973,50975,50977,50979,50981,50983,50985,50987,50989,50991,50993],{"class":202,"line":283},[200,50942,50943],{"class":235},"  type",[200,50945,239],{"class":214},[200,50947,50879],{"class":223},[200,50949,227],{"class":210},[200,50951,249],{"class":214},[200,50953,3786],{"class":245},[200,50955,249],{"class":214},[200,50957,549],{"class":214},[200,50959,318],{"class":214},[200,50961,50962],{"class":235}," enum",[200,50964,239],{"class":214},[200,50966,350],{"class":210},[200,50968,249],{"class":214},[200,50970,50763],{"class":245},[200,50972,249],{"class":214},[200,50974,549],{"class":214},[200,50976,242],{"class":214},[200,50978,50776],{"class":245},[200,50980,249],{"class":214},[200,50982,18849],{"class":210},[200,50984,413],{"class":214},[200,50986,2104],{"class":210},[200,50988,215],{"class":214},[200,50990,50894],{"class":223},[200,50992,1549],{"class":210},[200,50994,252],{"class":214},[200,50996,50997,50999,51001,51003,51005,51007,51009,51011,51013],{"class":202,"line":300},[200,50998,49667],{"class":235},[200,51000,239],{"class":214},[200,51002,50879],{"class":223},[200,51004,227],{"class":210},[200,51006,249],{"class":214},[200,51008,50661],{"class":245},[200,51010,249],{"class":214},[200,51012,2104],{"class":210},[200,51014,252],{"class":214},[200,51016,51017,51019],{"class":202,"line":310},[200,51018,413],{"class":214},[200,51020,416],{"class":210},[938,51022,51023],{},[35,51024,51025,51027],{},[200,51026,944],{},"\nN'oubliez pas d'exécuter la commande drizzle pour générer la migration SQL.",[35,51029,51030],{},"Dans le point de terminaison, il est maintenant possible d'insérer les modules dans la base de données :",[192,51032,51034],{"className":1380,"code":51033,"language":1383,"meta":13,"style":13},"await useDrizzle().insert(tables.modules).values(modules).execute()\n",[156,51035,51036],{"__ignoreMap":13},[200,51037,51038,51040,51042,51044,51046,51048,51051,51053,51056,51058,51060,51063,51065,51067],{"class":202,"line":203},[200,51039,207],{"class":206},[200,51041,47338],{"class":223},[200,51043,1549],{"class":210},[200,51045,215],{"class":214},[200,51047,47525],{"class":223},[200,51049,51050],{"class":210},"(tables",[200,51052,215],{"class":214},[200,51054,51055],{"class":210},"modules)",[200,51057,215],{"class":214},[200,51059,47540],{"class":223},[200,51061,51062],{"class":210},"(modules)",[200,51064,215],{"class":214},[200,51066,47590],{"class":223},[200,51068,466],{"class":210},[35,51070,51071,51072,51075,51076,51081],{},"MAIS, surprise, cela ",[6862,51073,51074],{},"ne fonctionne pas",". Je ne le savais pas, mais le ",[39,51077,51080],{"href":51078,"rel":51079},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002Fplatform\u002Flimits\u002F",[77],"Cloudflare D1 a une limite (ou plusieurs)"," sur les paramètres liés à 100. En réalité, cette limite existe parce qu'ils utilisent SQLite en arrière-plan, ce qui a cette limite pour des raisons de sécurité.",[35,51083,51084,51085,17897,51088,51090,51091,51094,51095,51098],{},"Qu'est-ce qu'un paramètre lié ? C'est un paramètre dans une requête SQL qui est remplacé par une valeur lorsque la requête est exécutée. Par exemple, dans la requête ",[156,51086,51087],{},"SELECT * FROM users WHERE id = ?",[156,51089,27725],{}," est un paramètre lié. Dans le cas de la requête d'insertion ",[156,51092,51093],{},"INSERT INTO users (name, email) VALUES (?, ?)",", les paramètres liés sont les valeurs à insérer dans la table. La limite est de 100 paramètres liés. Attention, ",[6862,51096,51097],{},"une ligne n'est pas un paramètre lié",", un paramètre lié est une valeur unique, ce qui signifie que si vous insérez une ligne avec 10 colonnes, cela comptera comme 10 paramètres liés. Il y a 42 modules avec 4 colonnes, donc 168 paramètres liés.",[35,51100,51101],{},"C'est un vrai problème car je devrai diviser l'insertion en plusieurs requêtes et un worker est limité dans le temps, donc s'il fonctionne trop longtemps, le worker pourrait échouer.",[192,51103,51105],{"className":1380,"code":51104,"language":1383,"meta":13,"style":13},"const columnsPerModule = Object.keys(modules[0]).length\nconst insertPerLoop = Math.floor(100 \u002F columnsPerModule)\nconst loops = Math.ceil(modules.length \u002F insertPerLoop)\n\nfor (let loop = 0; loop \u003C loops; loop++) {\n  const values = modules.slice(loop * insertPerLoop, (loop + 1) * insertPerLoop)\n  await useDrizzle().insert(tables.modules).values(values).onConflictDoNothing({ target: tables.modules.repo }).execute()\n}\n",[156,51106,51107,51133,51160,51189,51193,51231,51275,51340],{"__ignoreMap":13},[200,51108,51109,51111,51114,51116,51118,51120,51122,51125,51127,51129,51131],{"class":202,"line":203},[200,51110,443],{"class":379},[200,51112,51113],{"class":210}," columnsPerModule ",[200,51115,449],{"class":214},[200,51117,6460],{"class":210},[200,51119,215],{"class":214},[200,51121,27119],{"class":223},[200,51123,51124],{"class":210},"(modules[",[200,51126,3948],{"class":2060},[200,51128,32210],{"class":210},[200,51130,215],{"class":214},[200,51132,13230],{"class":210},[200,51134,51135,51137,51140,51142,51145,51147,51150,51152,51155,51157],{"class":202,"line":14},[200,51136,443],{"class":379},[200,51138,51139],{"class":210}," insertPerLoop ",[200,51141,449],{"class":214},[200,51143,51144],{"class":210}," Math",[200,51146,215],{"class":214},[200,51148,51149],{"class":223},"floor",[200,51151,227],{"class":210},[200,51153,51154],{"class":2060},"100",[200,51156,11960],{"class":214},[200,51158,51159],{"class":210}," columnsPerModule)\n",[200,51161,51162,51164,51167,51169,51171,51173,51176,51179,51181,51184,51186],{"class":202,"line":255},[200,51163,443],{"class":379},[200,51165,51166],{"class":210}," loops ",[200,51168,449],{"class":214},[200,51170,51144],{"class":210},[200,51172,215],{"class":214},[200,51174,51175],{"class":223},"ceil",[200,51177,51178],{"class":210},"(modules",[200,51180,215],{"class":214},[200,51182,51183],{"class":210},"length ",[200,51185,2898],{"class":214},[200,51187,51188],{"class":210}," insertPerLoop)\n",[200,51190,51191],{"class":202,"line":272},[200,51192,528],{"emptyLinePlaceholder":20},[200,51194,51195,51197,51199,51201,51204,51206,51209,51212,51214,51216,51219,51221,51224,51227,51229],{"class":202,"line":283},[200,51196,1089],{"class":206},[200,51198,1094],{"class":210},[200,51200,12337],{"class":379},[200,51202,51203],{"class":210}," loop ",[200,51205,449],{"class":214},[200,51207,51208],{"class":2060}," 0",[200,51210,51211],{"class":214},";",[200,51213,51203],{"class":210},[200,51215,599],{"class":214},[200,51217,51218],{"class":210}," loops",[200,51220,51211],{"class":214},[200,51222,51223],{"class":210}," loop",[200,51225,51226],{"class":214},"++",[200,51228,5282],{"class":210},[200,51230,230],{"class":214},[200,51232,51233,51235,51238,51240,51243,51245,51247,51249,51252,51254,51257,51259,51261,51263,51265,51267,51269,51271,51273],{"class":202,"line":300},[200,51234,3738],{"class":379},[200,51236,51237],{"class":210}," values",[200,51239,509],{"class":214},[200,51241,51242],{"class":210}," modules",[200,51244,215],{"class":214},[200,51246,3943],{"class":223},[200,51248,227],{"class":235},[200,51250,51251],{"class":210},"loop",[200,51253,29668],{"class":214},[200,51255,51256],{"class":210}," insertPerLoop",[200,51258,549],{"class":214},[200,51260,1094],{"class":235},[200,51262,51251],{"class":210},[200,51264,13215],{"class":214},[200,51266,3953],{"class":2060},[200,51268,5282],{"class":235},[200,51270,29200],{"class":214},[200,51272,51256],{"class":210},[200,51274,416],{"class":235},[200,51276,51277,51279,51281,51283,51285,51287,51289,51291,51293,51295,51297,51299,51301,51303,51305,51307,51309,51312,51314,51316,51318,51320,51322,51324,51326,51328,51330,51332,51334,51336,51338],{"class":202,"line":310},[200,51278,6177],{"class":206},[200,51280,47338],{"class":223},[200,51282,1549],{"class":235},[200,51284,215],{"class":214},[200,51286,47525],{"class":223},[200,51288,227],{"class":235},[200,51290,47418],{"class":210},[200,51292,215],{"class":214},[200,51294,2476],{"class":210},[200,51296,2104],{"class":235},[200,51298,215],{"class":214},[200,51300,47540],{"class":223},[200,51302,227],{"class":235},[200,51304,47540],{"class":210},[200,51306,2104],{"class":235},[200,51308,215],{"class":214},[200,51310,51311],{"class":223},"onConflictDoNothing",[200,51313,227],{"class":235},[200,51315,2767],{"class":214},[200,51317,43458],{"class":235},[200,51319,239],{"class":214},[200,51321,47357],{"class":210},[200,51323,215],{"class":214},[200,51325,2476],{"class":210},[200,51327,215],{"class":214},[200,51329,50656],{"class":210},[200,51331,980],{"class":214},[200,51333,2104],{"class":235},[200,51335,215],{"class":214},[200,51337,47590],{"class":223},[200,51339,466],{"class":235},[200,51341,51342],{"class":202,"line":336},[200,51343,1129],{"class":214},[35,51345,51346,51347,51349],{},"Pour avoir une insertion dynamique en cas de changement du nombre de colonnes, je calcule le nombre maximum de lignes que je peux insérer dans une seule requête. Ensuite, je boucle sur les modules et insère le nombre maximum de lignes dans chaque requête. J'utilise également la méthode ",[156,51348,51311],{}," pour éviter d'insérer le même module deux fois si je fais fonctionner le point de terminaison plusieurs fois.",[35,51351,51352],{},[1216,51353,51354],{},"J'ai essayé cela dans un environnement de production, et tout fonctionne bien.",[35,51356,51357],{},"Je choisis de retourner un code de statut 204 lorsque les modules sont récupérés. Si tout est correct, je peux rafraîchir la liste des modules dans l'interface utilisateur pour voir les nouveaux modules.",[38437,51359,51361],{"id":51360},"servir-les-modules","Servir les Modules",[35,51363,51364,51365,51368],{},"Cette partie est exactement la même que le point de terminaison d'index des catégories et des utilisateurs. Je crée un point de terminaison ",[156,51366,51367],{},"server\u002Fapi\u002Fmodules\u002Findex.get.ts"," pour obtenir les modules de la base de données :",[192,51370,51372],{"className":1380,"code":51371,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async () => {\n  const modules = await useDrizzle().select({\n    id: tables.modules.id,\n    name: tables.modules.name,\n    type: tables.modules.type,\n    icon: tables.modules.icon,\n  }).from(tables.modules)\n\n  return modules\n})\n",[156,51373,51374,51392,51414,51432,51450,51468,51487,51507,51511,51518],{"__ignoreMap":13},[200,51375,51376,51378,51380,51382,51384,51386,51388,51390],{"class":202,"line":203},[200,51377,1390],{"class":206},[200,51379,1393],{"class":206},[200,51381,38723],{"class":223},[200,51383,227],{"class":210},[200,51385,3726],{"class":379},[200,51387,3729],{"class":214},[200,51389,393],{"class":379},[200,51391,280],{"class":214},[200,51393,51394,51396,51398,51400,51402,51404,51406,51408,51410,51412],{"class":202,"line":14},[200,51395,3738],{"class":379},[200,51397,51242],{"class":210},[200,51399,509],{"class":214},[200,51401,452],{"class":206},[200,51403,47338],{"class":223},[200,51405,1549],{"class":235},[200,51407,215],{"class":214},[200,51409,769],{"class":223},[200,51411,227],{"class":235},[200,51413,230],{"class":214},[200,51415,51416,51418,51420,51422,51424,51426,51428,51430],{"class":202,"line":255},[200,51417,15861],{"class":235},[200,51419,239],{"class":214},[200,51421,47357],{"class":210},[200,51423,215],{"class":214},[200,51425,2476],{"class":210},[200,51427,215],{"class":214},[200,51429,3295],{"class":210},[200,51431,252],{"class":214},[200,51433,51434,51436,51438,51440,51442,51444,51446,51448],{"class":202,"line":272},[200,51435,11812],{"class":235},[200,51437,239],{"class":214},[200,51439,47357],{"class":210},[200,51441,215],{"class":214},[200,51443,2476],{"class":210},[200,51445,215],{"class":214},[200,51447,20112],{"class":210},[200,51449,252],{"class":214},[200,51451,51452,51454,51456,51458,51460,51462,51464,51466],{"class":202,"line":283},[200,51453,286],{"class":235},[200,51455,239],{"class":214},[200,51457,47357],{"class":210},[200,51459,215],{"class":214},[200,51461,2476],{"class":210},[200,51463,215],{"class":214},[200,51465,3786],{"class":210},[200,51467,252],{"class":214},[200,51469,51470,51473,51475,51477,51479,51481,51483,51485],{"class":202,"line":300},[200,51471,51472],{"class":235},"    icon",[200,51474,239],{"class":214},[200,51476,47357],{"class":210},[200,51478,215],{"class":214},[200,51480,2476],{"class":210},[200,51482,215],{"class":214},[200,51484,50661],{"class":210},[200,51486,252],{"class":214},[200,51488,51489,51491,51493,51495,51497,51499,51501,51503,51505],{"class":202,"line":310},[200,51490,5095],{"class":214},[200,51492,2104],{"class":235},[200,51494,215],{"class":214},[200,51496,1489],{"class":223},[200,51498,227],{"class":235},[200,51500,47418],{"class":210},[200,51502,215],{"class":214},[200,51504,2476],{"class":210},[200,51506,416],{"class":235},[200,51508,51509],{"class":202,"line":336},[200,51510,528],{"emptyLinePlaceholder":20},[200,51512,51513,51515],{"class":202,"line":342},[200,51514,3758],{"class":206},[200,51516,51517],{"class":210}," modules\n",[200,51519,51520,51522],{"class":202,"line":365},[200,51521,413],{"class":214},[200,51523,416],{"class":210},[35,51525,51526],{},"Rien de nouveau ici, je sélectionne simplement les modules depuis la base de données et les retourne.",[1929,51528,24146],{"id":51529},"conclusion-1",[35,51531,51532],{},"Tout cela nous donne ce résultat :",[35,51534,51535],{},[1707,51536],{"alt":50519,"src":51537},"\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fmodules-page.png",[35,51539,51540],{},"Ce stream était différent des autres. D'abord, j'ai maintenant un nouveau micro et le son est bien meilleur qu'avant !",[35,51542,51543],{},"Deuxièmement, j'ai rencontré un problème avec la limite D1 (ce qui pourrait entraîner un besoin de repenser le projet, espérons que ce ne sera pas le cas) sur les paramètres liés. C'est une bonne leçon pour moi, et j'espère que cela sera utile pour vous.",[51,51545,51547],{"id":51546},"factorisation-de-la-gestion-des-utilisateurs","Factorisation de la Gestion des Utilisateurs",[35,51549,51550],{},[1216,51551,51552],{},"Cette partie a été réalisée hors stream car elle n'était pas intéressante à voir et je n'étais pas sûr de la direction que je voulais prendre.",[35,51554,51555],{},"En écrivant une partie de cet article, je me demande si stocker les données des utilisateurs était une bonne idée. Ne pourrait-il pas être mieux ? Cette question vient de la façon dont je gère le point de terminaison d'authentification.",[35,51557,51558,51559,51564,51565],{},"En fait, lorsqu'un utilisateur se connecte, j'écris toujours les données de l'utilisateur dans la base de données pour être sûr qu'elles sont à jour. La principale préoccupation est qu'une écriture est beaucoup plus coûteuse qu'une lecture. Pour trouver des idées, j'ai lu le ",[39,51560,51563],{"href":51561,"rel":51562},"https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-todos-edge",[77],"projet de liste de tâches Nuxt d'Atinux",". Dans ce projet, il ne stocke pas les données des utilisateurs. Lorsque l'utilisateur se connecte, il ne stocke que l'ID de l'utilisateur dans le cookie. Lorsque l'utilisateur crée une tâche, il lui suffit de lire l'ID de l'utilisateur depuis le cookie pour savoir qui est l'utilisateur. C'est très intelligent et simple. Je vais faire de même. ",[1216,51566,51567],{},"Premièrement à travers.",[35,51569,51570],{},"Analysons si cette méthode est possible pour le projet Orion. Pour savoir si c'est possible, je dois répondre à quelques questions :",[68,51572,51573,51576,51579],{},[71,51574,51575],{},"Puis-je bannir ou promouvoir un utilisateur sans stocker les données de l'utilisateur ?",[71,51577,51578],{},"Un utilisateur peut-il créer des données sans stocker les données de l'utilisateur ?",[71,51580,51581],{},"Puis-je afficher les données des utilisateurs sans stocker les données de l'utilisateur ?",[35,51583,51584],{},"Pour la première question, c'est oui. Je peux imaginer un système où vous entrez le login d'un utilisateur, et lorsque l'utilisateur se connecte, je rechercherai dans la base de données si l'entrée existe et obtiendrai les données pertinentes. Ce n'est pas la meilleure méthode mais c'est possible.",[35,51586,51587,51588],{},"Pour la deuxième question, c'est oui. Puisque les données sont dans le cookie, je peux facilement savoir qui est l'utilisateur et créer des données en utilisant cette information. ",[1216,51589,51590],{},"Et c'est ainsi que ça fonctionne en fait. Je ne récupère pas l'utilisateur pour chaque requête.",[35,51592,51593,51594,51597,51598,51601,51602,51604,51605],{},"Pour la troisième question, c'est non. Imaginez que je veuille afficher le modèle créé par l'utilisateur 'X'. Dans notre schéma SQL, nous avons une colonne ",[156,51595,51596],{},"user_id"," dans la table ",[156,51599,51600],{},"templates",". Si je ne stocke pas les données de l'utilisateur, je ne pourrai pas savoir qui est l'utilisateur 'X', associé à ",[156,51603,51596],{},". Je devrai stocker les données de l'utilisateur dans la base de données. ",[1216,51606,51607],{},"Cela pourrait être possible avec une base de données non relationnelle mais ce n'est pas le cas ici. Nous pourrions aussi imaginer un système où l'utilisateur est sauvegardé lorsqu'il crée un modèle. C'est possible mais cela ajoutera beaucoup de complexité. Au final, cette réponse et celle à la question une compliquent le système pour aucun réel gain.",[35,51609,51610],{},"Maintenant que nous savons que nous devons stocker les utilisateurs dans la base de données, que pouvons-nous améliorer ?",[1593,51612,51613,51616,51619,51627,51637],{},[71,51614,51615],{},"Sauvegarder les données des utilisateurs seulement si nécessaire. Par exemple, je pourrais d'abord rechercher l'utilisateur avant de sauvegarder ses données lorsque l'utilisateur se connecte.",[71,51617,51618],{},"Sauvegarder l'email de l'utilisateur. Cela sera utile pour le système de feedback.",[71,51620,51621,51622,5569,51624,215],{},"Sauvegarder le nom de l'utilisateur et renommer la colonne ",[156,51623,49068],{},[156,51625,51626],{},"login",[71,51628,51629,51630,51633,51634,215],{},"Ajouter une colonne ",[156,51631,51632],{},"created_at"," et une colonne ",[156,51635,51636],{},"updated_at",[71,51638,51639],{},"Mettre à jour les données stockées dans le cookie.",[35,51641,51642],{},"Avec tous ces points, je mets également à jour l'interface utilisateur en conséquence. Il n'y a rien de difficile ici ; il s'agit principalement de renommer et d'ajouter des champs dans le schéma.",[192,51644,51646],{"className":1380,"code":51645,"language":1383,"meta":13,"style":13},"export const users = sqliteTable('users', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  githubId: integer('github_id').notNull().unique(),\n  email: text('email').notNull().unique(),\n  login: text('login').notNull().unique(),\n  name: text('name'),\n  avatarUrl: text('avatar_url').notNull(),\n  roleType: text('role_type', { enum: ['admin', 'creator'] }).default('creator'),\n  createdAt: text('created_at').notNull().$defaultFn(() => sql`(current_timestamp)`),\n  updatedAt: text('updated_at').notNull().$defaultFn(() => sql`(current_timestamp)`).$onUpdateFn(() => sql`(current_timestamp)`),\n})\n",[156,51647,51648,51672,51710,51744,51778,51811,51831,51859,51925,51975,52043],{"__ignoreMap":13},[200,51649,51650,51652,51654,51656,51658,51660,51662,51664,51666,51668,51670],{"class":202,"line":203},[200,51651,1390],{"class":206},[200,51653,14305],{"class":379},[200,51655,49329],{"class":210},[200,51657,449],{"class":214},[200,51659,50817],{"class":223},[200,51661,227],{"class":210},[200,51663,249],{"class":214},[200,51665,47155],{"class":245},[200,51667,249],{"class":214},[200,51669,549],{"class":214},[200,51671,280],{"class":214},[200,51673,51674,51676,51678,51680,51682,51684,51686,51688,51690,51692,51694,51696,51698,51700,51702,51704,51706,51708],{"class":202,"line":14},[200,51675,49638],{"class":235},[200,51677,239],{"class":214},[200,51679,50838],{"class":223},[200,51681,227],{"class":210},[200,51683,249],{"class":214},[200,51685,3295],{"class":245},[200,51687,249],{"class":214},[200,51689,2104],{"class":210},[200,51691,215],{"class":214},[200,51693,50853],{"class":223},[200,51695,227],{"class":210},[200,51697,2767],{"class":214},[200,51699,50860],{"class":235},[200,51701,239],{"class":214},[200,51703,2439],{"class":2438},[200,51705,980],{"class":214},[200,51707,2104],{"class":210},[200,51709,252],{"class":214},[200,51711,51712,51715,51717,51719,51721,51723,51726,51728,51730,51732,51734,51736,51738,51740,51742],{"class":202,"line":255},[200,51713,51714],{"class":235},"  githubId",[200,51716,239],{"class":214},[200,51718,50838],{"class":223},[200,51720,227],{"class":210},[200,51722,249],{"class":214},[200,51724,51725],{"class":245},"github_id",[200,51727,249],{"class":214},[200,51729,2104],{"class":210},[200,51731,215],{"class":214},[200,51733,50894],{"class":223},[200,51735,1549],{"class":210},[200,51737,215],{"class":214},[200,51739,50901],{"class":223},[200,51741,1549],{"class":210},[200,51743,252],{"class":214},[200,51745,51746,51749,51751,51753,51755,51757,51760,51762,51764,51766,51768,51770,51772,51774,51776],{"class":202,"line":272},[200,51747,51748],{"class":235},"  email",[200,51750,239],{"class":214},[200,51752,50879],{"class":223},[200,51754,227],{"class":210},[200,51756,249],{"class":214},[200,51758,51759],{"class":245},"email",[200,51761,249],{"class":214},[200,51763,2104],{"class":210},[200,51765,215],{"class":214},[200,51767,50894],{"class":223},[200,51769,1549],{"class":210},[200,51771,215],{"class":214},[200,51773,50901],{"class":223},[200,51775,1549],{"class":210},[200,51777,252],{"class":214},[200,51779,51780,51783,51785,51787,51789,51791,51793,51795,51797,51799,51801,51803,51805,51807,51809],{"class":202,"line":283},[200,51781,51782],{"class":235},"  login",[200,51784,239],{"class":214},[200,51786,50879],{"class":223},[200,51788,227],{"class":210},[200,51790,249],{"class":214},[200,51792,51626],{"class":245},[200,51794,249],{"class":214},[200,51796,2104],{"class":210},[200,51798,215],{"class":214},[200,51800,50894],{"class":223},[200,51802,1549],{"class":210},[200,51804,215],{"class":214},[200,51806,50901],{"class":223},[200,51808,1549],{"class":210},[200,51810,252],{"class":214},[200,51812,51813,51815,51817,51819,51821,51823,51825,51827,51829],{"class":202,"line":300},[200,51814,236],{"class":235},[200,51816,239],{"class":214},[200,51818,50879],{"class":223},[200,51820,227],{"class":210},[200,51822,249],{"class":214},[200,51824,20112],{"class":245},[200,51826,249],{"class":214},[200,51828,2104],{"class":210},[200,51830,252],{"class":214},[200,51832,51833,51836,51838,51840,51842,51844,51847,51849,51851,51853,51855,51857],{"class":202,"line":310},[200,51834,51835],{"class":235},"  avatarUrl",[200,51837,239],{"class":214},[200,51839,50879],{"class":223},[200,51841,227],{"class":210},[200,51843,249],{"class":214},[200,51845,51846],{"class":245},"avatar_url",[200,51848,249],{"class":214},[200,51850,2104],{"class":210},[200,51852,215],{"class":214},[200,51854,50894],{"class":223},[200,51856,1549],{"class":210},[200,51858,252],{"class":214},[200,51860,51861,51864,51866,51868,51870,51872,51875,51877,51879,51881,51883,51885,51887,51889,51891,51893,51895,51897,51900,51902,51904,51906,51908,51910,51913,51915,51917,51919,51921,51923],{"class":202,"line":336},[200,51862,51863],{"class":235},"  roleType",[200,51865,239],{"class":214},[200,51867,50879],{"class":223},[200,51869,227],{"class":210},[200,51871,249],{"class":214},[200,51873,51874],{"class":245},"role_type",[200,51876,249],{"class":214},[200,51878,549],{"class":214},[200,51880,318],{"class":214},[200,51882,50962],{"class":235},[200,51884,239],{"class":214},[200,51886,350],{"class":210},[200,51888,249],{"class":214},[200,51890,39189],{"class":245},[200,51892,249],{"class":214},[200,51894,549],{"class":214},[200,51896,242],{"class":214},[200,51898,51899],{"class":245},"creator",[200,51901,249],{"class":214},[200,51903,18849],{"class":210},[200,51905,413],{"class":214},[200,51907,2104],{"class":210},[200,51909,215],{"class":214},[200,51911,51912],{"class":223},"default",[200,51914,227],{"class":210},[200,51916,249],{"class":214},[200,51918,51899],{"class":245},[200,51920,249],{"class":214},[200,51922,2104],{"class":210},[200,51924,252],{"class":214},[200,51926,51927,51930,51932,51934,51936,51938,51940,51942,51944,51946,51948,51950,51952,51955,51957,51959,51961,51964,51966,51969,51971,51973],{"class":202,"line":342},[200,51928,51929],{"class":235},"  createdAt",[200,51931,239],{"class":214},[200,51933,50879],{"class":223},[200,51935,227],{"class":210},[200,51937,249],{"class":214},[200,51939,51632],{"class":245},[200,51941,249],{"class":214},[200,51943,2104],{"class":210},[200,51945,215],{"class":214},[200,51947,50894],{"class":223},[200,51949,1549],{"class":210},[200,51951,215],{"class":214},[200,51953,51954],{"class":223},"$defaultFn",[200,51956,227],{"class":210},[200,51958,1549],{"class":214},[200,51960,393],{"class":379},[200,51962,51963],{"class":223}," sql",[200,51965,3333],{"class":214},[200,51967,51968],{"class":245},"(current_timestamp)",[200,51970,3333],{"class":214},[200,51972,2104],{"class":210},[200,51974,252],{"class":214},[200,51976,51977,51980,51982,51984,51986,51988,51990,51992,51994,51996,51998,52000,52002,52004,52006,52008,52010,52012,52014,52016,52018,52020,52022,52025,52027,52029,52031,52033,52035,52037,52039,52041],{"class":202,"line":365},[200,51978,51979],{"class":235},"  updatedAt",[200,51981,239],{"class":214},[200,51983,50879],{"class":223},[200,51985,227],{"class":210},[200,51987,249],{"class":214},[200,51989,51636],{"class":245},[200,51991,249],{"class":214},[200,51993,2104],{"class":210},[200,51995,215],{"class":214},[200,51997,50894],{"class":223},[200,51999,1549],{"class":210},[200,52001,215],{"class":214},[200,52003,51954],{"class":223},[200,52005,227],{"class":210},[200,52007,1549],{"class":214},[200,52009,393],{"class":379},[200,52011,51963],{"class":223},[200,52013,3333],{"class":214},[200,52015,51968],{"class":245},[200,52017,3333],{"class":214},[200,52019,2104],{"class":210},[200,52021,215],{"class":214},[200,52023,52024],{"class":223},"$onUpdateFn",[200,52026,227],{"class":210},[200,52028,1549],{"class":214},[200,52030,393],{"class":379},[200,52032,51963],{"class":223},[200,52034,3333],{"class":214},[200,52036,51968],{"class":245},[200,52038,3333],{"class":214},[200,52040,2104],{"class":210},[200,52042,252],{"class":214},[200,52044,52045,52047],{"class":202,"line":371},[200,52046,413],{"class":214},[200,52048,416],{"class":210},[35,52050,52051,52052,1655,52054,52056,52057,52059,52060,52062],{},"Les fonctions ",[156,52053,51954],{},[156,52055,52024],{}," sont utilisées pour définir la valeur par défaut de la colonne. Dans le cas de ",[156,52058,37035],{},", la valeur par défaut est l'horodatage actuel. Dans le cas de ",[156,52061,37060],{},", la valeur par défaut est l'horodatage actuel, et la valeur est mise à jour lorsque la ligne est mise à jour. C'est entièrement automatique et géré par Drizzle.",[35,52064,52065,52066,52069],{},"Je mets également à jour le ",[156,52067,52068],{},"server\u002Froutes\u002Fauth\u002Fgithub.ts"," pour stocker les données de l'utilisateur seulement si nécessaire :",[192,52071,52073],{"className":1380,"code":52072,"language":1383,"meta":13,"style":13},"export default oauth.githubEventHandler({\n  config: {\n    emailRequired: true,\n  },\n  async onSuccess(event, result) {\n    const { user: ghUser } = result\n\n    const githubId = ghUser.id\n\n    let user: User | undefined\n\n    user = await useDrizzle().select().from(tables.users).where(eq(tables.users.githubId, githubId)).get()\n\n    \u002F**\n     * Si l'utilisateur n'est pas dans la base de données ou si ses données ont changé, mettre à jour les données de l'utilisateur.\n     * Une écriture est plus coûteuse qu'une lecture, donc nous écrivons seulement si nécessaire.\n     *\u002F\n    if (!user || userDataChanged(user, ghUser)) {\n      user = await useDrizzle().insert(tables.users).values({\n        githubId,\n        login: ghUser.login,\n        email: ghUser.email,\n        name: ghUser.name,\n        avatarUrl: ghUser.avatar_url,\n      }).onConflictDoUpdate({\n        target: tables.users.githubId,\n        set: {\n          login: ghUser.login,\n          email: ghUser.email,\n          name: ghUser.name,\n          avatarUrl: ghUser.avatar_url,\n        },\n      }).returning().get()\n    }\n\n    \u002F**\n     * Définir seulement les données nécessaires dans la session.\n     *\u002F\n    await setUserSession(event, {\n      user: {\n        id: user.id!,\n        login: ghUser.login,\n        email: ghUser.email,\n        name: ghUser.name,\n        avatarUrl: ghUser.avatar_url,\n        roleType: user.roleType || 'creator',\n      },\n    })\n\n    return sendRedirect(event, '\u002Fportal')\n  },\n})\n",[156,52074,52075,52091,52099,52109,52113,52131,52150,52154,52170,52174,52190,52194,52259,52263,52267,52272,52277,52281,52308,52342,52348,52363,52378,52392,52407,52422,52441,52450,52465,52480,52495,52510,52514,52533,52537,52541,52545,52550,52554,52568,52576,52591,52605,52619,52633,52647,52670,52674,52680,52684,52705,52709],{"__ignoreMap":13},[200,52076,52077,52079,52081,52083,52085,52087,52089],{"class":202,"line":203},[200,52078,1390],{"class":206},[200,52080,1393],{"class":206},[200,52082,45956],{"class":210},[200,52084,215],{"class":214},[200,52086,45961],{"class":223},[200,52088,227],{"class":210},[200,52090,230],{"class":214},[200,52092,52093,52095,52097],{"class":202,"line":14},[200,52094,45970],{"class":235},[200,52096,239],{"class":214},[200,52098,280],{"class":214},[200,52100,52101,52103,52105,52107],{"class":202,"line":255},[200,52102,45979],{"class":235},[200,52104,239],{"class":214},[200,52106,2439],{"class":2438},[200,52108,252],{"class":214},[200,52110,52111],{"class":202,"line":272},[200,52112,368],{"class":214},[200,52114,52115,52117,52119,52121,52123,52125,52127,52129],{"class":202,"line":283},[200,52116,3234],{"class":379},[200,52118,45996],{"class":235},[200,52120,227],{"class":214},[200,52122,25426],{"class":386},[200,52124,549],{"class":214},[200,52126,3561],{"class":386},[200,52128,2104],{"class":214},[200,52130,280],{"class":214},[200,52132,52133,52135,52137,52139,52141,52144,52146,52148],{"class":202,"line":300},[200,52134,2014],{"class":379},[200,52136,318],{"class":214},[200,52138,34533],{"class":235},[200,52140,239],{"class":214},[200,52142,52143],{"class":210}," ghUser",[200,52145,980],{"class":214},[200,52147,509],{"class":214},[200,52149,4215],{"class":210},[200,52151,52152],{"class":202,"line":310},[200,52153,528],{"emptyLinePlaceholder":20},[200,52155,52156,52158,52161,52163,52165,52167],{"class":202,"line":336},[200,52157,2014],{"class":379},[200,52159,52160],{"class":210}," githubId",[200,52162,509],{"class":214},[200,52164,52143],{"class":210},[200,52166,215],{"class":214},[200,52168,52169],{"class":210},"id\n",[200,52171,52172],{"class":202,"line":342},[200,52173,528],{"emptyLinePlaceholder":20},[200,52175,52176,52179,52181,52183,52185,52187],{"class":202,"line":365},[200,52177,52178],{"class":379},"    let",[200,52180,34533],{"class":210},[200,52182,239],{"class":214},[200,52184,34635],{"class":1364},[200,52186,39992],{"class":214},[200,52188,52189],{"class":1364}," undefined\n",[200,52191,52192],{"class":202,"line":371},[200,52193,528],{"emptyLinePlaceholder":20},[200,52195,52196,52199,52201,52203,52205,52207,52209,52211,52213,52215,52217,52219,52221,52223,52225,52227,52229,52231,52233,52235,52237,52239,52241,52243,52245,52247,52249,52251,52253,52255,52257],{"class":202,"line":398},[200,52197,52198],{"class":210},"    user",[200,52200,509],{"class":214},[200,52202,452],{"class":206},[200,52204,47338],{"class":223},[200,52206,1549],{"class":235},[200,52208,215],{"class":214},[200,52210,769],{"class":223},[200,52212,1549],{"class":235},[200,52214,215],{"class":214},[200,52216,1489],{"class":223},[200,52218,227],{"class":235},[200,52220,47418],{"class":210},[200,52222,215],{"class":214},[200,52224,47155],{"class":210},[200,52226,2104],{"class":235},[200,52228,215],{"class":214},[200,52230,48864],{"class":223},[200,52232,227],{"class":235},[200,52234,48869],{"class":223},[200,52236,227],{"class":235},[200,52238,47418],{"class":210},[200,52240,215],{"class":214},[200,52242,47155],{"class":210},[200,52244,215],{"class":214},[200,52246,46052],{"class":210},[200,52248,549],{"class":214},[200,52250,52160],{"class":210},[200,52252,6131],{"class":235},[200,52254,215],{"class":214},[200,52256,42718],{"class":223},[200,52258,466],{"class":235},[200,52260,52261],{"class":202,"line":405},[200,52262,528],{"emptyLinePlaceholder":20},[200,52264,52265],{"class":202,"line":410},[200,52266,19641],{"class":401},[200,52268,52269],{"class":202,"line":860},[200,52270,52271],{"class":401},"     * Si l'utilisateur n'est pas dans la base de données ou si ses données ont changé, mettre à jour les données de l'utilisateur.\n",[200,52273,52274],{"class":202,"line":889},[200,52275,52276],{"class":401},"     * Une écriture est plus coûteuse qu'une lecture, donc nous écrivons seulement si nécessaire.\n",[200,52278,52279],{"class":202,"line":899},[200,52280,19651],{"class":401},[200,52282,52283,52285,52287,52289,52291,52293,52296,52298,52300,52302,52304,52306],{"class":202,"line":904},[200,52284,5267],{"class":206},[200,52286,1094],{"class":235},[200,52288,11852],{"class":214},[200,52290,26847],{"class":210},[200,52292,15013],{"class":214},[200,52294,52295],{"class":223}," userDataChanged",[200,52297,227],{"class":235},[200,52299,26847],{"class":210},[200,52301,549],{"class":214},[200,52303,52143],{"class":210},[200,52305,6478],{"class":235},[200,52307,230],{"class":214},[200,52309,52310,52312,52314,52316,52318,52320,52322,52324,52326,52328,52330,52332,52334,52336,52338,52340],{"class":202,"line":930},[200,52311,46034],{"class":210},[200,52313,509],{"class":214},[200,52315,452],{"class":206},[200,52317,47338],{"class":223},[200,52319,1549],{"class":235},[200,52321,215],{"class":214},[200,52323,47525],{"class":223},[200,52325,227],{"class":235},[200,52327,47418],{"class":210},[200,52329,215],{"class":214},[200,52331,47155],{"class":210},[200,52333,2104],{"class":235},[200,52335,215],{"class":214},[200,52337,47540],{"class":223},[200,52339,227],{"class":235},[200,52341,230],{"class":214},[200,52343,52344,52346],{"class":202,"line":3655},[200,52345,46043],{"class":210},[200,52347,252],{"class":214},[200,52349,52350,52353,52355,52357,52359,52361],{"class":202,"line":3660},[200,52351,52352],{"class":235},"        login",[200,52354,239],{"class":214},[200,52356,52143],{"class":210},[200,52358,215],{"class":214},[200,52360,51626],{"class":210},[200,52362,252],{"class":214},[200,52364,52365,52368,52370,52372,52374,52376],{"class":202,"line":4035},[200,52366,52367],{"class":235},"        email",[200,52369,239],{"class":214},[200,52371,52143],{"class":210},[200,52373,215],{"class":214},[200,52375,51759],{"class":210},[200,52377,252],{"class":214},[200,52379,52380,52382,52384,52386,52388,52390],{"class":202,"line":4490},[200,52381,11737],{"class":235},[200,52383,239],{"class":214},[200,52385,52143],{"class":210},[200,52387,215],{"class":214},[200,52389,20112],{"class":210},[200,52391,252],{"class":214},[200,52393,52394,52397,52399,52401,52403,52405],{"class":202,"line":4496},[200,52395,52396],{"class":235},"        avatarUrl",[200,52398,239],{"class":214},[200,52400,52143],{"class":210},[200,52402,215],{"class":214},[200,52404,51846],{"class":210},[200,52406,252],{"class":214},[200,52408,52409,52411,52413,52415,52418,52420],{"class":202,"line":4502},[200,52410,2647],{"class":214},[200,52412,2104],{"class":235},[200,52414,215],{"class":214},[200,52416,52417],{"class":223},"onConflictDoUpdate",[200,52419,227],{"class":235},[200,52421,230],{"class":214},[200,52423,52424,52427,52429,52431,52433,52435,52437,52439],{"class":202,"line":4508},[200,52425,52426],{"class":235},"        target",[200,52428,239],{"class":214},[200,52430,47357],{"class":210},[200,52432,215],{"class":214},[200,52434,47155],{"class":210},[200,52436,215],{"class":214},[200,52438,46052],{"class":210},[200,52440,252],{"class":214},[200,52442,52443,52446,52448],{"class":202,"line":5469},[200,52444,52445],{"class":235},"        set",[200,52447,239],{"class":214},[200,52449,280],{"class":214},[200,52451,52452,52455,52457,52459,52461,52463],{"class":202,"line":5474},[200,52453,52454],{"class":235},"          login",[200,52456,239],{"class":214},[200,52458,52143],{"class":210},[200,52460,215],{"class":214},[200,52462,51626],{"class":210},[200,52464,252],{"class":214},[200,52466,52467,52470,52472,52474,52476,52478],{"class":202,"line":13237},[200,52468,52469],{"class":235},"          email",[200,52471,239],{"class":214},[200,52473,52143],{"class":210},[200,52475,215],{"class":214},[200,52477,51759],{"class":210},[200,52479,252],{"class":214},[200,52481,52482,52485,52487,52489,52491,52493],{"class":202,"line":13263},[200,52483,52484],{"class":235},"          name",[200,52486,239],{"class":214},[200,52488,52143],{"class":210},[200,52490,215],{"class":214},[200,52492,20112],{"class":210},[200,52494,252],{"class":214},[200,52496,52497,52500,52502,52504,52506,52508],{"class":202,"line":13279},[200,52498,52499],{"class":235},"          avatarUrl",[200,52501,239],{"class":214},[200,52503,52143],{"class":210},[200,52505,215],{"class":214},[200,52507,51846],{"class":210},[200,52509,252],{"class":214},[200,52511,52512],{"class":202,"line":13323},[200,52513,4965],{"class":214},[200,52515,52516,52518,52520,52522,52525,52527,52529,52531],{"class":202,"line":13329},[200,52517,2647],{"class":214},[200,52519,2104],{"class":235},[200,52521,215],{"class":214},[200,52523,52524],{"class":223},"returning",[200,52526,1549],{"class":235},[200,52528,215],{"class":214},[200,52530,42718],{"class":223},[200,52532,466],{"class":235},[200,52534,52535],{"class":202,"line":13334},[200,52536,4977],{"class":214},[200,52538,52539],{"class":202,"line":13360},[200,52540,528],{"emptyLinePlaceholder":20},[200,52542,52543],{"class":202,"line":13365},[200,52544,19641],{"class":401},[200,52546,52547],{"class":202,"line":13385},[200,52548,52549],{"class":401},"     * Définir seulement les données nécessaires dans la session.\n",[200,52551,52552],{"class":202,"line":13405},[200,52553,19651],{"class":401},[200,52555,52556,52558,52560,52562,52564,52566],{"class":202,"line":13410},[200,52557,19231],{"class":206},[200,52559,46021],{"class":223},[200,52561,227],{"class":235},[200,52563,25426],{"class":210},[200,52565,549],{"class":214},[200,52567,280],{"class":214},[200,52569,52570,52572,52574],{"class":202,"line":13418},[200,52571,46034],{"class":235},[200,52573,239],{"class":214},[200,52575,280],{"class":214},[200,52577,52578,52580,52582,52584,52586,52588],{"class":202,"line":13436},[200,52579,12065],{"class":235},[200,52581,239],{"class":214},[200,52583,34533],{"class":210},[200,52585,215],{"class":214},[200,52587,3295],{"class":210},[200,52589,52590],{"class":214},"!,\n",[200,52592,52593,52595,52597,52599,52601,52603],{"class":202,"line":13466},[200,52594,52352],{"class":235},[200,52596,239],{"class":214},[200,52598,52143],{"class":210},[200,52600,215],{"class":214},[200,52602,51626],{"class":210},[200,52604,252],{"class":214},[200,52606,52607,52609,52611,52613,52615,52617],{"class":202,"line":13472},[200,52608,52367],{"class":235},[200,52610,239],{"class":214},[200,52612,52143],{"class":210},[200,52614,215],{"class":214},[200,52616,51759],{"class":210},[200,52618,252],{"class":214},[200,52620,52621,52623,52625,52627,52629,52631],{"class":202,"line":13477},[200,52622,11737],{"class":235},[200,52624,239],{"class":214},[200,52626,52143],{"class":210},[200,52628,215],{"class":214},[200,52630,20112],{"class":210},[200,52632,252],{"class":214},[200,52634,52635,52637,52639,52641,52643,52645],{"class":202,"line":13482},[200,52636,52396],{"class":235},[200,52638,239],{"class":214},[200,52640,52143],{"class":210},[200,52642,215],{"class":214},[200,52644,51846],{"class":210},[200,52646,252],{"class":214},[200,52648,52649,52652,52654,52656,52658,52660,52662,52664,52666,52668],{"class":202,"line":13497},[200,52650,52651],{"class":235},"        roleType",[200,52653,239],{"class":214},[200,52655,34533],{"class":210},[200,52657,215],{"class":214},[200,52659,39182],{"class":210},[200,52661,15013],{"class":214},[200,52663,242],{"class":214},[200,52665,51899],{"class":245},[200,52667,249],{"class":214},[200,52669,252],{"class":214},[200,52671,52672],{"class":202,"line":13508},[200,52673,12098],{"class":214},[200,52675,52676,52678],{"class":202,"line":13513},[200,52677,2111],{"class":214},[200,52679,416],{"class":235},[200,52681,52682],{"class":202,"line":13518},[200,52683,528],{"emptyLinePlaceholder":20},[200,52685,52686,52688,52690,52692,52694,52696,52698,52701,52703],{"class":202,"line":20247},[200,52687,3311],{"class":206},[200,52689,46075],{"class":223},[200,52691,227],{"class":235},[200,52693,25426],{"class":210},[200,52695,549],{"class":214},[200,52697,242],{"class":214},[200,52699,52700],{"class":245},"\u002Fportal",[200,52702,249],{"class":214},[200,52704,416],{"class":235},[200,52706,52707],{"class":202,"line":20269},[200,52708,368],{"class":214},[200,52710,52711,52713],{"class":202,"line":20274},[200,52712,413],{"class":214},[200,52714,416],{"class":210},[35,52716,52717],{},"Avec cette nouvelle méthode de gestion des données des utilisateurs, je réduis le nombre d'écritures dans la base de données tout en mettant à jour les données des utilisateurs lorsque nécessaire.",[51,52719,52721],{"id":52720},"création-dun-formulaire-pour-les-modèles","Création d'un formulaire pour les modèles",[35,52723,52724],{},[1707,52725],{"alt":52726,"src":52727},"Vignette de mon sixième stream en direct.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-19.png",[35,52729,52730],{},[1216,52731,52732,47101,52735,47105,52738,45894,52741,215],{},[39,52733,38397],{"href":45853,"rel":52734},[77],[39,52736,45889],{"href":18579,"rel":52737},[77],[39,52739,19427],{"href":45844,"rel":52740},[77],[39,52742,11636],{"href":45853,"rel":52743},[77],[35,52745,52746],{},"Depuis le début de la série, je travaille sur l'interface d'administration de l'application web. Aujourd'hui, il est temps de créer le formulaire pour ajouter un nouveau modèle. Ce formulaire sera utilisé par les créateurs pour ajouter leurs modèles ou thèmes à la plateforme. Pour rappel, Orion est une collection de modèles alimentée par la communauté pour votre prochain projet, des pages de destination aux applications web complètes.",[1929,52748,52750],{"id":52749},"schéma-du-modèle","Schéma du modèle",[35,52752,52753],{},[1216,52754,52755],{},"N'oubliez pas que les choses évoluent et que le schéma n'est pas définitif.",[35,52757,52758],{},"Étant donné que la fonctionnalité de modèle est importante, je l'ai divisée en plusieurs streams en direct. La première étape consiste à créer le formulaire le plus simple mais fonctionnel possible et à ajouter des fonctionnalités au fil du temps. C'est pourquoi le schéma ci-dessus n'est pas exactement le même que celui que nous avons dessiné dans le schéma SQL (pas de modules, pas d'images et pas de markdown, pour l'instant).",[192,52760,52762],{"className":1380,"code":52761,"language":1383,"meta":13,"style":13},"export const templates = sqliteTable('templates', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  hash: text('hash').notNull().unique(),\n  slug: text('slug').notNull(),\n  title: text('title').notNull(),\n  status: text('status', { enum: ['submitted', 'refused', 'validated'] }).notNull().default('submitted'),\n  paidStatus: text('paid_status', { enum: ['free', 'paid'] }).notNull().default('free'),\n  liveUrl: text('live_url'),\n  accessUrl: text('access_url').notNull(),\n  description: text('description').notNull(),\n  userId: integer('user_id').notNull().references(() => users.id),\n  categoryId: integer('category_id').notNull().references(() => categories.id),\n})\n",[156,52763,52764,52789,52827,52861,52888,52914,52994,53066,53088,53116,53142,53187,53231],{"__ignoreMap":13},[200,52765,52766,52768,52770,52773,52775,52777,52779,52781,52783,52785,52787],{"class":202,"line":203},[200,52767,1390],{"class":206},[200,52769,14305],{"class":379},[200,52771,52772],{"class":210}," templates ",[200,52774,449],{"class":214},[200,52776,50817],{"class":223},[200,52778,227],{"class":210},[200,52780,249],{"class":214},[200,52782,51600],{"class":245},[200,52784,249],{"class":214},[200,52786,549],{"class":214},[200,52788,280],{"class":214},[200,52790,52791,52793,52795,52797,52799,52801,52803,52805,52807,52809,52811,52813,52815,52817,52819,52821,52823,52825],{"class":202,"line":14},[200,52792,49638],{"class":235},[200,52794,239],{"class":214},[200,52796,50838],{"class":223},[200,52798,227],{"class":210},[200,52800,249],{"class":214},[200,52802,3295],{"class":245},[200,52804,249],{"class":214},[200,52806,2104],{"class":210},[200,52808,215],{"class":214},[200,52810,50853],{"class":223},[200,52812,227],{"class":210},[200,52814,2767],{"class":214},[200,52816,50860],{"class":235},[200,52818,239],{"class":214},[200,52820,2439],{"class":2438},[200,52822,980],{"class":214},[200,52824,2104],{"class":210},[200,52826,252],{"class":214},[200,52828,52829,52832,52834,52836,52838,52840,52843,52845,52847,52849,52851,52853,52855,52857,52859],{"class":202,"line":255},[200,52830,52831],{"class":235},"  hash",[200,52833,239],{"class":214},[200,52835,50879],{"class":223},[200,52837,227],{"class":210},[200,52839,249],{"class":214},[200,52841,52842],{"class":245},"hash",[200,52844,249],{"class":214},[200,52846,2104],{"class":210},[200,52848,215],{"class":214},[200,52850,50894],{"class":223},[200,52852,1549],{"class":210},[200,52854,215],{"class":214},[200,52856,50901],{"class":223},[200,52858,1549],{"class":210},[200,52860,252],{"class":214},[200,52862,52863,52866,52868,52870,52872,52874,52876,52878,52880,52882,52884,52886],{"class":202,"line":272},[200,52864,52865],{"class":235},"  slug",[200,52867,239],{"class":214},[200,52869,50879],{"class":223},[200,52871,227],{"class":210},[200,52873,249],{"class":214},[200,52875,47385],{"class":245},[200,52877,249],{"class":214},[200,52879,2104],{"class":210},[200,52881,215],{"class":214},[200,52883,50894],{"class":223},[200,52885,1549],{"class":210},[200,52887,252],{"class":214},[200,52889,52890,52892,52894,52896,52898,52900,52902,52904,52906,52908,52910,52912],{"class":202,"line":283},[200,52891,23703],{"class":235},[200,52893,239],{"class":214},[200,52895,50879],{"class":223},[200,52897,227],{"class":210},[200,52899,249],{"class":214},[200,52901,3581],{"class":245},[200,52903,249],{"class":214},[200,52905,2104],{"class":210},[200,52907,215],{"class":214},[200,52909,50894],{"class":223},[200,52911,1549],{"class":210},[200,52913,252],{"class":214},[200,52915,52916,52919,52921,52923,52925,52927,52929,52931,52933,52935,52937,52939,52941,52943,52946,52948,52950,52952,52955,52957,52959,52961,52964,52966,52968,52970,52972,52974,52976,52978,52980,52982,52984,52986,52988,52990,52992],{"class":202,"line":300},[200,52917,52918],{"class":235},"  status",[200,52920,239],{"class":214},[200,52922,50879],{"class":223},[200,52924,227],{"class":210},[200,52926,249],{"class":214},[200,52928,39512],{"class":245},[200,52930,249],{"class":214},[200,52932,549],{"class":214},[200,52934,318],{"class":214},[200,52936,50962],{"class":235},[200,52938,239],{"class":214},[200,52940,350],{"class":210},[200,52942,249],{"class":214},[200,52944,52945],{"class":245},"submitted",[200,52947,249],{"class":214},[200,52949,549],{"class":214},[200,52951,242],{"class":214},[200,52953,52954],{"class":245},"refused",[200,52956,249],{"class":214},[200,52958,549],{"class":214},[200,52960,242],{"class":214},[200,52962,52963],{"class":245},"validated",[200,52965,249],{"class":214},[200,52967,18849],{"class":210},[200,52969,413],{"class":214},[200,52971,2104],{"class":210},[200,52973,215],{"class":214},[200,52975,50894],{"class":223},[200,52977,1549],{"class":210},[200,52979,215],{"class":214},[200,52981,51912],{"class":223},[200,52983,227],{"class":210},[200,52985,249],{"class":214},[200,52987,52945],{"class":245},[200,52989,249],{"class":214},[200,52991,2104],{"class":210},[200,52993,252],{"class":214},[200,52995,52996,52999,53001,53003,53005,53007,53010,53012,53014,53016,53018,53020,53022,53024,53027,53029,53031,53033,53036,53038,53040,53042,53044,53046,53048,53050,53052,53054,53056,53058,53060,53062,53064],{"class":202,"line":310},[200,52997,52998],{"class":235},"  paidStatus",[200,53000,239],{"class":214},[200,53002,50879],{"class":223},[200,53004,227],{"class":210},[200,53006,249],{"class":214},[200,53008,53009],{"class":245},"paid_status",[200,53011,249],{"class":214},[200,53013,549],{"class":214},[200,53015,318],{"class":214},[200,53017,50962],{"class":235},[200,53019,239],{"class":214},[200,53021,350],{"class":210},[200,53023,249],{"class":214},[200,53025,53026],{"class":245},"free",[200,53028,249],{"class":214},[200,53030,549],{"class":214},[200,53032,242],{"class":214},[200,53034,53035],{"class":245},"paid",[200,53037,249],{"class":214},[200,53039,18849],{"class":210},[200,53041,413],{"class":214},[200,53043,2104],{"class":210},[200,53045,215],{"class":214},[200,53047,50894],{"class":223},[200,53049,1549],{"class":210},[200,53051,215],{"class":214},[200,53053,51912],{"class":223},[200,53055,227],{"class":210},[200,53057,249],{"class":214},[200,53059,53026],{"class":245},[200,53061,249],{"class":214},[200,53063,2104],{"class":210},[200,53065,252],{"class":214},[200,53067,53068,53071,53073,53075,53077,53079,53082,53084,53086],{"class":202,"line":336},[200,53069,53070],{"class":235},"  liveUrl",[200,53072,239],{"class":214},[200,53074,50879],{"class":223},[200,53076,227],{"class":210},[200,53078,249],{"class":214},[200,53080,53081],{"class":245},"live_url",[200,53083,249],{"class":214},[200,53085,2104],{"class":210},[200,53087,252],{"class":214},[200,53089,53090,53093,53095,53097,53099,53101,53104,53106,53108,53110,53112,53114],{"class":202,"line":342},[200,53091,53092],{"class":235},"  accessUrl",[200,53094,239],{"class":214},[200,53096,50879],{"class":223},[200,53098,227],{"class":210},[200,53100,249],{"class":214},[200,53102,53103],{"class":245},"access_url",[200,53105,249],{"class":214},[200,53107,2104],{"class":210},[200,53109,215],{"class":214},[200,53111,50894],{"class":223},[200,53113,1549],{"class":210},[200,53115,252],{"class":214},[200,53117,53118,53120,53122,53124,53126,53128,53130,53132,53134,53136,53138,53140],{"class":202,"line":365},[200,53119,258],{"class":235},[200,53121,239],{"class":214},[200,53123,50879],{"class":223},[200,53125,227],{"class":210},[200,53127,249],{"class":214},[200,53129,3597],{"class":245},[200,53131,249],{"class":214},[200,53133,2104],{"class":210},[200,53135,215],{"class":214},[200,53137,50894],{"class":223},[200,53139,1549],{"class":210},[200,53141,252],{"class":214},[200,53143,53144,53147,53149,53151,53153,53155,53157,53159,53161,53163,53165,53167,53169,53172,53174,53176,53178,53180,53182,53185],{"class":202,"line":371},[200,53145,53146],{"class":235},"  userId",[200,53148,239],{"class":214},[200,53150,50838],{"class":223},[200,53152,227],{"class":210},[200,53154,249],{"class":214},[200,53156,51596],{"class":245},[200,53158,249],{"class":214},[200,53160,2104],{"class":210},[200,53162,215],{"class":214},[200,53164,50894],{"class":223},[200,53166,1549],{"class":210},[200,53168,215],{"class":214},[200,53170,53171],{"class":223},"references",[200,53173,227],{"class":210},[200,53175,1549],{"class":214},[200,53177,393],{"class":379},[200,53179,48997],{"class":210},[200,53181,215],{"class":214},[200,53183,53184],{"class":210},"id)",[200,53186,252],{"class":214},[200,53188,53189,53192,53194,53196,53198,53200,53203,53205,53207,53209,53211,53213,53215,53217,53219,53221,53223,53225,53227,53229],{"class":202,"line":398},[200,53190,53191],{"class":235},"  categoryId",[200,53193,239],{"class":214},[200,53195,50838],{"class":223},[200,53197,227],{"class":210},[200,53199,249],{"class":214},[200,53201,53202],{"class":245},"category_id",[200,53204,249],{"class":214},[200,53206,2104],{"class":210},[200,53208,215],{"class":214},[200,53210,50894],{"class":223},[200,53212,1549],{"class":210},[200,53214,215],{"class":214},[200,53216,53171],{"class":223},[200,53218,227],{"class":210},[200,53220,1549],{"class":214},[200,53222,393],{"class":379},[200,53224,47331],{"class":210},[200,53226,215],{"class":214},[200,53228,53184],{"class":210},[200,53230,252],{"class":214},[200,53232,53233,53235],{"class":202,"line":405},[200,53234,413],{"class":214},[200,53236,416],{"class":210},[35,53238,53239,53240,53242,53243,53246],{},"La colonne ",[156,53241,52842],{}," est un petit ID que je stockerai dans la base de données pour créer des URL comme Notion ou Dev.to : ",[156,53244,53245],{},"\u003Cslug>-\u003Chash>",". Cela est utile pour avoir une URL lisible, à la fois pour les humains et pour le SEO, mais comme deux modèles peuvent avoir le même nom, ou le nom d'un modèle peut changer, j'ai besoin d'un ID unique.",[35,53248,53249,53250,53252,53253,53255,53256,53259,53260,53262,53263,215],{},"Pour la première fois, nous créons une relation entre deux tables : une relation un-à-plusieurs entre la table ",[156,53251,47155],{}," et la table ",[156,53254,51600],{},". Cette relation est utilisée pour savoir qui est le créateur du modèle. La colonne ",[156,53257,53258],{},"userId"," est une clé étrangère qui fait référence à la colonne ",[156,53261,3295],{}," de la table ",[156,53264,47155],{},[35,53266,53239,53267,53270,53271,53262,53273,53275],{},[156,53268,53269],{},"categoryId"," est également une clé étrangère qui fait référence à la colonne ",[156,53272,3295],{},[156,53274,47161],{},". Cette relation est utilisée pour savoir dans quelle catégorie se trouve le modèle.",[35,53277,53278],{},"Afin de pouvoir récupérer les modèles d'un utilisateur ou d'une catégorie, je dois également définir la relation dans l'autre sens. Grâce à Drizzle, c'est assez facile.",[192,53280,53282],{"className":1380,"code":53281,"language":1383,"meta":13,"style":13},"export const usersRelations = relations(users, ({ many }) => ({\n  templates: many(templates),\n}))\n\nexport const categoriesRelations = relations(categories, ({ many }) => ({\n  templates: many(templates),\n}))\n",[156,53283,53284,53316,53330,53336,53340,53370,53382],{"__ignoreMap":13},[200,53285,53286,53288,53290,53293,53295,53298,53301,53303,53305,53308,53310,53312,53314],{"class":202,"line":203},[200,53287,1390],{"class":206},[200,53289,14305],{"class":379},[200,53291,53292],{"class":210}," usersRelations ",[200,53294,449],{"class":214},[200,53296,53297],{"class":223}," relations",[200,53299,53300],{"class":210},"(users",[200,53302,549],{"class":214},[200,53304,383],{"class":214},[200,53306,53307],{"class":386}," many",[200,53309,390],{"class":214},[200,53311,393],{"class":379},[200,53313,1094],{"class":210},[200,53315,230],{"class":214},[200,53317,53318,53321,53323,53325,53328],{"class":202,"line":14},[200,53319,53320],{"class":235},"  templates",[200,53322,239],{"class":214},[200,53324,53307],{"class":223},[200,53326,53327],{"class":210},"(templates)",[200,53329,252],{"class":214},[200,53331,53332,53334],{"class":202,"line":255},[200,53333,413],{"class":214},[200,53335,584],{"class":210},[200,53337,53338],{"class":202,"line":272},[200,53339,528],{"emptyLinePlaceholder":20},[200,53341,53342,53344,53346,53349,53351,53353,53356,53358,53360,53362,53364,53366,53368],{"class":202,"line":283},[200,53343,1390],{"class":206},[200,53345,14305],{"class":379},[200,53347,53348],{"class":210}," categoriesRelations ",[200,53350,449],{"class":214},[200,53352,53297],{"class":223},[200,53354,53355],{"class":210},"(categories",[200,53357,549],{"class":214},[200,53359,383],{"class":214},[200,53361,53307],{"class":386},[200,53363,390],{"class":214},[200,53365,393],{"class":379},[200,53367,1094],{"class":210},[200,53369,230],{"class":214},[200,53371,53372,53374,53376,53378,53380],{"class":202,"line":300},[200,53373,53320],{"class":235},[200,53375,239],{"class":214},[200,53377,53307],{"class":223},[200,53379,53327],{"class":210},[200,53381,252],{"class":214},[200,53383,53384,53386],{"class":202,"line":310},[200,53385,413],{"class":214},[200,53387,584],{"class":210},[1929,53389,53391],{"id":53390},"formulaire-de-modèle","Formulaire de modèle",[35,53393,53394],{},"Pendant cette phase, je crée le formulaire sur l'UI, exactement comme je l'ai fait pour les catégories. Je définis l'état, le schéma et l'action au moment de la soumission !",[35,53396,53397,53398,53401],{},"Maintenant qu'un utilisateur est capable de remplir un formulaire, j'ai besoin d'un point de terminaison pour enregistrer les données dans la base de données. Je crée le point de terminaison ",[156,53399,53400],{},"server\u002Fapi\u002Ftemplates\u002Findex.post.ts"," pour enregistrer le modèle dans la base de données :",[192,53403,53405],{"className":1380,"code":53404,"language":1383,"meta":13,"style":13},"import { number, object, string, enum as zEnum } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const { user } = await requireUserSession(event)\n\n  const body = await readValidatedBody(event, object({\n    title: string(),\n    paidStatus: zEnum(['free', 'paid']),\n    categoryId: number(),\n    liveUrl: string().optional(),\n    accessUrl: string(),\n    description: string().max(1000),\n  }).parse)\n\n  await useDrizzle().insert(tables.templates).values({\n    hash: useHash(),\n    slug: useSlugify(body.title),\n    title: body.title,\n    categoryId: body.categoryId,\n    paidStatus: body.paidStatus,\n    liveUrl: body.liveUrl,\n    accessUrl: body.accessUrl,\n    description: body.description,\n    userId: user.id,\n  }).execute()\n\n  return sendNoContent(event, 201)\n})\n",[156,53406,53407,53442,53446,53468,53490,53494,53518,53530,53559,53572,53592,53605,53628,53640,53644,53674,53688,53708,53722,53736,53751,53766,53781,53795,53810,53822,53826,53842],{"__ignoreMap":13},[200,53408,53409,53411,53413,53415,53417,53419,53421,53423,53425,53427,53429,53432,53434,53436,53438,53440],{"class":202,"line":203},[200,53410,972],{"class":206},[200,53412,318],{"class":214},[200,53414,20155],{"class":210},[200,53416,549],{"class":214},[200,53418,42642],{"class":210},[200,53420,549],{"class":214},[200,53422,22752],{"class":210},[200,53424,549],{"class":214},[200,53426,50962],{"class":210},[200,53428,14678],{"class":206},[200,53430,53431],{"class":210}," zEnum",[200,53433,980],{"class":214},[200,53435,983],{"class":206},[200,53437,242],{"class":214},[200,53439,19813],{"class":245},[200,53441,991],{"class":214},[200,53443,53444],{"class":202,"line":14},[200,53445,528],{"emptyLinePlaceholder":20},[200,53447,53448,53450,53452,53454,53456,53458,53460,53462,53464,53466],{"class":202,"line":255},[200,53449,1390],{"class":206},[200,53451,1393],{"class":206},[200,53453,38723],{"class":223},[200,53455,227],{"class":210},[200,53457,3726],{"class":379},[200,53459,1094],{"class":214},[200,53461,25426],{"class":386},[200,53463,2104],{"class":214},[200,53465,393],{"class":379},[200,53467,280],{"class":214},[200,53469,53470,53472,53474,53476,53478,53480,53482,53484,53486,53488],{"class":202,"line":272},[200,53471,3738],{"class":379},[200,53473,318],{"class":214},[200,53475,34533],{"class":210},[200,53477,980],{"class":214},[200,53479,509],{"class":214},[200,53481,452],{"class":206},[200,53483,38997],{"class":223},[200,53485,227],{"class":235},[200,53487,25426],{"class":210},[200,53489,416],{"class":235},[200,53491,53492],{"class":202,"line":283},[200,53493,528],{"emptyLinePlaceholder":20},[200,53495,53496,53498,53500,53502,53504,53506,53508,53510,53512,53514,53516],{"class":202,"line":300},[200,53497,3738],{"class":379},[200,53499,40809],{"class":210},[200,53501,509],{"class":214},[200,53503,452],{"class":206},[200,53505,40951],{"class":223},[200,53507,227],{"class":235},[200,53509,25426],{"class":210},[200,53511,549],{"class":214},[200,53513,42642],{"class":223},[200,53515,227],{"class":235},[200,53517,230],{"class":214},[200,53519,53520,53522,53524,53526,53528],{"class":202,"line":310},[200,53521,21482],{"class":235},[200,53523,239],{"class":214},[200,53525,22752],{"class":223},[200,53527,1549],{"class":235},[200,53529,252],{"class":214},[200,53531,53532,53535,53537,53539,53541,53543,53545,53547,53549,53551,53553,53555,53557],{"class":202,"line":336},[200,53533,53534],{"class":235},"    paidStatus",[200,53536,239],{"class":214},[200,53538,53431],{"class":223},[200,53540,25224],{"class":235},[200,53542,249],{"class":214},[200,53544,53026],{"class":245},[200,53546,249],{"class":214},[200,53548,549],{"class":214},[200,53550,242],{"class":214},[200,53552,53035],{"class":245},[200,53554,249],{"class":214},[200,53556,32210],{"class":235},[200,53558,252],{"class":214},[200,53560,53561,53564,53566,53568,53570],{"class":202,"line":342},[200,53562,53563],{"class":235},"    categoryId",[200,53565,239],{"class":214},[200,53567,20155],{"class":223},[200,53569,1549],{"class":235},[200,53571,252],{"class":214},[200,53573,53574,53577,53579,53581,53583,53585,53588,53590],{"class":202,"line":365},[200,53575,53576],{"class":235},"    liveUrl",[200,53578,239],{"class":214},[200,53580,22752],{"class":223},[200,53582,1549],{"class":235},[200,53584,215],{"class":214},[200,53586,53587],{"class":223},"optional",[200,53589,1549],{"class":235},[200,53591,252],{"class":214},[200,53593,53594,53597,53599,53601,53603],{"class":202,"line":371},[200,53595,53596],{"class":235},"    accessUrl",[200,53598,239],{"class":214},[200,53600,22752],{"class":223},[200,53602,1549],{"class":235},[200,53604,252],{"class":214},[200,53606,53607,53609,53611,53613,53615,53617,53619,53621,53624,53626],{"class":202,"line":398},[200,53608,3151],{"class":235},[200,53610,239],{"class":214},[200,53612,22752],{"class":223},[200,53614,1549],{"class":235},[200,53616,215],{"class":214},[200,53618,4138],{"class":223},[200,53620,227],{"class":235},[200,53622,53623],{"class":2060},"1000",[200,53625,2104],{"class":235},[200,53627,252],{"class":214},[200,53629,53630,53632,53634,53636,53638],{"class":202,"line":405},[200,53631,5095],{"class":214},[200,53633,2104],{"class":235},[200,53635,215],{"class":214},[200,53637,40994],{"class":210},[200,53639,416],{"class":235},[200,53641,53642],{"class":202,"line":410},[200,53643,528],{"emptyLinePlaceholder":20},[200,53645,53646,53648,53650,53652,53654,53656,53658,53660,53662,53664,53666,53668,53670,53672],{"class":202,"line":860},[200,53647,6177],{"class":206},[200,53649,47338],{"class":223},[200,53651,1549],{"class":235},[200,53653,215],{"class":214},[200,53655,47525],{"class":223},[200,53657,227],{"class":235},[200,53659,47418],{"class":210},[200,53661,215],{"class":214},[200,53663,51600],{"class":210},[200,53665,2104],{"class":235},[200,53667,215],{"class":214},[200,53669,47540],{"class":223},[200,53671,227],{"class":235},[200,53673,230],{"class":214},[200,53675,53676,53679,53681,53684,53686],{"class":202,"line":889},[200,53677,53678],{"class":235},"    hash",[200,53680,239],{"class":214},[200,53682,53683],{"class":223}," useHash",[200,53685,1549],{"class":235},[200,53687,252],{"class":214},[200,53689,53690,53692,53694,53696,53698,53700,53702,53704,53706],{"class":202,"line":899},[200,53691,47372],{"class":235},[200,53693,239],{"class":214},[200,53695,47553],{"class":223},[200,53697,227],{"class":235},[200,53699,23914],{"class":210},[200,53701,215],{"class":214},[200,53703,3581],{"class":210},[200,53705,2104],{"class":235},[200,53707,252],{"class":214},[200,53709,53710,53712,53714,53716,53718,53720],{"class":202,"line":904},[200,53711,21482],{"class":235},[200,53713,239],{"class":214},[200,53715,40809],{"class":210},[200,53717,215],{"class":214},[200,53719,3581],{"class":210},[200,53721,252],{"class":214},[200,53723,53724,53726,53728,53730,53732,53734],{"class":202,"line":930},[200,53725,53563],{"class":235},[200,53727,239],{"class":214},[200,53729,40809],{"class":210},[200,53731,215],{"class":214},[200,53733,53269],{"class":210},[200,53735,252],{"class":214},[200,53737,53738,53740,53742,53744,53746,53749],{"class":202,"line":3655},[200,53739,53534],{"class":235},[200,53741,239],{"class":214},[200,53743,40809],{"class":210},[200,53745,215],{"class":214},[200,53747,53748],{"class":210},"paidStatus",[200,53750,252],{"class":214},[200,53752,53753,53755,53757,53759,53761,53764],{"class":202,"line":3660},[200,53754,53576],{"class":235},[200,53756,239],{"class":214},[200,53758,40809],{"class":210},[200,53760,215],{"class":214},[200,53762,53763],{"class":210},"liveUrl",[200,53765,252],{"class":214},[200,53767,53768,53770,53772,53774,53776,53779],{"class":202,"line":4035},[200,53769,53596],{"class":235},[200,53771,239],{"class":214},[200,53773,40809],{"class":210},[200,53775,215],{"class":214},[200,53777,53778],{"class":210},"accessUrl",[200,53780,252],{"class":214},[200,53782,53783,53785,53787,53789,53791,53793],{"class":202,"line":4490},[200,53784,3151],{"class":235},[200,53786,239],{"class":214},[200,53788,40809],{"class":210},[200,53790,215],{"class":214},[200,53792,3597],{"class":210},[200,53794,252],{"class":214},[200,53796,53797,53800,53802,53804,53806,53808],{"class":202,"line":4496},[200,53798,53799],{"class":235},"    userId",[200,53801,239],{"class":214},[200,53803,34533],{"class":210},[200,53805,215],{"class":214},[200,53807,3295],{"class":210},[200,53809,252],{"class":214},[200,53811,53812,53814,53816,53818,53820],{"class":202,"line":4502},[200,53813,5095],{"class":214},[200,53815,2104],{"class":235},[200,53817,215],{"class":214},[200,53819,47590],{"class":223},[200,53821,466],{"class":235},[200,53823,53824],{"class":202,"line":4508},[200,53825,528],{"emptyLinePlaceholder":20},[200,53827,53828,53830,53832,53834,53836,53838,53840],{"class":202,"line":5469},[200,53829,3758],{"class":206},[200,53831,40842],{"class":223},[200,53833,227],{"class":235},[200,53835,25426],{"class":210},[200,53837,549],{"class":214},[200,53839,40851],{"class":2060},[200,53841,416],{"class":235},[200,53843,53844,53846],{"class":202,"line":5474},[200,53845,413],{"class":214},[200,53847,416],{"class":210},[35,53849,53850,53851,53854,53855,53857,53858,51597,53860,53862],{},"Assez similaire au point de terminaison pour créer une nouvelle catégorie. ",[6862,53852,53853],{},"Une différence"," est l'utilisation de l'utilitaire ",[156,53856,38959],{}," pour s'assurer que la demande provient d'un utilisateur authentifié et pour obtenir les données de l'utilisateur. J'utilise les données pour remplir la colonne ",[156,53859,53258],{},[156,53861,51600],{},". Bien sûr, je valide les données reçues du client à l'aide de Zod.",[35,53864,41033,53865,53868,53869,215],{},[156,53866,53867],{},"useHash"," est un petit wrapper autour du package ",[156,53870,53871],{},"nanoid",[1929,53873,53875],{"id":53874},"modèles-dans-linterface-dadministration","Modèles dans l'Interface d'Administration",[35,53877,53878],{},[1216,53879,53880],{},"Pendant cette partie, j'ai rencontré un bug important. Lisez attentivement la partie suivante, cela pourrait vous faire gagner beaucoup de temps si vous prévoyez également d'utiliser Drizzle sur Cloudflare.",[35,53882,53883,53884,53887,53888],{},"Les utilisateurs sont capables de créer des modèles, c'est bien. Pour les surveiller et les approuver, je dois créer une page dans l'interface d'administration pour gérer les modèles. Cette page affichera les modèles dans un tableau et permettra à l'administrateur de les approuver ou de les refuser. ",[1216,53885,53886],{},"Je n'implémente que la vue, pas l'action."," La tâche est simple, c'est comme toute autre page dans l'interface d'administration. ",[1216,53889,53890],{},"Si seulement je savais...",[35,53892,53893,53894,2186],{},"Tout d'abord, créons le point de terminaison pour obtenir les modèles depuis la base de données. Je crée le point de terminaison ",[156,53895,53896],{},"server\u002Fapi\u002Ftemplates\u002Findex.get.ts",[192,53898,53900],{"className":1380,"code":53899,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async () => {\n  const templates = await useDrizzle()\n    .select()\n    .from(tables.templates)\n    .leftJoin(tables.users, eq(tables.templates.userId, tables.users.id))\n    .leftJoin(tables.categories, eq(tables.templates.categoryId, tables.categories.id))\n\n  return templates\n})\n",[156,53901,53902,53920,53935,53943,53959,54005,54049,54053,54060],{"__ignoreMap":13},[200,53903,53904,53906,53908,53910,53912,53914,53916,53918],{"class":202,"line":203},[200,53905,1390],{"class":206},[200,53907,1393],{"class":206},[200,53909,38723],{"class":223},[200,53911,227],{"class":210},[200,53913,3726],{"class":379},[200,53915,3729],{"class":214},[200,53917,393],{"class":379},[200,53919,280],{"class":214},[200,53921,53922,53924,53927,53929,53931,53933],{"class":202,"line":14},[200,53923,3738],{"class":379},[200,53925,53926],{"class":210}," templates",[200,53928,509],{"class":214},[200,53930,452],{"class":206},[200,53932,47338],{"class":223},[200,53934,466],{"class":235},[200,53936,53937,53939,53941],{"class":202,"line":255},[200,53938,3770],{"class":214},[200,53940,769],{"class":223},[200,53942,466],{"class":235},[200,53944,53945,53947,53949,53951,53953,53955,53957],{"class":202,"line":272},[200,53946,3770],{"class":214},[200,53948,1489],{"class":223},[200,53950,227],{"class":235},[200,53952,47418],{"class":210},[200,53954,215],{"class":214},[200,53956,51600],{"class":210},[200,53958,416],{"class":235},[200,53960,53961,53963,53966,53968,53970,53972,53974,53976,53979,53981,53983,53985,53987,53989,53991,53993,53995,53997,53999,54001,54003],{"class":202,"line":283},[200,53962,3770],{"class":214},[200,53964,53965],{"class":223},"leftJoin",[200,53967,227],{"class":235},[200,53969,47418],{"class":210},[200,53971,215],{"class":214},[200,53973,47155],{"class":210},[200,53975,549],{"class":214},[200,53977,53978],{"class":223}," eq",[200,53980,227],{"class":235},[200,53982,47418],{"class":210},[200,53984,215],{"class":214},[200,53986,51600],{"class":210},[200,53988,215],{"class":214},[200,53990,53258],{"class":210},[200,53992,549],{"class":214},[200,53994,47357],{"class":210},[200,53996,215],{"class":214},[200,53998,47155],{"class":210},[200,54000,215],{"class":214},[200,54002,3295],{"class":210},[200,54004,584],{"class":235},[200,54006,54007,54009,54011,54013,54015,54017,54019,54021,54023,54025,54027,54029,54031,54033,54035,54037,54039,54041,54043,54045,54047],{"class":202,"line":300},[200,54008,3770],{"class":214},[200,54010,53965],{"class":223},[200,54012,227],{"class":235},[200,54014,47418],{"class":210},[200,54016,215],{"class":214},[200,54018,47161],{"class":210},[200,54020,549],{"class":214},[200,54022,53978],{"class":223},[200,54024,227],{"class":235},[200,54026,47418],{"class":210},[200,54028,215],{"class":214},[200,54030,51600],{"class":210},[200,54032,215],{"class":214},[200,54034,53269],{"class":210},[200,54036,549],{"class":214},[200,54038,47357],{"class":210},[200,54040,215],{"class":214},[200,54042,47161],{"class":210},[200,54044,215],{"class":214},[200,54046,3295],{"class":210},[200,54048,584],{"class":235},[200,54050,54051],{"class":202,"line":310},[200,54052,528],{"emptyLinePlaceholder":20},[200,54054,54055,54057],{"class":202,"line":336},[200,54056,3758],{"class":206},[200,54058,54059],{"class":210}," templates\n",[200,54061,54062,54064],{"class":202,"line":342},[200,54063,413],{"class":214},[200,54065,416],{"class":210},[35,54067,54068],{},"Ce point de terminaison récupère tous les modèles de la base de données et les remplit à l'aide d'une jointure et de la relation que nous avons définie plus tôt dans le schéma.",[35,54070,54071,54072,54075,54076,54078,54079,53252,54081,54083,54084,54086,54087,54089,54090,13741,54092,215],{},"Une ",[156,54073,54074],{},"jointure"," est une opération SQL qui combine les lignes de deux ou plusieurs tables en fonction d'une colonne liée entre elles. Dans ce cas, je fais une jointure de la table ",[156,54077,51600],{}," avec la table ",[156,54080,47155],{},[156,54082,47161],{},". La méthode ",[156,54085,53965],{}," est utilisée pour conserver toutes les lignes de la table ",[156,54088,51600],{},", même s'il n'y a pas de correspondance dans les tables ",[156,54091,47155],{},[156,54093,47161],{},[35,54095,54096],{},[1707,54097],{"alt":54098,"src":54099},"Une explication schématique de l'opération de jointure.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fleft-join.png",[35,54101,54102,54103,54105],{},"Maintenant que j'ai les données, je peux les afficher sur l'UI. J'utilise le composant ",[156,54104,47992],{}," de Nuxt UI Pro pour afficher les modèles dans un tableau :",[192,54107,54109],{"className":21457,"code":54108,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst columns = [{\n  key: 'id',\n  label: '#',\n}, {\n  key: 'title',\n  label: 'Title',\n  sortable: true,\n}, {\n  key: 'status',\n  label: 'Status',\n  sortable: true,\n}, {\n  key: 'paidStatus',\n  label: 'Paid Status',\n  sortable: true,\n}, {\n  key: 'liveUrl',\n  label: 'Live URL',\n}, {\n  key: 'accessUrl',\n  label: 'Access URL',\n}, {\n  key: 'description',\n  label: 'Description',\n}, {\n  key: 'category',\n  label: 'Category',\n}, {\n  key: 'createdBy',\n  label: 'Created By',\n}]\n\nconst { data: templates, refresh, pending } = await useFetch('\u002Fapi\u002Ftemplates', {\n  deep: false,\n  lazy: true,\n  default: () => [],\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUTable\n    :columns=\"columns\"\n    :rows=\"templates\"\n    :loading=\"pending\"\n  >\n    \u003Ctemplate #liveUrl-data=\"{ row }\">\n      \u003CUButton\n        v-if=\"row.liveUrl\"\n        variant=\"link\"\n        target=\"_blank\"\n        :to=\"row.liveUrl\"\n        class=\"flex flex-row items-center gap-1\"\n      >\n        \u003Cspan>{{ row.liveUrl }}\u003C\u002Fspan>\n\n        \u003Cspan class=\"i-heroicons-arrow-top-right-on-square-16-solid inline-block h-4 w-4\" \u002F>\n      \u003C\u002FUButton>\n      \u003Cspan v-else> - \u003C\u002Fspan>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate #accessUrl-data=\"{ row }\">\n      \u003CUButton\n        variant=\"link\"\n        target=\"_blank\"\n        :to=\"row.accessUrl\"\n        class=\"flex flex-row items-center gap-1\"\n      >\n        \u003Cspan>{{ row.accessUrl }}\u003C\u002Fspan>\n\n        \u003Cspan class=\"i-heroicons-arrow-top-right-on-square-16-solid inline-block h-4 w-4\" \u002F>\n      \u003C\u002FUButton>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate #paidStatus-data=\"{ row }\">\n      \u003Ctemplate v-if=\"row.paidStatus === 'free'\">\n        \u003CUBadge\n          color=\"green\"\n          variant=\"subtle\"\n        >\n          Free\n        \u003C\u002FUBadge>\n      \u003C\u002Ftemplate>\n      \u003Ctemplate v-else-if=\"row.paidStatus === 'paid'\">\n        \u003CUBadge\n          color=\"yellow\"\n          variant=\"subtle\"\n        >\n          Paid\n        \u003C\u002FUBadge>\n      \u003C\u002Ftemplate>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate #createdBy-data=\"{ row }\">\n      \u003Cdiv class=\"flex flex-row items-center gap-2\">\n        \u003Cimg\n          :src=\"row.user.avatarUrl\"\n          alt=\"avatar\"\n          class=\"h-6 w-6 rounded-full\"\n        >\n        \u003Cspan>{{ row.user.name ?? row.user.login }}\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate #category-data=\"{ row }\">\n      {{ row.category.name }}\n    \u003C\u002Ftemplate>\n  \u003C\u002FUTable>\n\u003C\u002Ftemplate>\n",[156,54110,54111,54131,54143,54157,54171,54177,54191,54206,54216,54222,54236,54251,54261,54267,54281,54296,54306,54312,54326,54341,54347,54361,54376,54382,54396,54411,54417,54432,54447,54453,54468,54483,54489,54493,54535,54545,54555,54569,54575,54583,54587,54595,54602,54616,54630,54644,54648,54673,54679,54696,54709,54721,54741,54755,54760,54777,54781,54801,54809,54829,54837,54862,54868,54880,54892,54910,54922,54926,54943,54947,54965,54973,54981,55006,55036,55044,55059,55073,55079,55085,55094,55103,55133,55140,55154,55167,55172,55178,55187,55196,55205,55231,55251,55258,55273,55288,55303,55308,55326,55335,55344,55370,55376,55385,55394],{"__ignoreMap":13},[200,54112,54113,54115,54117,54119,54121,54123,54125,54127,54129],{"class":202,"line":203},[200,54114,599],{"class":214},[200,54116,14896],{"class":235},[200,54118,14594],{"class":379},[200,54120,449],{"class":214},[200,54122,610],{"class":214},[200,54124,1383],{"class":245},[200,54126,610],{"class":214},[200,54128,21825],{"class":379},[200,54130,647],{"class":214},[200,54132,54133,54135,54137,54139,54141],{"class":202,"line":14},[200,54134,443],{"class":379},[200,54136,47788],{"class":210},[200,54138,449],{"class":214},[200,54140,350],{"class":210},[200,54142,230],{"class":214},[200,54144,54145,54147,54149,54151,54153,54155],{"class":202,"line":255},[200,54146,18976],{"class":235},[200,54148,239],{"class":214},[200,54150,242],{"class":214},[200,54152,3295],{"class":245},[200,54154,249],{"class":214},[200,54156,252],{"class":214},[200,54158,54159,54161,54163,54165,54167,54169],{"class":202,"line":272},[200,54160,47813],{"class":235},[200,54162,239],{"class":214},[200,54164,242],{"class":214},[200,54166,2902],{"class":245},[200,54168,249],{"class":214},[200,54170,252],{"class":214},[200,54172,54173,54175],{"class":202,"line":283},[200,54174,26897],{"class":214},[200,54176,280],{"class":214},[200,54178,54179,54181,54183,54185,54187,54189],{"class":202,"line":300},[200,54180,18976],{"class":235},[200,54182,239],{"class":214},[200,54184,242],{"class":214},[200,54186,3581],{"class":245},[200,54188,249],{"class":214},[200,54190,252],{"class":214},[200,54192,54193,54195,54197,54199,54202,54204],{"class":202,"line":310},[200,54194,47813],{"class":235},[200,54196,239],{"class":214},[200,54198,242],{"class":214},[200,54200,54201],{"class":245},"Title",[200,54203,249],{"class":214},[200,54205,252],{"class":214},[200,54207,54208,54210,54212,54214],{"class":202,"line":336},[200,54209,47860],{"class":235},[200,54211,239],{"class":214},[200,54213,2439],{"class":2438},[200,54215,252],{"class":214},[200,54217,54218,54220],{"class":202,"line":342},[200,54219,26897],{"class":214},[200,54221,280],{"class":214},[200,54223,54224,54226,54228,54230,54232,54234],{"class":202,"line":365},[200,54225,18976],{"class":235},[200,54227,239],{"class":214},[200,54229,242],{"class":214},[200,54231,39512],{"class":245},[200,54233,249],{"class":214},[200,54235,252],{"class":214},[200,54237,54238,54240,54242,54244,54247,54249],{"class":202,"line":371},[200,54239,47813],{"class":235},[200,54241,239],{"class":214},[200,54243,242],{"class":214},[200,54245,54246],{"class":245},"Status",[200,54248,249],{"class":214},[200,54250,252],{"class":214},[200,54252,54253,54255,54257,54259],{"class":202,"line":398},[200,54254,47860],{"class":235},[200,54256,239],{"class":214},[200,54258,2439],{"class":2438},[200,54260,252],{"class":214},[200,54262,54263,54265],{"class":202,"line":405},[200,54264,26897],{"class":214},[200,54266,280],{"class":214},[200,54268,54269,54271,54273,54275,54277,54279],{"class":202,"line":410},[200,54270,18976],{"class":235},[200,54272,239],{"class":214},[200,54274,242],{"class":214},[200,54276,53748],{"class":245},[200,54278,249],{"class":214},[200,54280,252],{"class":214},[200,54282,54283,54285,54287,54289,54292,54294],{"class":202,"line":860},[200,54284,47813],{"class":235},[200,54286,239],{"class":214},[200,54288,242],{"class":214},[200,54290,54291],{"class":245},"Paid Status",[200,54293,249],{"class":214},[200,54295,252],{"class":214},[200,54297,54298,54300,54302,54304],{"class":202,"line":889},[200,54299,47860],{"class":235},[200,54301,239],{"class":214},[200,54303,2439],{"class":2438},[200,54305,252],{"class":214},[200,54307,54308,54310],{"class":202,"line":899},[200,54309,26897],{"class":214},[200,54311,280],{"class":214},[200,54313,54314,54316,54318,54320,54322,54324],{"class":202,"line":904},[200,54315,18976],{"class":235},[200,54317,239],{"class":214},[200,54319,242],{"class":214},[200,54321,53763],{"class":245},[200,54323,249],{"class":214},[200,54325,252],{"class":214},[200,54327,54328,54330,54332,54334,54337,54339],{"class":202,"line":930},[200,54329,47813],{"class":235},[200,54331,239],{"class":214},[200,54333,242],{"class":214},[200,54335,54336],{"class":245},"Live URL",[200,54338,249],{"class":214},[200,54340,252],{"class":214},[200,54342,54343,54345],{"class":202,"line":3655},[200,54344,26897],{"class":214},[200,54346,280],{"class":214},[200,54348,54349,54351,54353,54355,54357,54359],{"class":202,"line":3660},[200,54350,18976],{"class":235},[200,54352,239],{"class":214},[200,54354,242],{"class":214},[200,54356,53778],{"class":245},[200,54358,249],{"class":214},[200,54360,252],{"class":214},[200,54362,54363,54365,54367,54369,54372,54374],{"class":202,"line":4035},[200,54364,47813],{"class":235},[200,54366,239],{"class":214},[200,54368,242],{"class":214},[200,54370,54371],{"class":245},"Access URL",[200,54373,249],{"class":214},[200,54375,252],{"class":214},[200,54377,54378,54380],{"class":202,"line":4490},[200,54379,26897],{"class":214},[200,54381,280],{"class":214},[200,54383,54384,54386,54388,54390,54392,54394],{"class":202,"line":4496},[200,54385,18976],{"class":235},[200,54387,239],{"class":214},[200,54389,242],{"class":214},[200,54391,3597],{"class":245},[200,54393,249],{"class":214},[200,54395,252],{"class":214},[200,54397,54398,54400,54402,54404,54407,54409],{"class":202,"line":4502},[200,54399,47813],{"class":235},[200,54401,239],{"class":214},[200,54403,242],{"class":214},[200,54405,54406],{"class":245},"Description",[200,54408,249],{"class":214},[200,54410,252],{"class":214},[200,54412,54413,54415],{"class":202,"line":4508},[200,54414,26897],{"class":214},[200,54416,280],{"class":214},[200,54418,54419,54421,54423,54425,54428,54430],{"class":202,"line":5469},[200,54420,18976],{"class":235},[200,54422,239],{"class":214},[200,54424,242],{"class":214},[200,54426,54427],{"class":245},"category",[200,54429,249],{"class":214},[200,54431,252],{"class":214},[200,54433,54434,54436,54438,54440,54443,54445],{"class":202,"line":5474},[200,54435,47813],{"class":235},[200,54437,239],{"class":214},[200,54439,242],{"class":214},[200,54441,54442],{"class":245},"Category",[200,54444,249],{"class":214},[200,54446,252],{"class":214},[200,54448,54449,54451],{"class":202,"line":13237},[200,54450,26897],{"class":214},[200,54452,280],{"class":214},[200,54454,54455,54457,54459,54461,54464,54466],{"class":202,"line":13263},[200,54456,18976],{"class":235},[200,54458,239],{"class":214},[200,54460,242],{"class":214},[200,54462,54463],{"class":245},"createdBy",[200,54465,249],{"class":214},[200,54467,252],{"class":214},[200,54469,54470,54472,54474,54476,54479,54481],{"class":202,"line":13279},[200,54471,47813],{"class":235},[200,54473,239],{"class":214},[200,54475,242],{"class":214},[200,54477,54478],{"class":245},"Created By",[200,54480,249],{"class":214},[200,54482,252],{"class":214},[200,54484,54485,54487],{"class":202,"line":13323},[200,54486,413],{"class":214},[200,54488,4511],{"class":210},[200,54490,54491],{"class":202,"line":13329},[200,54492,528],{"emptyLinePlaceholder":20},[200,54494,54495,54497,54499,54501,54503,54505,54507,54510,54512,54514,54516,54518,54520,54522,54524,54526,54529,54531,54533],{"class":202,"line":13334},[200,54496,443],{"class":379},[200,54498,318],{"class":214},[200,54500,5056],{"class":235},[200,54502,239],{"class":214},[200,54504,53926],{"class":210},[200,54506,549],{"class":214},[200,54508,54509],{"class":210}," refresh",[200,54511,549],{"class":214},[200,54513,47926],{"class":210},[200,54515,413],{"class":214},[200,54517,509],{"class":214},[200,54519,452],{"class":206},[200,54521,47935],{"class":223},[200,54523,227],{"class":210},[200,54525,249],{"class":214},[200,54527,54528],{"class":245},"\u002Fapi\u002Ftemplates",[200,54530,249],{"class":214},[200,54532,549],{"class":214},[200,54534,280],{"class":214},[200,54536,54537,54539,54541,54543],{"class":202,"line":13360},[200,54538,47953],{"class":235},[200,54540,239],{"class":214},[200,54542,16169],{"class":2438},[200,54544,252],{"class":214},[200,54546,54547,54549,54551,54553],{"class":202,"line":13365},[200,54548,50447],{"class":235},[200,54550,239],{"class":214},[200,54552,2439],{"class":2438},[200,54554,252],{"class":214},[200,54556,54557,54559,54561,54563,54565,54567],{"class":202,"line":13385},[200,54558,50461],{"class":223},[200,54560,239],{"class":214},[200,54562,3729],{"class":214},[200,54564,393],{"class":379},[200,54566,27681],{"class":210},[200,54568,252],{"class":214},[200,54570,54571,54573],{"class":202,"line":13405},[200,54572,413],{"class":214},[200,54574,416],{"class":210},[200,54576,54577,54579,54581],{"class":202,"line":13410},[200,54578,680],{"class":214},[200,54580,14896],{"class":235},[200,54582,647],{"class":214},[200,54584,54585],{"class":202,"line":13418},[200,54586,528],{"emptyLinePlaceholder":20},[200,54588,54589,54591,54593],{"class":202,"line":13436},[200,54590,599],{"class":214},[200,54592,21468],{"class":235},[200,54594,647],{"class":214},[200,54596,54597,54599],{"class":202,"line":13466},[200,54598,656],{"class":214},[200,54600,54601],{"class":235},"UTable\n",[200,54603,54604,54606,54608,54610,54612,54614],{"class":202,"line":13472},[200,54605,23865],{"class":214},[200,54607,47997],{"class":379},[200,54609,449],{"class":214},[200,54611,610],{"class":214},[200,54613,47997],{"class":210},[200,54615,616],{"class":214},[200,54617,54618,54620,54622,54624,54626,54628],{"class":202,"line":13477},[200,54619,23865],{"class":214},[200,54621,48010],{"class":379},[200,54623,449],{"class":214},[200,54625,610],{"class":214},[200,54627,51600],{"class":210},[200,54629,616],{"class":214},[200,54631,54632,54634,54636,54638,54640,54642],{"class":202,"line":13482},[200,54633,23865],{"class":214},[200,54635,9900],{"class":379},[200,54637,449],{"class":214},[200,54639,610],{"class":214},[200,54641,48029],{"class":210},[200,54643,616],{"class":214},[200,54645,54646],{"class":202,"line":13497},[200,54647,11550],{"class":214},[200,54649,54650,54652,54654,54656,54659,54661,54663,54665,54667,54669,54671],{"class":202,"line":13508},[200,54651,803],{"class":214},[200,54653,21468],{"class":235},[200,54655,23911],{"class":214},[200,54657,54658],{"class":379},"liveUrl-data",[200,54660,449],{"class":214},[200,54662,610],{"class":214},[200,54664,2767],{"class":214},[200,54666,49457],{"class":210},[200,54668,413],{"class":214},[200,54670,610],{"class":214},[200,54672,647],{"class":214},[200,54674,54675,54677],{"class":202,"line":13513},[200,54676,22071],{"class":214},[200,54678,48107],{"class":235},[200,54680,54681,54684,54686,54688,54690,54692,54694],{"class":202,"line":13518},[200,54682,54683],{"class":206},"        v-if",[200,54685,449],{"class":214},[200,54687,610],{"class":214},[200,54689,49495],{"class":210},[200,54691,215],{"class":214},[200,54693,53763],{"class":210},[200,54695,616],{"class":214},[200,54697,54698,54701,54703,54705,54707],{"class":202,"line":20247},[200,54699,54700],{"class":379},"        variant",[200,54702,449],{"class":214},[200,54704,610],{"class":214},[200,54706,14620],{"class":245},[200,54708,616],{"class":214},[200,54710,54711,54713,54715,54717,54719],{"class":202,"line":20269},[200,54712,52426],{"class":379},[200,54714,449],{"class":214},[200,54716,610],{"class":214},[200,54718,7156],{"class":245},[200,54720,616],{"class":214},[200,54722,54723,54726,54729,54731,54733,54735,54737,54739],{"class":202,"line":20274},[200,54724,54725],{"class":214},"        :",[200,54727,54728],{"class":379},"to",[200,54730,449],{"class":214},[200,54732,610],{"class":214},[200,54734,49495],{"class":210},[200,54736,215],{"class":214},[200,54738,53763],{"class":210},[200,54740,616],{"class":214},[200,54742,54743,54746,54748,54750,54753],{"class":202,"line":20279},[200,54744,54745],{"class":379},"        class",[200,54747,449],{"class":214},[200,54749,610],{"class":214},[200,54751,54752],{"class":245},"flex flex-row items-center gap-1",[200,54754,616],{"class":214},[200,54756,54757],{"class":202,"line":20284},[200,54758,54759],{"class":214},"      >\n",[200,54761,54762,54764,54766,54768,54771,54773,54775],{"class":202,"line":20305},[200,54763,49441],{"class":214},[200,54765,200],{"class":235},[200,54767,674],{"class":214},[200,54769,54770],{"class":210},"{{ row.liveUrl }}",[200,54772,680],{"class":214},[200,54774,200],{"class":235},[200,54776,647],{"class":214},[200,54778,54779],{"class":202,"line":20312},[200,54780,528],{"emptyLinePlaceholder":20},[200,54782,54783,54785,54787,54790,54792,54794,54797,54799],{"class":202,"line":20317},[200,54784,49441],{"class":214},[200,54786,200],{"class":235},[200,54788,54789],{"class":379}," class",[200,54791,449],{"class":214},[200,54793,610],{"class":214},[200,54795,54796],{"class":245},"i-heroicons-arrow-top-right-on-square-16-solid inline-block h-4 w-4",[200,54798,610],{"class":214},[200,54800,21527],{"class":214},[200,54802,54803,54805,54807],{"class":202,"line":20333},[200,54804,43803],{"class":214},[200,54806,48601],{"class":235},[200,54808,647],{"class":214},[200,54810,54811,54813,54815,54818,54820,54823,54825,54827],{"class":202,"line":46974},[200,54812,22071],{"class":214},[200,54814,200],{"class":235},[200,54816,54817],{"class":379}," v-else",[200,54819,674],{"class":214},[200,54821,54822],{"class":210}," - ",[200,54824,680],{"class":214},[200,54826,200],{"class":235},[200,54828,647],{"class":214},[200,54830,54831,54833,54835],{"class":202,"line":46979},[200,54832,15078],{"class":214},[200,54834,21468],{"class":235},[200,54836,647],{"class":214},[200,54838,54839,54841,54843,54845,54848,54850,54852,54854,54856,54858,54860],{"class":202,"line":46986},[200,54840,803],{"class":214},[200,54842,21468],{"class":235},[200,54844,23911],{"class":214},[200,54846,54847],{"class":379},"accessUrl-data",[200,54849,449],{"class":214},[200,54851,610],{"class":214},[200,54853,2767],{"class":214},[200,54855,49457],{"class":210},[200,54857,413],{"class":214},[200,54859,610],{"class":214},[200,54861,647],{"class":214},[200,54863,54864,54866],{"class":202,"line":46991},[200,54865,22071],{"class":214},[200,54867,48107],{"class":235},[200,54869,54870,54872,54874,54876,54878],{"class":202,"line":46996},[200,54871,54700],{"class":379},[200,54873,449],{"class":214},[200,54875,610],{"class":214},[200,54877,14620],{"class":245},[200,54879,616],{"class":214},[200,54881,54882,54884,54886,54888,54890],{"class":202,"line":47002},[200,54883,52426],{"class":379},[200,54885,449],{"class":214},[200,54887,610],{"class":214},[200,54889,7156],{"class":245},[200,54891,616],{"class":214},[200,54893,54894,54896,54898,54900,54902,54904,54906,54908],{"class":202,"line":47008},[200,54895,54725],{"class":214},[200,54897,54728],{"class":379},[200,54899,449],{"class":214},[200,54901,610],{"class":214},[200,54903,49495],{"class":210},[200,54905,215],{"class":214},[200,54907,53778],{"class":210},[200,54909,616],{"class":214},[200,54911,54912,54914,54916,54918,54920],{"class":202,"line":47014},[200,54913,54745],{"class":379},[200,54915,449],{"class":214},[200,54917,610],{"class":214},[200,54919,54752],{"class":245},[200,54921,616],{"class":214},[200,54923,54924],{"class":202,"line":47020},[200,54925,54759],{"class":214},[200,54927,54928,54930,54932,54934,54937,54939,54941],{"class":202,"line":47025},[200,54929,49441],{"class":214},[200,54931,200],{"class":235},[200,54933,674],{"class":214},[200,54935,54936],{"class":210},"{{ row.accessUrl }}",[200,54938,680],{"class":214},[200,54940,200],{"class":235},[200,54942,647],{"class":214},[200,54944,54945],{"class":202,"line":47040},[200,54946,528],{"emptyLinePlaceholder":20},[200,54948,54949,54951,54953,54955,54957,54959,54961,54963],{"class":202,"line":47047},[200,54950,49441],{"class":214},[200,54952,200],{"class":235},[200,54954,54789],{"class":379},[200,54956,449],{"class":214},[200,54958,610],{"class":214},[200,54960,54796],{"class":245},[200,54962,610],{"class":214},[200,54964,21527],{"class":214},[200,54966,54967,54969,54971],{"class":202,"line":47055},[200,54968,43803],{"class":214},[200,54970,48601],{"class":235},[200,54972,647],{"class":214},[200,54974,54975,54977,54979],{"class":202,"line":47062},[200,54976,15078],{"class":214},[200,54978,21468],{"class":235},[200,54980,647],{"class":214},[200,54982,54983,54985,54987,54989,54992,54994,54996,54998,55000,55002,55004],{"class":202,"line":47069},[200,54984,803],{"class":214},[200,54986,21468],{"class":235},[200,54988,23911],{"class":214},[200,54990,54991],{"class":379},"paidStatus-data",[200,54993,449],{"class":214},[200,54995,610],{"class":214},[200,54997,2767],{"class":214},[200,54999,49457],{"class":210},[200,55001,413],{"class":214},[200,55003,610],{"class":214},[200,55005,647],{"class":214},[200,55007,55009,55011,55013,55015,55017,55019,55021,55023,55026,55028,55030,55032,55034],{"class":202,"line":55008},74,[200,55010,22071],{"class":214},[200,55012,21468],{"class":235},[200,55014,34453],{"class":206},[200,55016,449],{"class":214},[200,55018,610],{"class":214},[200,55020,49495],{"class":210},[200,55022,215],{"class":214},[200,55024,55025],{"class":210},"paidStatus ",[200,55027,6558],{"class":214},[200,55029,242],{"class":214},[200,55031,53026],{"class":245},[200,55033,14706],{"class":214},[200,55035,647],{"class":214},[200,55037,55039,55041],{"class":202,"line":55038},75,[200,55040,49441],{"class":214},[200,55042,55043],{"class":235},"UBadge\n",[200,55045,55047,55050,55052,55054,55057],{"class":202,"line":55046},76,[200,55048,55049],{"class":379},"          color",[200,55051,449],{"class":214},[200,55053,610],{"class":214},[200,55055,55056],{"class":245},"green",[200,55058,616],{"class":214},[200,55060,55062,55065,55067,55069,55071],{"class":202,"line":55061},77,[200,55063,55064],{"class":379},"          variant",[200,55066,449],{"class":214},[200,55068,610],{"class":214},[200,55070,49481],{"class":245},[200,55072,616],{"class":214},[200,55074,55076],{"class":202,"line":55075},78,[200,55077,55078],{"class":214},"        >\n",[200,55080,55082],{"class":202,"line":55081},79,[200,55083,55084],{"class":210},"          Free\n",[200,55086,55088,55090,55092],{"class":202,"line":55087},80,[200,55089,49549],{"class":214},[200,55091,49471],{"class":235},[200,55093,647],{"class":214},[200,55095,55097,55099,55101],{"class":202,"line":55096},81,[200,55098,43803],{"class":214},[200,55100,21468],{"class":235},[200,55102,647],{"class":214},[200,55104,55106,55108,55110,55113,55115,55117,55119,55121,55123,55125,55127,55129,55131],{"class":202,"line":55105},82,[200,55107,22071],{"class":214},[200,55109,21468],{"class":235},[200,55111,55112],{"class":206}," v-else-if",[200,55114,449],{"class":214},[200,55116,610],{"class":214},[200,55118,49495],{"class":210},[200,55120,215],{"class":214},[200,55122,55025],{"class":210},[200,55124,6558],{"class":214},[200,55126,242],{"class":214},[200,55128,53035],{"class":245},[200,55130,14706],{"class":214},[200,55132,647],{"class":214},[200,55134,55136,55138],{"class":202,"line":55135},83,[200,55137,49441],{"class":214},[200,55139,55043],{"class":235},[200,55141,55143,55145,55147,55149,55152],{"class":202,"line":55142},84,[200,55144,55049],{"class":379},[200,55146,449],{"class":214},[200,55148,610],{"class":214},[200,55150,55151],{"class":245},"yellow",[200,55153,616],{"class":214},[200,55155,55157,55159,55161,55163,55165],{"class":202,"line":55156},85,[200,55158,55064],{"class":379},[200,55160,449],{"class":214},[200,55162,610],{"class":214},[200,55164,49481],{"class":245},[200,55166,616],{"class":214},[200,55168,55170],{"class":202,"line":55169},86,[200,55171,55078],{"class":214},[200,55173,55175],{"class":202,"line":55174},87,[200,55176,55177],{"class":210},"          Paid\n",[200,55179,55181,55183,55185],{"class":202,"line":55180},88,[200,55182,49549],{"class":214},[200,55184,49471],{"class":235},[200,55186,647],{"class":214},[200,55188,55190,55192,55194],{"class":202,"line":55189},89,[200,55191,43803],{"class":214},[200,55193,21468],{"class":235},[200,55195,647],{"class":214},[200,55197,55199,55201,55203],{"class":202,"line":55198},90,[200,55200,15078],{"class":214},[200,55202,21468],{"class":235},[200,55204,647],{"class":214},[200,55206,55208,55210,55212,55214,55217,55219,55221,55223,55225,55227,55229],{"class":202,"line":55207},91,[200,55209,803],{"class":214},[200,55211,21468],{"class":235},[200,55213,23911],{"class":214},[200,55215,55216],{"class":379},"createdBy-data",[200,55218,449],{"class":214},[200,55220,610],{"class":214},[200,55222,2767],{"class":214},[200,55224,49457],{"class":210},[200,55226,413],{"class":214},[200,55228,610],{"class":214},[200,55230,647],{"class":214},[200,55232,55234,55236,55238,55240,55242,55244,55247,55249],{"class":202,"line":55233},92,[200,55235,22071],{"class":214},[200,55237,11400],{"class":235},[200,55239,54789],{"class":379},[200,55241,449],{"class":214},[200,55243,610],{"class":214},[200,55245,55246],{"class":245},"flex flex-row items-center gap-2",[200,55248,610],{"class":214},[200,55250,647],{"class":214},[200,55252,55254,55256],{"class":202,"line":55253},93,[200,55255,49441],{"class":214},[200,55257,11463],{"class":235},[200,55259,55261,55264,55266,55268,55271],{"class":202,"line":55260},94,[200,55262,55263],{"class":379},"          :src",[200,55265,449],{"class":214},[200,55267,610],{"class":214},[200,55269,55270],{"class":245},"row.user.avatarUrl",[200,55272,616],{"class":214},[200,55274,55276,55279,55281,55283,55286],{"class":202,"line":55275},95,[200,55277,55278],{"class":379},"          alt",[200,55280,449],{"class":214},[200,55282,610],{"class":214},[200,55284,55285],{"class":245},"avatar",[200,55287,616],{"class":214},[200,55289,55291,55294,55296,55298,55301],{"class":202,"line":55290},96,[200,55292,55293],{"class":379},"          class",[200,55295,449],{"class":214},[200,55297,610],{"class":214},[200,55299,55300],{"class":245},"h-6 w-6 rounded-full",[200,55302,616],{"class":214},[200,55304,55306],{"class":202,"line":55305},97,[200,55307,55078],{"class":214},[200,55309,55311,55313,55315,55317,55320,55322,55324],{"class":202,"line":55310},98,[200,55312,49441],{"class":214},[200,55314,200],{"class":235},[200,55316,674],{"class":214},[200,55318,55319],{"class":210},"{{ row.user.name ?? row.user.login }}",[200,55321,680],{"class":214},[200,55323,200],{"class":235},[200,55325,647],{"class":214},[200,55327,55329,55331,55333],{"class":202,"line":55328},99,[200,55330,43803],{"class":214},[200,55332,11400],{"class":235},[200,55334,647],{"class":214},[200,55336,55338,55340,55342],{"class":202,"line":55337},100,[200,55339,15078],{"class":214},[200,55341,21468],{"class":235},[200,55343,647],{"class":214},[200,55345,55347,55349,55351,55353,55356,55358,55360,55362,55364,55366,55368],{"class":202,"line":55346},101,[200,55348,803],{"class":214},[200,55350,21468],{"class":235},[200,55352,23911],{"class":214},[200,55354,55355],{"class":379},"category-data",[200,55357,449],{"class":214},[200,55359,610],{"class":214},[200,55361,2767],{"class":214},[200,55363,49457],{"class":210},[200,55365,413],{"class":214},[200,55367,610],{"class":214},[200,55369,647],{"class":214},[200,55371,55373],{"class":202,"line":55372},102,[200,55374,55375],{"class":210},"      {{ row.category.name }}\n",[200,55377,55379,55381,55383],{"class":202,"line":55378},103,[200,55380,15078],{"class":214},[200,55382,21468],{"class":235},[200,55384,647],{"class":214},[200,55386,55388,55390,55392],{"class":202,"line":55387},104,[200,55389,892],{"class":214},[200,55391,47992],{"class":235},[200,55393,647],{"class":214},[200,55395,55397,55399,55401],{"class":202,"line":55396},105,[200,55398,680],{"class":214},[200,55400,21468],{"class":235},[200,55402,647],{"class":214},[35,55404,55405,55408],{},[6862,55406,55407],{},"Mais cela ne fonctionne pas."," Étant habitué à Adonis, j'étais familiarisé avec un objet comme celui-ci :",[192,55410,55412],{"className":28358,"code":55411,"language":28360,"meta":13,"style":13},"{\n  \"id\": 1,\n  \"title\": \"My template\",\n  \"status\": \"submitted\",\n  \"liveUrl\": \"https:\u002F\u002Fexample.com\",\n  \"user\": {\n    \"id\": 1,\n    \"name\": \"John Doe\",\n    \"avatarUrl\": \"https:\u002F\u002Fexample.com\u002Favatar.jpg\"\n  },\n  \"category\": {\n    \"id\": 1,\n    \"name\": \"Landing Page\"\n  }\n}\n",[156,55413,55414,55418,55432,55451,55469,55487,55499,55513,55531,55549,55553,55565,55579,55596,55600],{"__ignoreMap":13},[200,55415,55416],{"class":202,"line":203},[200,55417,230],{"class":214},[200,55419,55420,55422,55424,55426,55428,55430],{"class":202,"line":14},[200,55421,28489],{"class":214},[200,55423,3295],{"class":379},[200,55425,610],{"class":214},[200,55427,239],{"class":214},[200,55429,3953],{"class":2060},[200,55431,252],{"class":214},[200,55433,55434,55436,55438,55440,55442,55444,55447,55449],{"class":202,"line":255},[200,55435,28489],{"class":214},[200,55437,3581],{"class":379},[200,55439,610],{"class":214},[200,55441,239],{"class":214},[200,55443,28499],{"class":214},[200,55445,55446],{"class":245},"My template",[200,55448,610],{"class":214},[200,55450,252],{"class":214},[200,55452,55453,55455,55457,55459,55461,55463,55465,55467],{"class":202,"line":272},[200,55454,28489],{"class":214},[200,55456,39512],{"class":379},[200,55458,610],{"class":214},[200,55460,239],{"class":214},[200,55462,28499],{"class":214},[200,55464,52945],{"class":245},[200,55466,610],{"class":214},[200,55468,252],{"class":214},[200,55470,55471,55473,55475,55477,55479,55481,55483,55485],{"class":202,"line":283},[200,55472,28489],{"class":214},[200,55474,53763],{"class":379},[200,55476,610],{"class":214},[200,55478,239],{"class":214},[200,55480,28499],{"class":214},[200,55482,29434],{"class":245},[200,55484,610],{"class":214},[200,55486,252],{"class":214},[200,55488,55489,55491,55493,55495,55497],{"class":202,"line":300},[200,55490,28489],{"class":214},[200,55492,26847],{"class":379},[200,55494,610],{"class":214},[200,55496,239],{"class":214},[200,55498,280],{"class":214},[200,55500,55501,55503,55505,55507,55509,55511],{"class":202,"line":310},[200,55502,28529],{"class":214},[200,55504,3295],{"class":1364},[200,55506,610],{"class":214},[200,55508,239],{"class":214},[200,55510,3953],{"class":2060},[200,55512,252],{"class":214},[200,55514,55515,55517,55519,55521,55523,55525,55527,55529],{"class":202,"line":336},[200,55516,28529],{"class":214},[200,55518,20112],{"class":1364},[200,55520,610],{"class":214},[200,55522,239],{"class":214},[200,55524,28499],{"class":214},[200,55526,34550],{"class":245},[200,55528,610],{"class":214},[200,55530,252],{"class":214},[200,55532,55533,55535,55538,55540,55542,55544,55547],{"class":202,"line":342},[200,55534,28529],{"class":214},[200,55536,55537],{"class":1364},"avatarUrl",[200,55539,610],{"class":214},[200,55541,239],{"class":214},[200,55543,28499],{"class":214},[200,55545,55546],{"class":245},"https:\u002F\u002Fexample.com\u002Favatar.jpg",[200,55548,616],{"class":214},[200,55550,55551],{"class":202,"line":365},[200,55552,368],{"class":214},[200,55554,55555,55557,55559,55561,55563],{"class":202,"line":371},[200,55556,28489],{"class":214},[200,55558,54427],{"class":379},[200,55560,610],{"class":214},[200,55562,239],{"class":214},[200,55564,280],{"class":214},[200,55566,55567,55569,55571,55573,55575,55577],{"class":202,"line":398},[200,55568,28529],{"class":214},[200,55570,3295],{"class":1364},[200,55572,610],{"class":214},[200,55574,239],{"class":214},[200,55576,3953],{"class":2060},[200,55578,252],{"class":214},[200,55580,55581,55583,55585,55587,55589,55591,55594],{"class":202,"line":405},[200,55582,28529],{"class":214},[200,55584,20112],{"class":1364},[200,55586,610],{"class":214},[200,55588,239],{"class":214},[200,55590,28499],{"class":214},[200,55592,55593],{"class":245},"Landing Page",[200,55595,616],{"class":214},[200,55597,55598],{"class":202,"line":410},[200,55599,4505],{"class":214},[200,55601,55602],{"class":202,"line":860},[200,55603,1129],{"class":214},[35,55605,55606],{},"Mais j'ai reçu un objet comme celui-ci :",[192,55608,55610],{"className":28358,"code":55609,"language":28360,"meta":13,"style":13},"{\n  \"template\": {\n    \"id\": 1,\n    \"title\": \"My template\",\n    \"status\": \"submitted\",\n    \"liveUrl\": \"https:\u002F\u002Fexample.com\"\n  },\n  \"user\": {\n  },\n  \"category\": {\n  }\n}\n",[156,55611,55612,55616,55628,55642,55660,55678,55694,55698,55710,55714,55726,55730],{"__ignoreMap":13},[200,55613,55614],{"class":202,"line":203},[200,55615,230],{"class":214},[200,55617,55618,55620,55622,55624,55626],{"class":202,"line":14},[200,55619,28489],{"class":214},[200,55621,21468],{"class":379},[200,55623,610],{"class":214},[200,55625,239],{"class":214},[200,55627,280],{"class":214},[200,55629,55630,55632,55634,55636,55638,55640],{"class":202,"line":255},[200,55631,28529],{"class":214},[200,55633,3295],{"class":1364},[200,55635,610],{"class":214},[200,55637,239],{"class":214},[200,55639,3953],{"class":2060},[200,55641,252],{"class":214},[200,55643,55644,55646,55648,55650,55652,55654,55656,55658],{"class":202,"line":272},[200,55645,28529],{"class":214},[200,55647,3581],{"class":1364},[200,55649,610],{"class":214},[200,55651,239],{"class":214},[200,55653,28499],{"class":214},[200,55655,55446],{"class":245},[200,55657,610],{"class":214},[200,55659,252],{"class":214},[200,55661,55662,55664,55666,55668,55670,55672,55674,55676],{"class":202,"line":283},[200,55663,28529],{"class":214},[200,55665,39512],{"class":1364},[200,55667,610],{"class":214},[200,55669,239],{"class":214},[200,55671,28499],{"class":214},[200,55673,52945],{"class":245},[200,55675,610],{"class":214},[200,55677,252],{"class":214},[200,55679,55680,55682,55684,55686,55688,55690,55692],{"class":202,"line":300},[200,55681,28529],{"class":214},[200,55683,53763],{"class":1364},[200,55685,610],{"class":214},[200,55687,239],{"class":214},[200,55689,28499],{"class":214},[200,55691,29434],{"class":245},[200,55693,616],{"class":214},[200,55695,55696],{"class":202,"line":310},[200,55697,368],{"class":214},[200,55699,55700,55702,55704,55706,55708],{"class":202,"line":336},[200,55701,28489],{"class":214},[200,55703,26847],{"class":379},[200,55705,610],{"class":214},[200,55707,239],{"class":214},[200,55709,280],{"class":214},[200,55711,55712],{"class":202,"line":342},[200,55713,368],{"class":214},[200,55715,55716,55718,55720,55722,55724],{"class":202,"line":365},[200,55717,28489],{"class":214},[200,55719,54427],{"class":379},[200,55721,610],{"class":214},[200,55723,239],{"class":214},[200,55725,280],{"class":214},[200,55727,55728],{"class":202,"line":371},[200,55729,4505],{"class":214},[200,55731,55732],{"class":202,"line":398},[200,55733,1129],{"class":214},[35,55735,55736,55737],{},"et quelle surprise de voir que les clés et les valeurs étaient échangées entre elles. ",[1216,55738,55739],{},"Difficile à expliquer mais voyez l'exemple.",[192,55741,55743],{"className":28358,"code":55742,"language":28360,"meta":13,"style":13},"{\n  \"template\": {\n    \"id\": \"My template\",\n    \"status\": \"https:\u002F\u002Fexample.com\"\n  },\n  \"user\": {\n    \"id\": \"John Doe\",\n    \"login\": \"https:\u002F\u002Favatars.github.com\u002Fu\u002F1\"\n  }\n}\n",[156,55744,55745,55749,55761,55779,55795,55799,55811,55829,55846,55850],{"__ignoreMap":13},[200,55746,55747],{"class":202,"line":203},[200,55748,230],{"class":214},[200,55750,55751,55753,55755,55757,55759],{"class":202,"line":14},[200,55752,28489],{"class":214},[200,55754,21468],{"class":379},[200,55756,610],{"class":214},[200,55758,239],{"class":214},[200,55760,280],{"class":214},[200,55762,55763,55765,55767,55769,55771,55773,55775,55777],{"class":202,"line":255},[200,55764,28529],{"class":214},[200,55766,3295],{"class":1364},[200,55768,610],{"class":214},[200,55770,239],{"class":214},[200,55772,28499],{"class":214},[200,55774,55446],{"class":245},[200,55776,610],{"class":214},[200,55778,252],{"class":214},[200,55780,55781,55783,55785,55787,55789,55791,55793],{"class":202,"line":272},[200,55782,28529],{"class":214},[200,55784,39512],{"class":1364},[200,55786,610],{"class":214},[200,55788,239],{"class":214},[200,55790,28499],{"class":214},[200,55792,29434],{"class":245},[200,55794,616],{"class":214},[200,55796,55797],{"class":202,"line":283},[200,55798,368],{"class":214},[200,55800,55801,55803,55805,55807,55809],{"class":202,"line":300},[200,55802,28489],{"class":214},[200,55804,26847],{"class":379},[200,55806,610],{"class":214},[200,55808,239],{"class":214},[200,55810,280],{"class":214},[200,55812,55813,55815,55817,55819,55821,55823,55825,55827],{"class":202,"line":310},[200,55814,28529],{"class":214},[200,55816,3295],{"class":1364},[200,55818,610],{"class":214},[200,55820,239],{"class":214},[200,55822,28499],{"class":214},[200,55824,34550],{"class":245},[200,55826,610],{"class":214},[200,55828,252],{"class":214},[200,55830,55831,55833,55835,55837,55839,55841,55844],{"class":202,"line":336},[200,55832,28529],{"class":214},[200,55834,51626],{"class":1364},[200,55836,610],{"class":214},[200,55838,239],{"class":214},[200,55840,28499],{"class":214},[200,55842,55843],{"class":245},"https:\u002F\u002Favatars.github.com\u002Fu\u002F1",[200,55845,616],{"class":214},[200,55847,55848],{"class":202,"line":342},[200,55849,4505],{"class":214},[200,55851,55852],{"class":202,"line":365},[200,55853,1129],{"class":214},[35,55855,55856,55857],{},"Très difficile de manipuler les données maintenant ! ",[1216,55858,55859],{},"L'ID devrait être un nombre et le login ne devrait définitivement pas être une URL.",[35,55861,55862,55863,1655,55867,55871,55872,55876],{},"Après une longue recherche, j'ai trouvé deux problèmes : ",[39,55864,55865],{"href":55865,"rel":55866},"https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fworkers-sdk\u002Fissues\u002F3160",[77],[39,55868,55869],{"href":55869,"rel":55870},"https:\u002F\u002Fgithub.com\u002Fdrizzle-team\u002Fdrizzle-orm\u002Fissues\u002F555",[77]," expliquant pourquoi cela ne fonctionne pas. Lorsque deux tables ont le même nom de colonne, ",[55873,55874,55875],"del",{},"Drizzle et"," Cloudflare sont incapables de les résoudre correctement et mélangent les données. La solution consiste à sélectionner explicitement les colonnes et à renommer les colonnes similaires.",[192,55878,55880],{"className":1380,"code":55879,"language":1383,"meta":13,"style":13},"const templates = await useDrizzle().select({\n  id: tables.templates.id,\n  title: tables.templates.title,\n  description: tables.templates.description,\n  status: tables.templates.status,\n  paidStatus: tables.templates.paidStatus,\n  liveUrl: tables.templates.liveUrl,\n  accessUrl: tables.templates.accessUrl,\n  user: {\n    name: tables.users.name,\n    login: tables.users.login,\n    avatarUrl: tables.users.avatarUrl,\n  },\n  category: {\n    name: sql\u003Cstring>`${tables.categories.name}`.as('c_name'),\n  },\n}).from(tables.templates).leftJoin(tables.users, eq(tables.templates.userId, tables.users.id)).leftJoin(tables.categories, eq(tables.templates.categoryId, tables.categories.id))\n",[156,55881,55882,55904,55922,55940,55958,55976,55994,56012,56030,56039,56057,56076,56095,56099,56108,56153,56157],{"__ignoreMap":13},[200,55883,55884,55886,55888,55890,55892,55894,55896,55898,55900,55902],{"class":202,"line":203},[200,55885,443],{"class":379},[200,55887,52772],{"class":210},[200,55889,449],{"class":214},[200,55891,452],{"class":206},[200,55893,47338],{"class":223},[200,55895,1549],{"class":210},[200,55897,215],{"class":214},[200,55899,769],{"class":223},[200,55901,227],{"class":210},[200,55903,230],{"class":214},[200,55905,55906,55908,55910,55912,55914,55916,55918,55920],{"class":202,"line":14},[200,55907,49638],{"class":235},[200,55909,239],{"class":214},[200,55911,47357],{"class":210},[200,55913,215],{"class":214},[200,55915,51600],{"class":210},[200,55917,215],{"class":214},[200,55919,3295],{"class":210},[200,55921,252],{"class":214},[200,55923,55924,55926,55928,55930,55932,55934,55936,55938],{"class":202,"line":255},[200,55925,23703],{"class":235},[200,55927,239],{"class":214},[200,55929,47357],{"class":210},[200,55931,215],{"class":214},[200,55933,51600],{"class":210},[200,55935,215],{"class":214},[200,55937,3581],{"class":210},[200,55939,252],{"class":214},[200,55941,55942,55944,55946,55948,55950,55952,55954,55956],{"class":202,"line":272},[200,55943,258],{"class":235},[200,55945,239],{"class":214},[200,55947,47357],{"class":210},[200,55949,215],{"class":214},[200,55951,51600],{"class":210},[200,55953,215],{"class":214},[200,55955,3597],{"class":210},[200,55957,252],{"class":214},[200,55959,55960,55962,55964,55966,55968,55970,55972,55974],{"class":202,"line":283},[200,55961,52918],{"class":235},[200,55963,239],{"class":214},[200,55965,47357],{"class":210},[200,55967,215],{"class":214},[200,55969,51600],{"class":210},[200,55971,215],{"class":214},[200,55973,39512],{"class":210},[200,55975,252],{"class":214},[200,55977,55978,55980,55982,55984,55986,55988,55990,55992],{"class":202,"line":300},[200,55979,52998],{"class":235},[200,55981,239],{"class":214},[200,55983,47357],{"class":210},[200,55985,215],{"class":214},[200,55987,51600],{"class":210},[200,55989,215],{"class":214},[200,55991,53748],{"class":210},[200,55993,252],{"class":214},[200,55995,55996,55998,56000,56002,56004,56006,56008,56010],{"class":202,"line":310},[200,55997,53070],{"class":235},[200,55999,239],{"class":214},[200,56001,47357],{"class":210},[200,56003,215],{"class":214},[200,56005,51600],{"class":210},[200,56007,215],{"class":214},[200,56009,53763],{"class":210},[200,56011,252],{"class":214},[200,56013,56014,56016,56018,56020,56022,56024,56026,56028],{"class":202,"line":336},[200,56015,53092],{"class":235},[200,56017,239],{"class":214},[200,56019,47357],{"class":210},[200,56021,215],{"class":214},[200,56023,51600],{"class":210},[200,56025,215],{"class":214},[200,56027,53778],{"class":210},[200,56029,252],{"class":214},[200,56031,56032,56035,56037],{"class":202,"line":342},[200,56033,56034],{"class":235},"  user",[200,56036,239],{"class":214},[200,56038,280],{"class":214},[200,56040,56041,56043,56045,56047,56049,56051,56053,56055],{"class":202,"line":365},[200,56042,11812],{"class":235},[200,56044,239],{"class":214},[200,56046,47357],{"class":210},[200,56048,215],{"class":214},[200,56050,47155],{"class":210},[200,56052,215],{"class":214},[200,56054,20112],{"class":210},[200,56056,252],{"class":214},[200,56058,56059,56062,56064,56066,56068,56070,56072,56074],{"class":202,"line":371},[200,56060,56061],{"class":235},"    login",[200,56063,239],{"class":214},[200,56065,47357],{"class":210},[200,56067,215],{"class":214},[200,56069,47155],{"class":210},[200,56071,215],{"class":214},[200,56073,51626],{"class":210},[200,56075,252],{"class":214},[200,56077,56078,56081,56083,56085,56087,56089,56091,56093],{"class":202,"line":398},[200,56079,56080],{"class":235},"    avatarUrl",[200,56082,239],{"class":214},[200,56084,47357],{"class":210},[200,56086,215],{"class":214},[200,56088,47155],{"class":210},[200,56090,215],{"class":214},[200,56092,55537],{"class":210},[200,56094,252],{"class":214},[200,56096,56097],{"class":202,"line":405},[200,56098,368],{"class":214},[200,56100,56101,56104,56106],{"class":202,"line":410},[200,56102,56103],{"class":235},"  category",[200,56105,239],{"class":214},[200,56107,280],{"class":214},[200,56109,56110,56112,56114,56116,56118,56120,56122,56124,56126,56128,56130,56132,56134,56136,56138,56140,56142,56144,56147,56149,56151],{"class":202,"line":860},[200,56111,11812],{"class":235},[200,56113,239],{"class":214},[200,56115,51963],{"class":223},[200,56117,599],{"class":214},[200,56119,328],{"class":1364},[200,56121,674],{"class":214},[200,56123,3319],{"class":214},[200,56125,47418],{"class":210},[200,56127,215],{"class":214},[200,56129,47161],{"class":210},[200,56131,215],{"class":214},[200,56133,20112],{"class":210},[200,56135,23045],{"class":214},[200,56137,215],{"class":214},[200,56139,44022],{"class":223},[200,56141,227],{"class":210},[200,56143,249],{"class":214},[200,56145,56146],{"class":245},"c_name",[200,56148,249],{"class":214},[200,56150,2104],{"class":210},[200,56152,252],{"class":214},[200,56154,56155],{"class":202,"line":889},[200,56156,368],{"class":214},[200,56158,56159,56161,56163,56165,56167,56169,56171,56174,56176,56178,56180,56182,56184,56186,56188,56190,56192,56194,56196,56198,56200,56202,56204,56206,56208,56211,56213,56215,56217,56219,56221,56223,56225,56227,56229,56231,56233,56235,56237,56239,56241,56243,56245],{"class":202,"line":899},[200,56160,413],{"class":214},[200,56162,2104],{"class":210},[200,56164,215],{"class":214},[200,56166,1489],{"class":223},[200,56168,51050],{"class":210},[200,56170,215],{"class":214},[200,56172,56173],{"class":210},"templates)",[200,56175,215],{"class":214},[200,56177,53965],{"class":223},[200,56179,51050],{"class":210},[200,56181,215],{"class":214},[200,56183,47155],{"class":210},[200,56185,549],{"class":214},[200,56187,53978],{"class":223},[200,56189,51050],{"class":210},[200,56191,215],{"class":214},[200,56193,51600],{"class":210},[200,56195,215],{"class":214},[200,56197,53258],{"class":210},[200,56199,549],{"class":214},[200,56201,47357],{"class":210},[200,56203,215],{"class":214},[200,56205,47155],{"class":210},[200,56207,215],{"class":214},[200,56209,56210],{"class":210},"id))",[200,56212,215],{"class":214},[200,56214,53965],{"class":223},[200,56216,51050],{"class":210},[200,56218,215],{"class":214},[200,56220,47161],{"class":210},[200,56222,549],{"class":214},[200,56224,53978],{"class":223},[200,56226,51050],{"class":210},[200,56228,215],{"class":214},[200,56230,51600],{"class":210},[200,56232,215],{"class":214},[200,56234,53269],{"class":210},[200,56236,549],{"class":214},[200,56238,47357],{"class":210},[200,56240,215],{"class":214},[200,56242,47161],{"class":210},[200,56244,215],{"class":214},[200,56246,56247],{"class":210},"id))\n",[35,56249,56250,56253,56254,5569,56256,56258,56259,1655,56261,56263],{},[156,56251,56252],{},"sql\u003Cstring>'${tables.categories.name}'.as('c_name')"," est le code important. Je renomme la colonne ",[156,56255,20112],{},[156,56257,56146],{}," pour éviter le conflit. J'utilise également cette syntaxe pour formater l'objet avec les clés ",[156,56260,26847],{},[156,56262,54427],{},". Maintenant, tout fonctionne comme prévu.",[1929,56265,24146],{"id":56266},"conclusion-2",[35,56268,56269],{},"Travailler sur le côté client, et pas seulement sur la partie admin, rend le projet encore plus concret. Il reste encore beaucoup de travail sur le formulaire de modèle comme le markdown ou l'image, mais c'est un très bon début.",[35,56271,56272],{},"J'apprécie vraiment ce projet et j'espère que vous aussi.",[51,56274,56276],{"id":56275},"poursuivre-le-modèle-de-formulaire","Poursuivre le modèle de formulaire",[35,56278,56279],{},[1707,56280],{"alt":56281,"src":56282},"Vignette de mon huitième direct.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-20.png",[35,56284,56285],{},[1216,56286,56287,47101,56290,45890,56293,45894,56296,215],{},[39,56288,38397],{"href":45853,"rel":56289},[77],[39,56291,45889],{"href":18579,"rel":56292},[77],[39,56294,19427],{"href":45844,"rel":56295},[77],[39,56297,11636],{"href":45853,"rel":56298},[77],[35,56300,56301],{},"Ce direct était court, donc j'essaie de faire des choses intéressantes. Je fais principalement trois choses :",[1593,56303,56304,56307,56310],{},[71,56305,56306],{},"Créer la page de modèle de l'émission",[71,56308,56309],{},"Gérer le markdown côté serveur pour la description du modèle",[71,56311,56312],{},"Ajouter un éditeur de markdown pour la description du modèle",[35,56314,56315,56316],{},"Voyons comment ça se passe. ",[1216,56317,56318],{},"Pendant le direct, ça se passe bien mais depuis, j'ai complètement réécrit la partie éditeur de markdown.",[1929,56320,56322],{"id":56321},"page-de-modèle-de-lémission","Page de modèle de l'émission",[35,56324,56325,56326,56329,56330,56332,56333,56335,56336,56338,56339,56341,56342,56344,56345,56347],{},"Cet élément était davantage lié à l'utilisation de l'URL qu'au design de la page. En effet, l'URL fonctionnera comme Notion ou Dev.to : ",[156,56327,56328],{},"\u002F\u003Cslug>-\u003Chash>"," où le ",[156,56331,47385],{}," est le nom du modèle et le ",[156,56334,52842],{}," est un identifiant unique. Le ",[156,56337,47385],{}," est utilisé pour le SEO et le ",[156,56340,52842],{}," est utilisé pour obtenir le modèle. Le ",[156,56343,47385],{}," n'est pas unique et peut changer avec le temps. Le ",[156,56346,52842],{}," est unique et sert à obtenir le modèle.",[35,56349,56350],{},"Sur la page, nous commençons par obtenir les paramètres de la route :",[192,56352,56354],{"className":1380,"code":56353,"language":1383,"meta":13,"style":13},"const route = useRoute()\nconst slug = route.params.slug \u002F\u002F \u003Cslug>-\u003Chash>\n",[156,56355,56356,56370],{"__ignoreMap":13},[200,56357,56358,56360,56363,56365,56368],{"class":202,"line":203},[200,56359,443],{"class":379},[200,56361,56362],{"class":210}," route ",[200,56364,449],{"class":214},[200,56366,56367],{"class":223}," useRoute",[200,56369,466],{"class":210},[200,56371,56372,56374,56377,56379,56382,56384,56386,56388,56391],{"class":202,"line":14},[200,56373,443],{"class":379},[200,56375,56376],{"class":210}," slug ",[200,56378,449],{"class":214},[200,56380,56381],{"class":210}," route",[200,56383,215],{"class":214},[200,56385,20107],{"class":210},[200,56387,215],{"class":214},[200,56389,56390],{"class":210},"slug ",[200,56392,56393],{"class":401},"\u002F\u002F \u003Cslug>-\u003Chash>\n",[35,56395,10674,56396,56398,56399,56401,56402,56404,56405,56407,56408,39879,56410,2186],{},[156,56397,47385],{}," contient à la fois le ",[156,56400,47385],{}," du modèle et le ",[156,56403,52842],{},". Nous savons que le hash fait exactement 12 caractères donc nous pouvons découper le ",[156,56406,47385],{}," pour obtenir le ",[156,56409,47385],{},[156,56411,52842],{},[192,56413,56415],{"className":1380,"code":56414,"language":1383,"meta":13,"style":13},"slug.slice(-12)\n",[156,56416,56417],{"__ignoreMap":13},[200,56418,56419,56421,56423,56425,56427,56430,56433],{"class":202,"line":203},[200,56420,47385],{"class":210},[200,56422,215],{"class":214},[200,56424,3943],{"class":223},[200,56426,227],{"class":210},[200,56428,56429],{"class":214},"-",[200,56431,56432],{"class":2060},"12",[200,56434,416],{"class":210},[35,56436,56437],{},"Enfin, nous pouvons faire une requête au backend pour récupérer le modèle. Tout cela nous donne ce code :",[192,56439,56441],{"className":21457,"code":56440,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst route = useRoute()\nconst hash = computed(() => {\n  return (route.params.slug as string).slice(-12)\n})\nconst { data: template } = await useFetch(`\u002Fapi\u002Ftemplates\u002F${hash.value}`, {\n  deep: false,\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Carticle\n    v-if=\"template\"\n  >\n    {{ template }}\n  \u003C\u002Farticle>\n\u003C\u002Ftemplate>\n",[156,56442,56443,56463,56475,56495,56530,56536,56578,56588,56594,56602,56606,56614,56621,56634,56638,56643,56652],{"__ignoreMap":13},[200,56444,56445,56447,56449,56451,56453,56455,56457,56459,56461],{"class":202,"line":203},[200,56446,599],{"class":214},[200,56448,14896],{"class":235},[200,56450,14594],{"class":379},[200,56452,449],{"class":214},[200,56454,610],{"class":214},[200,56456,1383],{"class":245},[200,56458,610],{"class":214},[200,56460,21825],{"class":379},[200,56462,647],{"class":214},[200,56464,56465,56467,56469,56471,56473],{"class":202,"line":14},[200,56466,443],{"class":379},[200,56468,56362],{"class":210},[200,56470,449],{"class":214},[200,56472,56367],{"class":223},[200,56474,466],{"class":210},[200,56476,56477,56479,56482,56484,56487,56489,56491,56493],{"class":202,"line":255},[200,56478,443],{"class":379},[200,56480,56481],{"class":210}," hash ",[200,56483,449],{"class":214},[200,56485,56486],{"class":223}," computed",[200,56488,227],{"class":210},[200,56490,1549],{"class":214},[200,56492,393],{"class":379},[200,56494,280],{"class":214},[200,56496,56497,56499,56501,56504,56506,56508,56510,56512,56514,56516,56518,56520,56522,56524,56526,56528],{"class":202,"line":272},[200,56498,3758],{"class":206},[200,56500,1094],{"class":235},[200,56502,56503],{"class":210},"route",[200,56505,215],{"class":214},[200,56507,20107],{"class":210},[200,56509,215],{"class":214},[200,56511,47385],{"class":210},[200,56513,14678],{"class":206},[200,56515,22752],{"class":1364},[200,56517,2104],{"class":235},[200,56519,215],{"class":214},[200,56521,3943],{"class":223},[200,56523,227],{"class":235},[200,56525,56429],{"class":214},[200,56527,56432],{"class":2060},[200,56529,416],{"class":235},[200,56531,56532,56534],{"class":202,"line":283},[200,56533,413],{"class":214},[200,56535,416],{"class":210},[200,56537,56538,56540,56542,56544,56546,56549,56551,56553,56555,56557,56559,56561,56564,56566,56568,56570,56572,56574,56576],{"class":202,"line":300},[200,56539,443],{"class":379},[200,56541,318],{"class":214},[200,56543,5056],{"class":235},[200,56545,239],{"class":214},[200,56547,56548],{"class":210}," template ",[200,56550,413],{"class":214},[200,56552,509],{"class":214},[200,56554,452],{"class":206},[200,56556,47935],{"class":223},[200,56558,227],{"class":210},[200,56560,3333],{"class":214},[200,56562,56563],{"class":245},"\u002Fapi\u002Ftemplates\u002F",[200,56565,4868],{"class":214},[200,56567,52842],{"class":210},[200,56569,215],{"class":214},[200,56571,15840],{"class":210},[200,56573,23045],{"class":214},[200,56575,549],{"class":214},[200,56577,280],{"class":214},[200,56579,56580,56582,56584,56586],{"class":202,"line":310},[200,56581,47953],{"class":235},[200,56583,239],{"class":214},[200,56585,16169],{"class":2438},[200,56587,252],{"class":214},[200,56589,56590,56592],{"class":202,"line":336},[200,56591,413],{"class":214},[200,56593,416],{"class":210},[200,56595,56596,56598,56600],{"class":202,"line":342},[200,56597,680],{"class":214},[200,56599,14896],{"class":235},[200,56601,647],{"class":214},[200,56603,56604],{"class":202,"line":365},[200,56605,528],{"emptyLinePlaceholder":20},[200,56607,56608,56610,56612],{"class":202,"line":371},[200,56609,599],{"class":214},[200,56611,21468],{"class":235},[200,56613,647],{"class":214},[200,56615,56616,56618],{"class":202,"line":398},[200,56617,656],{"class":214},[200,56619,56620],{"class":235},"article\n",[200,56622,56623,56626,56628,56630,56632],{"class":202,"line":405},[200,56624,56625],{"class":379},"    v-if",[200,56627,449],{"class":214},[200,56629,610],{"class":214},[200,56631,21468],{"class":245},[200,56633,616],{"class":214},[200,56635,56636],{"class":202,"line":410},[200,56637,11550],{"class":214},[200,56639,56640],{"class":202,"line":860},[200,56641,56642],{"class":210},"    {{ template }}\n",[200,56644,56645,56647,56650],{"class":202,"line":889},[200,56646,892],{"class":214},[200,56648,56649],{"class":235},"article",[200,56651,647],{"class":214},[200,56653,56654,56656,56658],{"class":202,"line":899},[200,56655,680],{"class":214},[200,56657,21468],{"class":235},[200,56659,647],{"class":214},[35,56661,56662,56663,2186],{},"La partie serveur n'est pas si compliquée puisque nous obtenons seulement le hash à partir du paramètre de route et recherchons le modèle en utilisant une clause ",[156,56664,48864],{},[192,56666,56668],{"className":1380,"code":56667,"language":1383,"meta":13,"style":13},"import { object, string } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const params = await getValidatedRouterParams(event, object({\n    hash: string().length(12),\n  }).parse)\n\n  const { hash } = params\n\n  const template = await useDrizzle().select().from(tables.templates).where(eq(tables.templates.hash, hash))\n\n  return template\n})\n",[156,56669,56670,56692,56696,56718,56743,56766,56778,56782,56798,56802,56863,56867,56874],{"__ignoreMap":13},[200,56671,56672,56674,56676,56678,56680,56682,56684,56686,56688,56690],{"class":202,"line":203},[200,56673,972],{"class":206},[200,56675,318],{"class":214},[200,56677,42642],{"class":210},[200,56679,549],{"class":214},[200,56681,22752],{"class":210},[200,56683,980],{"class":214},[200,56685,983],{"class":206},[200,56687,242],{"class":214},[200,56689,19813],{"class":245},[200,56691,991],{"class":214},[200,56693,56694],{"class":202,"line":14},[200,56695,528],{"emptyLinePlaceholder":20},[200,56697,56698,56700,56702,56704,56706,56708,56710,56712,56714,56716],{"class":202,"line":255},[200,56699,1390],{"class":206},[200,56701,1393],{"class":206},[200,56703,38723],{"class":223},[200,56705,227],{"class":210},[200,56707,3726],{"class":379},[200,56709,1094],{"class":214},[200,56711,25426],{"class":386},[200,56713,2104],{"class":214},[200,56715,393],{"class":379},[200,56717,280],{"class":214},[200,56719,56720,56722,56724,56726,56728,56731,56733,56735,56737,56739,56741],{"class":202,"line":272},[200,56721,3738],{"class":379},[200,56723,48886],{"class":210},[200,56725,509],{"class":214},[200,56727,452],{"class":206},[200,56729,56730],{"class":223}," getValidatedRouterParams",[200,56732,227],{"class":235},[200,56734,25426],{"class":210},[200,56736,549],{"class":214},[200,56738,42642],{"class":223},[200,56740,227],{"class":235},[200,56742,230],{"class":214},[200,56744,56745,56747,56749,56751,56753,56755,56758,56760,56762,56764],{"class":202,"line":283},[200,56746,53678],{"class":235},[200,56748,239],{"class":214},[200,56750,22752],{"class":223},[200,56752,1549],{"class":235},[200,56754,215],{"class":214},[200,56756,56757],{"class":223},"length",[200,56759,227],{"class":235},[200,56761,56432],{"class":2060},[200,56763,2104],{"class":235},[200,56765,252],{"class":214},[200,56767,56768,56770,56772,56774,56776],{"class":202,"line":300},[200,56769,5095],{"class":214},[200,56771,2104],{"class":235},[200,56773,215],{"class":214},[200,56775,40994],{"class":210},[200,56777,416],{"class":235},[200,56779,56780],{"class":202,"line":310},[200,56781,528],{"emptyLinePlaceholder":20},[200,56783,56784,56786,56788,56791,56793,56795],{"class":202,"line":336},[200,56785,3738],{"class":379},[200,56787,318],{"class":214},[200,56789,56790],{"class":210}," hash",[200,56792,980],{"class":214},[200,56794,509],{"class":214},[200,56796,56797],{"class":210}," params\n",[200,56799,56800],{"class":202,"line":342},[200,56801,528],{"emptyLinePlaceholder":20},[200,56803,56804,56806,56809,56811,56813,56815,56817,56819,56821,56823,56825,56827,56829,56831,56833,56835,56837,56839,56841,56843,56845,56847,56849,56851,56853,56855,56857,56859,56861],{"class":202,"line":365},[200,56805,3738],{"class":379},[200,56807,56808],{"class":210}," template",[200,56810,509],{"class":214},[200,56812,452],{"class":206},[200,56814,47338],{"class":223},[200,56816,1549],{"class":235},[200,56818,215],{"class":214},[200,56820,769],{"class":223},[200,56822,1549],{"class":235},[200,56824,215],{"class":214},[200,56826,1489],{"class":223},[200,56828,227],{"class":235},[200,56830,47418],{"class":210},[200,56832,215],{"class":214},[200,56834,51600],{"class":210},[200,56836,2104],{"class":235},[200,56838,215],{"class":214},[200,56840,48864],{"class":223},[200,56842,227],{"class":235},[200,56844,48869],{"class":223},[200,56846,227],{"class":235},[200,56848,47418],{"class":210},[200,56850,215],{"class":214},[200,56852,51600],{"class":210},[200,56854,215],{"class":214},[200,56856,52842],{"class":210},[200,56858,549],{"class":214},[200,56860,56790],{"class":210},[200,56862,584],{"class":235},[200,56864,56865],{"class":202,"line":371},[200,56866,528],{"emptyLinePlaceholder":20},[200,56868,56869,56871],{"class":202,"line":398},[200,56870,3758],{"class":206},[200,56872,56873],{"class":210}," template\n",[200,56875,56876,56878],{"class":202,"line":405},[200,56877,413],{"class":214},[200,56879,416],{"class":210},[35,56881,56882],{},"Plus tard, nous devrons joindre les données de l'utilisateur et les données de la catégorie pour avoir un modèle complet.",[1929,56884,56886],{"id":56885},"markdown-côté-serveur","Markdown côté serveur",[35,56888,56889],{},"L'idée de recevoir du markdown du client est de le nettoyer facilement pour éviter les injections HTML.",[35,56891,56892],{},"Imaginez si le client envoie ce code HTML :",[192,56894,56896],{"className":590,"code":56895,"language":592,"meta":13,"style":13},"\u003Cp> Hello World!\u003C\u002Fp>\n\u003Cscript> console.log('hacked') \u003C\u002Fscript>\n",[156,56897,56898,56915],{"__ignoreMap":13},[200,56899,56900,56902,56904,56906,56909,56911,56913],{"class":202,"line":203},[200,56901,599],{"class":214},[200,56903,35],{"class":235},[200,56905,674],{"class":214},[200,56907,56908],{"class":210}," Hello World!",[200,56910,680],{"class":214},[200,56912,35],{"class":235},[200,56914,647],{"class":214},[200,56916,56917,56919,56921,56923,56926,56928,56930,56932,56934,56937,56939,56941,56943,56945],{"class":202,"line":14},[200,56918,599],{"class":214},[200,56920,14896],{"class":235},[200,56922,674],{"class":214},[200,56924,56925],{"class":210}," console",[200,56927,215],{"class":214},[200,56929,1117],{"class":223},[200,56931,227],{"class":210},[200,56933,249],{"class":214},[200,56935,56936],{"class":245},"hacked",[200,56938,249],{"class":214},[200,56940,5282],{"class":210},[200,56942,680],{"class":214},[200,56944,14896],{"class":235},[200,56946,647],{"class":214},[35,56948,56949,56950,56953],{},"Si le serveur stocke ce contenu dans la base de données et l'affiche ensuite en utilisant la directive ",[156,56951,56952],{},"v-html",", chaque utilisateur qui verra la page exécutera le script. C'est un réel problème de sécurité.",[35,56955,56956,56957,56959,56960,56963],{},"En recevant du markdown du client, je peux facilement le transformer en utilisant ",[156,56958,9689],{}," et stocker le ",[6862,56961,56962],{},"HTML nettoyé"," dans la base de données. Le HTML nettoyé est du HTML qui ne contient aucun script ou balise dangereuse.",[35,56965,56966,56967,56969,56970,56972,56973,56976],{},"Pour ce faire, je dois installer ",[156,56968,9689],{}," et mettre à jour le point de terminaison pour créer un modèle ",[156,56971,53400],{},". J'ajoute également une nouvelle colonne ",[156,56974,56975],{},"descriptionHTML"," au schéma pour stocker le HTML nettoyé. Stocker le HTML évite de le faire chaque fois que nous rendons la page.",[192,56978,56980],{"className":1380,"code":56979,"language":1383,"meta":13,"style":13},"await useDrizzle().insert(tables.templates).values({\n  hash: useHash(),\n  slug: useSlugify(body.title),\n  title: body.title,\n  categoryId: body.categoryId,\n  paidStatus: body.paidStatus,\n  liveUrl: body.liveUrl,\n  accessUrl: body.accessUrl,\n  description: body.description,\n  descriptionHTML: useMarkdown(body.description),\n  userId: user.id,\n}).execute()\n",[156,56981,56982,57008,57020,57038,57052,57066,57080,57094,57108,57122,57141,57155],{"__ignoreMap":13},[200,56983,56984,56986,56988,56990,56992,56994,56996,56998,57000,57002,57004,57006],{"class":202,"line":203},[200,56985,207],{"class":206},[200,56987,47338],{"class":223},[200,56989,1549],{"class":210},[200,56991,215],{"class":214},[200,56993,47525],{"class":223},[200,56995,51050],{"class":210},[200,56997,215],{"class":214},[200,56999,56173],{"class":210},[200,57001,215],{"class":214},[200,57003,47540],{"class":223},[200,57005,227],{"class":210},[200,57007,230],{"class":214},[200,57009,57010,57012,57014,57016,57018],{"class":202,"line":14},[200,57011,52831],{"class":235},[200,57013,239],{"class":214},[200,57015,53683],{"class":223},[200,57017,1549],{"class":210},[200,57019,252],{"class":214},[200,57021,57022,57024,57026,57028,57031,57033,57036],{"class":202,"line":255},[200,57023,52865],{"class":235},[200,57025,239],{"class":214},[200,57027,47553],{"class":223},[200,57029,57030],{"class":210},"(body",[200,57032,215],{"class":214},[200,57034,57035],{"class":210},"title)",[200,57037,252],{"class":214},[200,57039,57040,57042,57044,57046,57048,57050],{"class":202,"line":272},[200,57041,23703],{"class":235},[200,57043,239],{"class":214},[200,57045,40809],{"class":210},[200,57047,215],{"class":214},[200,57049,3581],{"class":210},[200,57051,252],{"class":214},[200,57053,57054,57056,57058,57060,57062,57064],{"class":202,"line":283},[200,57055,53191],{"class":235},[200,57057,239],{"class":214},[200,57059,40809],{"class":210},[200,57061,215],{"class":214},[200,57063,53269],{"class":210},[200,57065,252],{"class":214},[200,57067,57068,57070,57072,57074,57076,57078],{"class":202,"line":300},[200,57069,52998],{"class":235},[200,57071,239],{"class":214},[200,57073,40809],{"class":210},[200,57075,215],{"class":214},[200,57077,53748],{"class":210},[200,57079,252],{"class":214},[200,57081,57082,57084,57086,57088,57090,57092],{"class":202,"line":310},[200,57083,53070],{"class":235},[200,57085,239],{"class":214},[200,57087,40809],{"class":210},[200,57089,215],{"class":214},[200,57091,53763],{"class":210},[200,57093,252],{"class":214},[200,57095,57096,57098,57100,57102,57104,57106],{"class":202,"line":336},[200,57097,53092],{"class":235},[200,57099,239],{"class":214},[200,57101,40809],{"class":210},[200,57103,215],{"class":214},[200,57105,53778],{"class":210},[200,57107,252],{"class":214},[200,57109,57110,57112,57114,57116,57118,57120],{"class":202,"line":342},[200,57111,258],{"class":235},[200,57113,239],{"class":214},[200,57115,40809],{"class":210},[200,57117,215],{"class":214},[200,57119,3597],{"class":210},[200,57121,252],{"class":214},[200,57123,57124,57127,57129,57132,57134,57136,57139],{"class":202,"line":365},[200,57125,57126],{"class":235},"  descriptionHTML",[200,57128,239],{"class":214},[200,57130,57131],{"class":223}," useMarkdown",[200,57133,57030],{"class":210},[200,57135,215],{"class":214},[200,57137,57138],{"class":210},"description)",[200,57140,252],{"class":214},[200,57142,57143,57145,57147,57149,57151,57153],{"class":202,"line":371},[200,57144,53146],{"class":235},[200,57146,239],{"class":214},[200,57148,34533],{"class":210},[200,57150,215],{"class":214},[200,57152,3295],{"class":210},[200,57154,252],{"class":214},[200,57156,57157,57159,57161,57163,57165],{"class":202,"line":398},[200,57158,413],{"class":214},[200,57160,2104],{"class":210},[200,57162,215],{"class":214},[200,57164,47590],{"class":223},[200,57166,466],{"class":210},[35,57168,15736,57169,57172,57173,57175],{},[156,57170,57171],{},"useMarkdown"," est une fonction simple qui utilise ",[156,57174,9689],{}," pour transformer le markdown en HTML.",[1929,57177,57179],{"id":57178},"éditeur-de-markdown","Éditeur de Markdown",[35,57181,57182],{},[1216,57183,57184],{},"Cette partie est très expérimentale et pourrait changer à l'avenir. Il s'agit d'une première implémentation.",[35,57186,57187],{},[1216,57188,57189],{},"En écrivant cette partie, j'ai fait quelques recherches supplémentaires et tout va changer. Je pense que c'est encore intéressant de voir le processus.",[35,57191,57192,57193,57196],{},"Avant de commencer, je dois faire un choix. Quel éditeur de markdown devrais-je utiliser ? ",[1216,57194,57195],{},"Rappelez-vous que nous venons de mettre en place un point de terminaison serveur pour gérer le markdown."," J'ai de nombreuses options :",[1593,57198,57199,57202,57204,57207,57210,57212,57215,57218],{},[71,57200,57201],{},"SimpleMDE",[71,57203,34008],{},[71,57205,57206],{},"TinyMDE",[71,57208,57209],{},"Editor.js",[71,57211,34013],{},[71,57213,57214],{},"Trix",[71,57216,57217],{},"CKEditor",[71,57219,57220],{},"TinyMCE",[35,57222,57223],{},"C'est beaucoup d'options. Mais, quels sont mes besoins ? J'ai besoin d'un éditeur très simple qui peut retourner du markdown. Ça a l'air facile, non ?",[35,57225,57226,57227],{},"Editor.js, CKEditor et TinyMCE sont des éditeurs avancés donc je ne les utiliserai pas. SimpleMDE est une bonne option mais trop vieux. Il n'a pas reçu de mises à jour depuis 6 ans. SimpleMDE et TinyMDE sont des forks de SimpleMDE. EasyMDE est puissant, simple et retourne du markdown. C'est une bonne option. TinyMDE n'a pas assez d'options de personnalisation. Trix et Quill ne sont pas des éditeurs de markdown. Ce sont des éditeurs de texte enrichi donc ils ne produisent pas de markdown. Je vais opter pour EasyMDE. ",[1216,57228,57229],{},"Pour l'instant.",[35,57231,57232,57233,106,57236],{},"Nous pouvons donc maintenant installer le package et l'utiliser. L'utilisation est un peu délicate. Étant un package uniquement côté client, nous ne pouvons pas l'exécuter ni l'importer côté serveur. Pour l'utiliser, nous allons l'importer dynamiquement dans un hook ",[156,57234,57235],{},"onMounted",[1216,57237,57238,57241],{},[156,57239,57240],{},"onBeforeMount"," est encore mieux !",[192,57243,57245],{"className":1380,"code":57244,"language":1383,"meta":13,"style":13},"onMounted(async () => {\n  const EasyMDE = await import('easymde').then(m => m.default)\n  const easymde = new EasyMDE()\n})\n",[156,57246,57247,57261,57305,57320],{"__ignoreMap":13},[200,57248,57249,57251,57253,57255,57257,57259],{"class":202,"line":203},[200,57250,57235],{"class":223},[200,57252,227],{"class":210},[200,57254,3726],{"class":379},[200,57256,3729],{"class":214},[200,57258,393],{"class":379},[200,57260,280],{"class":214},[200,57262,57263,57265,57268,57270,57272,57274,57276,57278,57281,57283,57285,57287,57289,57291,57294,57296,57299,57301,57303],{"class":202,"line":14},[200,57264,3738],{"class":379},[200,57266,57267],{"class":210}," EasyMDE",[200,57269,509],{"class":214},[200,57271,452],{"class":206},[200,57273,27446],{"class":214},[200,57275,227],{"class":235},[200,57277,249],{"class":214},[200,57279,57280],{"class":245},"easymde",[200,57282,249],{"class":214},[200,57284,2104],{"class":235},[200,57286,215],{"class":214},[200,57288,10944],{"class":223},[200,57290,227],{"class":235},[200,57292,57293],{"class":386},"m",[200,57295,393],{"class":379},[200,57297,57298],{"class":210}," m",[200,57300,215],{"class":214},[200,57302,51912],{"class":210},[200,57304,416],{"class":235},[200,57306,57307,57309,57312,57314,57316,57318],{"class":202,"line":255},[200,57308,3738],{"class":379},[200,57310,57311],{"class":210}," easymde",[200,57313,509],{"class":214},[200,57315,13007],{"class":214},[200,57317,57267],{"class":223},[200,57319,466],{"class":235},[200,57321,57322,57324],{"class":202,"line":272},[200,57323,413],{"class":214},[200,57325,416],{"class":210},[35,57327,57328],{},"Et ça fonctionne ? Oui, mais je pense que nous avons complètement oublié d'importer le style !",[35,57330,57331],{},[1707,57332],{"alt":57333,"src":57334},"EasyMDE sans style","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Feasymde-without-style.png",[35,57336,57337],{},"Je pense que nous avons oublié d'ajouter le style pour EasyMDE. Grâce à Vite et son chargeur CSS, nous pouvons importer directement le fichier CSS dans notre script.",[192,57339,57341],{"className":21457,"code":57340,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport 'easymde\u002Fdist\u002Feasymde.min.css'\n\u003C\u002Fscript>\n",[156,57342,57343,57363,57374],{"__ignoreMap":13},[200,57344,57345,57347,57349,57351,57353,57355,57357,57359,57361],{"class":202,"line":203},[200,57346,599],{"class":214},[200,57348,14896],{"class":235},[200,57350,14594],{"class":379},[200,57352,449],{"class":214},[200,57354,610],{"class":214},[200,57356,1383],{"class":245},[200,57358,610],{"class":214},[200,57360,21825],{"class":379},[200,57362,647],{"class":214},[200,57364,57365,57367,57369,57372],{"class":202,"line":14},[200,57366,972],{"class":206},[200,57368,242],{"class":214},[200,57370,57371],{"class":245},"easymde\u002Fdist\u002Feasymde.min.css",[200,57373,991],{"class":214},[200,57375,57376,57378,57380],{"class":202,"line":255},[200,57377,680],{"class":214},[200,57379,14896],{"class":235},[200,57381,647],{"class":214},[35,57383,57384],{},[1707,57385],{"alt":57386,"src":57387},"EasyMDE avec style","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Feasymde-with-style.png",[35,57389,57390],{},"Ok, ce n'est pas parfait et j'aurai du travail à faire, tant sur les styles que sur les icônes. Mais c'est un bon début. La seule partie que je n'aime pas est le symbole markdown. Je préférerais ne pas les avoir parce que ce n'est pas convivial (tout le monde pourrait devoir créer un modèle).",[35,57392,57393],{},"Mais de toute façon, c'est un bon début et je peux maintenant facilement écouter les changements de l'éditeur et mettre à jour la description.",[192,57395,57397],{"className":21457,"code":57396,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst description = ref\u003Cstring>('')\nonMounted(async () => {\n  const EasyMDE = await import('easymde').then(m => m.default)\n  const easymde = new EasyMDE()\n  easyMDE.codemirror.on('change', () => {\n    description.value = easyMDE.value()\n  })\n})\n\u003C\u002Fscript>\n",[156,57398,57399,57419,57441,57455,57495,57509,57540,57559,57565,57571],{"__ignoreMap":13},[200,57400,57401,57403,57405,57407,57409,57411,57413,57415,57417],{"class":202,"line":203},[200,57402,599],{"class":214},[200,57404,14896],{"class":235},[200,57406,14594],{"class":379},[200,57408,449],{"class":214},[200,57410,610],{"class":214},[200,57412,1383],{"class":245},[200,57414,610],{"class":214},[200,57416,21825],{"class":379},[200,57418,647],{"class":214},[200,57420,57421,57423,57425,57427,57429,57431,57433,57435,57437,57439],{"class":202,"line":14},[200,57422,443],{"class":379},[200,57424,22710],{"class":210},[200,57426,449],{"class":214},[200,57428,18698],{"class":223},[200,57430,599],{"class":214},[200,57432,328],{"class":1364},[200,57434,674],{"class":214},[200,57436,227],{"class":210},[200,57438,22700],{"class":214},[200,57440,416],{"class":210},[200,57442,57443,57445,57447,57449,57451,57453],{"class":202,"line":255},[200,57444,57235],{"class":223},[200,57446,227],{"class":210},[200,57448,3726],{"class":379},[200,57450,3729],{"class":214},[200,57452,393],{"class":379},[200,57454,280],{"class":214},[200,57456,57457,57459,57461,57463,57465,57467,57469,57471,57473,57475,57477,57479,57481,57483,57485,57487,57489,57491,57493],{"class":202,"line":272},[200,57458,3738],{"class":379},[200,57460,57267],{"class":210},[200,57462,509],{"class":214},[200,57464,452],{"class":206},[200,57466,27446],{"class":214},[200,57468,227],{"class":235},[200,57470,249],{"class":214},[200,57472,57280],{"class":245},[200,57474,249],{"class":214},[200,57476,2104],{"class":235},[200,57478,215],{"class":214},[200,57480,10944],{"class":223},[200,57482,227],{"class":235},[200,57484,57293],{"class":386},[200,57486,393],{"class":379},[200,57488,57298],{"class":210},[200,57490,215],{"class":214},[200,57492,51912],{"class":210},[200,57494,416],{"class":235},[200,57496,57497,57499,57501,57503,57505,57507],{"class":202,"line":283},[200,57498,3738],{"class":379},[200,57500,57311],{"class":210},[200,57502,509],{"class":214},[200,57504,13007],{"class":214},[200,57506,57267],{"class":223},[200,57508,466],{"class":235},[200,57510,57511,57514,57516,57519,57521,57523,57525,57527,57530,57532,57534,57536,57538],{"class":202,"line":300},[200,57512,57513],{"class":210},"  easyMDE",[200,57515,215],{"class":214},[200,57517,57518],{"class":210},"codemirror",[200,57520,215],{"class":214},[200,57522,25410],{"class":223},[200,57524,227],{"class":235},[200,57526,249],{"class":214},[200,57528,57529],{"class":245},"change",[200,57531,249],{"class":214},[200,57533,549],{"class":214},[200,57535,3729],{"class":214},[200,57537,393],{"class":379},[200,57539,280],{"class":214},[200,57541,57542,57544,57546,57548,57550,57553,57555,57557],{"class":202,"line":310},[200,57543,3151],{"class":210},[200,57545,215],{"class":214},[200,57547,15840],{"class":210},[200,57549,509],{"class":214},[200,57551,57552],{"class":210}," easyMDE",[200,57554,215],{"class":214},[200,57556,15840],{"class":223},[200,57558,466],{"class":235},[200,57560,57561,57563],{"class":202,"line":336},[200,57562,5095],{"class":214},[200,57564,416],{"class":235},[200,57566,57567,57569],{"class":202,"line":342},[200,57568,413],{"class":214},[200,57570,416],{"class":210},[200,57572,57573,57575,57577],{"class":202,"line":365},[200,57574,680],{"class":214},[200,57576,14896],{"class":235},[200,57578,647],{"class":214},[35,57580,57581],{},"Maintenant, je peux envoyer la description au serveur et elle sera transformée en HTML !",[1929,57583,57585],{"id":57584},"quelle-est-la-prochaine-étape-pour-le-formulaire","Quelle est la prochaine étape pour le formulaire ?",[35,57587,57588],{},"Le formulaire est loin d'être terminé mais il progresse bien. Je vais ajouter le support pour les images et les modules dans le prochain stream.",[35,57590,57591],{},"Le projet Orion est presque prêt pour une version alpha et je suis impatient de voir comment la communauté l'utilisera et découvrir le contenu qu'ils partageront.",[51,57593,57595],{"id":57594},"remplacer-léditeur-markdown","Remplacer l'éditeur Markdown",[35,57597,57598],{},[1216,57599,57600],{},"Cette partie a été réalisée hors-stream parce que je n'étais pas satisfait de l'éditeur EasyMDE mais je ne savais pas quoi faire.",[35,57602,57603],{},"Après y avoir réfléchi, j'ai réalisé que l'utilisation de Markdown, pour une simple description et qui peut être utilisée par des utilisateurs non techniques, n'est pas la meilleure idée. De plus, l'éditeur EasyMDE utilise CodeMirror qui est lent et beaucoup trop lourd pour un simple éditeur.",[35,57605,57606,57607],{},"Maintenant que nous savons que l'éditeur EasyMDE n'est pas la meilleure solution pour nos besoins, nous devons en trouver un nouveau. Nous avons besoin d'un éditeur de texte enrichi simple et il y a deux options principales : Quill et Trix. Dans ce cas, Quill semble être plus simple donc je vais opter pour lui. ",[1216,57608,57609],{},"Je ne détaillerai pas l'installation de Quill, c'est assez simple.",[35,57611,57612,57613,57615,57616,215],{},"Comme Quill est un éditeur de texte enrichi, il ne génère pas de markdown mais du HTML. Je dois donc mettre à jour le point de terminaison pour stocker le HTML au lieu du markdown. Je dois également mettre à jour le schéma pour supprimer la colonne ",[156,57614,3597],{}," et ajouter une colonne ",[156,57617,56975],{},[1929,57619,57621],{"id":57620},"mise-à-jour-du-point-de-terminaison","Mise à jour du point de terminaison",[35,57623,57624],{},"Pourquoi dois-je mettre à jour le point de terminaison ? Quill renvoie du HTML déjà nettoyé, donc je pourrais le stocker directement dans la base de données. Oui mais non. Nous construisons une API et il est impossible de faire confiance au client, même à notre propre client. N'importe qui peut modifier le HTML ou utiliser un client HTTP pour envoyer une requête au serveur avec du HTML malveillant. Je dois donc nettoyer le HTML sur le serveur.",[35,57626,57627],{},"Par exemple, voici du HTML malveillant :",[192,57629,57631],{"className":590,"code":57630,"language":592,"meta":13,"style":13},"\u003Cp> Bonjour le monde ! \u003C\u002Fp>\n\u003Cscript> console.log('piraté') \u003C\u002Fscript>\n",[156,57632,57633,57650],{"__ignoreMap":13},[200,57634,57635,57637,57639,57641,57644,57646,57648],{"class":202,"line":203},[200,57636,599],{"class":214},[200,57638,35],{"class":235},[200,57640,674],{"class":214},[200,57642,57643],{"class":210}," Bonjour le monde ! ",[200,57645,680],{"class":214},[200,57647,35],{"class":235},[200,57649,647],{"class":214},[200,57651,57652,57654,57656,57658,57660,57662,57664,57666,57668,57671,57673,57675,57677,57679],{"class":202,"line":14},[200,57653,599],{"class":214},[200,57655,14896],{"class":235},[200,57657,674],{"class":214},[200,57659,56925],{"class":210},[200,57661,215],{"class":214},[200,57663,1117],{"class":223},[200,57665,227],{"class":210},[200,57667,249],{"class":214},[200,57669,57670],{"class":245},"piraté",[200,57672,249],{"class":214},[200,57674,5282],{"class":210},[200,57676,680],{"class":214},[200,57678,14896],{"class":235},[200,57680,647],{"class":214},[35,57682,57683,57684,57686,57687,57690],{},"Si vous stockez ce HTML dans la base de données et l'affichez en utilisant la directive ",[156,57685,56952],{},", chaque utilisateur qui verra la page exécutera le script. C'est un vrai problème de sécurité. Nous ne pouvons pas utiliser ",[156,57688,57689],{},"{{ html }}"," parce que Vue échappera le HTML donc nettoyer notre contenu est la seule solution.",[35,57692,57693,57694,57697],{},"Je vais utiliser le paquet ",[156,57695,57696],{},"sanitize-html"," pour nettoyer le HTML. Après avoir nettoyé avec ce paquet, le HTML sera :",[192,57699,57701],{"className":590,"code":57700,"language":592,"meta":13,"style":13},"\u003Cp> Bonjour le monde ! \u003C\u002Fp>\n",[156,57702,57703],{"__ignoreMap":13},[200,57704,57705,57707,57709,57711,57713,57715,57717],{"class":202,"line":203},[200,57706,599],{"class":214},[200,57708,35],{"class":235},[200,57710,674],{"class":214},[200,57712,57643],{"class":210},[200,57714,680],{"class":214},[200,57716,35],{"class":235},[200,57718,647],{"class":214},[35,57720,57721],{},"C'est parfait !",[192,57723,57725],{"className":1380,"code":57724,"language":1383,"meta":13,"style":13},"await useDrizzle().insert(tables.templates).values({\n  description: body.description ? sanitizeHtml(body.description) : null,\n})\n",[156,57726,57727,57753,57781],{"__ignoreMap":13},[200,57728,57729,57731,57733,57735,57737,57739,57741,57743,57745,57747,57749,57751],{"class":202,"line":203},[200,57730,207],{"class":206},[200,57732,47338],{"class":223},[200,57734,1549],{"class":210},[200,57736,215],{"class":214},[200,57738,47525],{"class":223},[200,57740,51050],{"class":210},[200,57742,215],{"class":214},[200,57744,56173],{"class":210},[200,57746,215],{"class":214},[200,57748,47540],{"class":223},[200,57750,227],{"class":210},[200,57752,230],{"class":214},[200,57754,57755,57757,57759,57761,57763,57765,57767,57770,57772,57774,57777,57779],{"class":202,"line":14},[200,57756,258],{"class":235},[200,57758,239],{"class":214},[200,57760,40809],{"class":210},[200,57762,215],{"class":214},[200,57764,3833],{"class":210},[200,57766,27725],{"class":214},[200,57768,57769],{"class":223}," sanitizeHtml",[200,57771,57030],{"class":210},[200,57773,215],{"class":214},[200,57775,57776],{"class":210},"description) ",[200,57778,239],{"class":214},[200,57780,11911],{"class":214},[200,57782,57783,57785],{"class":202,"line":255},[200,57784,413],{"class":214},[200,57786,416],{"class":210},[35,57788,15736,57789,57792,57793,57795],{},[156,57790,57791],{},"sanitizeHtml"," est l'exportation par défaut du paquet ",[156,57794,57696],{},". Facile, non ?",[1929,57797,57799],{"id":57798},"mise-à-jour-du-schéma","Mise à jour du schéma",[35,57801,57802,57803,57805,57806,57808],{},"Maintenant, je dois mettre à jour le schéma pour supprimer la colonne ",[156,57804,56975],{}," puisque la ",[156,57807,3597],{}," est maintenant par défaut en HTML. Il est important de savoir que si l'utilisateur doit modifier son modèle, le HTML sera utilisé par l'éditeur Quill.",[192,57810,57813],{"className":57811,"code":57812,"language":1383,"meta":13,"style":13},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight has-diff","export const templates = sqliteTable('templates', {\n  description: text('description'),\n  descriptionHTML: text('description_html'), \u002F\u002F [!code --]\n})\n",[156,57814,57815,57839,57859,57882],{"__ignoreMap":13},[200,57816,57817,57819,57821,57823,57825,57827,57829,57831,57833,57835,57837],{"class":202,"line":203},[200,57818,1390],{"class":206},[200,57820,14305],{"class":379},[200,57822,52772],{"class":210},[200,57824,449],{"class":214},[200,57826,50817],{"class":223},[200,57828,227],{"class":210},[200,57830,249],{"class":214},[200,57832,51600],{"class":245},[200,57834,249],{"class":214},[200,57836,549],{"class":214},[200,57838,280],{"class":214},[200,57840,57841,57843,57845,57847,57849,57851,57853,57855,57857],{"class":202,"line":14},[200,57842,258],{"class":235},[200,57844,239],{"class":214},[200,57846,50879],{"class":223},[200,57848,227],{"class":210},[200,57850,249],{"class":214},[200,57852,3597],{"class":245},[200,57854,249],{"class":214},[200,57856,2104],{"class":210},[200,57858,252],{"class":214},[200,57860,57863,57865,57867,57869,57871,57873,57876,57878,57880],{"class":57861,"line":255},[202,57862,37151],"diff",[200,57864,57126],{"class":235},[200,57866,239],{"class":214},[200,57868,50879],{"class":223},[200,57870,227],{"class":210},[200,57872,249],{"class":214},[200,57874,57875],{"class":245},"description_html",[200,57877,249],{"class":214},[200,57879,2104],{"class":210},[200,57881,549],{"class":214},[200,57883,57884,57886],{"class":202,"line":272},[200,57885,413],{"class":214},[200,57887,416],{"class":210},[1929,57889,57891],{"id":57890},"mise-à-jour-de-linterface-utilisateur","Mise à jour de l'interface utilisateur",[35,57893,57894],{},"L'utilisation de Quill n'est pas si différente de EasyMDE. Je charge paresseusement le paquet Quill, crée une nouvelle instance de celui-ci et écoute les changements. Je n'oublie pas d'importer le fichier CSS.",[192,57896,57898],{"className":21457,"code":57897,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nimport 'quill\u002Fdist\u002Fquill.snow.css'\n\nonBeforeMount(async () => {\n  const Quill = await import('quill').then(m => m.default)\n  quill.value = new Quill('#editor', {\n    theme: 'snow',\n  })\n  quill.value?.on('text-change', (_, __, source) => {\n    state.description = quill.value?.root.innerHTML ?? ''\n  })\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv id=\"editor\" \u002F>\n\u003C\u002Ftemplate>\n",[156,57899,57900,57920,57931,57935,57949,57991,58019,58035,58041,58084,58114,58120,58126,58134,58138,58146,58165],{"__ignoreMap":13},[200,57901,57902,57904,57906,57908,57910,57912,57914,57916,57918],{"class":202,"line":203},[200,57903,599],{"class":214},[200,57905,14896],{"class":235},[200,57907,14594],{"class":379},[200,57909,449],{"class":214},[200,57911,610],{"class":214},[200,57913,1383],{"class":245},[200,57915,610],{"class":214},[200,57917,21825],{"class":379},[200,57919,647],{"class":214},[200,57921,57922,57924,57926,57929],{"class":202,"line":14},[200,57923,972],{"class":206},[200,57925,242],{"class":214},[200,57927,57928],{"class":245},"quill\u002Fdist\u002Fquill.snow.css",[200,57930,991],{"class":214},[200,57932,57933],{"class":202,"line":255},[200,57934,528],{"emptyLinePlaceholder":20},[200,57936,57937,57939,57941,57943,57945,57947],{"class":202,"line":272},[200,57938,57240],{"class":223},[200,57940,227],{"class":210},[200,57942,3726],{"class":379},[200,57944,3729],{"class":214},[200,57946,393],{"class":379},[200,57948,280],{"class":214},[200,57950,57951,57953,57956,57958,57960,57962,57964,57966,57969,57971,57973,57975,57977,57979,57981,57983,57985,57987,57989],{"class":202,"line":283},[200,57952,3738],{"class":379},[200,57954,57955],{"class":210}," Quill",[200,57957,509],{"class":214},[200,57959,452],{"class":206},[200,57961,27446],{"class":214},[200,57963,227],{"class":235},[200,57965,249],{"class":214},[200,57967,57968],{"class":245},"quill",[200,57970,249],{"class":214},[200,57972,2104],{"class":235},[200,57974,215],{"class":214},[200,57976,10944],{"class":223},[200,57978,227],{"class":235},[200,57980,57293],{"class":386},[200,57982,393],{"class":379},[200,57984,57298],{"class":210},[200,57986,215],{"class":214},[200,57988,51912],{"class":210},[200,57990,416],{"class":235},[200,57992,57993,57996,57998,58000,58002,58004,58006,58008,58010,58013,58015,58017],{"class":202,"line":300},[200,57994,57995],{"class":210},"  quill",[200,57997,215],{"class":214},[200,57999,15840],{"class":210},[200,58001,509],{"class":214},[200,58003,13007],{"class":214},[200,58005,57955],{"class":223},[200,58007,227],{"class":235},[200,58009,249],{"class":214},[200,58011,58012],{"class":245},"#editor",[200,58014,249],{"class":214},[200,58016,549],{"class":214},[200,58018,280],{"class":214},[200,58020,58021,58024,58026,58028,58031,58033],{"class":202,"line":310},[200,58022,58023],{"class":235},"    theme",[200,58025,239],{"class":214},[200,58027,242],{"class":214},[200,58029,58030],{"class":245},"snow",[200,58032,249],{"class":214},[200,58034,252],{"class":214},[200,58036,58037,58039],{"class":202,"line":336},[200,58038,5095],{"class":214},[200,58040,416],{"class":235},[200,58042,58043,58045,58047,58049,58051,58053,58055,58057,58060,58062,58064,58066,58068,58070,58073,58075,58078,58080,58082],{"class":202,"line":342},[200,58044,57995],{"class":210},[200,58046,215],{"class":214},[200,58048,15840],{"class":210},[200,58050,6536],{"class":214},[200,58052,25410],{"class":223},[200,58054,227],{"class":235},[200,58056,249],{"class":214},[200,58058,58059],{"class":245},"text-change",[200,58061,249],{"class":214},[200,58063,549],{"class":214},[200,58065,1094],{"class":214},[200,58067,19215],{"class":386},[200,58069,549],{"class":214},[200,58071,58072],{"class":386}," __",[200,58074,549],{"class":214},[200,58076,58077],{"class":386}," source",[200,58079,2104],{"class":214},[200,58081,393],{"class":379},[200,58083,280],{"class":214},[200,58085,58086,58088,58090,58092,58094,58097,58099,58101,58103,58106,58108,58110,58112],{"class":202,"line":365},[200,58087,27151],{"class":210},[200,58089,215],{"class":214},[200,58091,3597],{"class":210},[200,58093,509],{"class":214},[200,58095,58096],{"class":210}," quill",[200,58098,215],{"class":214},[200,58100,15840],{"class":210},[200,58102,6536],{"class":214},[200,58104,58105],{"class":210},"root",[200,58107,215],{"class":214},[200,58109,15970],{"class":210},[200,58111,3906],{"class":214},[200,58113,18888],{"class":214},[200,58115,58116,58118],{"class":202,"line":371},[200,58117,5095],{"class":214},[200,58119,416],{"class":235},[200,58121,58122,58124],{"class":202,"line":398},[200,58123,413],{"class":214},[200,58125,416],{"class":210},[200,58127,58128,58130,58132],{"class":202,"line":405},[200,58129,680],{"class":214},[200,58131,14896],{"class":235},[200,58133,647],{"class":214},[200,58135,58136],{"class":202,"line":410},[200,58137,528],{"emptyLinePlaceholder":20},[200,58139,58140,58142,58144],{"class":202,"line":860},[200,58141,599],{"class":214},[200,58143,21468],{"class":235},[200,58145,647],{"class":214},[200,58147,58148,58150,58152,58154,58156,58158,58161,58163],{"class":202,"line":889},[200,58149,656],{"class":214},[200,58151,11400],{"class":235},[200,58153,3239],{"class":379},[200,58155,449],{"class":214},[200,58157,610],{"class":214},[200,58159,58160],{"class":245},"editor",[200,58162,610],{"class":214},[200,58164,21527],{"class":214},[200,58166,58167,58169,58171],{"class":202,"line":899},[200,58168,680],{"class":214},[200,58170,21468],{"class":235},[200,58172,647],{"class":214},[35,58174,58175],{},"Bien sûr, je dois ajouter quelques styles personnalisés à l'éditeur pour qu'il s'intègre plus naturellement dans le design.",[35,58177,58178],{},"Une dernière chose. J'utilise ceci dans un formulaire Nuxt UI. Dans ce formulaire, chaque élément est validé par rapport au schéma. Cela me permet d'afficher un message d'erreur si la description est vide ou trop longue. Le problème est que ce n'est pas intégré dans l'éditeur Quill, il ne déclenche aucune validation. Grâce à l'API du formulaire Nuxt UI, je peux facilement valider le contenu de l'éditeur Quill à chaque changement.",[192,58180,58182],{"className":21457,"code":58181,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst form = ref()\n\nonBeforeMount(async () => {\n  quill.value?.on('text-change', (_, __, source) => {\n    state.description = quill.value?.root.innerHTML ?? ''\n    form.value.validate('description', { silent: true })\n  })\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUFormGroup\n    label=\"Description\"\n    name=\"description\"\n    :hint=\"`${state.description?.length || 0}\u002F2000 characters`\"\n  >\n    \u003Cdiv id=\"editor\" \u002F>\n  \u003C\u002FUFormGroup>\n\u003C\u002Ftemplate>\n",[156,58183,58184,58204,58217,58221,58235,58275,58303,58340,58346,58352,58360,58364,58372,58379,58392,58404,58438,58442,58460,58468],{"__ignoreMap":13},[200,58185,58186,58188,58190,58192,58194,58196,58198,58200,58202],{"class":202,"line":203},[200,58187,599],{"class":214},[200,58189,14896],{"class":235},[200,58191,14594],{"class":379},[200,58193,449],{"class":214},[200,58195,610],{"class":214},[200,58197,1383],{"class":245},[200,58199,610],{"class":214},[200,58201,21825],{"class":379},[200,58203,647],{"class":214},[200,58205,58206,58208,58211,58213,58215],{"class":202,"line":14},[200,58207,443],{"class":379},[200,58209,58210],{"class":210}," form ",[200,58212,449],{"class":214},[200,58214,18698],{"class":223},[200,58216,466],{"class":210},[200,58218,58219],{"class":202,"line":255},[200,58220,528],{"emptyLinePlaceholder":20},[200,58222,58223,58225,58227,58229,58231,58233],{"class":202,"line":272},[200,58224,57240],{"class":223},[200,58226,227],{"class":210},[200,58228,3726],{"class":379},[200,58230,3729],{"class":214},[200,58232,393],{"class":379},[200,58234,280],{"class":214},[200,58236,58237,58239,58241,58243,58245,58247,58249,58251,58253,58255,58257,58259,58261,58263,58265,58267,58269,58271,58273],{"class":202,"line":283},[200,58238,57995],{"class":210},[200,58240,215],{"class":214},[200,58242,15840],{"class":210},[200,58244,6536],{"class":214},[200,58246,25410],{"class":223},[200,58248,227],{"class":235},[200,58250,249],{"class":214},[200,58252,58059],{"class":245},[200,58254,249],{"class":214},[200,58256,549],{"class":214},[200,58258,1094],{"class":214},[200,58260,19215],{"class":386},[200,58262,549],{"class":214},[200,58264,58072],{"class":386},[200,58266,549],{"class":214},[200,58268,58077],{"class":386},[200,58270,2104],{"class":214},[200,58272,393],{"class":379},[200,58274,280],{"class":214},[200,58276,58277,58279,58281,58283,58285,58287,58289,58291,58293,58295,58297,58299,58301],{"class":202,"line":300},[200,58278,27151],{"class":210},[200,58280,215],{"class":214},[200,58282,3597],{"class":210},[200,58284,509],{"class":214},[200,58286,58096],{"class":210},[200,58288,215],{"class":214},[200,58290,15840],{"class":210},[200,58292,6536],{"class":214},[200,58294,58105],{"class":210},[200,58296,215],{"class":214},[200,58298,15970],{"class":210},[200,58300,3906],{"class":214},[200,58302,18888],{"class":214},[200,58304,58305,58308,58310,58312,58314,58317,58319,58321,58323,58325,58327,58329,58332,58334,58336,58338],{"class":202,"line":310},[200,58306,58307],{"class":210},"    form",[200,58309,215],{"class":214},[200,58311,15840],{"class":210},[200,58313,215],{"class":214},[200,58315,58316],{"class":223},"validate",[200,58318,227],{"class":235},[200,58320,249],{"class":214},[200,58322,3597],{"class":245},[200,58324,249],{"class":214},[200,58326,549],{"class":214},[200,58328,318],{"class":214},[200,58330,58331],{"class":235}," silent",[200,58333,239],{"class":214},[200,58335,2439],{"class":2438},[200,58337,980],{"class":214},[200,58339,416],{"class":235},[200,58341,58342,58344],{"class":202,"line":336},[200,58343,5095],{"class":214},[200,58345,416],{"class":235},[200,58347,58348,58350],{"class":202,"line":342},[200,58349,413],{"class":214},[200,58351,416],{"class":210},[200,58353,58354,58356,58358],{"class":202,"line":365},[200,58355,680],{"class":214},[200,58357,14896],{"class":235},[200,58359,647],{"class":214},[200,58361,58362],{"class":202,"line":371},[200,58363,528],{"emptyLinePlaceholder":20},[200,58365,58366,58368,58370],{"class":202,"line":398},[200,58367,599],{"class":214},[200,58369,21468],{"class":235},[200,58371,647],{"class":214},[200,58373,58374,58376],{"class":202,"line":405},[200,58375,656],{"class":214},[200,58377,58378],{"class":235},"UFormGroup\n",[200,58380,58381,58384,58386,58388,58390],{"class":202,"line":410},[200,58382,58383],{"class":379},"    label",[200,58385,449],{"class":214},[200,58387,610],{"class":214},[200,58389,54406],{"class":245},[200,58391,616],{"class":214},[200,58393,58394,58396,58398,58400,58402],{"class":202,"line":860},[200,58395,11812],{"class":379},[200,58397,449],{"class":214},[200,58399,610],{"class":214},[200,58401,3597],{"class":245},[200,58403,616],{"class":214},[200,58405,58406,58408,58411,58413,58416,58418,58420,58422,58424,58426,58428,58430,58432,58435],{"class":202,"line":889},[200,58407,23865],{"class":214},[200,58409,58410],{"class":379},"hint",[200,58412,449],{"class":214},[200,58414,58415],{"class":214},"\"`${",[200,58417,26875],{"class":210},[200,58419,215],{"class":214},[200,58421,3597],{"class":210},[200,58423,6536],{"class":214},[200,58425,51183],{"class":210},[200,58427,14985],{"class":214},[200,58429,51208],{"class":2060},[200,58431,413],{"class":214},[200,58433,58434],{"class":245},"\u002F2000 characters",[200,58436,58437],{"class":214},"`\"\n",[200,58439,58440],{"class":202,"line":899},[200,58441,11550],{"class":214},[200,58443,58444,58446,58448,58450,58452,58454,58456,58458],{"class":202,"line":904},[200,58445,803],{"class":214},[200,58447,11400],{"class":235},[200,58449,3239],{"class":379},[200,58451,449],{"class":214},[200,58453,610],{"class":214},[200,58455,58160],{"class":245},[200,58457,610],{"class":214},[200,58459,21527],{"class":214},[200,58461,58462,58464,58466],{"class":202,"line":930},[200,58463,892],{"class":214},[200,58465,48537],{"class":235},[200,58467,647],{"class":214},[200,58469,58470,58472,58474],{"class":202,"line":3655},[200,58471,680],{"class":214},[200,58473,21468],{"class":235},[200,58475,647],{"class":214},[35,58477,58478],{},"Maintenant, l'éditeur Quill est complètement intégré dans le formulaire et un message d'erreur s'affichera si la description est vide ou trop longue.",[35,58480,58481],{},[1707,58482],{"alt":58483,"src":58484},"L'éditeur de texte enrichi personnalisé pour s'intégrer pleinement dans le formulaire.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Frich-text-editor.png",[1929,58486,24146],{"id":58487},"conclusion-3",[35,58489,58490],{},"Je suis vraiment content de ce changement. Depuis des années, je me demandais comment installer et utiliser un éditeur de texte enrichi et maintenant je sais. Vous pouvez utiliser le markdown pour les utilisateurs techniques, le parser sur le serveur et stocker le HTML pour simplifier une obtention, ou utiliser un éditeur de texte enrichi pour les utilisateurs non techniques et nettoyer le HTML sur le serveur. C'est une bonne leçon pour moi et j'espère qu'elle vous sera utile.",[51,58492,58494],{"id":58493},"ajout-de-modules-au-formulaire-de-modèle","Ajout de modules au formulaire de modèle",[35,58496,58497],{},[1707,58498],{"alt":58499,"src":58500},"Vignette de mon neuvième live stream.","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-28.png",[35,58502,58503],{},[1216,58504,58505,47101,58508,47105,58511,45894,58514,215],{},[39,58506,38397],{"href":45853,"rel":58507},[77],[39,58509,45889],{"href":18579,"rel":58510},[77],[39,58512,19427],{"href":45844,"rel":58513},[77],[39,58515,11636],{"href":45853,"rel":58516},[77],[35,58518,58519],{},"Pendant ce live stream, j'ai ajouté la possibilité d'ajouter des modules à un modèle dans le formulaire. Cette fonctionnalité est importante car elle permettra aux utilisateurs de filtrer les modèles par modules.",[35,58521,58522],{},"J'ai choisi de le faire dans un stream séparé car c'est une relation de plusieurs à plusieurs et un peu plus complexe que les autres parties. Je m'attendais à le faire en 1 heure, mais cela m'a pris 2 heures avec l'aide du chat. En effet, j'étais bloqué sur la requête SQL pour récupérer un modèle peuplé avec ses modules, et la documentation de Drizzle n'était pas claire à ce sujet.",[35,58524,58525],{},"Plongeons dans le code et comprenons comment fonctionne le concept des relations ORM de Drizzle.",[1929,58527,58529],{"id":58528},"schéma","Schéma",[35,58531,58532,58533],{},"Le créateur peut ajouter plusieurs modules à un modèle. En même temps, un module peut être utilisé par plusieurs modèles. La relation est une relation de plusieurs à plusieurs. Pour gérer cela, je dois créer une nouvelle table, appelée table de pivot, pour stocker la relation entre les modèles et les modules. Elle agit comme un lien entre les deux tables pour les connecter. ",[1216,58534,58535],{},"Nous utilisons une table de pivot au lieu d'un tableau de modules dans le modèle parce que c'est plus efficace et c'est la façon SQL de le faire.",[35,58537,58538],{},[1707,58539],{"alt":58540,"src":58541},"Relation de plusieurs à plusieurs entre les modèles et les modules","\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fmany-to-many-relation.png",[35,58543,58544,58545,58547,58548,215],{},"Le schéma est simplifié pour montrer uniquement la relation entre les modèles et les modules. À partir de la table ",[156,58546,51600],{},", vous pouvez récupérer tous les modules associés et inversement grâce à la table ",[156,58549,58550],{},"templates_modules",[192,58552,58554],{"className":1380,"code":58553,"language":1383,"meta":13,"style":13},"export const templates = sqliteTable('templates', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  title: text('title').notNull(),\n})\n\nexport const modules = sqliteTable('modules', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  name: text('name').notNull().unique(),\n})\n\nexport const modulesToTemplates = sqliteTable('modulesToTemplates', {\n  moduleId: integer('module_id').notNull().references(() => modules.id),\n  templateId: integer('template_id').notNull().references(() => templates.id),\n})\n",[156,58555,58556,58580,58618,58644,58650,58654,58678,58716,58748,58754,58758,58784,58828,58872],{"__ignoreMap":13},[200,58557,58558,58560,58562,58564,58566,58568,58570,58572,58574,58576,58578],{"class":202,"line":203},[200,58559,1390],{"class":206},[200,58561,14305],{"class":379},[200,58563,52772],{"class":210},[200,58565,449],{"class":214},[200,58567,50817],{"class":223},[200,58569,227],{"class":210},[200,58571,249],{"class":214},[200,58573,51600],{"class":245},[200,58575,249],{"class":214},[200,58577,549],{"class":214},[200,58579,280],{"class":214},[200,58581,58582,58584,58586,58588,58590,58592,58594,58596,58598,58600,58602,58604,58606,58608,58610,58612,58614,58616],{"class":202,"line":14},[200,58583,49638],{"class":235},[200,58585,239],{"class":214},[200,58587,50838],{"class":223},[200,58589,227],{"class":210},[200,58591,249],{"class":214},[200,58593,3295],{"class":245},[200,58595,249],{"class":214},[200,58597,2104],{"class":210},[200,58599,215],{"class":214},[200,58601,50853],{"class":223},[200,58603,227],{"class":210},[200,58605,2767],{"class":214},[200,58607,50860],{"class":235},[200,58609,239],{"class":214},[200,58611,2439],{"class":2438},[200,58613,980],{"class":214},[200,58615,2104],{"class":210},[200,58617,252],{"class":214},[200,58619,58620,58622,58624,58626,58628,58630,58632,58634,58636,58638,58640,58642],{"class":202,"line":255},[200,58621,23703],{"class":235},[200,58623,239],{"class":214},[200,58625,50879],{"class":223},[200,58627,227],{"class":210},[200,58629,249],{"class":214},[200,58631,3581],{"class":245},[200,58633,249],{"class":214},[200,58635,2104],{"class":210},[200,58637,215],{"class":214},[200,58639,50894],{"class":223},[200,58641,1549],{"class":210},[200,58643,252],{"class":214},[200,58645,58646,58648],{"class":202,"line":272},[200,58647,413],{"class":214},[200,58649,416],{"class":210},[200,58651,58652],{"class":202,"line":283},[200,58653,528],{"emptyLinePlaceholder":20},[200,58655,58656,58658,58660,58662,58664,58666,58668,58670,58672,58674,58676],{"class":202,"line":300},[200,58657,1390],{"class":206},[200,58659,14305],{"class":379},[200,58661,50674],{"class":210},[200,58663,449],{"class":214},[200,58665,50817],{"class":223},[200,58667,227],{"class":210},[200,58669,249],{"class":214},[200,58671,2476],{"class":245},[200,58673,249],{"class":214},[200,58675,549],{"class":214},[200,58677,280],{"class":214},[200,58679,58680,58682,58684,58686,58688,58690,58692,58694,58696,58698,58700,58702,58704,58706,58708,58710,58712,58714],{"class":202,"line":310},[200,58681,49638],{"class":235},[200,58683,239],{"class":214},[200,58685,50838],{"class":223},[200,58687,227],{"class":210},[200,58689,249],{"class":214},[200,58691,3295],{"class":245},[200,58693,249],{"class":214},[200,58695,2104],{"class":210},[200,58697,215],{"class":214},[200,58699,50853],{"class":223},[200,58701,227],{"class":210},[200,58703,2767],{"class":214},[200,58705,50860],{"class":235},[200,58707,239],{"class":214},[200,58709,2439],{"class":2438},[200,58711,980],{"class":214},[200,58713,2104],{"class":210},[200,58715,252],{"class":214},[200,58717,58718,58720,58722,58724,58726,58728,58730,58732,58734,58736,58738,58740,58742,58744,58746],{"class":202,"line":336},[200,58719,236],{"class":235},[200,58721,239],{"class":214},[200,58723,50879],{"class":223},[200,58725,227],{"class":210},[200,58727,249],{"class":214},[200,58729,20112],{"class":245},[200,58731,249],{"class":214},[200,58733,2104],{"class":210},[200,58735,215],{"class":214},[200,58737,50894],{"class":223},[200,58739,1549],{"class":210},[200,58741,215],{"class":214},[200,58743,50901],{"class":223},[200,58745,1549],{"class":210},[200,58747,252],{"class":214},[200,58749,58750,58752],{"class":202,"line":342},[200,58751,413],{"class":214},[200,58753,416],{"class":210},[200,58755,58756],{"class":202,"line":365},[200,58757,528],{"emptyLinePlaceholder":20},[200,58759,58760,58762,58764,58767,58769,58771,58773,58775,58778,58780,58782],{"class":202,"line":371},[200,58761,1390],{"class":206},[200,58763,14305],{"class":379},[200,58765,58766],{"class":210}," modulesToTemplates ",[200,58768,449],{"class":214},[200,58770,50817],{"class":223},[200,58772,227],{"class":210},[200,58774,249],{"class":214},[200,58776,58777],{"class":245},"modulesToTemplates",[200,58779,249],{"class":214},[200,58781,549],{"class":214},[200,58783,280],{"class":214},[200,58785,58786,58789,58791,58793,58795,58797,58800,58802,58804,58806,58808,58810,58812,58814,58816,58818,58820,58822,58824,58826],{"class":202,"line":398},[200,58787,58788],{"class":235},"  moduleId",[200,58790,239],{"class":214},[200,58792,50838],{"class":223},[200,58794,227],{"class":210},[200,58796,249],{"class":214},[200,58798,58799],{"class":245},"module_id",[200,58801,249],{"class":214},[200,58803,2104],{"class":210},[200,58805,215],{"class":214},[200,58807,50894],{"class":223},[200,58809,1549],{"class":210},[200,58811,215],{"class":214},[200,58813,53171],{"class":223},[200,58815,227],{"class":210},[200,58817,1549],{"class":214},[200,58819,393],{"class":379},[200,58821,51242],{"class":210},[200,58823,215],{"class":214},[200,58825,53184],{"class":210},[200,58827,252],{"class":214},[200,58829,58830,58833,58835,58837,58839,58841,58844,58846,58848,58850,58852,58854,58856,58858,58860,58862,58864,58866,58868,58870],{"class":202,"line":405},[200,58831,58832],{"class":235},"  templateId",[200,58834,239],{"class":214},[200,58836,50838],{"class":223},[200,58838,227],{"class":210},[200,58840,249],{"class":214},[200,58842,58843],{"class":245},"template_id",[200,58845,249],{"class":214},[200,58847,2104],{"class":210},[200,58849,215],{"class":214},[200,58851,50894],{"class":223},[200,58853,1549],{"class":210},[200,58855,215],{"class":214},[200,58857,53171],{"class":223},[200,58859,227],{"class":210},[200,58861,1549],{"class":214},[200,58863,393],{"class":379},[200,58865,53926],{"class":210},[200,58867,215],{"class":214},[200,58869,53184],{"class":210},[200,58871,252],{"class":214},[200,58873,58874,58876],{"class":202,"line":410},[200,58875,413],{"class":214},[200,58877,416],{"class":210},[1929,58879,58881],{"id":58880},"formulaire-de-modules","Formulaire de modules",[35,58883,58884],{},"Dans cette partie, je vais ajouter les modules au formulaire, du côté client au côté serveur.",[38437,58886,58888],{"id":58887},"ajout-de-modules-au-modèle","Ajout de modules au modèle",[35,58890,58891,58892,58895],{},"Dans l'interface utilisateur, gérer les modules est assez simple. Je récupère les modules du serveur et les affiche dans un ",[156,58893,58894],{},"USelectMenu"," avec une prop multiple.",[192,58897,58899],{"className":21457,"code":58898,"language":12075,"meta":13,"style":13},"\u003Cscript lang=\"ts\" setup>\nconst { data: modules } = await useFetch('\u002Fapi\u002Fmodules', {\n  deep: false,\n  default: () => [],\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUSelectMenu\n    :options=\"modules\"\n    multiple\n    value-attribute=\"id\"\n    option-attribute=\"name\"\n  \u002F>\n\u003C\u002Ftemplate>\n",[156,58900,58901,58921,58954,58964,58978,58984,58992,58996,59004,59011,59025,59030,59043,59056,59060],{"__ignoreMap":13},[200,58902,58903,58905,58907,58909,58911,58913,58915,58917,58919],{"class":202,"line":203},[200,58904,599],{"class":214},[200,58906,14896],{"class":235},[200,58908,14594],{"class":379},[200,58910,449],{"class":214},[200,58912,610],{"class":214},[200,58914,1383],{"class":245},[200,58916,610],{"class":214},[200,58918,21825],{"class":379},[200,58920,647],{"class":214},[200,58922,58923,58925,58927,58929,58931,58933,58935,58937,58939,58941,58943,58945,58948,58950,58952],{"class":202,"line":14},[200,58924,443],{"class":379},[200,58926,318],{"class":214},[200,58928,5056],{"class":235},[200,58930,239],{"class":214},[200,58932,50674],{"class":210},[200,58934,413],{"class":214},[200,58936,509],{"class":214},[200,58938,452],{"class":206},[200,58940,47935],{"class":223},[200,58942,227],{"class":210},[200,58944,249],{"class":214},[200,58946,58947],{"class":245},"\u002Fapi\u002Fmodules",[200,58949,249],{"class":214},[200,58951,549],{"class":214},[200,58953,280],{"class":214},[200,58955,58956,58958,58960,58962],{"class":202,"line":255},[200,58957,47953],{"class":235},[200,58959,239],{"class":214},[200,58961,16169],{"class":2438},[200,58963,252],{"class":214},[200,58965,58966,58968,58970,58972,58974,58976],{"class":202,"line":272},[200,58967,50461],{"class":223},[200,58969,239],{"class":214},[200,58971,3729],{"class":214},[200,58973,393],{"class":379},[200,58975,27681],{"class":210},[200,58977,252],{"class":214},[200,58979,58980,58982],{"class":202,"line":283},[200,58981,413],{"class":214},[200,58983,416],{"class":210},[200,58985,58986,58988,58990],{"class":202,"line":300},[200,58987,680],{"class":214},[200,58989,14896],{"class":235},[200,58991,647],{"class":214},[200,58993,58994],{"class":202,"line":310},[200,58995,528],{"emptyLinePlaceholder":20},[200,58997,58998,59000,59002],{"class":202,"line":336},[200,58999,599],{"class":214},[200,59001,21468],{"class":235},[200,59003,647],{"class":214},[200,59005,59006,59008],{"class":202,"line":342},[200,59007,656],{"class":214},[200,59009,59010],{"class":235},"USelectMenu\n",[200,59012,59013,59015,59017,59019,59021,59023],{"class":202,"line":365},[200,59014,23865],{"class":214},[200,59016,29900],{"class":379},[200,59018,449],{"class":214},[200,59020,610],{"class":214},[200,59022,2476],{"class":210},[200,59024,616],{"class":214},[200,59026,59027],{"class":202,"line":371},[200,59028,59029],{"class":379},"    multiple\n",[200,59031,59032,59035,59037,59039,59041],{"class":202,"line":398},[200,59033,59034],{"class":379},"    value-attribute",[200,59036,449],{"class":214},[200,59038,610],{"class":214},[200,59040,3295],{"class":245},[200,59042,616],{"class":214},[200,59044,59045,59048,59050,59052,59054],{"class":202,"line":405},[200,59046,59047],{"class":379},"    option-attribute",[200,59049,449],{"class":214},[200,59051,610],{"class":214},[200,59053,20112],{"class":245},[200,59055,616],{"class":214},[200,59057,59058],{"class":202,"line":410},[200,59059,23365],{"class":214},[200,59061,59062,59064,59066],{"class":202,"line":860},[200,59063,680],{"class":214},[200,59065,21468],{"class":235},[200,59067,647],{"class":214},[38437,59069,59071],{"id":59070},"validation-des-modules","Validation des modules",[35,59073,59074,59075,59078],{},"Dans le validateur, j'ajoute un nouveau champ ",[156,59076,59077],{},"moduleIds"," qui vérifie que les modules sont un tableau d'entiers.",[192,59080,59082],{"className":1380,"code":59081,"language":1383,"meta":13,"style":13},"import { array, number, object, string } from 'zod'\n\nexport const createTemplateValidator = object({\n  title: string({ message: 'Requis' }),\n  moduleIds: array(number()).max(6, { message: 'Max 6 modules' }).optional(),\n})\n",[156,59083,59084,59115,59119,59136,59164,59217],{"__ignoreMap":13},[200,59085,59086,59088,59090,59093,59095,59097,59099,59101,59103,59105,59107,59109,59111,59113],{"class":202,"line":203},[200,59087,972],{"class":206},[200,59089,318],{"class":214},[200,59091,59092],{"class":210}," array",[200,59094,549],{"class":214},[200,59096,20155],{"class":210},[200,59098,549],{"class":214},[200,59100,42642],{"class":210},[200,59102,549],{"class":214},[200,59104,22752],{"class":210},[200,59106,980],{"class":214},[200,59108,983],{"class":206},[200,59110,242],{"class":214},[200,59112,19813],{"class":245},[200,59114,991],{"class":214},[200,59116,59117],{"class":202,"line":14},[200,59118,528],{"emptyLinePlaceholder":20},[200,59120,59121,59123,59125,59128,59130,59132,59134],{"class":202,"line":255},[200,59122,1390],{"class":206},[200,59124,14305],{"class":379},[200,59126,59127],{"class":210}," createTemplateValidator ",[200,59129,449],{"class":214},[200,59131,42642],{"class":223},[200,59133,227],{"class":210},[200,59135,230],{"class":214},[200,59137,59138,59140,59142,59144,59146,59148,59150,59152,59154,59156,59158,59160,59162],{"class":202,"line":272},[200,59139,23703],{"class":235},[200,59141,239],{"class":214},[200,59143,22752],{"class":223},[200,59145,227],{"class":210},[200,59147,2767],{"class":214},[200,59149,39109],{"class":235},[200,59151,239],{"class":214},[200,59153,242],{"class":214},[200,59155,48309],{"class":245},[200,59157,249],{"class":214},[200,59159,980],{"class":214},[200,59161,2104],{"class":210},[200,59163,252],{"class":214},[200,59165,59166,59169,59171,59173,59175,59178,59181,59183,59185,59187,59190,59192,59194,59196,59198,59200,59203,59205,59207,59209,59211,59213,59215],{"class":202,"line":283},[200,59167,59168],{"class":235},"  moduleIds",[200,59170,239],{"class":214},[200,59172,59092],{"class":223},[200,59174,227],{"class":210},[200,59176,59177],{"class":223},"number",[200,59179,59180],{"class":210},"())",[200,59182,215],{"class":214},[200,59184,4138],{"class":223},[200,59186,227],{"class":210},[200,59188,59189],{"class":2060},"6",[200,59191,549],{"class":214},[200,59193,318],{"class":214},[200,59195,39109],{"class":235},[200,59197,239],{"class":214},[200,59199,242],{"class":214},[200,59201,59202],{"class":245},"Max 6 modules",[200,59204,249],{"class":214},[200,59206,980],{"class":214},[200,59208,2104],{"class":210},[200,59210,215],{"class":214},[200,59212,53587],{"class":223},[200,59214,1549],{"class":210},[200,59216,252],{"class":214},[200,59218,59219,59221],{"class":202,"line":300},[200,59220,413],{"class":214},[200,59222,416],{"class":210},[35,59224,59225],{},"J'ai arbitrairement fixé le nombre maximum de modules à 6. C'est un nombre que je pense suffisant pour un modèle.",[38437,59227,59229],{"id":59228},"enregistrement-des-modules","Enregistrement des modules",[35,59231,59232],{},"Enfin, je mets à jour le point de terminaison pour enregistrer le modèle et les modules associés. Je commence par enregistrer le modèle, puis les modules dans la table de pivot.",[192,59234,59236],{"className":1380,"code":59235,"language":1383,"meta":13,"style":13},"import { createTemplateValidator } from '~\u002Futils\u002Fvalidators'\n\nexport default defineEventHandler(async (event) => {\n  const { user } = await requireUserSession(event)\n\n  const body = await readValidatedBody(event, createTemplateValidator.parse)\n\n  const [template] = await useDrizzle().insert(tables.templates).values({\n    title: body.title,\n  }).returning({\n    id: tables.templates.id,\n  })\n\n  if (body.moduleIds)\n    await useDrizzle().insert(tables.modulesToTemplates).values(body.moduleIds.map(id => ({ moduleId: id, templateId: template.id }))).execute()\n\n  return sendNoContent(event, 201)\n})\n",[156,59237,59238,59257,59261,59283,59305,59309,59335,59339,59379,59393,59407,59425,59431,59435,59449,59528,59532,59548],{"__ignoreMap":13},[200,59239,59240,59242,59244,59247,59249,59251,59253,59255],{"class":202,"line":203},[200,59241,972],{"class":206},[200,59243,318],{"class":214},[200,59245,59246],{"class":210}," createTemplateValidator",[200,59248,980],{"class":214},[200,59250,983],{"class":206},[200,59252,242],{"class":214},[200,59254,42350],{"class":245},[200,59256,991],{"class":214},[200,59258,59259],{"class":202,"line":14},[200,59260,528],{"emptyLinePlaceholder":20},[200,59262,59263,59265,59267,59269,59271,59273,59275,59277,59279,59281],{"class":202,"line":255},[200,59264,1390],{"class":206},[200,59266,1393],{"class":206},[200,59268,38723],{"class":223},[200,59270,227],{"class":210},[200,59272,3726],{"class":379},[200,59274,1094],{"class":214},[200,59276,25426],{"class":386},[200,59278,2104],{"class":214},[200,59280,393],{"class":379},[200,59282,280],{"class":214},[200,59284,59285,59287,59289,59291,59293,59295,59297,59299,59301,59303],{"class":202,"line":272},[200,59286,3738],{"class":379},[200,59288,318],{"class":214},[200,59290,34533],{"class":210},[200,59292,980],{"class":214},[200,59294,509],{"class":214},[200,59296,452],{"class":206},[200,59298,38997],{"class":223},[200,59300,227],{"class":235},[200,59302,25426],{"class":210},[200,59304,416],{"class":235},[200,59306,59307],{"class":202,"line":283},[200,59308,528],{"emptyLinePlaceholder":20},[200,59310,59311,59313,59315,59317,59319,59321,59323,59325,59327,59329,59331,59333],{"class":202,"line":300},[200,59312,3738],{"class":379},[200,59314,40809],{"class":210},[200,59316,509],{"class":214},[200,59318,452],{"class":206},[200,59320,40951],{"class":223},[200,59322,227],{"class":235},[200,59324,25426],{"class":210},[200,59326,549],{"class":214},[200,59328,59246],{"class":210},[200,59330,215],{"class":214},[200,59332,40994],{"class":210},[200,59334,416],{"class":235},[200,59336,59337],{"class":202,"line":310},[200,59338,528],{"emptyLinePlaceholder":20},[200,59340,59341,59343,59345,59347,59349,59351,59353,59355,59357,59359,59361,59363,59365,59367,59369,59371,59373,59375,59377],{"class":202,"line":336},[200,59342,3738],{"class":379},[200,59344,350],{"class":214},[200,59346,21468],{"class":210},[200,59348,360],{"class":214},[200,59350,509],{"class":214},[200,59352,452],{"class":206},[200,59354,47338],{"class":223},[200,59356,1549],{"class":235},[200,59358,215],{"class":214},[200,59360,47525],{"class":223},[200,59362,227],{"class":235},[200,59364,47418],{"class":210},[200,59366,215],{"class":214},[200,59368,51600],{"class":210},[200,59370,2104],{"class":235},[200,59372,215],{"class":214},[200,59374,47540],{"class":223},[200,59376,227],{"class":235},[200,59378,230],{"class":214},[200,59380,59381,59383,59385,59387,59389,59391],{"class":202,"line":342},[200,59382,21482],{"class":235},[200,59384,239],{"class":214},[200,59386,40809],{"class":210},[200,59388,215],{"class":214},[200,59390,3581],{"class":210},[200,59392,252],{"class":214},[200,59394,59395,59397,59399,59401,59403,59405],{"class":202,"line":365},[200,59396,5095],{"class":214},[200,59398,2104],{"class":235},[200,59400,215],{"class":214},[200,59402,52524],{"class":223},[200,59404,227],{"class":235},[200,59406,230],{"class":214},[200,59408,59409,59411,59413,59415,59417,59419,59421,59423],{"class":202,"line":371},[200,59410,15861],{"class":235},[200,59412,239],{"class":214},[200,59414,47357],{"class":210},[200,59416,215],{"class":214},[200,59418,51600],{"class":210},[200,59420,215],{"class":214},[200,59422,3295],{"class":210},[200,59424,252],{"class":214},[200,59426,59427,59429],{"class":202,"line":398},[200,59428,5095],{"class":214},[200,59430,416],{"class":235},[200,59432,59433],{"class":202,"line":405},[200,59434,528],{"emptyLinePlaceholder":20},[200,59436,59437,59439,59441,59443,59445,59447],{"class":202,"line":410},[200,59438,20098],{"class":206},[200,59440,1094],{"class":235},[200,59442,23914],{"class":210},[200,59444,215],{"class":214},[200,59446,59077],{"class":210},[200,59448,416],{"class":235},[200,59450,59451,59453,59455,59457,59459,59461,59463,59465,59467,59469,59471,59473,59475,59477,59479,59481,59483,59485,59487,59489,59491,59493,59495,59497,59500,59502,59504,59506,59509,59511,59513,59515,59517,59519,59522,59524,59526],{"class":202,"line":860},[200,59452,19231],{"class":206},[200,59454,47338],{"class":223},[200,59456,1549],{"class":235},[200,59458,215],{"class":214},[200,59460,47525],{"class":223},[200,59462,227],{"class":235},[200,59464,47418],{"class":210},[200,59466,215],{"class":214},[200,59468,58777],{"class":210},[200,59470,2104],{"class":235},[200,59472,215],{"class":214},[200,59474,47540],{"class":223},[200,59476,227],{"class":235},[200,59478,23914],{"class":210},[200,59480,215],{"class":214},[200,59482,59077],{"class":210},[200,59484,215],{"class":214},[200,59486,3541],{"class":223},[200,59488,227],{"class":235},[200,59490,3295],{"class":386},[200,59492,393],{"class":379},[200,59494,1094],{"class":235},[200,59496,2767],{"class":214},[200,59498,59499],{"class":235}," moduleId",[200,59501,239],{"class":214},[200,59503,3239],{"class":210},[200,59505,549],{"class":214},[200,59507,59508],{"class":235}," templateId",[200,59510,239],{"class":214},[200,59512,56808],{"class":210},[200,59514,215],{"class":214},[200,59516,3295],{"class":210},[200,59518,980],{"class":214},[200,59520,59521],{"class":235},")))",[200,59523,215],{"class":214},[200,59525,47590],{"class":223},[200,59527,466],{"class":235},[200,59529,59530],{"class":202,"line":889},[200,59531,528],{"emptyLinePlaceholder":20},[200,59533,59534,59536,59538,59540,59542,59544,59546],{"class":202,"line":899},[200,59535,3758],{"class":206},[200,59537,40842],{"class":223},[200,59539,227],{"class":235},[200,59541,25426],{"class":210},[200,59543,549],{"class":214},[200,59545,40851],{"class":2060},[200,59547,416],{"class":235},[200,59549,59550,59552],{"class":202,"line":904},[200,59551,413],{"class":214},[200,59553,416],{"class":210},[35,59555,59556],{},[1216,59557,59558],{},"Je dois admettre que je ne suis pas très confiant avec cette partie. Je devrai vérifier s'il y a une meilleure façon de le faire (en utilisant une transaction ou une fonctionnalité intégrée de Drizzle).",[1929,59560,59562],{"id":59561},"remplir-le-modèle-avec-des-modules","Remplir le modèle avec des modules",[35,59564,59565],{},[1216,59566,59567],{},"C'est là que les choses deviennent difficiles.",[35,59569,59570,59571,59573,59574,53252,59576,59578,59579,215],{},"Au départ, j'avais une requête SQL personnalisée qui joignait la table ",[156,59572,51600],{},", la table ",[156,59575,47155],{},[156,59577,47161],{},". Vous pouvez en savoir plus à ce sujet dans la ",[39,59580,59582],{"href":59581},"#creating-a-form-for-templates","partie précédente",[35,59584,59585,59586,59588,59589,59591,59592,59594],{},"Pour lier la table ",[156,59587,2476],{},", j'ai essayé d'ajouter une jointure avec la table ",[156,59590,58777],{}," puis la table ",[156,59593,2476],{},". Le problème que je rencontre est comment grouper les modules par modèle pour produire un tableau de modules pour chaque modèle.",[35,59596,59597],{},"Quelque chose comme :",[192,59599,59601],{"className":28358,"code":59600,"language":28360,"meta":13,"style":13},"{\n  \"id\": 1,\n  \"title\": \"Mon modèle\",\n  \"modules\": [\n    { \"id\": 1, \"name\": \"Module 1\" },\n    { \"id\": 2, \"name\": \"Module 2\" }\n  ]\n}\n",[156,59602,59603,59607,59621,59640,59652,59686,59720,59724],{"__ignoreMap":13},[200,59604,59605],{"class":202,"line":203},[200,59606,230],{"class":214},[200,59608,59609,59611,59613,59615,59617,59619],{"class":202,"line":14},[200,59610,28489],{"class":214},[200,59612,3295],{"class":379},[200,59614,610],{"class":214},[200,59616,239],{"class":214},[200,59618,3953],{"class":2060},[200,59620,252],{"class":214},[200,59622,59623,59625,59627,59629,59631,59633,59636,59638],{"class":202,"line":255},[200,59624,28489],{"class":214},[200,59626,3581],{"class":379},[200,59628,610],{"class":214},[200,59630,239],{"class":214},[200,59632,28499],{"class":214},[200,59634,59635],{"class":245},"Mon modèle",[200,59637,610],{"class":214},[200,59639,252],{"class":214},[200,59641,59642,59644,59646,59648,59650],{"class":202,"line":272},[200,59643,28489],{"class":214},[200,59645,2476],{"class":379},[200,59647,610],{"class":214},[200,59649,239],{"class":214},[200,59651,1410],{"class":214},[200,59653,59654,59657,59659,59661,59663,59665,59667,59669,59671,59673,59675,59677,59679,59682,59684],{"class":202,"line":283},[200,59655,59656],{"class":214},"    {",[200,59658,28499],{"class":214},[200,59660,3295],{"class":1364},[200,59662,610],{"class":214},[200,59664,239],{"class":214},[200,59666,3953],{"class":2060},[200,59668,549],{"class":214},[200,59670,28499],{"class":214},[200,59672,20112],{"class":1364},[200,59674,610],{"class":214},[200,59676,239],{"class":214},[200,59678,28499],{"class":214},[200,59680,59681],{"class":245},"Module 1",[200,59683,610],{"class":214},[200,59685,333],{"class":214},[200,59687,59688,59690,59692,59694,59696,59698,59701,59703,59705,59707,59709,59711,59713,59716,59718],{"class":202,"line":300},[200,59689,59656],{"class":214},[200,59691,28499],{"class":214},[200,59693,3295],{"class":1364},[200,59695,610],{"class":214},[200,59697,239],{"class":214},[200,59699,59700],{"class":2060}," 2",[200,59702,549],{"class":214},[200,59704,28499],{"class":214},[200,59706,20112],{"class":1364},[200,59708,610],{"class":214},[200,59710,239],{"class":214},[200,59712,28499],{"class":214},[200,59714,59715],{"class":245},"Module 2",[200,59717,610],{"class":214},[200,59719,34638],{"class":214},[200,59721,59722],{"class":202,"line":310},[200,59723,13683],{"class":214},[200,59725,59726],{"class":202,"line":336},[200,59727,1129],{"class":214},[35,59729,59730,59731,59734],{},"J'ai trouvé la fonction SQLite ",[156,59732,59733],{},"group_concat"," qui peut être utilisée pour concaténer les modules. J'étais un peu déçu de devoir le faire manuellement car Drizzle se présente comme un ORM. Je m'attendais à avoir un moyen de le faire facilement.",[35,59736,59737,59738,59745],{},"MAIS, le chat Twitch est venu à la rescousse et m'a suggéré d'utiliser la ",[39,59739,59742],{"href":59740,"rel":59741},"https:\u002F\u002Form.drizzle.team\u002Fdocs\u002Frqb#include-relations",[77],[156,59743,59744],{},"clé with de la méthode findMany",". J'ai essayé, et cela n'a pas vraiment fonctionné. Cloudflare se plaignait d'une relation manquante. J'étais un peu perdu.",[35,59747,59748,59749,59751,59752,59754],{},"J'ai fini par lire attentivement la documentation sur la page de la relation de plusieurs à plusieurs, et j'ai compris que je devais définir explicitement chaque relation entre chaque table. En d'autres termes, je dois dire à Drizzle comment joindre les tables. Un modèle a plusieurs modules en utilisant la table ",[156,59750,58777],{},". Un module a plusieurs modèles en utilisant la table ",[156,59753,58777],{},". C'est ainsi que l'on définit la relation de plusieurs à plusieurs.",[192,59756,59758],{"className":1380,"code":59757,"language":1383,"meta":13,"style":13},"\u002F\u002F Un modèle a une relation de plusieurs à plusieurs\nexport const templatesRelations = relations(templates, ({ one, many }) => ({\n  \u002F\u002F Lié à la table de pivot et non directement à la table des modules\n  modules: many(modulesToTemplates),\n}))\n\n\u002F\u002F La table de pivot a 2 relations\nexport const modulesToTemplatesRelations = relations(modulesToTemplates, ({ one }) => ({\n  module: one(modules, {\n    fields: [modulesToTemplates.moduleId],\n    references: [modules.id],\n  }),\n  template: one(templates, {\n    fields: [modulesToTemplates.templateId],\n    references: [templates.id],\n  }),\n}))\n\n\u002F\u002F Un module a une relation de plusieurs à plusieurs\nexport const modulesRelations = relations(modules, ({ one, many }) => ({\n  \u002F\u002F Lié à la table de pivot et non directement à la table des modèles\n  templates: many(modulesToTemplates),\n}))\n",[156,59759,59760,59765,59800,59805,59818,59824,59828,59833,59863,59878,59895,59912,59920,59934,59949,59964,59972,59978,59982,59987,60020,60025,60037],{"__ignoreMap":13},[200,59761,59762],{"class":202,"line":203},[200,59763,59764],{"class":401},"\u002F\u002F Un modèle a une relation de plusieurs à plusieurs\n",[200,59766,59767,59769,59771,59774,59776,59778,59781,59783,59785,59788,59790,59792,59794,59796,59798],{"class":202,"line":14},[200,59768,1390],{"class":206},[200,59770,14305],{"class":379},[200,59772,59773],{"class":210}," templatesRelations ",[200,59775,449],{"class":214},[200,59777,53297],{"class":223},[200,59779,59780],{"class":210},"(templates",[200,59782,549],{"class":214},[200,59784,383],{"class":214},[200,59786,59787],{"class":386}," one",[200,59789,549],{"class":214},[200,59791,53307],{"class":386},[200,59793,390],{"class":214},[200,59795,393],{"class":379},[200,59797,1094],{"class":210},[200,59799,230],{"class":214},[200,59801,59802],{"class":202,"line":255},[200,59803,59804],{"class":401},"  \u002F\u002F Lié à la table de pivot et non directement à la table des modules\n",[200,59806,59807,59809,59811,59813,59816],{"class":202,"line":272},[200,59808,1405],{"class":235},[200,59810,239],{"class":214},[200,59812,53307],{"class":223},[200,59814,59815],{"class":210},"(modulesToTemplates)",[200,59817,252],{"class":214},[200,59819,59820,59822],{"class":202,"line":283},[200,59821,413],{"class":214},[200,59823,584],{"class":210},[200,59825,59826],{"class":202,"line":300},[200,59827,528],{"emptyLinePlaceholder":20},[200,59829,59830],{"class":202,"line":310},[200,59831,59832],{"class":401},"\u002F\u002F La table de pivot a 2 relations\n",[200,59834,59835,59837,59839,59842,59844,59846,59849,59851,59853,59855,59857,59859,59861],{"class":202,"line":336},[200,59836,1390],{"class":206},[200,59838,14305],{"class":379},[200,59840,59841],{"class":210}," modulesToTemplatesRelations ",[200,59843,449],{"class":214},[200,59845,53297],{"class":223},[200,59847,59848],{"class":210},"(modulesToTemplates",[200,59850,549],{"class":214},[200,59852,383],{"class":214},[200,59854,59787],{"class":386},[200,59856,390],{"class":214},[200,59858,393],{"class":379},[200,59860,1094],{"class":210},[200,59862,230],{"class":214},[200,59864,59865,59868,59870,59872,59874,59876],{"class":202,"line":342},[200,59866,59867],{"class":235},"  module",[200,59869,239],{"class":214},[200,59871,59787],{"class":223},[200,59873,51178],{"class":210},[200,59875,549],{"class":214},[200,59877,280],{"class":214},[200,59879,59880,59883,59885,59888,59890,59893],{"class":202,"line":365},[200,59881,59882],{"class":235},"    fields",[200,59884,239],{"class":214},[200,59886,59887],{"class":210}," [modulesToTemplates",[200,59889,215],{"class":214},[200,59891,59892],{"class":210},"moduleId]",[200,59894,252],{"class":214},[200,59896,59897,59900,59902,59905,59907,59910],{"class":202,"line":371},[200,59898,59899],{"class":235},"    references",[200,59901,239],{"class":214},[200,59903,59904],{"class":210}," [modules",[200,59906,215],{"class":214},[200,59908,59909],{"class":210},"id]",[200,59911,252],{"class":214},[200,59913,59914,59916,59918],{"class":202,"line":398},[200,59915,5095],{"class":214},[200,59917,2104],{"class":210},[200,59919,252],{"class":214},[200,59921,59922,59924,59926,59928,59930,59932],{"class":202,"line":405},[200,59923,15700],{"class":235},[200,59925,239],{"class":214},[200,59927,59787],{"class":223},[200,59929,59780],{"class":210},[200,59931,549],{"class":214},[200,59933,280],{"class":214},[200,59935,59936,59938,59940,59942,59944,59947],{"class":202,"line":410},[200,59937,59882],{"class":235},[200,59939,239],{"class":214},[200,59941,59887],{"class":210},[200,59943,215],{"class":214},[200,59945,59946],{"class":210},"templateId]",[200,59948,252],{"class":214},[200,59950,59951,59953,59955,59958,59960,59962],{"class":202,"line":860},[200,59952,59899],{"class":235},[200,59954,239],{"class":214},[200,59956,59957],{"class":210}," [templates",[200,59959,215],{"class":214},[200,59961,59909],{"class":210},[200,59963,252],{"class":214},[200,59965,59966,59968,59970],{"class":202,"line":889},[200,59967,5095],{"class":214},[200,59969,2104],{"class":210},[200,59971,252],{"class":214},[200,59973,59974,59976],{"class":202,"line":899},[200,59975,413],{"class":214},[200,59977,584],{"class":210},[200,59979,59980],{"class":202,"line":904},[200,59981,528],{"emptyLinePlaceholder":20},[200,59983,59984],{"class":202,"line":930},[200,59985,59986],{"class":401},"\u002F\u002F Un module a une relation de plusieurs à plusieurs\n",[200,59988,59989,59991,59993,59996,59998,60000,60002,60004,60006,60008,60010,60012,60014,60016,60018],{"class":202,"line":3655},[200,59990,1390],{"class":206},[200,59992,14305],{"class":379},[200,59994,59995],{"class":210}," modulesRelations ",[200,59997,449],{"class":214},[200,59999,53297],{"class":223},[200,60001,51178],{"class":210},[200,60003,549],{"class":214},[200,60005,383],{"class":214},[200,60007,59787],{"class":386},[200,60009,549],{"class":214},[200,60011,53307],{"class":386},[200,60013,390],{"class":214},[200,60015,393],{"class":379},[200,60017,1094],{"class":210},[200,60019,230],{"class":214},[200,60021,60022],{"class":202,"line":3660},[200,60023,60024],{"class":401},"  \u002F\u002F Lié à la table de pivot et non directement à la table des modèles\n",[200,60026,60027,60029,60031,60033,60035],{"class":202,"line":4035},[200,60028,53320],{"class":235},[200,60030,239],{"class":214},[200,60032,53307],{"class":223},[200,60034,59815],{"class":210},[200,60036,252],{"class":214},[200,60038,60039,60041],{"class":202,"line":4490},[200,60040,413],{"class":214},[200,60042,584],{"class":210},[35,60044,60045,60046,60051],{},"Cette partie est super importante. Dans un ORM comme ",[39,60047,60050],{"href":60048,"rel":60049},"https:\u002F\u002Flucid.adonisjs.com",[77],"Lucid",", l'utilisation d'une table de pivot est transparente. Avec Drizzle, vous devez définir la relation entre chaque table. C'est un peu plus verbeux, mais c'est aussi plus explicite.",[35,60053,60054],{},"Enfin, je peux mettre à jour le point de terminaison pour récupérer les modèles avec leurs modules :",[192,60056,60058],{"className":1380,"code":60057,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async () => {\n  const templates = await useDrizzle().query.templates.findMany({\n    with: {\n      category: true,\n      creator: true,\n      modules: {\n        with: {\n          module: true\n        },\n      },\n    },\n  })\n\n  return templates\n})\n",[156,60059,60060,60078,60108,60117,60128,60139,60148,60156,60165,60169,60173,60177,60183,60187,60193],{"__ignoreMap":13},[200,60061,60062,60064,60066,60068,60070,60072,60074,60076],{"class":202,"line":203},[200,60063,1390],{"class":206},[200,60065,1393],{"class":206},[200,60067,38723],{"class":223},[200,60069,227],{"class":210},[200,60071,3726],{"class":379},[200,60073,3729],{"class":214},[200,60075,393],{"class":379},[200,60077,280],{"class":214},[200,60079,60080,60082,60084,60086,60088,60090,60092,60094,60096,60098,60100,60102,60104,60106],{"class":202,"line":14},[200,60081,3738],{"class":379},[200,60083,53926],{"class":210},[200,60085,509],{"class":214},[200,60087,452],{"class":206},[200,60089,47338],{"class":223},[200,60091,1549],{"class":235},[200,60093,215],{"class":214},[200,60095,3524],{"class":210},[200,60097,215],{"class":214},[200,60099,51600],{"class":210},[200,60101,215],{"class":214},[200,60103,40103],{"class":223},[200,60105,227],{"class":235},[200,60107,230],{"class":214},[200,60109,60110,60113,60115],{"class":202,"line":255},[200,60111,60112],{"class":235},"    with",[200,60114,239],{"class":214},[200,60116,280],{"class":214},[200,60118,60119,60122,60124,60126],{"class":202,"line":272},[200,60120,60121],{"class":235},"      category",[200,60123,239],{"class":214},[200,60125,2439],{"class":2438},[200,60127,252],{"class":214},[200,60129,60130,60133,60135,60137],{"class":202,"line":283},[200,60131,60132],{"class":235},"      creator",[200,60134,239],{"class":214},[200,60136,2439],{"class":2438},[200,60138,252],{"class":214},[200,60140,60141,60144,60146],{"class":202,"line":300},[200,60142,60143],{"class":235},"      modules",[200,60145,239],{"class":214},[200,60147,280],{"class":214},[200,60149,60150,60152,60154],{"class":202,"line":310},[200,60151,32485],{"class":235},[200,60153,239],{"class":214},[200,60155,280],{"class":214},[200,60157,60158,60161,60163],{"class":202,"line":336},[200,60159,60160],{"class":235},"          module",[200,60162,239],{"class":214},[200,60164,22236],{"class":2438},[200,60166,60167],{"class":202,"line":342},[200,60168,4965],{"class":214},[200,60170,60171],{"class":202,"line":365},[200,60172,12098],{"class":214},[200,60174,60175],{"class":202,"line":371},[200,60176,339],{"class":214},[200,60178,60179,60181],{"class":202,"line":398},[200,60180,5095],{"class":214},[200,60182,416],{"class":235},[200,60184,60185],{"class":202,"line":405},[200,60186,528],{"emptyLinePlaceholder":20},[200,60188,60189,60191],{"class":202,"line":410},[200,60190,3758],{"class":206},[200,60192,54059],{"class":210},[200,60194,60195,60197],{"class":202,"line":860},[200,60196,413],{"class":214},[200,60198,416],{"class":210},[35,60200,60201,60202,60205],{},"L'utilisation de ",[156,60203,60204],{},"with",", une fois que tout est correctement défini, rend les choses tellement plus faciles ; j'adore vraiment ça.",[1929,60207,24146],{"id":60208},"conclusion-4",[35,60210,60211],{},"Dans mon esprit, cette partie était facile, et en 30 minutes, c'était fait. J'avais déjà fait quelque chose de similaire en utilisant Lucid d'Adonis. En réalité, ce n'était pas vraiment difficile, mais trouver la bonne partie de la documentation de Drizzle puis la comprendre était bien plus difficile que prévu. Cela est largement dû au fait que Drizzle n'abstrait pas SQL mais agit comme un petit wrapper, et tout doit être explicite.",[35,60213,60214],{},"Je suis heureux de l'avoir fait, j'ai beaucoup appris, et un grand merci au chat pour leur aide.",[1170,60216,60217],{},"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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}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 .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 .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}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":13,"searchDepth":14,"depth":14,"links":60219},[60220,60221],{"id":45870,"depth":14,"text":45871},{"id":46152,"depth":14,"text":46153},"2024-05-01","Cela fait des mois que je veux commencer à streamer sur Twitch pour le plaisir de partager des choses et de coder en direct. J'ai finalement sauté le pas.",{},"\u002Ffr\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey","---\ncontentId: 2349bbcd-1535-4f33-8da7-299f46af18b9\ntitle: \"Créer une App Nuxt Full-Stack : une Aventure sur Twitch\"\ndescription: Cela fait des mois que je veux commencer à streamer sur Twitch pour le plaisir de partager des choses et de coder en direct. J'ai finalement sauté le pas.\ndate: 2024-05-01\n---\n\n**Je l'ai enfin fait !**\n\nAprès des mois à y penser, j'ai enfin lancé mon premier stream sur Twitch, le 28 avril 2024. C'est une étape très importante pour moi car j'adore partager mes connaissances et parler de ce que je fais.\n\n\u003Cdiv class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n  \u003CCalloutFollowMeOnTwitch \u002F>\n  \u003CCallout icon=\"i-simple-icons-youtube\" text=\"Voir les rediffusions\" href=\"https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes\"  \u002F>\n  \u003CCallout icon=\"i-simple-icons-x\" text=\"Ne manquez rien\" href=\"https:\u002F\u002Fx.com\u002Fsoubiran_\"  \u002F>\n  \u003CCallout icon=\"i-simple-icons-github\" text=\"L'application Full-Stack\" href=\"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion\"  \u002F>\n\u003C\u002Fdiv>\n\nQuand j'étais à l'école, j'organisais des cours pour expliquer la programmation à mes camarades. J'ai aussi rejoint une communauté pour expliquer les choses sur les ordinateurs, j'ai donné quelques conférences, et récemment, j'ai commencé à écrire des articles sur mon blog.\n\n**Twitch est juste une nouvelle façon de continuer à faire ce que j'aime !**\n\nDans les semaines à venir, je partagerai avec vous mon parcours pour construire une application Full-Stack Nuxt, déployée sur Cloudflare, appelée Orion. J'expliquerai comment je l'ai construite, les choix que j'ai faits, et les problèmes que j'ai rencontrés. Mon objectif à travers cet article est de vous donner une vue honnête et réaliste des avantages et des inconvénients de la création d'une application Full-Stack en edge avec Nuxt en mai 2024, à travers le processus de construction d'une application réelle.\n\nDans cet article, je partagerai avec vous chaque étape de cette première série de streams. Si vous ne parlez pas français (car les streams sont en français), si vous avez manqué un stream, ou si vous préférez simplement lire, cet article est fait pour vous !\n\n## Découverte de NuxtHub et Début de l'Application Full-Stack Nuxt\n\n![Vignette de mon premier live stream.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-04-28.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) est une application Full-Stack construite avec Nuxt et développée en direct [sur Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). Vous pouvez voir la rediffusion sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) et le code source est open-source sur [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nC'était mon premier stream, et pour être honnête, j'étais un peu stressé. Le moment où vous devez passer de la scène \"Starting Soon\" à la caméra est difficile. Mais une fois que j'ai commencé à parler, tout s'est bien passé. _Cependant, cela ne veut pas dire que c'est le meilleur stream que vous verrez jamais. Nous allons nous améliorer !_\n\n![Moi, pas très à l'aise devant la caméra.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-04-28-me.png)\n\nTout d'abord, je présente [NuxtHub](https:\u002F\u002Fhub.nuxt.com). C'est une plateforme développée par l'équipe Nuxt pour faciliter le déploiement et la gestion des applications Nuxt déployées sur Cloudflare. En quelques clics, vous pouvez déployer votre application Nuxt sur Cloudflare. C'est vraiment magique ! Grâce à un module Nuxt, vous pourrez développer votre application localement et ensuite la déployer sans modifier votre code. Tout, de la base de données au stockage du blog, fonctionne parfaitement dans les deux environnements.\n\nJ'ai essayé de déployer un modèle depuis NuxtHub pour montrer à quel point il est facile d'utiliser la plateforme. J'ai choisi le modèle Atinotes. Malheureusement, cette application produit un bundle de plus de 1 Mo, la limite pour le niveau gratuit, donc le déploiement échoue. Dommage pour une démonstration. _Après cet échec, j'ai pensé à un moyen de prévenir cela et d'informer les utilisateurs si un modèle nécessite un plan payant ou non. [J'ai soulevé un problème](https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F13) qui a été rapidement corrigé !_. La démo attendra plus tard.\n\nAprès cet effet de démonstration, nous avons commencé à coder l'application Full-Stack Nuxt en ajoutant l'authentification. Pour ce faire, nous utilisons le module [`nuxt-auth-utils`](https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils) de [Sébastien Chopin](https:\u002F\u002Fgithub.com\u002FAtinux). Avec quelques lignes de code, nous ajoutons l'authentification. Voici le code que nous avons utilisé :\n\n```ts\nexport default oauth.githubEventHandler({\n  config: {\n    emailRequired: true,\n  },\n  async onSuccess(event, { user, tokens }) {\n    await setUserSession(event, {\n      user: {\n        githubId: user.githubId,\n      },\n    })\n\n    return sendRedirect(event, '\u002F')\n  },\n})\n```\n\nC'est vraiment simple et ça fonctionne à merveille, bien intégré dans une application Nuxt.\n\nPour terminer le stream, j'ai rapidement configuré NuxtHub dans mon application pour montrer à quel point il est facile de jouer avec une base de données. J'ai essayé d'utiliser SQL brut, mais la recette pour migrer la base de données ne fonctionnait pas. _[J'ai soulevé un problème](https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F15)_ à ce sujet et il est maintenant corrigé ! Après ce deuxième échec, j'ai décidé de commencer à utiliser Drizzle ORM, un ORM simple mais puissant compatible avec les workers de Cloudflare. Dans tous les cas, j'aurais dû utiliser Drizzle car gérer tout avec SQL brut est trop de travail.\n\nPendant le live, quelqu'un a demandé comment appliquer les migrations à la base de données de production. Je dois admettre que je ne connaissais pas la réponse et rien dans la documentation n'était clair à ce sujet. _[J'ai soulevé un problème](https:\u002F\u002Fgithub.com\u002Fnuxt-hub\u002Ffeedback\u002Fissues\u002F14)_ à ce sujet, qui a reçu une réponse. _Il faut déployer votre application puis démarrer le développement local avec une connexion distante pour exécuter les migrations_ Clairement, c'est loin d'être idéal pour le moment, mais avec les prochaines [tâches Nitro](https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Ftasks), cela devrait être plus facile dans un avenir proche. _Je l'espère vraiment._\n\nGlobalement, je suis tellement heureux d'avoir commencé ce stream. Même si ce n'est pas encore parfait, commencer est déjà un grand pas en avant, et je sais que les futurs streams ne feront que devenir plus faciles à commencer.\n\n_J'étais tellement stressé que je suis allé très vite dans mes explications, donc j'ai décidé que le prochain stream serait plus bavard pour réexpliquer différents concepts._\n\nLe projet est open-source et disponible sur GitHub à \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F> ! Donnez-lui une étoile si vous l'aimez ! ⭐\n\n## Discussion sur Cloudflare, NuxtHub et GitHub OAuth\n\n![Vignette de mon deuxième live stream.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-02.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) est une application Full-Stack construite avec Nuxt et développée en direct [sur Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). Vous pouvez voir la rediffusion sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) et le code source est open-source sur [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nJ'étais frustré par mon premier live stream. Avec le stress, je n'ai pas expliqué des concepts comme ce qu'est Cloudflare, ce qu'est l'edge et comment NuxtHub est lié à Cloudflare. Mais je sais qu'il est important d'expliquer ces concepts puisque ce sont des technologies nouvelles et que tout le monde ne les connaît pas.\n\nÀ StrasbourgJS ou Devoxx, j'ai parlé d'UnJS et des problèmes résolus par l'écosystème. Beaucoup de personnes, pendant ou après, m'ont demandé ce qu'est l'edge. Qu'est-ce que c'est ? Pourquoi en avons-nous besoin ? D'où cela vient-il ?\n\n\u003CCalloutReadMore id=\"a1291894-3de4-444e-b49b-a5dec98f3e27\" \u002F>\n\n\u003CCalloutReadMore id=\"f3e97a6a-ce47-42b8-8493-06eb37f8e964\" \u002F>\n\nLe but de mes streams est de tester cette nouvelle technologie, et avec ces retours, il était nécessaire d'expliquer ces concepts.\n\n### Cloudflare en un coup d'œil\n\nCloudflare est une entreprise qui propose de nombreux services pour rendre le web plus rapide et plus sécurisé, comme une solution zéro-trust, un WAF, un CDN, un DNS, une protection contre les DDoS, et plus encore, pour les entreprises comme pour les particuliers.\n\nLeur produit le plus connu est le proxy inverse. Cloudflare agit comme le point d'entrée principal et unique pour votre trafic. Ensuite, il dirigera, en interne, le trafic vers le bon serveur en utilisant le DNS. Cela permet de cacher votre IP serveur, de mettre en cache les ressources statiques et de protéger votre serveur contre les attaques DDoS.\n\n![Schéma du proxy inverse.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Freverse-proxy.png)\n\nPour fournir ces services, ils ont construit un réseau de centres de données à travers le monde pour être le plus proche possible des utilisateurs afin de servir le contenu mis en cache plus rapidement et de pouvoir atténuer les attaques. Cela s'appelle le réseau edge. Il est à la frontière entre les utilisateurs et le web, pour lequel il constitue la passerelle.\n\nEn septembre 2017, [ils ont annoncé Cloudflare Workers](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-cloudflare-workers). Pour comprendre l'objectif à l'époque, citons l'article :\n\n> Que faire si vous souhaitez équilibrer la charge avec un algorithme d'affinité personnalisé ? Que faire si les règles de mise en cache HTTP standard ne sont pas tout à fait adaptées et que vous avez besoin de logique personnalisée pour améliorer votre taux de cache ? Que faire si vous voulez écrire des règles WAF personnalisées adaptées à votre application ?\n>\n> **Vous voulez écrire du code**\n\nLes Workers ont été lancés pour permettre aux développeurs de personnaliser l'infrastructure selon leurs besoins. La solution Cloudflare Workers est construite sur l'API Service Worker, une norme dans le navigateur pour intercepter et modifier les requêtes et les réponses.\n\nDepuis lors, ils ont construit une plateforme complète pour les développeurs autour des workers :\n\n\u003C!-- TODO: créer une grille de composants (et documenter : éléments fortement liés, meilleure présentation qu'une simple liste) -->\n\n- [Workers KV, magasin de clés-valeurs sans serveur](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-workers-kv)\n- [D1, base de données SQL sans serveur](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-d1)\n- [R2, stockage d'objets sans serveur](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-r2-object-storage)\n- [Queues, files d'attente de tâches sans serveur](https:\u002F\u002Fblog.cloudflare.com\u002Fintroducing-cloudflare-queues)\n- [Workers AI, inférence GPU sans serveur](https:\u002F\u002Fblog.cloudflare.com\u002Fworkers-ai)\n\nAvec tous ces services, il est maintenant (théoriquement, et c'est le but de cette série de répondre à la question) possible de construire une application entièrement sur le réseau mondial de Cloudflare.\n\n### NuxtHub\n\n![Page d'accueil de NuxtHub.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-home-page.png)\n\nDepuis la version 3 de Nuxt, l'équipe pousse l'edge. Nitro, la partie serveur de Nuxt, peut être déployée partout, sur les plateformes actuelles et futures, et est optimisée pour l'edge. H3, le serveur HTTP sous-jacent, se réveille en moins de 2 ms, et la sortie de build de Nitro est inférieure à 1 Mo, modules Node inclus.\n\nNuxtHub est la continuité de cet effort. D'abord, c'est une plateforme pour déployer des applications Nuxt sur Cloudflare. C'est un déploiement en deux clics. Vous connectez votre compte Cloudflare car c'est construit sur Cloudflare, vous choisissez un modèle ou un dépôt, vous cliquez sur déployer, et c'est tout. _Votre application est en direct sur l'edge._ On a l'impression que c'est de la magie.\n\n![Modèles NuxtHub pouvant être déployés en deux clics.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-deploy.png)\n\nNuxtHub est également un module pour votre application Nuxt afin d'ajouter des fonctionnalités d'accès aux données. Localement, vous pourrez jouer instantanément avec une base de données, un magasin KV, et même un stockage d'objets. En production, vous n'avez pas besoin de changer votre code, il fonctionnera de la même manière. C'est clairement la véritable magie de NuxtHub, alimentée par [`workerd`](https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fworker).\n\n![Schéma de NuxtHub (de hub.nuxt.com).](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fnuxt-hub-schema.png)\n\nIl y a aussi une fonctionnalité permettant à un environnement de développement local d'interagir avec les données distantes. Pas sûr du cas d'utilisation, mais c'est définitivement intéressant.\n\nMême si la plateforme est encore nouvelle, annoncée lors de Vue Amsterdam 2024, elle est déjà très prometteuse. L'équipe est très réactive, et la plateforme évolue rapidement. Je suis très impatient de voir ce que l'avenir réserve à NuxtHub, mais il y a quelques indices dans la documentation :\n\n> Nous prévoyons de fournir une expérience backend complète pour les applications Nuxt à travers divers packages @nuxthub.\n>\n> - @nuxthub\u002Fcore : Package principal pour fournir des fonctionnalités de stockage\n> - @nuxthub\u002Fauth : Ajouter l'authentification pour la gestion des utilisateurs (bientôt)\n> - @nuxthub\u002Femail : Envoyer des e-mails transactionnels à vos utilisateurs (bientôt)\n> - @nuxthub\u002Fanalytics : Comprendre votre trafic et suivre les événements au sein de votre application et API (bientôt)\n> - @nuxthub\u002F... : Vous le nommez !\n\n### GitHub OAuth\n\nLors du précédent live stream, j'ai ajouté l'authentification, en utilisant `nuxt-auth-utils` à Orion, l'application Full-Stack Nuxt. J'ai utilisé GitHub OAuth, et je pense qu'il est important de voir comment cela fonctionne en coulisses.\n\nPendant le live, nous avons plongé dans le code, et j'ai expliqué comment cela fonctionne. Voici un schéma pour vous aider à comprendre le processus :\n\n![Schéma GitHub OAuth.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Foauth-github.png)\n\n1. L'utilisateur clique sur le bouton de connexion, qui pointe vers `\u002Fauth\u002Fgithub`.\n2. Le serveur envoie une redirection vers la page OAuth de GitHub.\n3. L'utilisateur accepte l'autorisation de GitHub.\n4. GitHub redirige l'utilisateur vers l'URL de rappel de l'application avec un code.\n5. Le serveur échange le code contre des tokens.\n6. Le serveur utilise les tokens pour obtenir les informations de l'utilisateur.\n7. Le serveur stocke l'utilisateur dans une session.\n8. Le serveur redirige l'utilisateur vers la page d'accueil.\n\nDans ces étapes, le serveur est Nitro, et nous ne parlons pas des erreurs possibles. Il y a de nombreuses erreurs possibles comme le refus de l'autorisation par l'utilisateur, un code invalide, des tokens invalides, l'utilisateur non trouvé, etc. Chaque erreur doit être gérée pour offrir une bonne expérience utilisateur.\n\n#### Code du gestionnaire OAuth GitHub\n\nVoyons maintenant le code de `nuxt-auth-utils` pour comprendre comment il fonctionne.\n\nPour simplifier, j'ai retiré certaines parties du code qui ne sont pas nécessaires à la compréhension du processus, et je me suis concentré sur les étapes principales. Vous pouvez trouver le code complet dans le [dépôt GitHub](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils).\n\n```ts\n\u002F**\n * Gestionnaire d'événements OAuth GitHub.\n *\n * _Le code est simplifié pour l'article et ne fonctionne pas tel quel._\n *\u002F\nexport function githubEventHandler({ config, onSuccess }: OAuthConfig\u003COAuthGitHubConfig>) {\n  return eventHandler(async (event: H3Event) => {\n    config = { \u002F** ... *\u002F}\n\n    \u002F**\n     * Récupérer la requête depuis l'événement.\n     *\u002F\n    const query = getQuery(event)\n\n    if (query.error) {\n      \u002F\u002F Gérer l'erreur\n      return\n    }\n\n    if (!query.code) {\n      \u002F**\n       * Rediriger vers la page OAuth GitHub s'il n'y a pas de code dans la requête.\n       * Si l'utilisateur accepte l'autorisation, GitHub redirigera l'utilisateur vers l'application avec le code.\n       * Le code est un code unique à échanger contre les tokens.\n       *\u002F\n      const redirectUrl = getRequestURL(event).href\n      return sendRedirect(\n        event,\n        withQuery(config.authorizationURL as string, {\n          client_id: config.clientId,\n          redirect_uri: redirectUrl,\n          scope: config.scope.join(' '),\n          ...config.authorizationParams,\n        }),\n      )\n    }\n\n    \u002F**\n     * Utiliser le code pour obtenir les tokens.\n     *\u002F\n    const tokens: any = await $fetch(\n      config.tokenURL as string,\n      {\n        method: 'POST',\n        body: {\n          code: query.code,\n        },\n      },\n    )\n\n    const accessToken = tokens.access_token\n\n    \u002F**\n     * Utiliser le token d'accès pour obtenir les informations de l'utilisateur.\n     *\u002F\n    const user: any = await ofetch('https:\u002F\u002Fapi.github.com\u002Fuser', {\n      headers: {\n        Authorization: `token ${accessToken}`,\n      },\n    })\n\n    \u002F**\n     * Appeler le callback onSuccess avec l'utilisateur et les tokens.\n     *\n     * L'OAuth est terminé, et l'utilisateur peut maintenant être authentifié avec notre système.\n     * Avec Nitro, nous stockons l'utilisateur dans une session au sein d'un cookie.\n     *\u002F\n    return onSuccess(event, {\n      user,\n      tokens,\n    })\n  })\n}\n```\n\nComme on peut s'y attendre, le code est assez simple, et nous reconnaissons facilement les étapes que nous avons vues dans le schéma.\n\n### Conclusion\n\nJe n'ai pas codé durant ce live. Je voulais expliquer de nombreux concepts importants pour aider chacun à comprendre le contexte du projet Orion. J'espère que c'était clair et que vous avez appris quelque chose de nouveau.\n\nLe projet est open-source et disponible sur GitHub à \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F> ! Donnez-lui une étoile si vous l'aimez ! ⭐\n\n## Schéma SQL et Premiers Éléments de l'Interface Admin d'Orion\n\n![Vignette de mon troisième live stream.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-03.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) est une application full-stack construite avec Nuxt et développée en direct [sur Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). Vous pouvez voir le replay sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) et le code source est open-source sur [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nDans ce troisième stream, j'ai créé le schéma SQL pour la base de données. J'ai ensuite configuré Nuxt UI et Nuxt UI Pro pour créer la mise en page de l'administration et le premier formulaire pour les catégories. Cet article est un résumé du stream et du travail réalisé.\n\n### Schéma SQL\n\nQu'est-ce qu'un schéma SQL et pourquoi ai-je créé ce schéma SQL ? Ce sont deux bonnes questions.\n\nUn schéma SQL est un moyen d'organiser la base de données. C'est une feuille de route sur la manière dont la base de données sera structurée. Il est donc très utile de réfléchir à cela avant de commencer à coder. Il n'est pas nécessaire qu'il soit parfait, mais il aide à itérer sur la structure de la base de données, à éviter les erreurs et à avoir une vision claire du projet. _Je pense personnellement que c'est un indispensable lorsqu'on commence un nouveau projet._\n\nAvant d'écrire quoi que ce soit pour le projet Orion, j'ai donc créé le schéma SQL.\n\n![Schéma SQL](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fsql-schema.png)\n\nPour construire ce schéma, j'ai utilisé [drawSQL](https:\u002F\u002Fdrawsql.app\u002F). C'est une application web pour créer des schémas SQL avec une interface utilisateur agréable. Elle est très facile à utiliser et gratuite pour les petits projets. Je la recommande.\n\nLa base de données pour le projet Orion, l'application full-stack Nuxt, est simple et courante pour ce type de projet.\n\n- Une table `users` pour stocker les utilisateurs de l'application. Stocker les utilisateurs est utile pour l'interface admin, pour bannir un utilisateur ou pour donner des droits d'administrateur.\n- Une table `categories` pour stocker les catégories du thème comme blog, e-commerce, documentation, etc. C'est une table simple avec un nom et un slug. Le slug est utilisé pour l'URL de la catégorie.\n- Une table `modules` pour stocker les modules du thème. La table aura un nom, un slug et un type (officiel, communauté). Cela sera utile pour filtrer les thèmes par modules.\n- Une table `themes` pour stocker le contenu principal de l'application. Elle aura de nombreuses colonnes, mais nous verrons cela dans un autre stream.\n- Une table `feedbacks` pour stocker les retours des administrateurs sur un thème. En effet, un thème doit être évalué avant d'être publié et les retours seront utilisés pour indiquer au créateur ce qu'il doit améliorer.\n\nCe schéma est un bon point de départ pour clarifier les choses et savoir où commencer, mais je sais déjà qu'il évoluera.\n\n### Nuxt UI et Nuxt UI Pro\n\nNuxt UI est une bibliothèque de composants créée par l'équipe de Nuxt. Initialement, c'était un outil interne pour leurs propres projets comme [Volta](https:\u002F\u002Fvolta.net). En mai 2023, ils ont décidé de [le rendre open-source](https:\u002F\u002Fgithub.com\u002Fnuxt\u002Fui\u002Freleases\u002Ftag\u002Fv2.0.0) et de le rendre disponible gratuitement pour tout le monde. _Aujourd'hui, Nuxt UI est mon choix de prédilection pour construire des interfaces utilisateur avec Nuxt car il est simple, beau et pourtant très personnalisable._\n\nNuxt UI Pro est construit sur Nuxt UI et fournit des composants liés aux mises en page, comme une page, un hero, un en-tête et des mises en page complètes comme un tableau de bord, un blog, une documentation, etc. C'est un produit payant, mais il en vaut la peine. _Je l'utilise pour Orion, l'application full-stack, car il fait gagner du temps et est magnifique._ Le mieux, c'est qu'il est gratuit pendant le développement (le processus de build nécessitera une clé), donc vous pouvez l'essayer avant de l'acheter.\n\nPendant le stream, j'ai commencé par installer Nuxt UI et Nuxt UI Pro. J'ai ensuite créé la mise en page pour l'interface d'administration et la page pour les catégories. Voyons le code.\n\n### Mise en Page Admin\n\nAvec Nuxt UI Pro, cette partie a été très facile. Il n'y a pas encore de navbar ni de sidebar. _L'objectif est d'ajouter des fonctionnalités et des composants étape par étape, selon les besoins, et d'éviter de trop anticiper._ Voici le code pour la mise en page `layouts\u002Fadmin.vue`.\n\n```vue\n\u003Ctemplate>\n  \u003CUDashboardLayout>\n    \u003Cslot \u002F>\n  \u003C\u002FUDashboardLayout>\n\u003C\u002Ftemplate>\n```\n\n### Page des Catégories\n\nAvant de développer la vue, j'ai créé l'endpoint API pour obtenir et mettre à jour les catégories.\n\n![Résultat de la mise en page et de la page pour les catégories.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Flayout-and-page-for-categories.png)\n\n#### API des Catégories\n\nGrâce à Nitro et Drizzle (configuré dans le premier stream), c'est très simple. Dans le fichier `server\u002Fapi\u002Fcategories\u002Findex.get.ts` (le `.get.` est utilisé pour spécifier le verbe HTTP), j'ai écrit le code suivant :\n\n```ts\nexport default defineEventHandler(async () => {\n  const categories = await useDrizzle().select({\n    id: tables.categories.id,\n    slug: tables.categories.slug,\n    name: tables.categories.name\n  }).from(tables.categories)\n\n  return categories\n})\n```\n\nL'utilitaire `useDrizzle` est un wrapper autour de l'ORM Drizzle. Ce code renverra toutes les catégories avec seulement les champs `id`, `slug`, et `name`. _C'est une bonne pratique de ne renvoyer que les champs dont vous avez besoin pour éviter d'envoyer trop de données._\n\nEnsuite, j'ai créé l'endpoint pour créer une nouvelle catégorie, et c'est le même processus. J'ai créé le fichier `server\u002Fapi\u002Fcategories\u002Findex.post.ts` avec le code suivant :\n\n```ts\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  await useDrizzle().insert(tables.categories).values({\n    slug: useSlugify(body.name),\n    name: body.name,\n  }).execute()\n\n  return body\n})\n```\n\nDans ce code, il n'y a pas de vérification de sécurité, et chaque utilisateur peut créer une catégorie. Ce n'est clairement pas un cas d'utilisation pour notre application, mais la vérification sera ajoutée plus tard. En même temps, je n'ai pas vérifié le corps de la requête. Il est important de valider les données reçues d'un client pour éviter les problèmes de sécurité. **Ce sera le sujet du prochain stream**.\n\n## Contenu des Catégories\n\nLa page des catégories est construite avec Nuxt UI Pro, ce qui rend le code très simple.\n\n```vue\n\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n  layout: 'admin'\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUDashboardPage>\n    \u003CUDashboardPanel grow>\n      \u003C!-- Contenu -->\n    \u003C\u002FUDashboardPanel>\n  \u003C\u002FUDashboardPage>\n\u003C\u002Ftemplate>\n```\n\nDans le `UDashboardPanel`, nous pouvons ajouter un en-tête avec le titre de la page, un bouton pour créer une nouvelle catégorie, et un tableau pour afficher les catégories.\n\nCommençons par ajouter le tableau dans le `UDashboardPanel` :\n\n```vue\n\u003Cscript setup lang=\"ts\">\nconst columns = [{\n  key: 'id',\n  label: '#'\n}, {\n  key: 'name',\n  label: 'Nom',\n  sortable: true\n}, {\n  key: 'slug',\n  label: 'Slug',\n}]\n\nconst { data: categories, pending } = await useFetch('\u002Fapi\u002Fcategories', {\n  deep: false,\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUTable :columns=\"columns\" :rows=\"categories\" :loading=\"pending\" \u002F>\n\u003C\u002Ftemplate>\n```\n\nC'est difficile d'être plus simple ! _Merci à Nuxt UI pour ce travail !_\n\nEnsuite, ajoutons l'en-tête avec le titre et le bouton pour créer une nouvelle catégorie :\n\n```vue\n\u003Ctemplate>\n  \u003CUDashboardNavbar\n    title=\"Catégories\"\n  >\n    \u003Ctemplate #right>\n      \u003CUButton\n        label=\"Nouvelle catégorie\"\n        trailing-icon=\"i-heroicons-plus\"\n        color=\"gray\"\n      \u002F>\n    \u003C\u002Ftemplate>\n  \u003C\u002FUDashboardNavbar>\n\u003C\u002Ftemplate>\n```\n\nToujours très facile. Le bouton sera utilisé pour ouvrir un modal avec un formulaire pour créer une nouvelle catégorie. J'ai commencé à créer le formulaire mais de manière très simple :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nimport type { FormSubmitEvent } from '#ui\u002Ftypes'\nimport type { output } from 'zod'\nimport { object, string } from 'zod'\n\nconst schema = object({\n  name: string({ message: 'Requis' }),\n})\n\ntype Schema = output\u003Ctypeof schema>\n\nconst state = reactive({\n  name: undefined,\n})\n\nasync function onSubmit(event: FormSubmitEvent\u003CSchema>) {\n  await $fetch('\u002Fapi\u002Fcategories', {\n    method: 'POST',\n    body: event.data,\n  })\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUForm :schema=\"schema\" :state=\"state\" @submit=\"onSubmit\">\n    \u003CUFormGroup label=\"Nom\" name=\"name\">\n      \u003CUInput v-model=\"state.name\" \u002F>\n    \u003C\u002FUFormGroup>\n\n    \u003CUButton type=\"submit\">\n      Soumettre\n    \u003C\u002FUButton>\n  \u003C\u002FUForm>\n\u003C\u002Ftemplate>\n```\n\nJ'utilise le formulaire tel qu'expliqué dans la documentation de Nuxt UI. Je n'ai pas vraiment choisi Yup pour la validation mais c'était la première option proposée. _Nous verrons si c'est un bon choix à l'avenir._ Dans la méthode `onSubmit`, je fais simplement une requête `fetch` à l'API pour créer une nouvelle catégorie. Dans le prochain stream, nous l'améliorerons en gérant les états de succès et d'erreur.\n\nCe composant est placé dans un modal en utilisant le composant `UModal` et en écoutant l'événement de clic sur le bouton pour ouvrir le modal.\n\n_Un point sur lequel je ne suis pas sûr est la gestion de la réponse de l'API. Devrais-je retourner la nouvelle catégorie et l'ajouter dans le tableau, ou devrais-je rafraîchir le tableau ? Pour le moment, j'ai opté pour la seconde solution._\n\nUtiliser Nuxt UI m'aide à construire l'interface admin rapidement, sans avoir besoin de composants personnalisés.\n\n\u003CCallout icon=\"i-simple-icons-github\" text=\"Voir le code source\" to=\"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion\" target=\"_blank\" \u002F>\n\n### Fin du Stream\n\nC'était définitivement un bon stream. Le projet avance sans trop de difficultés et c'est un plaisir de travailler dessus. J'attends avec impatience le prochain stream pour continuer à travailler sur l'interface admin et les formulaires. Nuxt UI et Nuxt UI Pro sont des changeurs de jeu : code simple, facile à utiliser, et je peux me concentrer sur les fonctionnalités de l'application.\n\nLe projet est open-source et disponible sur GitHub à \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F> ! Donnez-lui une étoile si vous l'aimez ! ⭐\n\n## Continuons l'Interface Admin\n\n![Thumbnail de mon quatrième live stream.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-04.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) est une application full-stack construite avec Nuxt et développée en direct [sur Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). Vous pouvez voir la rediffusion sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) et le code source est open-source sur [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nLors du stream précédent, j'ai commencé à créer la page des catégories. J'ai mis en place les endpoints API pour obtenir et créer des catégories, et j'ai construit l'interface utilisateur pour afficher les catégories et le formulaire pour en créer une nouvelle. Dans ce stream, j'ai continué à travailler sur l'interface admin en ajoutant le formulaire de modification de catégorie et en créant une nouvelle page pour gérer les utilisateurs. J'ai également ajouté le bouncer pour sécuriser l'accès à la partie admin de l'application, tant pour le frontend que pour le backend. Voyons les travaux réalisés en détail.\n\n### Formulaire de Modification de Catégorie\n\nCette partie était très similaire au formulaire de création de catégorie. Au lieu de soumettre les données du formulaire à l'API pour créer une nouvelle catégorie, j'ai changé l'endpoint pour mettre à jour une catégorie existante.\n\nJ'ai créé le formulaire dans un nouveau composant `components\u002Fcategories\u002FCategoryEditForm.vue` (et renommé le précédent formulaire en `CategoryCreateForm.vue`). J'ai également créé un nouvel endpoint `server\u002Fapi\u002Fcategories\u002F[id].put.ts` pour mettre à jour une catégorie. Cet endpoint met à jour une catégorie en utilisant l'`id` dans l'URL et le corps de la requête :\n\n```ts\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event)\n\n  await useDrizzle().update(tables.categories).set({\n    slug: useSlugify(body.name),\n    name: body.name,\n  }).where(eq(tables.categories.id, params.id)).execute()\n\n  return sendNoContent(event, 200)\n})\n```\n\n_Le code de statut 200 pourrait ne pas être le meilleur choix (204 est mieux pour un `PUT`), mais je réfléchis encore à savoir si je devrais retourner la catégorie mise à jour ou non._\n\nJ'ai également tenté de valider le corps de la requête en utilisant Yup, car c'était l'outil utilisé dans le frontend. Cependant, j'ai rapidement réalisé qu'il n'était pas possible de caster une chaîne en nombre. Je sais que myzod peut le faire, alors je l'ai essayé. J'ai rencontré [un problème](https:\u002F\u002Fgithub.com\u002Funjs\u002Fh3\u002Fissues\u002F745) et j'ai donc utilisé Zod. _J'ai déplacé le frontend vers Zod en dehors du stream._\n\n### Page des Utilisateurs\n\nEn réalité, lorsqu'un utilisateur se connecte, les données associées de GitHub sont stockées dans la base de données (_en écrivant cette partie, je me demande si cela est nécessaire car cela signifie qu'une connexion est une opération d'écriture, ce qui est plus coûteux qu'une lecture_) et dans la session. Ce comportement est utile pour promouvoir l'utilisateur au statut d'admin ou pour bannir un utilisateur. Mais pour le moment, il n'y a pas de moyen de gérer les utilisateurs. Donc, j'ai créé une nouvelle page pour gérer les utilisateurs. Comme la page des catégories, la page affichera les utilisateurs dans un tableau. Cependant, il ne sera pas possible de créer ou de mettre à jour un utilisateur pour le moment.\n\nPour récupérer les utilisateurs depuis la base de données, j'ai créé l'endpoint API `server\u002Fapi\u002Fusers\u002Findex.get.ts` :\n\n```ts\nexport default defineEventHandler(async (event) => {\n  const users = await useDrizzle().select({\n    id: tables.users.id,\n    githubId: tables.users.githubId,\n    username: tables.users.username,\n    roleType: tables.users.roleType\n  }).from(tables.users)\n\n  return users\n})\n```\n\nPour les afficher, j'utilise le composant `UTable` de Nuxt UI Pro. Le code est très similaire à celui de la page des catégories :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst columns = [{\n  key: 'id',\n  label: '#'\n}, {\n  key: 'githubId',\n  label: 'GitHub ID',\n}, {\n  key: 'username',\n  label: 'Nom d\\'utilisateur',\n  sortable: true\n}, {\n  key: 'roleType',\n  label: 'Type de rôle'\n}]\nconst { data: users } = await useFetch('\u002Fapi\u002Fusers', {\n  deep: false,\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUDashboardPage>\n    \u003CUDashboardPanel grow>\n      \u003CUTable :columns=\"columns\" :rows=\"users\">\n        \u003Ctemplate #roleType-data=\"{ row }\">\n          \u003CUBadge variant=\"subtle\" :color=\"row.roleType === 'admin' ? 'amber' : 'primary'\">\n            {{ row.roleType }}\n          \u003C\u002FUBadge>\n        \u003C\u002Ftemplate>\n      \u003C\u002FUTable>\n    \u003C\u002FUDashboardPanel>\n  \u003C\u002FUDashboardPage>\n\u003C\u002Ftemplate>\n```\n\nComme prévu, le code est très similaire à celui de la page des catégories.\n\nMaintenant que nous avons deux pages, une pour les catégories et une pour les utilisateurs, j'ai ajouté une barre latérale pour naviguer entre elles. Rien de compliqué ici, j'ai simplement utilisé le composant `UDashboardSidebar` de Nuxt UI Pro et ajouté les liens vers les pages des catégories et des utilisateurs :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst links = [{\n  id: 'categories',\n  label: 'Catégories',\n  icon: 'i-heroicons-tag',\n  to: '\u002Fadmin\u002Fcategories',\n  tooltip: {\n    text: 'Catégories',\n  }\n}, {\n  id: 'users',\n  label: 'Utilisateurs',\n  icon: 'i-heroicons-user-group',\n  to: '\u002Fadmin\u002Fusers',\n  tooltip: {\n    text: 'Utilisateurs',\n  }\n}]\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUDashboardLayout>\n    \u003CUDashboardPanel>\n      \u003CUDashboardSidebar>\n        \u003CUDashboardSidebarLinks :links=\"links\" \u002F>\n      \u003C\u002FUDashboardSidebar>\n    \u003C\u002FUDashboardPanel>\n\n    \u003Cslot \u002F>\n  \u003C\u002FUDashboardLayout>\n\u003C\u002Ftemplate>\n```\n\n## Sécuriser la Partie Administration\n\nLors de la construction de la partie administration d'une application, il est crucial de sécuriser et de restreindre l'accès, tant pour le frontend que pour le backend. Cela signifie que seuls les utilisateurs autorisés peuvent visualiser et manipuler les données.\n\nPour restreindre l'accès à la partie UI de l'administration, c'est-à-dire le frontend, j'ai utilisé un middleware. Il s'agit d'une fonction qui s'exécute avant la navigation vers la page et peut être utilisée pour vérifier si l'utilisateur est authentifié et dispose des bonnes permissions. Dans ce cas, j'ai créé un middleware `middleware\u002Fadmin.ts` pour vérifier si l'utilisateur est authentifié et a le rôle d'admin :\n\n```ts\nexport default defineNuxtRouteMiddleware(() => {\n  const { user } = useUserSession()\n\n  const isAdmin = user.value?.roleType === 'admin'\n\n  if (!isAdmin)\n    return redirectTo('\u002F')\n})\n```\n\n_Le middleware doit être utilisé sur les pages nécessitant une protection en utilisant `definePageMeta`._\n\nJe récupère l'utilisateur depuis la session en utilisant `useUserSession` et vérifie si l'utilisateur a le rôle d'admin. Sinon, je redirige l'utilisateur vers la page d'accueil.\n\nLe backend est beaucoup plus critique que le frontend, mais il n'est pas plus difficile à sécuriser. Pour ce faire, j'ai créé un utilitaire nommé `requireAdminUser` dans le fichier `server\u002Futils\u002Fsession.ts` :\n\n```ts\nimport type { UserSessionRequired } from '#auth-utils'\nimport type { H3Event } from 'h3'\n\nexport async function requireAdminUser(event: H3Event): Promise\u003CUserSessionRequired> {\n  const userSession = await getUserSession(event)\n\n  if (!userSession.user || userSession.user.roleType !== 'admin') {\n    throw createError({\n      statusCode: 401,\n      message: 'Unauthorized',\n    })\n  }\n\n  return userSession as UserSessionRequired\n}\n```\n\nCette fonction vérifie si l'utilisateur est authentifié et possède le rôle d'admin. Sinon, elle renvoie une erreur avec un code de statut 401. Cette fonction est utilisée dans les endpoints API pour les sécuriser :\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await requireAdminUser(event)\n\n  \u002F\u002F Code pour gérer la requête\n})\n```\n\nAvec ces deux fonctions, le frontend et le backend de la partie admin sont sécurisés. Seuls les utilisateurs authentifiés avec le rôle d'admin peuvent accéder à la partie administration de l'application.\n\n### À Venir\n\nCe stream a été très productif et a mis en place des éléments importants pour l'avenir. Le prochain stream sera consacré aux modules et au créateur de thèmes. J'espère vraiment pouvoir mettre cela en production bientôt. Je suis vraiment impatient de voir les retours de la communauté.\n\nLe projet est open-source et disponible sur GitHub à \u003CGitHubLink repo=\"barbapapazes\u002Forion\" \u002F> ! Donnez-lui une étoile si vous l'aimez ! ⭐\n\n## Récupérer une API externe pour les modules\n\n![Vignette de mon cinquième direct.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-18.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) est une application full-stack construite avec Nuxt et développée en direct [sur Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). Vous pouvez voir le replay sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) et le code source est open-source sur [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nAvant de continuer et d'implémenter la page des modules, j'ai décidé de modifier le comportement de `useFetch` sur les pages actuelles des Catégories et des Utilisateurs. En effet, le bloc composable `useFetch` bloque la navigation côté client jusqu'à ce que les données soient récupérées. Cela donne la sensation d'une application lente. Pour éviter cela, je peux utiliser le drapeau `lazy`. Côté serveur, cela ne change rien et la page sera toujours servie avec les données. Côté client, la navigation sera instantanée et j'afficherai un squelette pendant que les données sont en cours de récupération en utilisant le `pending` ref.\n\n```ts\nconst { data, pending } = await useFetch('\u002Fapi\u002Fcategories', {\n  deep: false,\n  lazy: true, \u002F\u002F \u003C= Ne bloque pas la navigation\n  default: () => []\n})\n```\n\nLe `pending` ref est un booléen qui indique si les données sont en cours de récupération ou non. Je le passe simplement aux props `loading` du composant `UTable` de Nuxt UI Pro et _voilà_ ! L'utilisateur verra un loader pendant que les données sont en cours de récupération et la navigation sera instantanée.\n\n\u003Cdiv flex=\"~ col\" md=\"flex-row\" gap-x-2>\n  \u003Cfigure>\n    \u003Cimg src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fblocking-fetch.gif\" alt=\"Sans drapeau\" loading=\"lazy\"\u002F>\n    \u003Cfigcaption>Sans drapeau lazy (blocage de la navigation)\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n  \u003Cfigure>\n    \u003Cimg src=\"https:\u002F\u002Fimages.soubiran.dev\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Flazy-fetch.gif\" alt=\"Avec drapeau lazy\" loading=\"lazy\" \u002F>\n    \u003Cfigcaption>Avec drapeau lazy (navigation instantanée)\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n\u003CCallout icon=\"i-simple-icons-youtube\" text=\"En savoir plus sur useFetch\" to=\"https:\u002F\u002Fyoutu.be\u002Fb1S5os65Urs\" target=\"_blank\" \u002F>\n\n### Page des Modules\n\nLes modules sont [des modules Nuxt](https:\u002F\u002Fnuxt.com\u002Fmodules). Ils seront liés aux thèmes et seront utilisés pour filtrer les thèmes. Imaginez que vous recherchez un thème avec un bon SEO et précisément un sitemap, vous pourrez filtrer les thèmes en fonction des modules qu'ils utilisent. C'est l'objectif de la fonctionnalité des modules. Cela aidera également l'utilisateur à comprendre comment le thème est construit et quelles sont les fonctionnalités.\n\nLa première chose que je fais est d'ajouter une nouvelle page `pages\u002Fadmin\u002Fmodules.vue`. Ensuite, j'ajoute le tableau pour afficher les modules et les métadonnées de la page pour utiliser la mise en page admin et le middleware admin. C'est similaire aux pages des catégories et des utilisateurs, donc je ne vais pas le détailler ici.\n\n#### Remplir la Base de Données\n\nLa question principale est : comment remplir la base de données avec les modules ? Pour les catégories, je les crée manuellement. Cela convient car il n'y a pas beaucoup de catégories et il n'y a pas de moyen d'obtenir une liste prédéfinie, mais pour les modules, c'est différent. Les modules sont créés par l'équipe principale ou la communauté et il y en a beaucoup. De plus, la liste n'est pas statique et peut changer au fil du temps. Donc, je dois trouver une API pour récupérer les modules. Heureusement, Nuxt a une API pour obtenir la liste des modules : [`api.nuxt.com\u002Fmodules`](https:\u002F\u002Fapi.nuxt.com\u002Fmodules). Cette API n'est pas documentée mais elle est publique et je peux l'utiliser.\n\nGrâce à cette API, je pourrai récupérer les données, les assainir pour ne garder que les informations utiles et les stocker dans la base de données. Je vais créer un point de terminaison, accessible uniquement à l'admin, pour récupérer les modules et les stocker dans la base de données. Un deuxième point de terminaison sera disponible pour récupérer les modules depuis la base de données. Cela sera utile pour le frontend afin d'afficher les modules.\n\n#### Récupération de l'API Externe\n\nJe crée un point de terminaison `server\u002Fapi\u002Fmodules\u002Ffetch.post.ts` (_hors stream, j'ai trouvé que nommer ce point de terminaison `sync` était une meilleure idée_) pour récupérer les modules depuis l'API externe et les stocker dans la base de données.\n\nTout d'abord, j'ajoute l'utilitaire `requireAdminUser` pour sécuriser le point de terminaison. Ensuite, je récupère les modules depuis l'API externe en utilisant la fonction globale `$fetch` :\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await requireAdminUser(event)\n\n  const data = await $fetch('https:\u002F\u002Fapi.nuxt.com\u002Fmodules')\n})\n```\n\nLes données retournées par l'API contiennent beaucoup de données dont je n'ai pas besoin. Je n'ai besoin que du `name`, `repo`, `type`, et `icon` des modules. Donc, j'assainis les données pour ne garder que les informations utiles :\n\n```ts\nconst modules = data.modules\n  .map(({ name, type, icon, repo }) => ({ name, type, icon, repo }))\n  .filter(({ type }) => type === 'official' || type === 'community')\n```\n\nJe choisis de ne pas garder le type `third party` car il peut s'agir d'un module de n'importe qui et je ne connais pas la qualité de ces modules. La clé `repo` n'était pas prévue mais je pense que c'est une bonne idée de l'avoir. Je pourrais l'utiliser pour ajouter un lien vers le dépôt GitHub du module sur une page de thème.\n\nMaintenant que j'ai mes modules, je dois les stocker dans la base de données, et je dois également créer la table pour les stocker. Dans le fichier `server\u002Fdatabase\u002Fschema.ts`, je crée la table `modules` :\n\n```ts\nexport const modules = sqliteTable('modules', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  name: text('name').notNull().unique(),\n  repo: text('repo').notNull().unique(),\n  type: text('type', { enum: ['official', 'community'] }).notNull(),\n  icon: text('icon'),\n})\n```\n\n> [!NOTE]\n> N'oubliez pas d'exécuter la commande drizzle pour générer la migration SQL.\n\nDans le point de terminaison, il est maintenant possible d'insérer les modules dans la base de données :\n\n```ts\nawait useDrizzle().insert(tables.modules).values(modules).execute()\n```\n\nMAIS, surprise, cela **ne fonctionne pas**. Je ne le savais pas, mais le [Cloudflare D1 a une limite (ou plusieurs)](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fd1\u002Fplatform\u002Flimits\u002F) sur les paramètres liés à 100. En réalité, cette limite existe parce qu'ils utilisent SQLite en arrière-plan, ce qui a cette limite pour des raisons de sécurité.\n\nQu'est-ce qu'un paramètre lié ? C'est un paramètre dans une requête SQL qui est remplacé par une valeur lorsque la requête est exécutée. Par exemple, dans la requête `SELECT * FROM users WHERE id = ?`, le `?` est un paramètre lié. Dans le cas de la requête d'insertion `INSERT INTO users (name, email) VALUES (?, ?)`, les paramètres liés sont les valeurs à insérer dans la table. La limite est de 100 paramètres liés. Attention, **une ligne n'est pas un paramètre lié**, un paramètre lié est une valeur unique, ce qui signifie que si vous insérez une ligne avec 10 colonnes, cela comptera comme 10 paramètres liés. Il y a 42 modules avec 4 colonnes, donc 168 paramètres liés.\n\nC'est un vrai problème car je devrai diviser l'insertion en plusieurs requêtes et un worker est limité dans le temps, donc s'il fonctionne trop longtemps, le worker pourrait échouer.\n\n```ts\nconst columnsPerModule = Object.keys(modules[0]).length\nconst insertPerLoop = Math.floor(100 \u002F columnsPerModule)\nconst loops = Math.ceil(modules.length \u002F insertPerLoop)\n\nfor (let loop = 0; loop \u003C loops; loop++) {\n  const values = modules.slice(loop * insertPerLoop, (loop + 1) * insertPerLoop)\n  await useDrizzle().insert(tables.modules).values(values).onConflictDoNothing({ target: tables.modules.repo }).execute()\n}\n```\n\nPour avoir une insertion dynamique en cas de changement du nombre de colonnes, je calcule le nombre maximum de lignes que je peux insérer dans une seule requête. Ensuite, je boucle sur les modules et insère le nombre maximum de lignes dans chaque requête. J'utilise également la méthode `onConflictDoNothing` pour éviter d'insérer le même module deux fois si je fais fonctionner le point de terminaison plusieurs fois.\n\n_J'ai essayé cela dans un environnement de production, et tout fonctionne bien._\n\nJe choisis de retourner un code de statut 204 lorsque les modules sont récupérés. Si tout est correct, je peux rafraîchir la liste des modules dans l'interface utilisateur pour voir les nouveaux modules.\n\n#### Servir les Modules\n\nCette partie est exactement la même que le point de terminaison d'index des catégories et des utilisateurs. Je crée un point de terminaison `server\u002Fapi\u002Fmodules\u002Findex.get.ts` pour obtenir les modules de la base de données :\n\n```ts\nexport default defineEventHandler(async () => {\n  const modules = await useDrizzle().select({\n    id: tables.modules.id,\n    name: tables.modules.name,\n    type: tables.modules.type,\n    icon: tables.modules.icon,\n  }).from(tables.modules)\n\n  return modules\n})\n```\n\nRien de nouveau ici, je sélectionne simplement les modules depuis la base de données et les retourne.\n\n### Conclusion\n\nTout cela nous donne ce résultat :\n\n![Page des Modules](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fmodules-page.png)\n\nCe stream était différent des autres. D'abord, j'ai maintenant un nouveau micro et le son est bien meilleur qu'avant !\n\nDeuxièmement, j'ai rencontré un problème avec la limite D1 (ce qui pourrait entraîner un besoin de repenser le projet, espérons que ce ne sera pas le cas) sur les paramètres liés. C'est une bonne leçon pour moi, et j'espère que cela sera utile pour vous.\n\n## Factorisation de la Gestion des Utilisateurs\n\n_Cette partie a été réalisée hors stream car elle n'était pas intéressante à voir et je n'étais pas sûr de la direction que je voulais prendre._\n\nEn écrivant une partie de cet article, je me demande si stocker les données des utilisateurs était une bonne idée. Ne pourrait-il pas être mieux ? Cette question vient de la façon dont je gère le point de terminaison d'authentification.\n\nEn fait, lorsqu'un utilisateur se connecte, j'écris toujours les données de l'utilisateur dans la base de données pour être sûr qu'elles sont à jour. La principale préoccupation est qu'une écriture est beaucoup plus coûteuse qu'une lecture. Pour trouver des idées, j'ai lu le [projet de liste de tâches Nuxt d'Atinux](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-todos-edge). Dans ce projet, il ne stocke pas les données des utilisateurs. Lorsque l'utilisateur se connecte, il ne stocke que l'ID de l'utilisateur dans le cookie. Lorsque l'utilisateur crée une tâche, il lui suffit de lire l'ID de l'utilisateur depuis le cookie pour savoir qui est l'utilisateur. C'est très intelligent et simple. Je vais faire de même. _Premièrement à travers._\n\nAnalysons si cette méthode est possible pour le projet Orion. Pour savoir si c'est possible, je dois répondre à quelques questions :\n\n1. Puis-je bannir ou promouvoir un utilisateur sans stocker les données de l'utilisateur ?\n2. Un utilisateur peut-il créer des données sans stocker les données de l'utilisateur ?\n3. Puis-je afficher les données des utilisateurs sans stocker les données de l'utilisateur ?\n\nPour la première question, c'est oui. Je peux imaginer un système où vous entrez le login d'un utilisateur, et lorsque l'utilisateur se connecte, je rechercherai dans la base de données si l'entrée existe et obtiendrai les données pertinentes. Ce n'est pas la meilleure méthode mais c'est possible.\n\nPour la deuxième question, c'est oui. Puisque les données sont dans le cookie, je peux facilement savoir qui est l'utilisateur et créer des données en utilisant cette information. _Et c'est ainsi que ça fonctionne en fait. Je ne récupère pas l'utilisateur pour chaque requête._\n\nPour la troisième question, c'est non. Imaginez que je veuille afficher le modèle créé par l'utilisateur 'X'. Dans notre schéma SQL, nous avons une colonne `user_id` dans la table `templates`. Si je ne stocke pas les données de l'utilisateur, je ne pourrai pas savoir qui est l'utilisateur 'X', associé à `user_id`. Je devrai stocker les données de l'utilisateur dans la base de données. _Cela pourrait être possible avec une base de données non relationnelle mais ce n'est pas le cas ici. Nous pourrions aussi imaginer un système où l'utilisateur est sauvegardé lorsqu'il crée un modèle. C'est possible mais cela ajoutera beaucoup de complexité. Au final, cette réponse et celle à la question une compliquent le système pour aucun réel gain._\n\nMaintenant que nous savons que nous devons stocker les utilisateurs dans la base de données, que pouvons-nous améliorer ?\n\n- Sauvegarder les données des utilisateurs seulement si nécessaire. Par exemple, je pourrais d'abord rechercher l'utilisateur avant de sauvegarder ses données lorsque l'utilisateur se connecte.\n- Sauvegarder l'email de l'utilisateur. Cela sera utile pour le système de feedback.\n- Sauvegarder le nom de l'utilisateur et renommer la colonne `username` en `login`.\n- Ajouter une colonne `created_at` et une colonne `updated_at`.\n- Mettre à jour les données stockées dans le cookie.\n\nAvec tous ces points, je mets également à jour l'interface utilisateur en conséquence. Il n'y a rien de difficile ici ; il s'agit principalement de renommer et d'ajouter des champs dans le schéma.\n\n```ts\nexport const users = sqliteTable('users', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  githubId: integer('github_id').notNull().unique(),\n  email: text('email').notNull().unique(),\n  login: text('login').notNull().unique(),\n  name: text('name'),\n  avatarUrl: text('avatar_url').notNull(),\n  roleType: text('role_type', { enum: ['admin', 'creator'] }).default('creator'),\n  createdAt: text('created_at').notNull().$defaultFn(() => sql`(current_timestamp)`),\n  updatedAt: text('updated_at').notNull().$defaultFn(() => sql`(current_timestamp)`).$onUpdateFn(() => sql`(current_timestamp)`),\n})\n```\n\nLes fonctions `$defaultFn` et `$onUpdateFn` sont utilisées pour définir la valeur par défaut de la colonne. Dans le cas de `createdAt`, la valeur par défaut est l'horodatage actuel. Dans le cas de `updatedAt`, la valeur par défaut est l'horodatage actuel, et la valeur est mise à jour lorsque la ligne est mise à jour. C'est entièrement automatique et géré par Drizzle.\n\nJe mets également à jour le `server\u002Froutes\u002Fauth\u002Fgithub.ts` pour stocker les données de l'utilisateur seulement si nécessaire :\n\n```ts\nexport default oauth.githubEventHandler({\n  config: {\n    emailRequired: true,\n  },\n  async onSuccess(event, result) {\n    const { user: ghUser } = result\n\n    const githubId = ghUser.id\n\n    let user: User | undefined\n\n    user = await useDrizzle().select().from(tables.users).where(eq(tables.users.githubId, githubId)).get()\n\n    \u002F**\n     * Si l'utilisateur n'est pas dans la base de données ou si ses données ont changé, mettre à jour les données de l'utilisateur.\n     * Une écriture est plus coûteuse qu'une lecture, donc nous écrivons seulement si nécessaire.\n     *\u002F\n    if (!user || userDataChanged(user, ghUser)) {\n      user = await useDrizzle().insert(tables.users).values({\n        githubId,\n        login: ghUser.login,\n        email: ghUser.email,\n        name: ghUser.name,\n        avatarUrl: ghUser.avatar_url,\n      }).onConflictDoUpdate({\n        target: tables.users.githubId,\n        set: {\n          login: ghUser.login,\n          email: ghUser.email,\n          name: ghUser.name,\n          avatarUrl: ghUser.avatar_url,\n        },\n      }).returning().get()\n    }\n\n    \u002F**\n     * Définir seulement les données nécessaires dans la session.\n     *\u002F\n    await setUserSession(event, {\n      user: {\n        id: user.id!,\n        login: ghUser.login,\n        email: ghUser.email,\n        name: ghUser.name,\n        avatarUrl: ghUser.avatar_url,\n        roleType: user.roleType || 'creator',\n      },\n    })\n\n    return sendRedirect(event, '\u002Fportal')\n  },\n})\n```\n\nAvec cette nouvelle méthode de gestion des données des utilisateurs, je réduis le nombre d'écritures dans la base de données tout en mettant à jour les données des utilisateurs lorsque nécessaire.\n\n## Création d'un formulaire pour les modèles\n\n![Vignette de mon sixième stream en direct.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-19.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) est une application full-stack construite avec Nuxt et développée en direct [sur Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). Vous pouvez voir le replay sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) et le code source est open-source sur [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nDepuis le début de la série, je travaille sur l'interface d'administration de l'application web. Aujourd'hui, il est temps de créer le formulaire pour ajouter un nouveau modèle. Ce formulaire sera utilisé par les créateurs pour ajouter leurs modèles ou thèmes à la plateforme. Pour rappel, Orion est une collection de modèles alimentée par la communauté pour votre prochain projet, des pages de destination aux applications web complètes.\n\n### Schéma du modèle\n\n_N'oubliez pas que les choses évoluent et que le schéma n'est pas définitif._\n\nÉtant donné que la fonctionnalité de modèle est importante, je l'ai divisée en plusieurs streams en direct. La première étape consiste à créer le formulaire le plus simple mais fonctionnel possible et à ajouter des fonctionnalités au fil du temps. C'est pourquoi le schéma ci-dessus n'est pas exactement le même que celui que nous avons dessiné dans le schéma SQL (pas de modules, pas d'images et pas de markdown, pour l'instant).\n\n```ts\nexport const templates = sqliteTable('templates', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  hash: text('hash').notNull().unique(),\n  slug: text('slug').notNull(),\n  title: text('title').notNull(),\n  status: text('status', { enum: ['submitted', 'refused', 'validated'] }).notNull().default('submitted'),\n  paidStatus: text('paid_status', { enum: ['free', 'paid'] }).notNull().default('free'),\n  liveUrl: text('live_url'),\n  accessUrl: text('access_url').notNull(),\n  description: text('description').notNull(),\n  userId: integer('user_id').notNull().references(() => users.id),\n  categoryId: integer('category_id').notNull().references(() => categories.id),\n})\n```\n\nLa colonne `hash` est un petit ID que je stockerai dans la base de données pour créer des URL comme Notion ou Dev.to : `\u003Cslug>-\u003Chash>`. Cela est utile pour avoir une URL lisible, à la fois pour les humains et pour le SEO, mais comme deux modèles peuvent avoir le même nom, ou le nom d'un modèle peut changer, j'ai besoin d'un ID unique.\n\nPour la première fois, nous créons une relation entre deux tables : une relation un-à-plusieurs entre la table `users` et la table `templates`. Cette relation est utilisée pour savoir qui est le créateur du modèle. La colonne `userId` est une clé étrangère qui fait référence à la colonne `id` de la table `users`.\n\nLa colonne `categoryId` est également une clé étrangère qui fait référence à la colonne `id` de la table `categories`. Cette relation est utilisée pour savoir dans quelle catégorie se trouve le modèle.\n\nAfin de pouvoir récupérer les modèles d'un utilisateur ou d'une catégorie, je dois également définir la relation dans l'autre sens. Grâce à Drizzle, c'est assez facile.\n\n```ts\nexport const usersRelations = relations(users, ({ many }) => ({\n  templates: many(templates),\n}))\n\nexport const categoriesRelations = relations(categories, ({ many }) => ({\n  templates: many(templates),\n}))\n```\n\n### Formulaire de modèle\n\nPendant cette phase, je crée le formulaire sur l'UI, exactement comme je l'ai fait pour les catégories. Je définis l'état, le schéma et l'action au moment de la soumission !\n\nMaintenant qu'un utilisateur est capable de remplir un formulaire, j'ai besoin d'un point de terminaison pour enregistrer les données dans la base de données. Je crée le point de terminaison `server\u002Fapi\u002Ftemplates\u002Findex.post.ts` pour enregistrer le modèle dans la base de données :\n\n```ts\nimport { number, object, string, enum as zEnum } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const { user } = await requireUserSession(event)\n\n  const body = await readValidatedBody(event, object({\n    title: string(),\n    paidStatus: zEnum(['free', 'paid']),\n    categoryId: number(),\n    liveUrl: string().optional(),\n    accessUrl: string(),\n    description: string().max(1000),\n  }).parse)\n\n  await useDrizzle().insert(tables.templates).values({\n    hash: useHash(),\n    slug: useSlugify(body.title),\n    title: body.title,\n    categoryId: body.categoryId,\n    paidStatus: body.paidStatus,\n    liveUrl: body.liveUrl,\n    accessUrl: body.accessUrl,\n    description: body.description,\n    userId: user.id,\n  }).execute()\n\n  return sendNoContent(event, 201)\n})\n```\n\nAssez similaire au point de terminaison pour créer une nouvelle catégorie. **Une différence** est l'utilisation de l'utilitaire `requireUserSession` pour s'assurer que la demande provient d'un utilisateur authentifié et pour obtenir les données de l'utilisateur. J'utilise les données pour remplir la colonne `userId` dans la table `templates`. Bien sûr, je valide les données reçues du client à l'aide de Zod.\n\nL'utilitaire `useHash` est un petit wrapper autour du package `nanoid`.\n\n### Modèles dans l'Interface d'Administration\n\n_Pendant cette partie, j'ai rencontré un bug important. Lisez attentivement la partie suivante, cela pourrait vous faire gagner beaucoup de temps si vous prévoyez également d'utiliser Drizzle sur Cloudflare._\n\nLes utilisateurs sont capables de créer des modèles, c'est bien. Pour les surveiller et les approuver, je dois créer une page dans l'interface d'administration pour gérer les modèles. Cette page affichera les modèles dans un tableau et permettra à l'administrateur de les approuver ou de les refuser. _Je n'implémente que la vue, pas l'action._ La tâche est simple, c'est comme toute autre page dans l'interface d'administration. _Si seulement je savais..._\n\nTout d'abord, créons le point de terminaison pour obtenir les modèles depuis la base de données. Je crée le point de terminaison `server\u002Fapi\u002Ftemplates\u002Findex.get.ts` :\n\n```ts\nexport default defineEventHandler(async () => {\n  const templates = await useDrizzle()\n    .select()\n    .from(tables.templates)\n    .leftJoin(tables.users, eq(tables.templates.userId, tables.users.id))\n    .leftJoin(tables.categories, eq(tables.templates.categoryId, tables.categories.id))\n\n  return templates\n})\n```\n\nCe point de terminaison récupère tous les modèles de la base de données et les remplit à l'aide d'une jointure et de la relation que nous avons définie plus tôt dans le schéma.\n\nUne `jointure` est une opération SQL qui combine les lignes de deux ou plusieurs tables en fonction d'une colonne liée entre elles. Dans ce cas, je fais une jointure de la table `templates` avec la table `users` et la table `categories`. La méthode `leftJoin` est utilisée pour conserver toutes les lignes de la table `templates`, même s'il n'y a pas de correspondance dans les tables `users` ou `categories`.\n\n![Une explication schématique de l'opération de jointure.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fleft-join.png)\n\nMaintenant que j'ai les données, je peux les afficher sur l'UI. J'utilise le composant `UTable` de Nuxt UI Pro pour afficher les modèles dans un tableau :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst columns = [{\n  key: 'id',\n  label: '#',\n}, {\n  key: 'title',\n  label: 'Title',\n  sortable: true,\n}, {\n  key: 'status',\n  label: 'Status',\n  sortable: true,\n}, {\n  key: 'paidStatus',\n  label: 'Paid Status',\n  sortable: true,\n}, {\n  key: 'liveUrl',\n  label: 'Live URL',\n}, {\n  key: 'accessUrl',\n  label: 'Access URL',\n}, {\n  key: 'description',\n  label: 'Description',\n}, {\n  key: 'category',\n  label: 'Category',\n}, {\n  key: 'createdBy',\n  label: 'Created By',\n}]\n\nconst { data: templates, refresh, pending } = await useFetch('\u002Fapi\u002Ftemplates', {\n  deep: false,\n  lazy: true,\n  default: () => [],\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUTable\n    :columns=\"columns\"\n    :rows=\"templates\"\n    :loading=\"pending\"\n  >\n    \u003Ctemplate #liveUrl-data=\"{ row }\">\n      \u003CUButton\n        v-if=\"row.liveUrl\"\n        variant=\"link\"\n        target=\"_blank\"\n        :to=\"row.liveUrl\"\n        class=\"flex flex-row items-center gap-1\"\n      >\n        \u003Cspan>{{ row.liveUrl }}\u003C\u002Fspan>\n\n        \u003Cspan class=\"i-heroicons-arrow-top-right-on-square-16-solid inline-block h-4 w-4\" \u002F>\n      \u003C\u002FUButton>\n      \u003Cspan v-else> - \u003C\u002Fspan>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate #accessUrl-data=\"{ row }\">\n      \u003CUButton\n        variant=\"link\"\n        target=\"_blank\"\n        :to=\"row.accessUrl\"\n        class=\"flex flex-row items-center gap-1\"\n      >\n        \u003Cspan>{{ row.accessUrl }}\u003C\u002Fspan>\n\n        \u003Cspan class=\"i-heroicons-arrow-top-right-on-square-16-solid inline-block h-4 w-4\" \u002F>\n      \u003C\u002FUButton>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate #paidStatus-data=\"{ row }\">\n      \u003Ctemplate v-if=\"row.paidStatus === 'free'\">\n        \u003CUBadge\n          color=\"green\"\n          variant=\"subtle\"\n        >\n          Free\n        \u003C\u002FUBadge>\n      \u003C\u002Ftemplate>\n      \u003Ctemplate v-else-if=\"row.paidStatus === 'paid'\">\n        \u003CUBadge\n          color=\"yellow\"\n          variant=\"subtle\"\n        >\n          Paid\n        \u003C\u002FUBadge>\n      \u003C\u002Ftemplate>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate #createdBy-data=\"{ row }\">\n      \u003Cdiv class=\"flex flex-row items-center gap-2\">\n        \u003Cimg\n          :src=\"row.user.avatarUrl\"\n          alt=\"avatar\"\n          class=\"h-6 w-6 rounded-full\"\n        >\n        \u003Cspan>{{ row.user.name ?? row.user.login }}\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n    \u003C\u002Ftemplate>\n    \u003Ctemplate #category-data=\"{ row }\">\n      {{ row.category.name }}\n    \u003C\u002Ftemplate>\n  \u003C\u002FUTable>\n\u003C\u002Ftemplate>\n```\n\n**Mais cela ne fonctionne pas.** Étant habitué à Adonis, j'étais familiarisé avec un objet comme celui-ci :\n\n```json\n{\n  \"id\": 1,\n  \"title\": \"My template\",\n  \"status\": \"submitted\",\n  \"liveUrl\": \"https:\u002F\u002Fexample.com\",\n  \"user\": {\n    \"id\": 1,\n    \"name\": \"John Doe\",\n    \"avatarUrl\": \"https:\u002F\u002Fexample.com\u002Favatar.jpg\"\n  },\n  \"category\": {\n    \"id\": 1,\n    \"name\": \"Landing Page\"\n  }\n}\n```\n\nMais j'ai reçu un objet comme celui-ci :\n\n```json\n{\n  \"template\": {\n    \"id\": 1,\n    \"title\": \"My template\",\n    \"status\": \"submitted\",\n    \"liveUrl\": \"https:\u002F\u002Fexample.com\"\n  },\n  \"user\": {\n  },\n  \"category\": {\n  }\n}\n```\n\net quelle surprise de voir que les clés et les valeurs étaient échangées entre elles. _Difficile à expliquer mais voyez l'exemple._\n\n```json\n{\n  \"template\": {\n    \"id\": \"My template\",\n    \"status\": \"https:\u002F\u002Fexample.com\"\n  },\n  \"user\": {\n    \"id\": \"John Doe\",\n    \"login\": \"https:\u002F\u002Favatars.github.com\u002Fu\u002F1\"\n  }\n}\n```\n\nTrès difficile de manipuler les données maintenant ! _L'ID devrait être un nombre et le login ne devrait définitivement pas être une URL._\n\nAprès une longue recherche, j'ai trouvé deux problèmes : https:\u002F\u002Fgithub.com\u002Fcloudflare\u002Fworkers-sdk\u002Fissues\u002F3160 et https:\u002F\u002Fgithub.com\u002Fdrizzle-team\u002Fdrizzle-orm\u002Fissues\u002F555 expliquant pourquoi cela ne fonctionne pas. Lorsque deux tables ont le même nom de colonne, ~~Drizzle et~~ Cloudflare sont incapables de les résoudre correctement et mélangent les données. La solution consiste à sélectionner explicitement les colonnes et à renommer les colonnes similaires.\n\n```ts\nconst templates = await useDrizzle().select({\n  id: tables.templates.id,\n  title: tables.templates.title,\n  description: tables.templates.description,\n  status: tables.templates.status,\n  paidStatus: tables.templates.paidStatus,\n  liveUrl: tables.templates.liveUrl,\n  accessUrl: tables.templates.accessUrl,\n  user: {\n    name: tables.users.name,\n    login: tables.users.login,\n    avatarUrl: tables.users.avatarUrl,\n  },\n  category: {\n    name: sql\u003Cstring>`${tables.categories.name}`.as('c_name'),\n  },\n}).from(tables.templates).leftJoin(tables.users, eq(tables.templates.userId, tables.users.id)).leftJoin(tables.categories, eq(tables.templates.categoryId, tables.categories.id))\n```\n\n`sql\u003Cstring>'${tables.categories.name}'.as('c_name')` est le code important. Je renomme la colonne `name` en `c_name` pour éviter le conflit. J'utilise également cette syntaxe pour formater l'objet avec les clés `user` et `category`. Maintenant, tout fonctionne comme prévu.\n\n### Conclusion\n\nTravailler sur le côté client, et pas seulement sur la partie admin, rend le projet encore plus concret. Il reste encore beaucoup de travail sur le formulaire de modèle comme le markdown ou l'image, mais c'est un très bon début.\n\nJ'apprécie vraiment ce projet et j'espère que vous aussi.\n\n## Poursuivre le modèle de formulaire\n\n![Vignette de mon huitième direct.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-20.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) est une application full-stack construite avec Nuxt et développée en direct [sur Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). Vous pouvez voir la rediffusion sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) et le code source est open-source sur [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nCe direct était court, donc j'essaie de faire des choses intéressantes. Je fais principalement trois choses :\n\n- Créer la page de modèle de l'émission\n- Gérer le markdown côté serveur pour la description du modèle\n- Ajouter un éditeur de markdown pour la description du modèle\n\nVoyons comment ça se passe. _Pendant le direct, ça se passe bien mais depuis, j'ai complètement réécrit la partie éditeur de markdown._\n\n### Page de modèle de l'émission\n\nCet élément était davantage lié à l'utilisation de l'URL qu'au design de la page. En effet, l'URL fonctionnera comme Notion ou Dev.to : `\u002F\u003Cslug>-\u003Chash>` où le `slug` est le nom du modèle et le `hash` est un identifiant unique. Le `slug` est utilisé pour le SEO et le `hash` est utilisé pour obtenir le modèle. Le `slug` n'est pas unique et peut changer avec le temps. Le `hash` est unique et sert à obtenir le modèle.\n\nSur la page, nous commençons par obtenir les paramètres de la route :\n\n```ts\nconst route = useRoute()\nconst slug = route.params.slug \u002F\u002F \u003Cslug>-\u003Chash>\n```\n\nLa variable `slug` contient à la fois le `slug` du modèle et le `hash`. Nous savons que le hash fait exactement 12 caractères donc nous pouvons découper le `slug` pour obtenir le `slug` et le `hash` :\n\n```ts\nslug.slice(-12)\n```\n\nEnfin, nous pouvons faire une requête au backend pour récupérer le modèle. Tout cela nous donne ce code :\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst route = useRoute()\nconst hash = computed(() => {\n  return (route.params.slug as string).slice(-12)\n})\nconst { data: template } = await useFetch(`\u002Fapi\u002Ftemplates\u002F${hash.value}`, {\n  deep: false,\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Carticle\n    v-if=\"template\"\n  >\n    {{ template }}\n  \u003C\u002Farticle>\n\u003C\u002Ftemplate>\n```\n\nLa partie serveur n'est pas si compliquée puisque nous obtenons seulement le hash à partir du paramètre de route et recherchons le modèle en utilisant une clause `where` :\n\n```ts\nimport { object, string } from 'zod'\n\nexport default defineEventHandler(async (event) => {\n  const params = await getValidatedRouterParams(event, object({\n    hash: string().length(12),\n  }).parse)\n\n  const { hash } = params\n\n  const template = await useDrizzle().select().from(tables.templates).where(eq(tables.templates.hash, hash))\n\n  return template\n})\n```\n\nPlus tard, nous devrons joindre les données de l'utilisateur et les données de la catégorie pour avoir un modèle complet.\n\n### Markdown côté serveur\n\nL'idée de recevoir du markdown du client est de le nettoyer facilement pour éviter les injections HTML.\n\nImaginez si le client envoie ce code HTML :\n\n```html\n\u003Cp> Hello World!\u003C\u002Fp>\n\u003Cscript> console.log('hacked') \u003C\u002Fscript>\n```\n\nSi le serveur stocke ce contenu dans la base de données et l'affiche ensuite en utilisant la directive `v-html`, chaque utilisateur qui verra la page exécutera le script. C'est un réel problème de sécurité.\n\nEn recevant du markdown du client, je peux facilement le transformer en utilisant `markdown-it` et stocker le **HTML nettoyé** dans la base de données. Le HTML nettoyé est du HTML qui ne contient aucun script ou balise dangereuse.\n\nPour ce faire, je dois installer `markdown-it` et mettre à jour le point de terminaison pour créer un modèle `server\u002Fapi\u002Ftemplates\u002Findex.post.ts`. J'ajoute également une nouvelle colonne `descriptionHTML` au schéma pour stocker le HTML nettoyé. Stocker le HTML évite de le faire chaque fois que nous rendons la page.\n\n```ts\nawait useDrizzle().insert(tables.templates).values({\n  hash: useHash(),\n  slug: useSlugify(body.title),\n  title: body.title,\n  categoryId: body.categoryId,\n  paidStatus: body.paidStatus,\n  liveUrl: body.liveUrl,\n  accessUrl: body.accessUrl,\n  description: body.description,\n  descriptionHTML: useMarkdown(body.description),\n  userId: user.id,\n}).execute()\n```\n\nLa fonction `useMarkdown` est une fonction simple qui utilise `markdown-it` pour transformer le markdown en HTML.\n\n### Éditeur de Markdown\n\n_Cette partie est très expérimentale et pourrait changer à l'avenir. Il s'agit d'une première implémentation._\n\n_En écrivant cette partie, j'ai fait quelques recherches supplémentaires et tout va changer. Je pense que c'est encore intéressant de voir le processus._\n\nAvant de commencer, je dois faire un choix. Quel éditeur de markdown devrais-je utiliser ? _Rappelez-vous que nous venons de mettre en place un point de terminaison serveur pour gérer le markdown._ J'ai de nombreuses options :\n\n- SimpleMDE\n- EasyMDE\n- TinyMDE\n- Editor.js\n- Quill\n- Trix\n- CKEditor\n- TinyMCE\n\nC'est beaucoup d'options. Mais, quels sont mes besoins ? J'ai besoin d'un éditeur très simple qui peut retourner du markdown. Ça a l'air facile, non ?\n\nEditor.js, CKEditor et TinyMCE sont des éditeurs avancés donc je ne les utiliserai pas. SimpleMDE est une bonne option mais trop vieux. Il n'a pas reçu de mises à jour depuis 6 ans. SimpleMDE et TinyMDE sont des forks de SimpleMDE. EasyMDE est puissant, simple et retourne du markdown. C'est une bonne option. TinyMDE n'a pas assez d'options de personnalisation. Trix et Quill ne sont pas des éditeurs de markdown. Ce sont des éditeurs de texte enrichi donc ils ne produisent pas de markdown. Je vais opter pour EasyMDE. _Pour l'instant._\n\nNous pouvons donc maintenant installer le package et l'utiliser. L'utilisation est un peu délicate. Étant un package uniquement côté client, nous ne pouvons pas l'exécuter ni l'importer côté serveur. Pour l'utiliser, nous allons l'importer dynamiquement dans un hook `onMounted`. _`onBeforeMount` est encore mieux !_\n\n```ts\nonMounted(async () => {\n  const EasyMDE = await import('easymde').then(m => m.default)\n  const easymde = new EasyMDE()\n})\n```\n\nEt ça fonctionne ? Oui, mais je pense que nous avons complètement oublié d'importer le style !\n\n![EasyMDE sans style](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Feasymde-without-style.png)\n\nJe pense que nous avons oublié d'ajouter le style pour EasyMDE. Grâce à Vite et son chargeur CSS, nous pouvons importer directement le fichier CSS dans notre script.\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nimport 'easymde\u002Fdist\u002Feasymde.min.css'\n\u003C\u002Fscript>\n```\n\n![EasyMDE avec style](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Feasymde-with-style.png)\n\nOk, ce n'est pas parfait et j'aurai du travail à faire, tant sur les styles que sur les icônes. Mais c'est un bon début. La seule partie que je n'aime pas est le symbole markdown. Je préférerais ne pas les avoir parce que ce n'est pas convivial (tout le monde pourrait devoir créer un modèle).\n\nMais de toute façon, c'est un bon début et je peux maintenant facilement écouter les changements de l'éditeur et mettre à jour la description.\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst description = ref\u003Cstring>('')\nonMounted(async () => {\n  const EasyMDE = await import('easymde').then(m => m.default)\n  const easymde = new EasyMDE()\n  easyMDE.codemirror.on('change', () => {\n    description.value = easyMDE.value()\n  })\n})\n\u003C\u002Fscript>\n```\n\nMaintenant, je peux envoyer la description au serveur et elle sera transformée en HTML !\n\n### Quelle est la prochaine étape pour le formulaire ?\n\nLe formulaire est loin d'être terminé mais il progresse bien. Je vais ajouter le support pour les images et les modules dans le prochain stream.\n\nLe projet Orion est presque prêt pour une version alpha et je suis impatient de voir comment la communauté l'utilisera et découvrir le contenu qu'ils partageront.\n\n## Remplacer l'éditeur Markdown\n\n_Cette partie a été réalisée hors-stream parce que je n'étais pas satisfait de l'éditeur EasyMDE mais je ne savais pas quoi faire._\n\nAprès y avoir réfléchi, j'ai réalisé que l'utilisation de Markdown, pour une simple description et qui peut être utilisée par des utilisateurs non techniques, n'est pas la meilleure idée. De plus, l'éditeur EasyMDE utilise CodeMirror qui est lent et beaucoup trop lourd pour un simple éditeur.\n\nMaintenant que nous savons que l'éditeur EasyMDE n'est pas la meilleure solution pour nos besoins, nous devons en trouver un nouveau. Nous avons besoin d'un éditeur de texte enrichi simple et il y a deux options principales : Quill et Trix. Dans ce cas, Quill semble être plus simple donc je vais opter pour lui. _Je ne détaillerai pas l'installation de Quill, c'est assez simple._\n\nComme Quill est un éditeur de texte enrichi, il ne génère pas de markdown mais du HTML. Je dois donc mettre à jour le point de terminaison pour stocker le HTML au lieu du markdown. Je dois également mettre à jour le schéma pour supprimer la colonne `description` et ajouter une colonne `descriptionHTML`.\n\n### Mise à jour du point de terminaison\n\nPourquoi dois-je mettre à jour le point de terminaison ? Quill renvoie du HTML déjà nettoyé, donc je pourrais le stocker directement dans la base de données. Oui mais non. Nous construisons une API et il est impossible de faire confiance au client, même à notre propre client. N'importe qui peut modifier le HTML ou utiliser un client HTTP pour envoyer une requête au serveur avec du HTML malveillant. Je dois donc nettoyer le HTML sur le serveur.\n\nPar exemple, voici du HTML malveillant :\n\n```html\n\u003Cp> Bonjour le monde ! \u003C\u002Fp>\n\u003Cscript> console.log('piraté') \u003C\u002Fscript>\n```\n\nSi vous stockez ce HTML dans la base de données et l'affichez en utilisant la directive `v-html`, chaque utilisateur qui verra la page exécutera le script. C'est un vrai problème de sécurité. Nous ne pouvons pas utiliser `{{ html }}` parce que Vue échappera le HTML donc nettoyer notre contenu est la seule solution.\n\nJe vais utiliser le paquet `sanitize-html` pour nettoyer le HTML. Après avoir nettoyé avec ce paquet, le HTML sera :\n\n```html\n\u003Cp> Bonjour le monde ! \u003C\u002Fp>\n```\n\nC'est parfait !\n\n```ts\nawait useDrizzle().insert(tables.templates).values({\n  description: body.description ? sanitizeHtml(body.description) : null,\n})\n```\n\nLa fonction `sanitizeHtml` est l'exportation par défaut du paquet `sanitize-html`. Facile, non ?\n\n### Mise à jour du schéma\n\nMaintenant, je dois mettre à jour le schéma pour supprimer la colonne `descriptionHTML` puisque la `description` est maintenant par défaut en HTML. Il est important de savoir que si l'utilisateur doit modifier son modèle, le HTML sera utilisé par l'éditeur Quill.\n\n```ts\nexport const templates = sqliteTable('templates', {\n  description: text('description'),\n  descriptionHTML: text('description_html'), \u002F\u002F [!code --]\n})\n```\n\n### Mise à jour de l'interface utilisateur\n\nL'utilisation de Quill n'est pas si différente de EasyMDE. Je charge paresseusement le paquet Quill, crée une nouvelle instance de celui-ci et écoute les changements. Je n'oublie pas d'importer le fichier CSS.\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nimport 'quill\u002Fdist\u002Fquill.snow.css'\n\nonBeforeMount(async () => {\n  const Quill = await import('quill').then(m => m.default)\n  quill.value = new Quill('#editor', {\n    theme: 'snow',\n  })\n  quill.value?.on('text-change', (_, __, source) => {\n    state.description = quill.value?.root.innerHTML ?? ''\n  })\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv id=\"editor\" \u002F>\n\u003C\u002Ftemplate>\n```\n\nBien sûr, je dois ajouter quelques styles personnalisés à l'éditeur pour qu'il s'intègre plus naturellement dans le design.\n\nUne dernière chose. J'utilise ceci dans un formulaire Nuxt UI. Dans ce formulaire, chaque élément est validé par rapport au schéma. Cela me permet d'afficher un message d'erreur si la description est vide ou trop longue. Le problème est que ce n'est pas intégré dans l'éditeur Quill, il ne déclenche aucune validation. Grâce à l'API du formulaire Nuxt UI, je peux facilement valider le contenu de l'éditeur Quill à chaque changement.\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst form = ref()\n\nonBeforeMount(async () => {\n  quill.value?.on('text-change', (_, __, source) => {\n    state.description = quill.value?.root.innerHTML ?? ''\n    form.value.validate('description', { silent: true })\n  })\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUFormGroup\n    label=\"Description\"\n    name=\"description\"\n    :hint=\"`${state.description?.length || 0}\u002F2000 characters`\"\n  >\n    \u003Cdiv id=\"editor\" \u002F>\n  \u003C\u002FUFormGroup>\n\u003C\u002Ftemplate>\n```\n\nMaintenant, l'éditeur Quill est complètement intégré dans le formulaire et un message d'erreur s'affichera si la description est vide ou trop longue.\n\n![L'éditeur de texte enrichi personnalisé pour s'intégrer pleinement dans le formulaire.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Frich-text-editor.png)\n\n### Conclusion\n\nJe suis vraiment content de ce changement. Depuis des années, je me demandais comment installer et utiliser un éditeur de texte enrichi et maintenant je sais. Vous pouvez utiliser le markdown pour les utilisateurs techniques, le parser sur le serveur et stocker le HTML pour simplifier une obtention, ou utiliser un éditeur de texte enrichi pour les utilisateurs non techniques et nettoyer le HTML sur le serveur. C'est une bonne leçon pour moi et j'espère qu'elle vous sera utile.\n\n## Ajout de modules au formulaire de modèle\n\n![Vignette de mon neuvième live stream.](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002F2024-05-28.png)\n\n_[Orion](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion) est une application full-stack construite avec Nuxt et développée en direct [sur Twitch](https:\u002F\u002Fwww.twitch.tv\u002Fbarbapapazes). Vous pouvez voir le replay sur [YouTube](https:\u002F\u002Fwww.youtube.com\u002F@barbapapazes) et le code source est open-source sur [GitHub](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Forion)._\n\nPendant ce live stream, j'ai ajouté la possibilité d'ajouter des modules à un modèle dans le formulaire. Cette fonctionnalité est importante car elle permettra aux utilisateurs de filtrer les modèles par modules.\n\nJ'ai choisi de le faire dans un stream séparé car c'est une relation de plusieurs à plusieurs et un peu plus complexe que les autres parties. Je m'attendais à le faire en 1 heure, mais cela m'a pris 2 heures avec l'aide du chat. En effet, j'étais bloqué sur la requête SQL pour récupérer un modèle peuplé avec ses modules, et la documentation de Drizzle n'était pas claire à ce sujet.\n\nPlongeons dans le code et comprenons comment fonctionne le concept des relations ORM de Drizzle.\n\n### Schéma\n\nLe créateur peut ajouter plusieurs modules à un modèle. En même temps, un module peut être utilisé par plusieurs modèles. La relation est une relation de plusieurs à plusieurs. Pour gérer cela, je dois créer une nouvelle table, appelée table de pivot, pour stocker la relation entre les modèles et les modules. Elle agit comme un lien entre les deux tables pour les connecter. _Nous utilisons une table de pivot au lieu d'un tableau de modules dans le modèle parce que c'est plus efficace et c'est la façon SQL de le faire._\n\n![Relation de plusieurs à plusieurs entre les modèles et les modules](\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey\u002Fmany-to-many-relation.png)\n\nLe schéma est simplifié pour montrer uniquement la relation entre les modèles et les modules. À partir de la table `templates`, vous pouvez récupérer tous les modules associés et inversement grâce à la table `templates_modules`.\n\n```ts\nexport const templates = sqliteTable('templates', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  title: text('title').notNull(),\n})\n\nexport const modules = sqliteTable('modules', {\n  id: integer('id').primaryKey({ autoIncrement: true }),\n  name: text('name').notNull().unique(),\n})\n\nexport const modulesToTemplates = sqliteTable('modulesToTemplates', {\n  moduleId: integer('module_id').notNull().references(() => modules.id),\n  templateId: integer('template_id').notNull().references(() => templates.id),\n})\n```\n\n### Formulaire de modules\n\nDans cette partie, je vais ajouter les modules au formulaire, du côté client au côté serveur.\n\n#### Ajout de modules au modèle\n\nDans l'interface utilisateur, gérer les modules est assez simple. Je récupère les modules du serveur et les affiche dans un `USelectMenu` avec une prop multiple.\n\n```vue\n\u003Cscript lang=\"ts\" setup>\nconst { data: modules } = await useFetch('\u002Fapi\u002Fmodules', {\n  deep: false,\n  default: () => [],\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003CUSelectMenu\n    :options=\"modules\"\n    multiple\n    value-attribute=\"id\"\n    option-attribute=\"name\"\n  \u002F>\n\u003C\u002Ftemplate>\n```\n\n#### Validation des modules\n\nDans le validateur, j'ajoute un nouveau champ `moduleIds` qui vérifie que les modules sont un tableau d'entiers.\n\n```ts\nimport { array, number, object, string } from 'zod'\n\nexport const createTemplateValidator = object({\n  title: string({ message: 'Requis' }),\n  moduleIds: array(number()).max(6, { message: 'Max 6 modules' }).optional(),\n})\n```\n\nJ'ai arbitrairement fixé le nombre maximum de modules à 6. C'est un nombre que je pense suffisant pour un modèle.\n\n#### Enregistrement des modules\n\nEnfin, je mets à jour le point de terminaison pour enregistrer le modèle et les modules associés. Je commence par enregistrer le modèle, puis les modules dans la table de pivot.\n\n```ts\nimport { createTemplateValidator } from '~\u002Futils\u002Fvalidators'\n\nexport default defineEventHandler(async (event) => {\n  const { user } = await requireUserSession(event)\n\n  const body = await readValidatedBody(event, createTemplateValidator.parse)\n\n  const [template] = await useDrizzle().insert(tables.templates).values({\n    title: body.title,\n  }).returning({\n    id: tables.templates.id,\n  })\n\n  if (body.moduleIds)\n    await useDrizzle().insert(tables.modulesToTemplates).values(body.moduleIds.map(id => ({ moduleId: id, templateId: template.id }))).execute()\n\n  return sendNoContent(event, 201)\n})\n```\n\n_Je dois admettre que je ne suis pas très confiant avec cette partie. Je devrai vérifier s'il y a une meilleure façon de le faire (en utilisant une transaction ou une fonctionnalité intégrée de Drizzle)._\n\n### Remplir le modèle avec des modules\n\n_C'est là que les choses deviennent difficiles._\n\nAu départ, j'avais une requête SQL personnalisée qui joignait la table `templates`, la table `users` et la table `categories`. Vous pouvez en savoir plus à ce sujet dans la [partie précédente](#creating-a-form-for-templates).\n\nPour lier la table `modules`, j'ai essayé d'ajouter une jointure avec la table `modulesToTemplates` puis la table `modules`. Le problème que je rencontre est comment grouper les modules par modèle pour produire un tableau de modules pour chaque modèle.\n\nQuelque chose comme :\n\n```json\n{\n  \"id\": 1,\n  \"title\": \"Mon modèle\",\n  \"modules\": [\n    { \"id\": 1, \"name\": \"Module 1\" },\n    { \"id\": 2, \"name\": \"Module 2\" }\n  ]\n}\n```\n\nJ'ai trouvé la fonction SQLite `group_concat` qui peut être utilisée pour concaténer les modules. J'étais un peu déçu de devoir le faire manuellement car Drizzle se présente comme un ORM. Je m'attendais à avoir un moyen de le faire facilement.\n\nMAIS, le chat Twitch est venu à la rescousse et m'a suggéré d'utiliser la [`clé with de la méthode findMany`](https:\u002F\u002Form.drizzle.team\u002Fdocs\u002Frqb#include-relations). J'ai essayé, et cela n'a pas vraiment fonctionné. Cloudflare se plaignait d'une relation manquante. J'étais un peu perdu.\n\nJ'ai fini par lire attentivement la documentation sur la page de la relation de plusieurs à plusieurs, et j'ai compris que je devais définir explicitement chaque relation entre chaque table. En d'autres termes, je dois dire à Drizzle comment joindre les tables. Un modèle a plusieurs modules en utilisant la table `modulesToTemplates`. Un module a plusieurs modèles en utilisant la table `modulesToTemplates`. C'est ainsi que l'on définit la relation de plusieurs à plusieurs.\n\n```ts\n\u002F\u002F Un modèle a une relation de plusieurs à plusieurs\nexport const templatesRelations = relations(templates, ({ one, many }) => ({\n  \u002F\u002F Lié à la table de pivot et non directement à la table des modules\n  modules: many(modulesToTemplates),\n}))\n\n\u002F\u002F La table de pivot a 2 relations\nexport const modulesToTemplatesRelations = relations(modulesToTemplates, ({ one }) => ({\n  module: one(modules, {\n    fields: [modulesToTemplates.moduleId],\n    references: [modules.id],\n  }),\n  template: one(templates, {\n    fields: [modulesToTemplates.templateId],\n    references: [templates.id],\n  }),\n}))\n\n\u002F\u002F Un module a une relation de plusieurs à plusieurs\nexport const modulesRelations = relations(modules, ({ one, many }) => ({\n  \u002F\u002F Lié à la table de pivot et non directement à la table des modèles\n  templates: many(modulesToTemplates),\n}))\n```\n\nCette partie est super importante. Dans un ORM comme [Lucid](https:\u002F\u002Flucid.adonisjs.com), l'utilisation d'une table de pivot est transparente. Avec Drizzle, vous devez définir la relation entre chaque table. C'est un peu plus verbeux, mais c'est aussi plus explicite.\n\nEnfin, je peux mettre à jour le point de terminaison pour récupérer les modèles avec leurs modules :\n\n```ts\nexport default defineEventHandler(async () => {\n  const templates = await useDrizzle().query.templates.findMany({\n    with: {\n      category: true,\n      creator: true,\n      modules: {\n        with: {\n          module: true\n        },\n      },\n    },\n  })\n\n  return templates\n})\n```\n\nL'utilisation de `with`, une fois que tout est correctement défini, rend les choses tellement plus faciles ; j'adore vraiment ça.\n\n### Conclusion\n\nDans mon esprit, cette partie était facile, et en 30 minutes, c'était fait. J'avais déjà fait quelque chose de similaire en utilisant Lucid d'Adonis. En réalité, ce n'était pas vraiment difficile, mais trouver la bonne partie de la documentation de Drizzle puis la comprendre était bien plus difficile que prévu. Cela est largement dû au fait que Drizzle n'abstrait pas SQL mais agit comme un petit wrapper, et tout doit être explicite.\n\nJe suis heureux de l'avoir fait, j'ai beaucoup appris, et un grand merci au chat pour leur aide.\n",{"title":45820,"description":60223},{"loc":60225,"images":60229},[60230,60231,60232,60233,60234,60235,60236,60237,60238,60239,60240,60241,60242,60243,60244,60245,60246,60247,60248,60249,60250,60251,60252,60253],{"loc":45877},{"loc":45909},{"loc":46159},{"loc":46205},{"loc":46282},{"loc":46298},{"loc":46314},{"loc":46361},{"loc":47093},{"loc":47135},{"loc":47286},{"loc":48694},{"loc":50364},{"loc":50498},{"loc":50507},{"loc":51537},{"loc":52727},{"loc":54099},{"loc":56282},{"loc":57334},{"loc":57387},{"loc":58484},{"loc":58500},{"loc":58541},"fr\u002Fposts\u002Fcreate-a-full-stack-nuxt-application-a-twitch-journey","xxRStz1JEhvLivIm0iYslnGqI1b-fM3tw1J7iN1gR24",{"id":60257,"title":60258,"body":60259,"contentId":20859,"date":60689,"description":60690,"extension":18,"meta":60691,"navigation":20,"path":60692,"rawbody":60693,"robots":23,"schemaOrg":23,"seo":60694,"sitemap":60695,"stem":60703,"__hash__":60704},"posts\u002Ffr\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights.md","Devoxx France 2024 : Intervention et Faits Marquants",{"type":7,"value":60260,"toc":60682},[60261,60271,60279,60285],[35,60262,60263,60264,60267,60268,215],{},"Devoxx France est une grande conférence qui se tient à Paris. Cette année, plus de ",[6862,60265,60266],{},"4500 participants"," étaient présents avec 70 exposants et ",[6862,60269,60270],{},"319 intervenants",[35,60272,60273,60274,60278],{},"C'est si vaste qu'il est impossible d'assister à toutes les conférences. Parfois, il y a plus de 10 conférences en même temps, mais grâce à la ",[39,60275,60277],{"href":7155,"rel":60276},[77],"rediffusion sur YouTube",", il est possible de les regarder plus tard.",[35,60280,60281],{},[1707,60282],{"alt":60283,"src":60284},"Photo du hall d'exposition et de mon badge d'intervenant.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Ffirst-day.jpg",[7151,60286,60289,60291,60298,60307,60317,60320,60481,60493,60496,60503,60510,60517,60523,60527,60530,60534,60537,60544,60550,60557,60563,60569,60576,60582,60589,60592,60598,60602,60605,60625,60628,60631,60634,60637,60640,60643],{"w-full":13,"icon":7153,"text":60287,"to":60288,"target":7156},"Regardez ma conférence sur UnJS","https:\u002F\u002Fyoutu.be\u002F0BApl8r1VWc",[51,60290,20877],{"id":20876},[938,60292,60293],{},[35,60294,60295,60297],{},[200,60296,944],{},"\nCe n'est pas une recommandation, ce sont simplement des conférences que j'ai pu voir.",[35,60299,60300,60301,60306],{},"Avant de me rendre à Devoxx France, j'ai consulté le ",[39,60302,60305],{"href":60303,"rel":60304},"https:\u002F\u002Fmobile.devoxx.com\u002F",[77],"programme"," et j'ai dressé une liste des conférences que je voulais suivre. Il y a tellement de monde que certaines conférences étaient déjà complètes lorsque je suis arrivé dans la salle, et j'ai découvert d'autres conférences que j'ai préférées durant l'événement (et choisir parmi toutes ces conférences n'est pas facile).",[35,60308,60309,60310,1655,60313,60316],{},"Cette année, mon objectif était de voir un maximum de conférences sur ",[6862,60311,60312],{},"DevOps",[6862,60314,60315],{},"le code propre",". Je voulais également être surpris en assistant à des conférences que je n'aurais jamais suivies simplement parce que je connais l'intervenant ou parce que le titre est intéressant (et je n'ai pas été déçu).",[35,60318,60319],{},"Enfin, j'ai pu voir (les titres de conférences sont en français) :",[1593,60321,60322,60369,60422],{},[71,60323,60324,60325],{},"Mercredi 17 avril\n",[1593,60326,60327,60333,60339,60345,60351,60357,60363],{},[71,60328,60329,60332],{},[6862,60330,60331],{},"IA en médecine : où en sommes-nous ?"," Présenté par Jean-Emmanuel Bibault",[71,60334,60335,60338],{},[6862,60336,60337],{},"React Server Components : Effet de mode ou réel progrès ?"," Présenté par Thomas Simonnet",[71,60340,60341,60344],{},[6862,60342,60343],{},"DDD Haute Vitesse (revisité)."," Présenté par Thomas Pierrain",[71,60346,60347,60350],{},[6862,60348,60349],{},"V, comme Vautledétour."," Présenté par Emmanuel Remy",[71,60352,60353,60356],{},[6862,60354,60355],{},"Comment bien planter un side project ?"," Présenté par David Laizé",[71,60358,60359,60362],{},[6862,60360,60361],{},"Toutes et tous Mercenaires de DevOps!"," Présenté par Arnaud Héritier, Henri Gomez, Pierre-Antoine Grégoire, Dimitri Baeli et Gildas Cuisinier",[71,60364,60365,60368],{},[6862,60366,60367],{},"La recherche sous stéroïdes - une histoire de sémantique."," Présenté par Mathilde Rigabert et Martin Labenne",[71,60370,60371,60372],{},"Jeudi 18 avril\n",[1593,60373,60374,60380,60386,60392,60398,60404,60410,60416],{},[71,60375,60376,60379],{},[6862,60377,60378],{},"Les plus grandes erreurs de programmation."," Présenté par Mark Rendle",[71,60381,60382,60385],{},[6862,60383,60384],{},"Un monde shooté aux métaux."," Présenté par Guillaume Pitron et Agnes Crepet",[71,60387,60388,60391],{},[6862,60389,60390],{},"GatewayAPI, 10 ans de maturation pour une nouvelle API Kubernetes."," Présenté par Kevin Davin",[71,60393,60394,60397],{},[6862,60395,60396],{},"Comment K8S a changé le monde de l'Ops."," Présenté par Aurélien Moreau et Loïc Ortola",[71,60399,60400,60403],{},[6862,60401,60402],{},"Architecture Hexagonale : Comment venir à bout du code spaghetti ?"," Présenté par Kévin LLOPIS",[71,60405,60406,60409],{},[6862,60407,60408],{},"Java rencontre l'IA : Comment intégrer les LLMs dans vos applications avec LangChain4j."," Présenté par Lize Raes",[71,60411,60412,60415],{},[6862,60413,60414],{},"Les nouveautés de Java 21."," Présenté par Jean-Michel Doudoux",[71,60417,60418,60421],{},[6862,60419,60420],{},"Débugguez votre salaire ! Mes stratégies gagnantes pour réussir sa négociation salariale."," Présenté par Shirley Almosni-Chiche",[71,60423,60424,60425],{},"Vendredi 19 avril\n",[1593,60426,60427,60433,60439,60445,60451,60457,60463,60469,60475],{},[71,60428,60429,60432],{},[6862,60430,60431],{},"Comment modéliser l'état du monde en 2100 ?"," Le Rapport Meadows. Présenté par Anatole Chouard",[71,60434,60435,60438],{},[6862,60436,60437],{},"Cybersécurité et cyberdéfense : un sujet géopolitique."," Présenté par Guillaume Poupard",[71,60440,60441,60444],{},[6862,60442,60443],{},"Model Mitosis : ne plus se tromper entre les microservices et le monolithe."," Présenté par Julien Topçu et Josian Chevalier",[71,60446,60447,60450],{},[6862,60448,60449],{},"L'architecture micro frontend : comment transformer son site web en puzzle géant collaboratif."," Présenté par Maxime Ribera et Ludovic Lagatie",[71,60452,60453,60456],{},[6862,60454,60455],{},"Unifié et Universel, le JavaScript avec UnJS."," Présenté par Estéban Soubiran (c'est moi)",[71,60458,60459,60462],{},[6862,60460,60461],{},"Au-delà des heures : La semaine de 4 jours comme levier d'égalité."," Présenté par Albane Veyron",[71,60464,60465,60468],{},[6862,60466,60467],{},"Créer sa propre VM avec compilation JIT pour les nuls."," Présenté par Olivier Poncet",[71,60470,60471,60474],{},[6862,60472,60473],{},"L'Architecture Hexagonale par la pratique, le live coding qui rendra vos applications plus pérennes."," Présenté par Julien Topçu",[71,60476,60477,60480],{},[6862,60478,60479],{},"Accessibilité du web : du design au code comment tendre vers davantage d'inclusion ?"," Présenté par Marjorie Aubert",[938,60482,60483],{},[35,60484,60485,60487,60488,215],{},[200,60486,944],{},"\nCe ne sont que des conférences que j'ai pu voir (une petite partie de toutes les conférences disponibles). Pour voir le programme complet, rendez-vous sur le ",[39,60489,60492],{"href":60490,"rel":60491},"https:\u002F\u002Fmobile.devoxx.com\u002Fevents\u002Fdevoxxfr2024\u002Fschedule",[77],"programme Devoxx France 2024",[35,60494,60495],{},"Oui, il y a un peu de Java (je suis développeur web faisant du JavaScript) parce que j'aime découvrir d'autres langages et comment ils résolvent des problèmes, leur façon de penser et les outils qu'ils utilisent. En même temps, plus je passe de temps dans le monde du web, plus je pense que je dois apprendre Java car il est très pratique pour les gros projets avec son typage fort et son puissant système d'injection de dépendances.",[35,60497,60498,60499,60502],{},"Cette année, il y a ",[6862,60500,60501],{},"une tendance autour des LLM, de l'IA, du RAG",", et comment les intégrer dans votre entreprise. Puisque l'IA est disponible pour tout le monde à bas prix, il y a de plus en plus d'usages (et d'usages réels) de celle-ci. Ce n'est pas seulement pour les grandes entreprises mais aussi pour les petites entreprises et même pour les particuliers. Tant de possibilités, c'est très excitant et de nombreuses conférences le montrent.",[35,60504,60505,60506,60509],{},"À la fin de la journée, toutes ces conférences sont bonnes et vous allez certainement apprendre quelque chose en les visionnant sur la ",[39,60507,21034],{"href":7155,"rel":60508},[77],". Allez-y !",[35,60511,60512,60513,60516],{},"Une des parties intéressantes de Devoxx France est les ",[6862,60514,60515],{},"exposants",". Il y a de nombreux stands où vous pouvez rencontrer des partenaires, découvrir de nouveaux outils et vous amuser. Cette année, j'ai découvert que je n'avais pas pris beaucoup de temps pour les visiter, mais j'ai trouvé quelques nouveaux outils et projets. L'un d'eux aide les équipes à se mettre d'accord sur les meilleures pratiques et à les suivre, ou un autre qui construit un centre de données utilisant une stack GCP mais sans Google afin d'être conforme au RGPD et de construire un 'cloud de confiance'.",[35,60518,60519],{},[1707,60520],{"alt":60521,"src":60522},"Photo du logo JavaScript construit avec des LEGO sur l'un des stands partenaires.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Flego.jpg",[1929,60524,60526],{"id":60525},"dîner-des-intervenants","Dîner des intervenants",[35,60528,60529],{},"Mercredi soir, j'ai eu la chance, avec tous les autres intervenants, d'assister au dîner des intervenants. C'était un moment privé pour rencontrer les autres intervenants et se détendre avant le reste de l'événement. Je dois admettre que ce n'est pas vraiment ma tasse de thé de parler à des inconnus, mais la nourriture était excellente, donc c'était un beau moment.",[1929,60531,60533],{"id":60532},"mes-conférences-préférées","Mes conférences préférées",[35,60535,60536],{},"Je souhaite vraiment mettre en avant certaines conférences que j'ai beaucoup appréciées et que je recommande à tous (certaines d'entre elles sont vraiment époustouflantes).",[35,60538,60539,60540,60543],{},"La première est la conférence sur le ",[6862,60541,60542],{},"RAG"," (Retrieval-Augmented Generation) où j'ai beaucoup appris sur ce concept, comment l'utiliser et sur les frameworks qui facilitent son utilisation. Je pense vraiment que cette technologie est l'avenir, mais ce sera le sujet d'un autre article (je dois encore en apprendre davantage et commencer à l'utiliser).",[35,60545,60546],{},[1707,60547],{"alt":60548,"src":60549},"Photo de la conférence sur le RAG.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Frag.jpg",[35,60551,60552,60553,60556],{},"La deuxième est la conférence sur le ",[6862,60554,60555],{},"micro front-end",". Sur le papier, le sujet est intéressant mais trop souvent, les conférences sur le micro front-end sont très théoriques, pas très pratiques, et l'utilisation, avec ses avantages, n'est pas claire. Mais, cette conférence était à l'opposé de cela. Ils ont expliqué, en détail, comment ils l'ont mis en œuvre et utilisé et, plus important encore, pourquoi ils l'ont fait. À la fin, le live coding était sur le site web en direct (oui, sur leroymerlin.fr) et c'était très impressionnant.",[35,60558,60559],{},[1707,60560],{"alt":60561,"src":60562},"Photo de la conférence Leroy Merlin sur les micro front-ends.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Fmicro-front-end.jpg",[35,60564,60565,60566,60568],{},"La troisième est la conférence qui retrace l'histoire de ",[6862,60567,60312],{}," depuis 25 ans. C'était très intéressant de voir comment les technologies successives sont liées entre elles et de comprendre que l'état actuel de DevOps est le fruit d'années de travail, étape par étape. Ce n'est pas une révolution mais une évolution progressive, des machines virtuelles aux conteneurs, orchestrateurs et IaC.",[35,60570,60571,60572,60575],{},"Par hasard, mais parce que j'aime le contenu de la personne sur X, je suis allé à une conférence sur le ",[6862,60573,60574],{},"JIT",". Je connaissais ce terme et l'objectif mais pas comment il était implémenté. En 45 minutes, l'intervenant a codé un simple mais fonctionnel compilateur JIT en C++. Cela a presque l'air facile et je pense qu'il est important de comprendre comment ces morceaux de technologie, que nous utilisons chaque jour, fonctionnent.",[35,60577,60578],{},[1707,60579],{"alt":60580,"src":60581},"Photo de la conférence sur le JIT.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Fjit.jpg",[35,60583,60584,60585,60588],{},"La dernière portait sur l'",[6862,60586,60587],{},"architecture hexagonale",". Je connais déjà le concept, mais je ne l'ai jamais implémenté. L'intervenant a réalisé une session de live coding où il a implémenté une simple application avec une architecture hexagonale. C'était très intéressant de voir comment le code est organisé et à quel point il est facile à tester et à maintenir. Le fait qu'il ait implémenté le domaine (la logique métier) d'abord avec des tests, puis de le connecter à l'infrastructure (le framework) était très agréable à découvrir. Je vais définitivement approfondir ce concept et écrire des articles à ce sujet. Lorsque vous assistez à une conférence comme celle-ci, c'est comme aller au théâtre et voir un film, j'adore cette expérience.",[35,60590,60591],{},"Dans l'ensemble, vous devriez vraiment regarder ces conférences. Elles sont très intéressantes et vous allez beaucoup apprendre.",[35,60593,60594],{},[1707,60595],{"alt":60596,"src":60597},"Photo du grand amphithéâtre Devoxx.","\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Famphi.jpg",[51,60599,60601],{"id":60600},"ma-conférence","Ma conférence",[35,60603,60604],{},"Cette année, j'ai soumis 3 conférences à l'appel à papers (Call For Paper) :",[1593,60606,60607,60613,60619],{},[71,60608,60609,60612],{},[6862,60610,60611],{},"Construire un méta framework front-end pour l'edge"," (en anglais : Building a meta front-end framework for the edge)",[71,60614,60615,60618],{},[6862,60616,60617],{},"Unifié et Universel, le JavaScript avec UnJS"," (en anglais : Unified and Universal, JavaScript with UnJS)",[71,60620,60621,60624],{},[6862,60622,60623],{},"Atteindre l'edge avec Nuxt"," (en anglais : Reaching the edge with Nuxt)",[35,60626,60627],{},"Et l'une d'elles a été acceptée. Youpi ! Il est important de noter que Devoxx France est, à l'origine, une conférence Java, et je suis assez sûr que l'edge est inconnu pour la plupart des participants, ce qui pourrait expliquer pourquoi ces conférences n'ont pas été acceptées.",[35,60629,60630],{},"Mais une a été acceptée et je dois la préparer !",[35,60632,60633],{},"J'ai choisi de parler d'UnJS car j'aime la philosophie qui le sous-tend, et je pense que c'est un très bon outil pour un développeur JavaScript. Parler pendant 15 minutes sur UnJS est également un défi car ce n'est pas beaucoup de temps, et la quantité et la diversité des packages qu'UnJS propose est énorme.",[35,60635,60636],{},"La première étape consiste à choisir les packages que je voulais présenter. Ensuite, je dois sélectionner quelle partie des packages sélectionnés je souhaite présenter, celle qui pourrait être intéressante pour la majorité des participants. C'est beaucoup plus difficile que d'avoir une conférence d'une heure où je pourrais approfondir le sujet, mais c'est un bon exercice d'être concis.",[35,60638,60639],{},"Finalement, j'ai fini par parler de l'histoire et de la philosophie d'UnJS et à présenter des packages utiles pour la plupart des développeurs comme unbuild, nitro ou magic-regexp en allant droit au but pour chacun d'eux. Mon objectif était de donner un bon aperçu d'UnJS et de donner envie aux gens de l'essayer.",[35,60641,60642],{},"Heureusement, j'ai pu m'entraîner à StrasbourgJS. Cela m'a aidé à affiner certaines parties et à être plus confiant.",[7918,60644,60645,60651],{"id":46184},[35,60646,60647,60648,215],{},"Ma conférence a eu lieu vendredi à 12h35, et jusqu'à 12h20, j'étais confiant. J'ai répété ma conférence plusieurs fois, et je connais UnJS. Mais quand je suis entré dans la salle et que je me suis tenu derrière le bureau, le stress est arrivé. Puis, j'ai commencé à parler et tout s'est bien passé. Je suis fier de ma conférence. J'espère vraiment que vous apprécierez la rediffusion sur la ",[39,60649,21034],{"href":7155,"rel":60650},[77],[7151,60652,60653,60658,60661],{"w-full":13,"icon":7153,"text":60287,"to":60288,"target":7156},[35,60654,60655],{},[1216,60656,60657],{},"Les diapositives arrivent bientôt.",[35,60659,60660],{},"Une bonne chose faite, et c'est tellement agréable de partager des connaissances avec les autres et de discuter avec eux. Je sens que j'ai encore un long chemin à parcourir avant de réaliser la présentation parfaite, donc je continuerai à soumettre des conférences à des événements et à des meetups.",[60662,60663,60664,60666,60669,60676,60679],"callout-available-for-giving-talks",{},[51,60665,19349],{"id":19348},[35,60667,60668],{},"Devoxx France est un événement unique. Il y a tellement de conférences, tant de diversité dans les sujets, et tant de personnes que vous apprendrez définitivement quelque chose. Cette année était la plus grande Devoxx France jamais réalisée avec plus de 4000 participants.",[35,60670,60671,60672,60675],{},"Et même si l'événement est physiquement terminé, il continue en ligne avec la ",[39,60673,60277],{"href":7155,"rel":60674},[77],". Donc, si vous ne pouvez pas assister, vous pouvez toujours regarder les conférences !",[35,60677,60678],{},"Merci aux organisateurs et à l'équipe des red coats pour cet événement incroyable. J'espère vous voir l'année prochaine (je travaille déjà sur de nombreuses conférences pour la prochaine édition) !",[35,60680,60681],{},"D'ici là, prenez soin de vous et continuez à apprendre !",{"title":13,"searchDepth":14,"depth":14,"links":60683},[60684,60688],{"id":20876,"depth":14,"text":20877,"children":60685},[60686,60687],{"id":60525,"depth":255,"text":60526},{"id":60532,"depth":255,"text":60533},{"id":60600,"depth":14,"text":60601},"2024-04-22","Découvrez les conférences de Devoxx France 2024 qui ont mis l'accent sur l'IA, le LLM, le RAG, ainsi qu'une ma conférence sur UnJS !",{},"\u002Ffr\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights","---\ncontentId: f3e97a6a-ce47-42b8-8493-06eb37f8e964\ntitle: \"Devoxx France 2024 : Intervention et Faits Marquants\"\ndescription: Découvrez les conférences de Devoxx France 2024 qui ont mis l'accent sur l'IA, le LLM, le RAG, ainsi qu'une ma conférence sur UnJS !\ndate: 2024-04-22\n---\n\nDevoxx France est une grande conférence qui se tient à Paris. Cette année, plus de **4500 participants** étaient présents avec 70 exposants et **319 intervenants**.\n\nC'est si vaste qu'il est impossible d'assister à toutes les conférences. Parfois, il y a plus de 10 conférences en même temps, mais grâce à la [rediffusion sur YouTube](https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos), il est possible de les regarder plus tard.\n\n![Photo du hall d'exposition et de mon badge d'intervenant.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Ffirst-day.jpg)\n\n\u003CCallout w-full icon=\"i-simple-icons-youtube\" text=\"Regardez ma conférence sur UnJS\" to=\"https:\u002F\u002Fyoutu.be\u002F0BApl8r1VWc\" target=\"_blank\" \u002F>\n\n## Mon emploi du temps\n\n> [!NOTE]\n> Ce n'est pas une recommandation, ce sont simplement des conférences que j'ai pu voir.\n\nAvant de me rendre à Devoxx France, j'ai consulté le [programme](https:\u002F\u002Fmobile.devoxx.com\u002F) et j'ai dressé une liste des conférences que je voulais suivre. Il y a tellement de monde que certaines conférences étaient déjà complètes lorsque je suis arrivé dans la salle, et j'ai découvert d'autres conférences que j'ai préférées durant l'événement (et choisir parmi toutes ces conférences n'est pas facile).\n\nCette année, mon objectif était de voir un maximum de conférences sur **DevOps** et **le code propre**. Je voulais également être surpris en assistant à des conférences que je n'aurais jamais suivies simplement parce que je connais l'intervenant ou parce que le titre est intéressant (et je n'ai pas été déçu).\n\nEnfin, j'ai pu voir (les titres de conférences sont en français) :\n\n- Mercredi 17 avril\n  - **IA en médecine : où en sommes-nous ?** Présenté par Jean-Emmanuel Bibault\n  - **React Server Components : Effet de mode ou réel progrès ?** Présenté par Thomas Simonnet\n  - **DDD Haute Vitesse (revisité).** Présenté par Thomas Pierrain\n  - **V, comme Vautledétour.** Présenté par Emmanuel Remy\n  - **Comment bien planter un side project ?** Présenté par David Laizé\n  - **Toutes et tous Mercenaires de DevOps!** Présenté par Arnaud Héritier, Henri Gomez, Pierre-Antoine Grégoire, Dimitri Baeli et Gildas Cuisinier\n  - **La recherche sous stéroïdes - une histoire de sémantique.** Présenté par Mathilde Rigabert et Martin Labenne\n- Jeudi 18 avril\n  - **Les plus grandes erreurs de programmation.** Présenté par Mark Rendle\n  - **Un monde shooté aux métaux.** Présenté par Guillaume Pitron et Agnes Crepet\n  - **GatewayAPI, 10 ans de maturation pour une nouvelle API Kubernetes.** Présenté par Kevin Davin\n  - **Comment K8S a changé le monde de l'Ops.** Présenté par Aurélien Moreau et Loïc Ortola\n  - **Architecture Hexagonale : Comment venir à bout du code spaghetti ?** Présenté par Kévin LLOPIS\n  - **Java rencontre l'IA : Comment intégrer les LLMs dans vos applications avec LangChain4j.** Présenté par Lize Raes\n  - **Les nouveautés de Java 21.** Présenté par Jean-Michel Doudoux\n  - **Débugguez votre salaire ! Mes stratégies gagnantes pour réussir sa négociation salariale.** Présenté par Shirley Almosni-Chiche\n- Vendredi 19 avril\n  - **Comment modéliser l'état du monde en 2100 ?** Le Rapport Meadows. Présenté par Anatole Chouard\n  - **Cybersécurité et cyberdéfense : un sujet géopolitique.** Présenté par Guillaume Poupard\n  - **Model Mitosis : ne plus se tromper entre les microservices et le monolithe.** Présenté par Julien Topçu et Josian Chevalier\n  - **L'architecture micro frontend : comment transformer son site web en puzzle géant collaboratif.** Présenté par Maxime Ribera et Ludovic Lagatie\n  - **Unifié et Universel, le JavaScript avec UnJS.** Présenté par Estéban Soubiran (c'est moi)\n  - **Au-delà des heures : La semaine de 4 jours comme levier d'égalité.** Présenté par Albane Veyron\n  - **Créer sa propre VM avec compilation JIT pour les nuls.** Présenté par Olivier Poncet\n  - **L'Architecture Hexagonale par la pratique, le live coding qui rendra vos applications plus pérennes.** Présenté par Julien Topçu\n  - **Accessibilité du web : du design au code comment tendre vers davantage d'inclusion ?** Présenté par Marjorie Aubert\n\n> [!NOTE]\n> Ce ne sont que des conférences que j'ai pu voir (une petite partie de toutes les conférences disponibles). Pour voir le programme complet, rendez-vous sur le [programme Devoxx France 2024](https:\u002F\u002Fmobile.devoxx.com\u002Fevents\u002Fdevoxxfr2024\u002Fschedule).\n\nOui, il y a un peu de Java (je suis développeur web faisant du JavaScript) parce que j'aime découvrir d'autres langages et comment ils résolvent des problèmes, leur façon de penser et les outils qu'ils utilisent. En même temps, plus je passe de temps dans le monde du web, plus je pense que je dois apprendre Java car il est très pratique pour les gros projets avec son typage fort et son puissant système d'injection de dépendances.\n\nCette année, il y a **une tendance autour des LLM, de l'IA, du RAG**, et comment les intégrer dans votre entreprise. Puisque l'IA est disponible pour tout le monde à bas prix, il y a de plus en plus d'usages (et d'usages réels) de celle-ci. Ce n'est pas seulement pour les grandes entreprises mais aussi pour les petites entreprises et même pour les particuliers. Tant de possibilités, c'est très excitant et de nombreuses conférences le montrent.\n\nÀ la fin de la journée, toutes ces conférences sont bonnes et vous allez certainement apprendre quelque chose en les visionnant sur la [chaîne YouTube de Devoxx France](https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos). Allez-y !\n\nUne des parties intéressantes de Devoxx France est les **exposants**. Il y a de nombreux stands où vous pouvez rencontrer des partenaires, découvrir de nouveaux outils et vous amuser. Cette année, j'ai découvert que je n'avais pas pris beaucoup de temps pour les visiter, mais j'ai trouvé quelques nouveaux outils et projets. L'un d'eux aide les équipes à se mettre d'accord sur les meilleures pratiques et à les suivre, ou un autre qui construit un centre de données utilisant une stack GCP mais sans Google afin d'être conforme au RGPD et de construire un 'cloud de confiance'.\n\n![Photo du logo JavaScript construit avec des LEGO sur l'un des stands partenaires.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Flego.jpg)\n\n### Dîner des intervenants\n\nMercredi soir, j'ai eu la chance, avec tous les autres intervenants, d'assister au dîner des intervenants. C'était un moment privé pour rencontrer les autres intervenants et se détendre avant le reste de l'événement. Je dois admettre que ce n'est pas vraiment ma tasse de thé de parler à des inconnus, mais la nourriture était excellente, donc c'était un beau moment.\n\n### Mes conférences préférées\n\nJe souhaite vraiment mettre en avant certaines conférences que j'ai beaucoup appréciées et que je recommande à tous (certaines d'entre elles sont vraiment époustouflantes).\n\nLa première est la conférence sur le **RAG** (Retrieval-Augmented Generation) où j'ai beaucoup appris sur ce concept, comment l'utiliser et sur les frameworks qui facilitent son utilisation. Je pense vraiment que cette technologie est l'avenir, mais ce sera le sujet d'un autre article (je dois encore en apprendre davantage et commencer à l'utiliser).\n\n![Photo de la conférence sur le RAG.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Frag.jpg)\n\nLa deuxième est la conférence sur le **micro front-end**. Sur le papier, le sujet est intéressant mais trop souvent, les conférences sur le micro front-end sont très théoriques, pas très pratiques, et l'utilisation, avec ses avantages, n'est pas claire. Mais, cette conférence était à l'opposé de cela. Ils ont expliqué, en détail, comment ils l'ont mis en œuvre et utilisé et, plus important encore, pourquoi ils l'ont fait. À la fin, le live coding était sur le site web en direct (oui, sur leroymerlin.fr) et c'était très impressionnant.\n\n![Photo de la conférence Leroy Merlin sur les micro front-ends.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Fmicro-front-end.jpg)\n\nLa troisième est la conférence qui retrace l'histoire de **DevOps** depuis 25 ans. C'était très intéressant de voir comment les technologies successives sont liées entre elles et de comprendre que l'état actuel de DevOps est le fruit d'années de travail, étape par étape. Ce n'est pas une révolution mais une évolution progressive, des machines virtuelles aux conteneurs, orchestrateurs et IaC.\n\nPar hasard, mais parce que j'aime le contenu de la personne sur X, je suis allé à une conférence sur le **JIT**. Je connaissais ce terme et l'objectif mais pas comment il était implémenté. En 45 minutes, l'intervenant a codé un simple mais fonctionnel compilateur JIT en C++. Cela a presque l'air facile et je pense qu'il est important de comprendre comment ces morceaux de technologie, que nous utilisons chaque jour, fonctionnent.\n\n![Photo de la conférence sur le JIT.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Fjit.jpg)\n\nLa dernière portait sur l'**architecture hexagonale**. Je connais déjà le concept, mais je ne l'ai jamais implémenté. L'intervenant a réalisé une session de live coding où il a implémenté une simple application avec une architecture hexagonale. C'était très intéressant de voir comment le code est organisé et à quel point il est facile à tester et à maintenir. Le fait qu'il ait implémenté le domaine (la logique métier) d'abord avec des tests, puis de le connecter à l'infrastructure (le framework) était très agréable à découvrir. Je vais définitivement approfondir ce concept et écrire des articles à ce sujet. Lorsque vous assistez à une conférence comme celle-ci, c'est comme aller au théâtre et voir un film, j'adore cette expérience.\n\nDans l'ensemble, vous devriez vraiment regarder ces conférences. Elles sont très intéressantes et vous allez beaucoup apprendre.\n\n![Photo du grand amphithéâtre Devoxx.](\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights\u002Famphi.jpg)\n\n## Ma conférence\n\nCette année, j'ai soumis 3 conférences à l'appel à papers (Call For Paper) :\n\n- **Construire un méta framework front-end pour l'edge** (en anglais : Building a meta front-end framework for the edge)\n- **Unifié et Universel, le JavaScript avec UnJS** (en anglais : Unified and Universal, JavaScript with UnJS)\n- **Atteindre l'edge avec Nuxt** (en anglais : Reaching the edge with Nuxt)\n\nEt l'une d'elles a été acceptée. Youpi ! Il est important de noter que Devoxx France est, à l'origine, une conférence Java, et je suis assez sûr que l'edge est inconnu pour la plupart des participants, ce qui pourrait expliquer pourquoi ces conférences n'ont pas été acceptées.\n\nMais une a été acceptée et je dois la préparer !\n\nJ'ai choisi de parler d'UnJS car j'aime la philosophie qui le sous-tend, et je pense que c'est un très bon outil pour un développeur JavaScript. Parler pendant 15 minutes sur UnJS est également un défi car ce n'est pas beaucoup de temps, et la quantité et la diversité des packages qu'UnJS propose est énorme.\n\nLa première étape consiste à choisir les packages que je voulais présenter. Ensuite, je dois sélectionner quelle partie des packages sélectionnés je souhaite présenter, celle qui pourrait être intéressante pour la majorité des participants. C'est beaucoup plus difficile que d'avoir une conférence d'une heure où je pourrais approfondir le sujet, mais c'est un bon exercice d'être concis.\n\nFinalement, j'ai fini par parler de l'histoire et de la philosophie d'UnJS et à présenter des packages utiles pour la plupart des développeurs comme unbuild, nitro ou magic-regexp en allant droit au but pour chacun d'eux. Mon objectif était de donner un bon aperçu d'UnJS et de donner envie aux gens de l'essayer.\n\nHeureusement, j'ai pu m'entraîner à StrasbourgJS. Cela m'a aidé à affiner certaines parties et à être plus confiant.\n\n\u003CCalloutReadMore id=\"a1291894-3de4-444e-b49b-a5dec98f3e27\" \u002F>\n\nMa conférence a eu lieu vendredi à 12h35, et jusqu'à 12h20, j'étais confiant. J'ai répété ma conférence plusieurs fois, et je connais UnJS. Mais quand je suis entré dans la salle et que je me suis tenu derrière le bureau, le stress est arrivé. Puis, j'ai commencé à parler et tout s'est bien passé. Je suis fier de ma conférence. J'espère vraiment que vous apprécierez la rediffusion sur la [chaîne YouTube de Devoxx France](https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos).\n\n\u003CCallout w-full icon=\"i-simple-icons-youtube\" text=\"Regardez ma conférence sur UnJS\" to=\"https:\u002F\u002Fyoutu.be\u002F0BApl8r1VWc\" target=\"_blank\" \u002F>\n\n_Les diapositives arrivent bientôt._\n\nUne bonne chose faite, et c'est tellement agréable de partager des connaissances avec les autres et de discuter avec eux. Je sens que j'ai encore un long chemin à parcourir avant de réaliser la présentation parfaite, donc je continuerai à soumettre des conférences à des événements et à des meetups.\n\n\u003CCalloutAvailableForGivingTalks \u002F>\n\n## Enfin\n\nDevoxx France est un événement unique. Il y a tellement de conférences, tant de diversité dans les sujets, et tant de personnes que vous apprendrez définitivement quelque chose. Cette année était la plus grande Devoxx France jamais réalisée avec plus de 4000 participants.\n\nEt même si l'événement est physiquement terminé, il continue en ligne avec la [rediffusion sur YouTube](https:\u002F\u002Fwww.youtube.com\u002F@DevoxxFRvideos). Donc, si vous ne pouvez pas assister, vous pouvez toujours regarder les conférences !\n\nMerci aux organisateurs et à l'équipe des red coats pour cet événement incroyable. J'espère vous voir l'année prochaine (je travaille déjà sur de nombreuses conférences pour la prochaine édition) !\n\nD'ici là, prenez soin de vous et continuez à apprendre !\n",{"title":60258,"description":60690},{"loc":60692,"images":60696},[60697,60698,60699,60700,60701,60702],{"loc":60284},{"loc":60522},{"loc":60549},{"loc":60562},{"loc":60581},{"loc":60597},"fr\u002Fposts\u002Fspeaking-at-devoxx-france-2024-insights-and-highlights","TgzmwtqXGSSA-0EHXHvSy28Bq2fdTOIuCeANoLN8mY8",{"id":60706,"title":60707,"body":60708,"contentId":46184,"date":60888,"description":60889,"extension":18,"meta":60890,"navigation":20,"path":60891,"rawbody":60892,"robots":23,"schemaOrg":23,"seo":60893,"sitemap":60894,"stem":60902,"__hash__":60903},"posts\u002Ffr\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem.md","Premier Talk à un Meetup : Découverte de l'Écosystème UnJS",{"type":7,"value":60709,"toc":60881},[60710,60713,60716,60722,60727,60730,60734,60737,60746,60755,60758,60762,60765,60773,60776,60784,60788,60791,60799,60806,60810,60813,60816,60822,60825,60831,60834,60840,60843,60849,60852,60855,60861,60863,60866,60869,60876],[35,60711,60712],{},"En avril 2022, j'ai déménagé à Paris pour un stage de 4 mois dans une entreprise de conseil. C'était la première fois que je vivais dans une ville de cette taille.",[35,60714,60715],{},"L'une des choses intéressantes à propos de Paris, c'est que l'on peut facilement assister à un événement chaque soir. J'en ai profité pour aller à de nombreux meetups. J'ai assisté à ParisJS, TypeScript Paris, Vue.js Paris, et bien d'autres encore. Après ce stage, je suis resté à Paris pour une année de césure et je suis continué à participer à des meetups. J'ai rencontré beaucoup de personnes incroyables et j'ai appris beaucoup de choses allant de JavaScript à HTTP, en passant par la sécurité et l'entrepreneuriat.",[35,60717,60718],{},[1707,60719],{"alt":60720,"src":60721},"GDI Live meetup","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fgdi-live.jpg",[35,60723,60724],{},[1216,60725,60726],{},"Un meetup auquel j'ai assisté à Paris.",[35,60728,60729],{},"En septembre 2024, je devais retourner dans mon école au milieu de la France pour 6 mois de ma dernière année d'études. J'étais un peu triste car je n'allais plus pouvoir assister à des meetups. Je vis dans une petite ville où les événements sont rares. Mais avant de quitter la ville, j'ai soumis deux présentations au meetup ParisJS. Je n'ai malheureusement jamais eu l'occasion de les présenter, mais l'idée de donner une présentation était dans mon esprit, et soumettre des présentations était un premier et concret pas.",[51,60731,60733],{"id":60732},"retour-aux-meetups","Retour aux Meetups",[35,60735,60736],{},"La patience est importante, et je savais (j'espérais vraiment) que j'aurais un jour l'opportunité de donner une présentation lors d'un meetup.",[35,60738,60739,60740,60745],{},"En novembre 2023, ",[39,60741,60744],{"href":60742,"rel":60743},"https:\u002F\u002Ftwitter.com\u002Fedimitchel",[77],"Michel",", l'organisateur de StrasbourgJS, m'a contacté pour me proposer de donner une présentation lors de son meetup. J'étais tellement heureux. Retourner à un meetup, et en tant qu'intervenant, c'est tellement cool !",[35,60747,60748,60749,60754],{},"Bien sûr, j'ai accepté ! Puis, il a annoncé que ",[39,60750,60753],{"href":60751,"rel":60752},"https:\u002F\u002Ftwitter.com\u002Fromainlanz",[77],"Romain Lanz"," donnerait également une présentation lors du meetup. Ce meetup allait être encore plus incroyable car j'adore les streams de Romain, que je suis depuis longtemps, et sa vision de l'écosystème JavaScript.",[35,60756,60757],{},"Le meetup se tiendra en avril 2024.",[51,60759,60761],{"id":60760},"travailler-sur-la-présentation","Travailler sur la Présentation",[35,60763,60764],{},"De janvier 2024 à avril 2024, 3 mois pour travailler sur la présentation, ce n'est pas beaucoup.",[35,60766,60767,60768,60772],{},"D'abord, j'avais besoin de choisir un sujet. En raison de mon stage de 6 mois en Suisse et de ma présentation à Devoxx France que je devais préparer, le temps était un réel contrainte. J'ai décidé de parler de l'",[39,60769,60771],{"href":34910,"rel":60770},[77],"écosystème UnJS",". Un sujet similaire à celui que je vais présenter à Devoxx France mais sous un angle différent.",[35,60774,60775],{},"Le fait est que l'écosystème UnJS est immense, avec plus de 63 paquets, principalement pour les développeurs de paquets, allant de Nitro, l'outil serveur, à unbuild qui aide les développeurs à créer leurs paquets npm. De quoi voulais-je parler ? Découvrons-le avec une présentation de 20 minutes montrant les principaux paquets et comment ils peuvent aider les développeurs.",[35,60777,60778,60779,60783],{},"Ensuite, travaillons là-dessus en ouvrant mon ",[39,60780,60782],{"href":7920,"rel":60781},[77],"dépôt de présentations"," et en commençant à rédiger les diapositives.",[51,60785,60787],{"id":60786},"améliorer-ma-présentation","Améliorer ma Présentation",[35,60789,60790],{},"Des cours magistraux aux présentations d'entreprise, je vois malheureusement beaucoup de présentations ennuyeuses. Quand j'étais dans des associations étudiantes, j'ai eu l'opportunité d'apprendre à faire une présentation et à former d'autres étudiants.",[35,60792,60793,60794,60798],{},"L'idée m'est venue après avoir vu la présentation \"State of Nuxt 2024\" de ",[39,60795,16112],{"href":60796,"rel":60797},"https:\u002F\u002Ftwitter.com\u002Fdanielcroe",[77]," à Vue Amsterdam 2024. Il a demandé aux gens d'aller sur un lien, de répondre à une question sur Nuxt, et magiquement, les réponses sont apparues à l'écran. Puis, il a cliqué sur un bouton pour générer un résumé avec l'IA. L'État de Nuxt s'est fait avec le public. Plus qu'une simple présentation, c'était une véritable expérience.",[35,60800,60801,60802,60805],{},"Je voulais faire la même chose avec ma présentation, la rendre interactive et centrée sur le public. \"Découvrons ",[1216,60803,60804],{},"ensemble"," l'écosystème UnJS\" est né.",[51,60807,60809],{"id":60808},"_12-avril-2024-strasbourg","12 avril 2024, Strasbourg",[35,60811,60812],{},"C'est le grand jour. Après 4 semaines à construire la partie interactive de la présentation (ce sera le sujet d'un autre article), j'étais prêt à la présenter. (Je dois confesser que j'ai terminé mes diapositives 3 heures avant le meetup 😅).",[35,60814,60815],{},"Avec Romain, qui venait également de Suisse, nous sommes arrivés sur le lieu et avons découvert l'amphithéâtre (grand). Inattendu car je suis plus familier avec les meetups dans des open-spaces d'entreprise où les tables sont écartées et les chaises rapprochées.",[35,60817,60818],{},[1707,60819],{"alt":60820,"src":60821},"Amphithéâtre StrasbourgJS","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Famphitheatre.jpg",[35,60823,60824],{},"Le meetup a commencé avec Michel présentant le meetup et les intervenants (nous !).",[35,60826,60827],{},[1707,60828],{"alt":60829,"src":60830},"Michel sur scène","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fmichel-on-stage.jpg",[35,60832,60833],{},"Ma présentation, censée durer 20 minutes, a duré 30 minutes et aurait pu être beaucoup plus longue. La partie interactive a été un succès car elle a fonctionné, mais aussi parce que les gens participaient. J'étais tellement heureux après tout le travail fourni.",[35,60835,60836],{},[1707,60837],{"alt":60838,"src":60839},"Présentation Estéban Soubiran","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Festeban-soubiran-talk.jpg",[35,60841,60842],{},"J'ai été suivi par Romain qui a parlé de son parcours et d'AdonisJS. 45 minutes de pur plaisir à l'écouter et à redécouvrir la beauté d'AdonisJS. Je pense qu'il a convaincu certains membres du public d'essayer le framework ! 🥇",[35,60844,60845],{},[1707,60846],{"alt":60847,"src":60848},"Présentation Romain Lanz","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fromain-lanz-talk.jpg",[35,60850,60851],{},"Après les présentations, il était temps de manger, de boire et de discuter avec les personnes, environ 35 personnes, qui étaient venues au meetup.",[35,60853,60854],{},"Sur la photo, ce sont Romain et moi. Derrière, c'est toute l'équipe de StrasbourgJS qui a organisé le meetup !",[35,60856,60857],{},[1707,60858],{"alt":60859,"src":60860},"Équipe StrasbourgJS et Intervenants","\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fteam-and-speakers.jpeg",[51,60862,19349],{"id":19348},[35,60864,60865],{},"Les sensations d'assister à un meetup, de rencontrer des personnes, de parler de tech avec des gens travaillant dans des entreprises variées autour de nourriture me manquaient. StrasbourgJS était très bien pour ça. Merci pour l'organisation ! 💛",[35,60867,60868],{},"Le voyage continue à Devoxx France du 17 au 19 avril 2024. Je vais présenter une conférence sur l'écosystème UnJS. J'espère vous y voir !",[35,60870,60871,60872,215],{},"Je suis ouvert à donner d'autres présentations lors de meetups ! N'hésitez pas à ",[39,60873,60875],{"href":36274,"rel":60874},[77],"me contacter",[35,60877,60878],{},[1216,60879,60880],{},"Les diapositives et le replay arrivent bientôt",{"title":13,"searchDepth":14,"depth":14,"links":60882},[60883,60884,60885,60886,60887],{"id":60732,"depth":14,"text":60733},{"id":60760,"depth":14,"text":60761},{"id":60786,"depth":14,"text":60787},{"id":60808,"depth":14,"text":60809},{"id":19348,"depth":14,"text":19349},"2024-04-12","Explorer le vaste écosystème UnJS lors de mon premier meetup à StrasbourgJS. Mon expérience très personnelle à StrasbourgJS.",{},"\u002Ffr\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem","---\ncontentId: a1291894-3de4-444e-b49b-a5dec98f3e27\ntitle: \"Premier Talk à un Meetup : Découverte de l'Écosystème UnJS\"\ndescription: Explorer le vaste écosystème UnJS lors de mon premier meetup à StrasbourgJS. Mon expérience très personnelle à StrasbourgJS.\ndate: 2024-04-12\n---\n\nEn avril 2022, j'ai déménagé à Paris pour un stage de 4 mois dans une entreprise de conseil. C'était la première fois que je vivais dans une ville de cette taille.\n\nL'une des choses intéressantes à propos de Paris, c'est que l'on peut facilement assister à un événement chaque soir. J'en ai profité pour aller à de nombreux meetups. J'ai assisté à ParisJS, TypeScript Paris, Vue.js Paris, et bien d'autres encore. Après ce stage, je suis resté à Paris pour une année de césure et je suis continué à participer à des meetups. J'ai rencontré beaucoup de personnes incroyables et j'ai appris beaucoup de choses allant de JavaScript à HTTP, en passant par la sécurité et l'entrepreneuriat.\n\n![GDI Live meetup](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fgdi-live.jpg)\n\n_Un meetup auquel j'ai assisté à Paris._\n\nEn septembre 2024, je devais retourner dans mon école au milieu de la France pour 6 mois de ma dernière année d'études. J'étais un peu triste car je n'allais plus pouvoir assister à des meetups. Je vis dans une petite ville où les événements sont rares. Mais avant de quitter la ville, j'ai soumis deux présentations au meetup ParisJS. Je n'ai malheureusement jamais eu l'occasion de les présenter, mais l'idée de donner une présentation était dans mon esprit, et soumettre des présentations était un premier et concret pas.\n\n## Retour aux Meetups\n\nLa patience est importante, et je savais (j'espérais vraiment) que j'aurais un jour l'opportunité de donner une présentation lors d'un meetup.\n\nEn novembre 2023, [Michel](https:\u002F\u002Ftwitter.com\u002Fedimitchel), l'organisateur de StrasbourgJS, m'a contacté pour me proposer de donner une présentation lors de son meetup. J'étais tellement heureux. Retourner à un meetup, et en tant qu'intervenant, c'est tellement cool !\n\nBien sûr, j'ai accepté ! Puis, il a annoncé que [Romain Lanz](https:\u002F\u002Ftwitter.com\u002Fromainlanz) donnerait également une présentation lors du meetup. Ce meetup allait être encore plus incroyable car j'adore les streams de Romain, que je suis depuis longtemps, et sa vision de l'écosystème JavaScript.\n\nLe meetup se tiendra en avril 2024.\n\n## Travailler sur la Présentation\n\nDe janvier 2024 à avril 2024, 3 mois pour travailler sur la présentation, ce n'est pas beaucoup.\n\nD'abord, j'avais besoin de choisir un sujet. En raison de mon stage de 6 mois en Suisse et de ma présentation à Devoxx France que je devais préparer, le temps était un réel contrainte. J'ai décidé de parler de l'[écosystème UnJS](https:\u002F\u002Funjs.io). Un sujet similaire à celui que je vais présenter à Devoxx France mais sous un angle différent.\n\nLe fait est que l'écosystème UnJS est immense, avec plus de 63 paquets, principalement pour les développeurs de paquets, allant de Nitro, l'outil serveur, à unbuild qui aide les développeurs à créer leurs paquets npm. De quoi voulais-je parler ? Découvrons-le avec une présentation de 20 minutes montrant les principaux paquets et comment ils peuvent aider les développeurs.\n\nEnsuite, travaillons là-dessus en ouvrant mon [dépôt de présentations](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Ftalks) et en commençant à rédiger les diapositives.\n\n## Améliorer ma Présentation\n\nDes cours magistraux aux présentations d'entreprise, je vois malheureusement beaucoup de présentations ennuyeuses. Quand j'étais dans des associations étudiantes, j'ai eu l'opportunité d'apprendre à faire une présentation et à former d'autres étudiants.\n\nL'idée m'est venue après avoir vu la présentation \"State of Nuxt 2024\" de [Daniel Roe](https:\u002F\u002Ftwitter.com\u002Fdanielcroe) à Vue Amsterdam 2024. Il a demandé aux gens d'aller sur un lien, de répondre à une question sur Nuxt, et magiquement, les réponses sont apparues à l'écran. Puis, il a cliqué sur un bouton pour générer un résumé avec l'IA. L'État de Nuxt s'est fait avec le public. Plus qu'une simple présentation, c'était une véritable expérience.\n\nJe voulais faire la même chose avec ma présentation, la rendre interactive et centrée sur le public. \"Découvrons _ensemble_ l'écosystème UnJS\" est né.\n\n## 12 avril 2024, Strasbourg\n\nC'est le grand jour. Après 4 semaines à construire la partie interactive de la présentation (ce sera le sujet d'un autre article), j'étais prêt à la présenter. (Je dois confesser que j'ai terminé mes diapositives 3 heures avant le meetup 😅).\n\nAvec Romain, qui venait également de Suisse, nous sommes arrivés sur le lieu et avons découvert l'amphithéâtre (grand). Inattendu car je suis plus familier avec les meetups dans des open-spaces d'entreprise où les tables sont écartées et les chaises rapprochées.\n\n![Amphithéâtre StrasbourgJS](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Famphitheatre.jpg)\n\nLe meetup a commencé avec Michel présentant le meetup et les intervenants (nous !).\n\n![Michel sur scène](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fmichel-on-stage.jpg)\n\nMa présentation, censée durer 20 minutes, a duré 30 minutes et aurait pu être beaucoup plus longue. La partie interactive a été un succès car elle a fonctionné, mais aussi parce que les gens participaient. J'étais tellement heureux après tout le travail fourni.\n\n![Présentation Estéban Soubiran](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Festeban-soubiran-talk.jpg)\n\nJ'ai été suivi par Romain qui a parlé de son parcours et d'AdonisJS. 45 minutes de pur plaisir à l'écouter et à redécouvrir la beauté d'AdonisJS. Je pense qu'il a convaincu certains membres du public d'essayer le framework ! 🥇\n\n![Présentation Romain Lanz](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fromain-lanz-talk.jpg)\n\nAprès les présentations, il était temps de manger, de boire et de discuter avec les personnes, environ 35 personnes, qui étaient venues au meetup.\n\nSur la photo, ce sont Romain et moi. Derrière, c'est toute l'équipe de StrasbourgJS qui a organisé le meetup !\n\n![Équipe StrasbourgJS et Intervenants](\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem\u002Fteam-and-speakers.jpeg)\n\n## Enfin\n\nLes sensations d'assister à un meetup, de rencontrer des personnes, de parler de tech avec des gens travaillant dans des entreprises variées autour de nourriture me manquaient. StrasbourgJS était très bien pour ça. Merci pour l'organisation ! 💛\n\nLe voyage continue à Devoxx France du 17 au 19 avril 2024. Je vais présenter une conférence sur l'écosystème UnJS. J'espère vous y voir !\n\nJe suis ouvert à donner d'autres présentations lors de meetups ! N'hésitez pas à [me contacter](https:\u002F\u002Fx.com\u002Fsoubiran_).\n\n_Les diapositives et le replay arrivent bientôt_\n",{"title":60707,"description":60889},{"loc":60891,"images":60895},[60896,60897,60898,60899,60900,60901],{"loc":60721},{"loc":60821},{"loc":60830},{"loc":60839},{"loc":60848},{"loc":60860},"fr\u002Fposts\u002Fmy-first-meetup-talk-a-dive-into-the-large-unjs-ecosystem","tYPzwHFx3DoHmkpiZc7o4JgMzRvrtNHx1YVBYCG2t0A",{"id":60905,"title":60906,"body":60907,"contentId":63440,"date":63441,"description":63442,"extension":18,"meta":63443,"navigation":20,"path":63444,"rawbody":63445,"robots":23,"schemaOrg":23,"seo":63446,"sitemap":63447,"stem":63451,"__hash__":63452},"posts\u002Ffr\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events.md","Créer des Applications Node.js en Temps Réel avec les SSE",{"type":7,"value":60908,"toc":63430},[60909,60916,60924,60927,60931,60934,60938,60945,60956,60962,61305,61314,61317,61329,61333,61338,61478,61481,61485,61488,61495,61785,61809,61813,61816,61819,62116,62125,62131,62357,62361,62364,62367,62373,62376,62379,62865,62875,62881,62890,62910,62920,62924,62929,62936,62939,63367,63376,63382,63388,63391,63397,63401,63404,63407,63421,63424,63427],[35,60910,60911,60912,60915],{},"Je suis convaincu que pour comprendre un concept ou une technologie, nous devons tous essayer ",[6862,60913,60914],{},"de cuisiner quelque chose avec"," en utilisant le plus bas niveau possible. Cela ne concerne pas seulement la programmation, mais tout dans la vie.",[35,60917,60918,60919,60923],{},"Dans la suite de l'article ",[39,60920,60922],{"href":60921},".\u002Fsimplify-real-time-with-server-sent-events-over-websockets","Temps réel sans WebSocket",", je pense que construire une petite application client-serveur utilisant des événements sent par le serveur (SSE) est un bon moyen de comprendre comment cela fonctionne et de nous donner la confiance nécessaire pour l'utiliser dans nos projets.",[35,60925,60926],{},"Cette application client-serveur sera un petit chat où le client enverra un message au serveur et le serveur diffusera le message à tous les clients connectés. Pas de bibliothèque externe, pas de framework, juste Node.js.",[51,60928,60930],{"id":60929},"exigences","Exigences",[35,60932,60933],{},"Pour suivre ce tutoriel, nous devons avoir Node.js installé sur notre machine. C'est tout.",[51,60935,60937],{"id":60936},"le-serveur","Le Serveur",[35,60939,60940,60941,60944],{},"Créez un fichier nommé ",[156,60942,60943],{},"server.mjs",". Ce fichier contiendra le code du serveur pour effectuer les actions suivantes :",[1593,60946,60947,60950,60953],{},[71,60948,60949],{},"Servir le code côté client c'est-à-dire un simple fichier HTML",[71,60951,60952],{},"Recevoir des messages des clients",[71,60954,60955],{},"Accepter les connexions des événements envoyés par le serveur et diffuser des messages",[35,60957,60958,60959,60961],{},"Mais pour l'instant, commençons par servir le code côté client. Notre serveur sera construit en utilisant le module ",[156,60960,28166],{}," natif de Node.js.",[192,60963,60965],{"className":25070,"code":60964,"language":25072,"meta":13,"style":13},"import { readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join } from 'node:path'\n\nconst hostname = '127.0.0.1'\nconst port = 3000\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Servir le fichier index.html\n  res.statusCode = 200\n  res.setHeader('Content-Type', 'text\u002Fhtml')\n\n  const htmlFile = join(process.cwd(), 'index.html')\n  const html = readFileSync(htmlFile, 'utf-8')\n\n  res.write(html)\n  res.end()\n})\n\nserver.listen(port, hostname, () => {\n  console.log(`Serveur en cours d'exécution à http:\u002F\u002F${hostname}:${port}\u002F`)\n})\n",[156,60966,60967,60987,61007,61025,61029,61045,61057,61061,61088,61093,61107,61136,61140,61173,61198,61202,61217,61227,61233,61237,61262,61299],{"__ignoreMap":13},[200,60968,60969,60971,60973,60976,60978,60980,60982,60985],{"class":202,"line":203},[200,60970,972],{"class":206},[200,60972,318],{"class":214},[200,60974,60975],{"class":210}," readFileSync",[200,60977,980],{"class":214},[200,60979,983],{"class":206},[200,60981,242],{"class":214},[200,60983,60984],{"class":245},"node:fs",[200,60986,991],{"class":214},[200,60988,60989,60991,60993,60996,60998,61000,61002,61005],{"class":202,"line":14},[200,60990,972],{"class":206},[200,60992,318],{"class":214},[200,60994,60995],{"class":210}," createServer",[200,60997,980],{"class":214},[200,60999,983],{"class":206},[200,61001,242],{"class":214},[200,61003,61004],{"class":245},"node:http",[200,61006,991],{"class":214},[200,61008,61009,61011,61013,61015,61017,61019,61021,61023],{"class":202,"line":255},[200,61010,972],{"class":206},[200,61012,318],{"class":214},[200,61014,12472],{"class":210},[200,61016,980],{"class":214},[200,61018,983],{"class":206},[200,61020,242],{"class":214},[200,61022,12481],{"class":245},[200,61024,991],{"class":214},[200,61026,61027],{"class":202,"line":272},[200,61028,528],{"emptyLinePlaceholder":20},[200,61030,61031,61033,61036,61038,61040,61043],{"class":202,"line":283},[200,61032,443],{"class":379},[200,61034,61035],{"class":210}," hostname ",[200,61037,449],{"class":214},[200,61039,242],{"class":214},[200,61041,61042],{"class":245},"127.0.0.1",[200,61044,991],{"class":214},[200,61046,61047,61049,61052,61054],{"class":202,"line":300},[200,61048,443],{"class":379},[200,61050,61051],{"class":210}," port ",[200,61053,449],{"class":214},[200,61055,61056],{"class":2060}," 3000\n",[200,61058,61059],{"class":202,"line":310},[200,61060,528],{"emptyLinePlaceholder":20},[200,61062,61063,61065,61067,61069,61071,61073,61075,61078,61080,61082,61084,61086],{"class":202,"line":336},[200,61064,443],{"class":379},[200,61066,19846],{"class":210},[200,61068,449],{"class":214},[200,61070,60995],{"class":223},[200,61072,227],{"class":210},[200,61074,227],{"class":214},[200,61076,61077],{"class":386},"req",[200,61079,549],{"class":214},[200,61081,10954],{"class":386},[200,61083,2104],{"class":214},[200,61085,393],{"class":379},[200,61087,280],{"class":214},[200,61089,61090],{"class":202,"line":342},[200,61091,61092],{"class":401},"  \u002F\u002F Servir le fichier index.html\n",[200,61094,61095,61098,61100,61102,61104],{"class":202,"line":365},[200,61096,61097],{"class":210},"  res",[200,61099,215],{"class":214},[200,61101,39221],{"class":210},[200,61103,509],{"class":214},[200,61105,61106],{"class":2060}," 200\n",[200,61108,61109,61111,61113,61116,61118,61120,61123,61125,61127,61129,61132,61134],{"class":202,"line":371},[200,61110,61097],{"class":210},[200,61112,215],{"class":214},[200,61114,61115],{"class":223},"setHeader",[200,61117,227],{"class":235},[200,61119,249],{"class":214},[200,61121,61122],{"class":245},"Content-Type",[200,61124,249],{"class":214},[200,61126,549],{"class":214},[200,61128,242],{"class":214},[200,61130,61131],{"class":245},"text\u002Fhtml",[200,61133,249],{"class":214},[200,61135,416],{"class":235},[200,61137,61138],{"class":202,"line":398},[200,61139,528],{"emptyLinePlaceholder":20},[200,61141,61142,61144,61147,61149,61151,61153,61155,61157,61160,61162,61164,61166,61169,61171],{"class":202,"line":405},[200,61143,3738],{"class":379},[200,61145,61146],{"class":210}," htmlFile",[200,61148,509],{"class":214},[200,61150,12472],{"class":223},[200,61152,227],{"class":235},[200,61154,15568],{"class":210},[200,61156,215],{"class":214},[200,61158,61159],{"class":223},"cwd",[200,61161,1549],{"class":235},[200,61163,549],{"class":214},[200,61165,242],{"class":214},[200,61167,61168],{"class":245},"index.html",[200,61170,249],{"class":214},[200,61172,416],{"class":235},[200,61174,61175,61177,61179,61181,61183,61185,61188,61190,61192,61194,61196],{"class":202,"line":410},[200,61176,3738],{"class":379},[200,61178,10290],{"class":210},[200,61180,509],{"class":214},[200,61182,60975],{"class":223},[200,61184,227],{"class":235},[200,61186,61187],{"class":210},"htmlFile",[200,61189,549],{"class":214},[200,61191,242],{"class":214},[200,61193,14750],{"class":245},[200,61195,249],{"class":214},[200,61197,416],{"class":235},[200,61199,61200],{"class":202,"line":860},[200,61201,528],{"emptyLinePlaceholder":20},[200,61203,61204,61206,61208,61211,61213,61215],{"class":202,"line":889},[200,61205,61097],{"class":210},[200,61207,215],{"class":214},[200,61209,61210],{"class":223},"write",[200,61212,227],{"class":235},[200,61214,592],{"class":210},[200,61216,416],{"class":235},[200,61218,61219,61221,61223,61225],{"class":202,"line":899},[200,61220,61097],{"class":210},[200,61222,215],{"class":214},[200,61224,28570],{"class":223},[200,61226,466],{"class":235},[200,61228,61229,61231],{"class":202,"line":904},[200,61230,413],{"class":214},[200,61232,416],{"class":210},[200,61234,61235],{"class":202,"line":930},[200,61236,528],{"emptyLinePlaceholder":20},[200,61238,61239,61241,61243,61246,61249,61251,61254,61256,61258,61260],{"class":202,"line":3655},[200,61240,3123],{"class":210},[200,61242,215],{"class":214},[200,61244,61245],{"class":223},"listen",[200,61247,61248],{"class":210},"(port",[200,61250,549],{"class":214},[200,61252,61253],{"class":210}," hostname",[200,61255,549],{"class":214},[200,61257,3729],{"class":214},[200,61259,393],{"class":379},[200,61261,280],{"class":214},[200,61263,61264,61266,61268,61270,61272,61274,61277,61279,61282,61284,61286,61288,61291,61293,61295,61297],{"class":202,"line":3660},[200,61265,1112],{"class":210},[200,61267,215],{"class":214},[200,61269,1117],{"class":223},[200,61271,227],{"class":235},[200,61273,3333],{"class":214},[200,61275,61276],{"class":245},"Serveur en cours d'exécution à http:\u002F\u002F",[200,61278,4868],{"class":214},[200,61280,61281],{"class":210},"hostname",[200,61283,413],{"class":214},[200,61285,239],{"class":245},[200,61287,4868],{"class":214},[200,61289,61290],{"class":210},"port",[200,61292,413],{"class":214},[200,61294,2898],{"class":245},[200,61296,3333],{"class":214},[200,61298,416],{"class":235},[200,61300,61301,61303],{"class":202,"line":4035},[200,61302,413],{"class":214},[200,61304,416],{"class":210},[35,61306,61307,61308,61310,61311,215],{},"Lorsqu'une requête atteint le serveur, il répondra avec le contenu du fichier ",[156,61309,61168],{}," en le lisant sur le disque. Le serveur écoute sur le port ",[156,61312,61313],{},"3000",[35,61315,61316],{},"Nous pouvons démarrer notre serveur avec la commande suivante :",[192,61318,61320],{"className":1354,"code":61319,"language":1357,"meta":13,"style":13},"node server.mjs\n",[156,61321,61322],{"__ignoreMap":13},[200,61323,61324,61326],{"class":202,"line":203},[200,61325,10040],{"class":1364},[200,61327,61328],{"class":245}," server.mjs\n",[51,61330,61332],{"id":61331},"le-client","Le Client",[35,61334,60940,61335,61337],{},[156,61336,61168],{}," à la racine du projet. Ce fichier contiendra le code côté client :",[192,61339,61341],{"className":590,"code":61340,"language":592,"meta":13,"style":13},"\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[156,61342,61343,61355,61373,61381,61400,61429,61446,61454,61462,61470],{"__ignoreMap":13},[200,61344,61345,61348,61351,61353],{"class":202,"line":203},[200,61346,61347],{"class":214},"\u003C!",[200,61349,61350],{"class":235},"doctype",[200,61352,10290],{"class":379},[200,61354,647],{"class":214},[200,61356,61357,61359,61361,61363,61365,61367,61369,61371],{"class":202,"line":14},[200,61358,599],{"class":214},[200,61360,592],{"class":235},[200,61362,14594],{"class":379},[200,61364,449],{"class":214},[200,61366,610],{"class":214},[200,61368,14601],{"class":245},[200,61370,610],{"class":214},[200,61372,647],{"class":214},[200,61374,61375,61377,61379],{"class":202,"line":255},[200,61376,656],{"class":214},[200,61378,14578],{"class":235},[200,61380,647],{"class":214},[200,61382,61383,61385,61387,61389,61391,61393,61396,61398],{"class":202,"line":272},[200,61384,803],{"class":214},[200,61386,2030],{"class":235},[200,61388,14743],{"class":379},[200,61390,449],{"class":214},[200,61392,610],{"class":214},[200,61394,61395],{"class":245},"UTF-8",[200,61397,610],{"class":214},[200,61399,21527],{"class":214},[200,61401,61402,61404,61406,61408,61410,61412,61414,61416,61418,61420,61422,61425,61427],{"class":202,"line":283},[200,61403,803],{"class":214},[200,61405,2030],{"class":235},[200,61407,700],{"class":379},[200,61409,449],{"class":214},[200,61411,610],{"class":214},[200,61413,14798],{"class":245},[200,61415,610],{"class":214},[200,61417,6053],{"class":379},[200,61419,449],{"class":214},[200,61421,610],{"class":214},[200,61423,61424],{"class":245},"width=device-width, initial-scale=1.0",[200,61426,610],{"class":214},[200,61428,21527],{"class":214},[200,61430,61431,61433,61435,61437,61440,61442,61444],{"class":202,"line":300},[200,61432,803],{"class":214},[200,61434,3581],{"class":235},[200,61436,674],{"class":214},[200,61438,61439],{"class":210},"Node SSE",[200,61441,680],{"class":214},[200,61443,3581],{"class":235},[200,61445,647],{"class":214},[200,61447,61448,61450,61452],{"class":202,"line":310},[200,61449,892],{"class":214},[200,61451,14578],{"class":235},[200,61453,647],{"class":214},[200,61455,61456,61458,61460],{"class":202,"line":336},[200,61457,656],{"class":214},[200,61459,23914],{"class":235},[200,61461,647],{"class":214},[200,61463,61464,61466,61468],{"class":202,"line":342},[200,61465,892],{"class":214},[200,61467,23914],{"class":235},[200,61469,647],{"class":214},[200,61471,61472,61474,61476],{"class":202,"line":365},[200,61473,680],{"class":214},[200,61475,592],{"class":235},[200,61477,647],{"class":214},[35,61479,61480],{},"Oui, c'est un fichier HTML très simple, rien de fantaisiste pour l'instant.",[51,61482,61484],{"id":61483},"connexion-des-événements-envoyés-par-le-serveur","Connexion des Événements Envoyés par le Serveur",[35,61486,61487],{},"Maintenant, ajoutons la connexion des événements envoyés par le serveur au serveur. Pour gérer les SSE, nous devons répondre avec certains en-têtes pour garder la connexion ouverte et envoyer des données. Ainsi, le client demandera une ressource et le serveur répondra avec un flux.",[35,61489,61490,61491,61494],{},"Cette ressource sera ",[156,61492,61493],{},"\u002Fevents",". Mettons à jour le code du serveur :",[192,61496,61498],{"className":25070,"code":61497,"language":25072,"meta":13,"style":13},"\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    \u002F\u002F TODO: Gérer la connexion\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    res.statusCode = 200\n    res.setHeader('Content-Type', 'text\u002Fhtml')\n\n    const htmlFile = join(process.cwd(), 'index.html')\n    const html = readFileSync(htmlFile, 'utf-8')\n\n    res.write(html)\n    res.end()\n  }\n})\n\n\u002F\u002F ...\n",[156,61499,61500,61504,61508,61534,61539,61563,61591,61619,61623,61628,61632,61638,61643,61655,61681,61685,61715,61739,61743,61757,61767,61771,61777,61781],{"__ignoreMap":13},[200,61501,61502],{"class":202,"line":203},[200,61503,32897],{"class":401},[200,61505,61506],{"class":202,"line":14},[200,61507,528],{"emptyLinePlaceholder":20},[200,61509,61510,61512,61514,61516,61518,61520,61522,61524,61526,61528,61530,61532],{"class":202,"line":255},[200,61511,443],{"class":379},[200,61513,19846],{"class":210},[200,61515,449],{"class":214},[200,61517,60995],{"class":223},[200,61519,227],{"class":210},[200,61521,227],{"class":214},[200,61523,61077],{"class":386},[200,61525,549],{"class":214},[200,61527,10954],{"class":386},[200,61529,2104],{"class":214},[200,61531,393],{"class":379},[200,61533,280],{"class":214},[200,61535,61536],{"class":202,"line":272},[200,61537,61538],{"class":401},"  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n",[200,61540,61541,61543,61545,61547,61549,61551,61553,61555,61557,61559,61561],{"class":202,"line":283},[200,61542,20098],{"class":206},[200,61544,1094],{"class":235},[200,61546,61077],{"class":210},[200,61548,215],{"class":214},[200,61550,2035],{"class":210},[200,61552,3298],{"class":214},[200,61554,242],{"class":214},[200,61556,61493],{"class":245},[200,61558,249],{"class":214},[200,61560,5282],{"class":235},[200,61562,230],{"class":214},[200,61564,61565,61568,61570,61572,61574,61576,61578,61580,61582,61584,61587,61589],{"class":202,"line":300},[200,61566,61567],{"class":210},"    res",[200,61569,215],{"class":214},[200,61571,61115],{"class":223},[200,61573,227],{"class":235},[200,61575,249],{"class":214},[200,61577,61122],{"class":245},[200,61579,249],{"class":214},[200,61581,549],{"class":214},[200,61583,242],{"class":214},[200,61585,61586],{"class":245},"text\u002Fevent-stream",[200,61588,249],{"class":214},[200,61590,416],{"class":235},[200,61592,61593,61595,61597,61599,61601,61603,61606,61608,61610,61612,61615,61617],{"class":202,"line":310},[200,61594,61567],{"class":210},[200,61596,215],{"class":214},[200,61598,61115],{"class":223},[200,61600,227],{"class":235},[200,61602,249],{"class":214},[200,61604,61605],{"class":245},"Cache-Control",[200,61607,249],{"class":214},[200,61609,549],{"class":214},[200,61611,242],{"class":214},[200,61613,61614],{"class":245},"no-cache",[200,61616,249],{"class":214},[200,61618,416],{"class":235},[200,61620,61621],{"class":202,"line":336},[200,61622,528],{"emptyLinePlaceholder":20},[200,61624,61625],{"class":202,"line":342},[200,61626,61627],{"class":401},"    \u002F\u002F TODO: Gérer la connexion\n",[200,61629,61630],{"class":202,"line":365},[200,61631,4505],{"class":214},[200,61633,61634,61636],{"class":202,"line":371},[200,61635,45720],{"class":206},[200,61637,280],{"class":214},[200,61639,61640],{"class":202,"line":398},[200,61641,61642],{"class":401},"    \u002F\u002F Servir le fichier index.html\n",[200,61644,61645,61647,61649,61651,61653],{"class":202,"line":405},[200,61646,61567],{"class":210},[200,61648,215],{"class":214},[200,61650,39221],{"class":210},[200,61652,509],{"class":214},[200,61654,61106],{"class":2060},[200,61656,61657,61659,61661,61663,61665,61667,61669,61671,61673,61675,61677,61679],{"class":202,"line":410},[200,61658,61567],{"class":210},[200,61660,215],{"class":214},[200,61662,61115],{"class":223},[200,61664,227],{"class":235},[200,61666,249],{"class":214},[200,61668,61122],{"class":245},[200,61670,249],{"class":214},[200,61672,549],{"class":214},[200,61674,242],{"class":214},[200,61676,61131],{"class":245},[200,61678,249],{"class":214},[200,61680,416],{"class":235},[200,61682,61683],{"class":202,"line":860},[200,61684,528],{"emptyLinePlaceholder":20},[200,61686,61687,61689,61691,61693,61695,61697,61699,61701,61703,61705,61707,61709,61711,61713],{"class":202,"line":889},[200,61688,2014],{"class":379},[200,61690,61146],{"class":210},[200,61692,509],{"class":214},[200,61694,12472],{"class":223},[200,61696,227],{"class":235},[200,61698,15568],{"class":210},[200,61700,215],{"class":214},[200,61702,61159],{"class":223},[200,61704,1549],{"class":235},[200,61706,549],{"class":214},[200,61708,242],{"class":214},[200,61710,61168],{"class":245},[200,61712,249],{"class":214},[200,61714,416],{"class":235},[200,61716,61717,61719,61721,61723,61725,61727,61729,61731,61733,61735,61737],{"class":202,"line":899},[200,61718,2014],{"class":379},[200,61720,10290],{"class":210},[200,61722,509],{"class":214},[200,61724,60975],{"class":223},[200,61726,227],{"class":235},[200,61728,61187],{"class":210},[200,61730,549],{"class":214},[200,61732,242],{"class":214},[200,61734,14750],{"class":245},[200,61736,249],{"class":214},[200,61738,416],{"class":235},[200,61740,61741],{"class":202,"line":904},[200,61742,528],{"emptyLinePlaceholder":20},[200,61744,61745,61747,61749,61751,61753,61755],{"class":202,"line":930},[200,61746,61567],{"class":210},[200,61748,215],{"class":214},[200,61750,61210],{"class":223},[200,61752,227],{"class":235},[200,61754,592],{"class":210},[200,61756,416],{"class":235},[200,61758,61759,61761,61763,61765],{"class":202,"line":3655},[200,61760,61567],{"class":210},[200,61762,215],{"class":214},[200,61764,28570],{"class":223},[200,61766,466],{"class":235},[200,61768,61769],{"class":202,"line":3660},[200,61770,4505],{"class":214},[200,61772,61773,61775],{"class":202,"line":4035},[200,61774,413],{"class":214},[200,61776,416],{"class":210},[200,61778,61779],{"class":202,"line":4490},[200,61780,528],{"emptyLinePlaceholder":20},[200,61782,61783],{"class":202,"line":4496},[200,61784,32897],{"class":401},[35,61786,61787,61788,61790,61791,1655,61794,61797,61798,61801,61802,61805,61806,61808],{},"Lorsque le client demande la ressource ",[156,61789,61493],{},", le serveur répondra avec les en-têtes ",[156,61792,61793],{},"Content-Type: text\u002Fevent-stream",[156,61795,61796],{},"Cache-Control: no-cache",". Cela informe le client que la connexion est un flux, c'est-à-dire que le serveur enverra des données au client à tout moment. La connexion ne doit pas être mise en cache pour éviter des problèmes avec le flux.\nIl est important de noter que nous n'appelons jamais la fonction ",[156,61799,61800],{},"res.end()",". Cela est dû au fait que ",[6862,61803,61804],{},"la connexion doit rester ouverte"," pour permettre au serveur d'envoyer des données au client. La fonction ",[156,61807,61800],{}," ferme la connexion.",[51,61810,61812],{"id":61811},"logique-de-chat","Logique de Chat",[35,61814,61815],{},"C'est très simple et simplifié au maximum, mais la logique sous-jacente reste la même qu'une application plus complexe.",[35,61817,61818],{},"Dans notre serveur, nous aurons une logique pour recevoir des messages des clients.",[192,61820,61822],{"className":25070,"code":61821,"language":25072,"meta":13,"style":13},"\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    \u002F\u002F ...\n\n    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F TODO: diffuser le message\n\n      \u002F\u002F Rediriger vers le fichier index.html\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    \u002F\u002F ...\n  }\n})\n",[156,61823,61824,61828,61832,61858,61862,61886,61890,61894,61899,61903,61930,61940,61944,61973,61983,61989,61993,62017,62022,62026,62031,62045,62072,62082,62088,62092,62098,62102,62106,62110],{"__ignoreMap":13},[200,61825,61826],{"class":202,"line":203},[200,61827,32897],{"class":401},[200,61829,61830],{"class":202,"line":14},[200,61831,528],{"emptyLinePlaceholder":20},[200,61833,61834,61836,61838,61840,61842,61844,61846,61848,61850,61852,61854,61856],{"class":202,"line":255},[200,61835,443],{"class":379},[200,61837,19846],{"class":210},[200,61839,449],{"class":214},[200,61841,60995],{"class":223},[200,61843,227],{"class":210},[200,61845,227],{"class":214},[200,61847,61077],{"class":386},[200,61849,549],{"class":214},[200,61851,10954],{"class":386},[200,61853,2104],{"class":214},[200,61855,393],{"class":379},[200,61857,280],{"class":214},[200,61859,61860],{"class":202,"line":272},[200,61861,61538],{"class":401},[200,61863,61864,61866,61868,61870,61872,61874,61876,61878,61880,61882,61884],{"class":202,"line":283},[200,61865,20098],{"class":206},[200,61867,1094],{"class":235},[200,61869,61077],{"class":210},[200,61871,215],{"class":214},[200,61873,2035],{"class":210},[200,61875,3298],{"class":214},[200,61877,242],{"class":214},[200,61879,61493],{"class":245},[200,61881,249],{"class":214},[200,61883,5282],{"class":235},[200,61885,230],{"class":214},[200,61887,61888],{"class":202,"line":300},[200,61889,13678],{"class":401},[200,61891,61892],{"class":202,"line":310},[200,61893,528],{"emptyLinePlaceholder":20},[200,61895,61896],{"class":202,"line":336},[200,61897,61898],{"class":401},"    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n",[200,61900,61901],{"class":202,"line":342},[200,61902,4505],{"class":214},[200,61904,61905,61907,61909,61911,61913,61915,61917,61919,61921,61924,61926,61928],{"class":202,"line":365},[200,61906,45720],{"class":206},[200,61908,45723],{"class":206},[200,61910,1094],{"class":235},[200,61912,61077],{"class":210},[200,61914,215],{"class":214},[200,61916,2035],{"class":210},[200,61918,3298],{"class":214},[200,61920,242],{"class":214},[200,61922,61923],{"class":245},"\u002Fmessage",[200,61925,249],{"class":214},[200,61927,5282],{"class":235},[200,61929,230],{"class":214},[200,61931,61932,61934,61936,61938],{"class":202,"line":371},[200,61933,52178],{"class":379},[200,61935,40809],{"class":210},[200,61937,509],{"class":214},[200,61939,18888],{"class":214},[200,61941,61942],{"class":202,"line":398},[200,61943,528],{"emptyLinePlaceholder":20},[200,61945,61946,61949,61951,61953,61955,61957,61959,61961,61963,61965,61967,61969,61971],{"class":202,"line":405},[200,61947,61948],{"class":210},"    req",[200,61950,215],{"class":214},[200,61952,25410],{"class":223},[200,61954,227],{"class":235},[200,61956,249],{"class":214},[200,61958,12736],{"class":245},[200,61960,249],{"class":214},[200,61962,549],{"class":214},[200,61964,1094],{"class":214},[200,61966,3613],{"class":386},[200,61968,2104],{"class":214},[200,61970,393],{"class":379},[200,61972,280],{"class":214},[200,61974,61975,61977,61980],{"class":202,"line":410},[200,61976,41853],{"class":210},[200,61978,61979],{"class":214}," +=",[200,61981,61982],{"class":210}," chunk\n",[200,61984,61985,61987],{"class":202,"line":860},[200,61986,2111],{"class":214},[200,61988,416],{"class":235},[200,61990,61991],{"class":202,"line":889},[200,61992,528],{"emptyLinePlaceholder":20},[200,61994,61995,61997,61999,62001,62003,62005,62007,62009,62011,62013,62015],{"class":202,"line":899},[200,61996,61948],{"class":210},[200,61998,215],{"class":214},[200,62000,25410],{"class":223},[200,62002,227],{"class":235},[200,62004,249],{"class":214},[200,62006,28570],{"class":245},[200,62008,249],{"class":214},[200,62010,549],{"class":214},[200,62012,3729],{"class":214},[200,62014,393],{"class":379},[200,62016,280],{"class":214},[200,62018,62019],{"class":202,"line":904},[200,62020,62021],{"class":401},"      \u002F\u002F TODO: diffuser le message\n",[200,62023,62024],{"class":202,"line":930},[200,62025,528],{"emptyLinePlaceholder":20},[200,62027,62028],{"class":202,"line":3655},[200,62029,62030],{"class":401},"      \u002F\u002F Rediriger vers le fichier index.html\n",[200,62032,62033,62036,62038,62040,62042],{"class":202,"line":3660},[200,62034,62035],{"class":210},"      res",[200,62037,215],{"class":214},[200,62039,39221],{"class":210},[200,62041,509],{"class":214},[200,62043,62044],{"class":2060}," 302\n",[200,62046,62047,62049,62051,62053,62055,62057,62060,62062,62064,62066,62068,62070],{"class":202,"line":4035},[200,62048,62035],{"class":210},[200,62050,215],{"class":214},[200,62052,61115],{"class":223},[200,62054,227],{"class":235},[200,62056,249],{"class":214},[200,62058,62059],{"class":245},"Location",[200,62061,249],{"class":214},[200,62063,549],{"class":214},[200,62065,242],{"class":214},[200,62067,2898],{"class":245},[200,62069,249],{"class":214},[200,62071,416],{"class":235},[200,62073,62074,62076,62078,62080],{"class":202,"line":4490},[200,62075,62035],{"class":210},[200,62077,215],{"class":214},[200,62079,28570],{"class":223},[200,62081,466],{"class":235},[200,62083,62084,62086],{"class":202,"line":4496},[200,62085,2111],{"class":214},[200,62087,416],{"class":235},[200,62089,62090],{"class":202,"line":4502},[200,62091,4505],{"class":214},[200,62093,62094,62096],{"class":202,"line":4508},[200,62095,45720],{"class":206},[200,62097,280],{"class":214},[200,62099,62100],{"class":202,"line":5469},[200,62101,61642],{"class":401},[200,62103,62104],{"class":202,"line":5474},[200,62105,13678],{"class":401},[200,62107,62108],{"class":202,"line":13237},[200,62109,4505],{"class":214},[200,62111,62112,62114],{"class":202,"line":13263},[200,62113,413],{"class":214},[200,62115,416],{"class":210},[35,62117,62118,62119,62121,62122,62124],{},"Lorsque l'URL demandée est ",[156,62120,61923],{},", le serveur écoutera les données envoyées par le client, qui sont le corps de la demande. Lorsque la demande est terminée, le serveur redirige le client vers le fichier ",[156,62123,61168],{},". C'est un comportement classique lorsque un formulaire est soumis dans une application rendue par le serveur.",[35,62126,62127,62128,62130],{},"Dans notre fichier ",[156,62129,61168],{},", ajoutons un formulaire pour envoyer des messages au serveur :",[192,62132,62134],{"className":590,"code":62133,"language":592,"meta":13,"style":13},"\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Envoyer\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[156,62135,62136,62146,62164,62172,62190,62218,62234,62242,62250,62278,62306,62333,62341,62349],{"__ignoreMap":13},[200,62137,62138,62140,62142,62144],{"class":202,"line":203},[200,62139,61347],{"class":214},[200,62141,61350],{"class":235},[200,62143,10290],{"class":379},[200,62145,647],{"class":214},[200,62147,62148,62150,62152,62154,62156,62158,62160,62162],{"class":202,"line":14},[200,62149,599],{"class":214},[200,62151,592],{"class":235},[200,62153,14594],{"class":379},[200,62155,449],{"class":214},[200,62157,610],{"class":214},[200,62159,14601],{"class":245},[200,62161,610],{"class":214},[200,62163,647],{"class":214},[200,62165,62166,62168,62170],{"class":202,"line":255},[200,62167,656],{"class":214},[200,62169,14578],{"class":235},[200,62171,647],{"class":214},[200,62173,62174,62176,62178,62180,62182,62184,62186,62188],{"class":202,"line":272},[200,62175,803],{"class":214},[200,62177,2030],{"class":235},[200,62179,14743],{"class":379},[200,62181,449],{"class":214},[200,62183,610],{"class":214},[200,62185,61395],{"class":245},[200,62187,610],{"class":214},[200,62189,21527],{"class":214},[200,62191,62192,62194,62196,62198,62200,62202,62204,62206,62208,62210,62212,62214,62216],{"class":202,"line":283},[200,62193,803],{"class":214},[200,62195,2030],{"class":235},[200,62197,700],{"class":379},[200,62199,449],{"class":214},[200,62201,610],{"class":214},[200,62203,14798],{"class":245},[200,62205,610],{"class":214},[200,62207,6053],{"class":379},[200,62209,449],{"class":214},[200,62211,610],{"class":214},[200,62213,61424],{"class":245},[200,62215,610],{"class":214},[200,62217,21527],{"class":214},[200,62219,62220,62222,62224,62226,62228,62230,62232],{"class":202,"line":300},[200,62221,803],{"class":214},[200,62223,3581],{"class":235},[200,62225,674],{"class":214},[200,62227,61439],{"class":210},[200,62229,680],{"class":214},[200,62231,3581],{"class":235},[200,62233,647],{"class":214},[200,62235,62236,62238,62240],{"class":202,"line":310},[200,62237,892],{"class":214},[200,62239,14578],{"class":235},[200,62241,647],{"class":214},[200,62243,62244,62246,62248],{"class":202,"line":336},[200,62245,656],{"class":214},[200,62247,23914],{"class":235},[200,62249,647],{"class":214},[200,62251,62252,62254,62256,62258,62260,62262,62264,62266,62268,62270,62272,62274,62276],{"class":202,"line":342},[200,62253,803],{"class":214},[200,62255,602],{"class":235},[200,62257,40681],{"class":379},[200,62259,449],{"class":214},[200,62261,610],{"class":214},[200,62263,61923],{"class":245},[200,62265,610],{"class":214},[200,62267,6589],{"class":379},[200,62269,449],{"class":214},[200,62271,610],{"class":214},[200,62273,3793],{"class":245},[200,62275,610],{"class":214},[200,62277,647],{"class":214},[200,62279,62280,62282,62284,62286,62288,62290,62292,62294,62296,62298,62300,62302,62304],{"class":202,"line":365},[200,62281,22071],{"class":214},[200,62283,355],{"class":235},[200,62285,321],{"class":379},[200,62287,449],{"class":214},[200,62289,610],{"class":214},[200,62291,697],{"class":245},[200,62293,610],{"class":214},[200,62295,700],{"class":379},[200,62297,449],{"class":214},[200,62299,610],{"class":214},[200,62301,29303],{"class":245},[200,62303,610],{"class":214},[200,62305,21527],{"class":214},[200,62307,62308,62310,62312,62314,62316,62318,62320,62322,62324,62327,62329,62331],{"class":202,"line":371},[200,62309,22071],{"class":214},[200,62311,909],{"class":235},[200,62313,321],{"class":379},[200,62315,449],{"class":214},[200,62317,610],{"class":214},[200,62319,916],{"class":245},[200,62321,610],{"class":214},[200,62323,674],{"class":214},[200,62325,62326],{"class":210},"Envoyer",[200,62328,680],{"class":214},[200,62330,909],{"class":235},[200,62332,647],{"class":214},[200,62334,62335,62337,62339],{"class":202,"line":398},[200,62336,15078],{"class":214},[200,62338,602],{"class":235},[200,62340,647],{"class":214},[200,62342,62343,62345,62347],{"class":202,"line":405},[200,62344,892],{"class":214},[200,62346,23914],{"class":235},[200,62348,647],{"class":214},[200,62350,62351,62353,62355],{"class":202,"line":410},[200,62352,680],{"class":214},[200,62354,592],{"class":235},[200,62356,647],{"class":214},[51,62358,62360],{"id":62359},"diffuser-le-message","Diffuser le Message",[35,62362,62363],{},"La fonctionnalité manquante est la diffusion du message. Comment diffusons-nous le message et qu'est-ce que cela signifie ?",[35,62365,62366],{},"Une diffusion consiste à envoyer un message à tout le monde. Sinon, c'est unicast, lorsque un message est envoyé à une personne spécifique.",[35,62368,62369],{},[1707,62370],{"alt":62371,"src":62372},"Diffusion vs Unicast","\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fbroadcast-vs-unicast.png",[35,62374,62375],{},"Dans notre cas, le client A enverra un message au serveur (unicast). Ensuite, le serveur enverra ce message à chaque client (diffusion).",[35,62377,62378],{},"Le unicast a été mis en œuvre dans la section précédente. Pour la diffusion, nous devons garder une trace des clients connectés afin de passer en revue lorsque un message est reçu.",[192,62380,62382],{"className":25070,"code":62381,"language":25072,"meta":13,"style":13},"\u002F\u002F ...\n\n\u002F\u002F Garder une trace des clients connectés\nconst connection = new Set()\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    connection.add(res)\n\n    req.on('close', () => {\n      connection.delete(res)\n    })\n\n    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F Diffuser le message à tous les clients connectés\n      for (const client of connection)\n        client.write(`data: ${body}\\n\\n`)\n\n      \u002F\u002F Rediriger vers le fichier index.html\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    \u002F\u002F ...\n  }\n})\n\n\u002F\u002F ...\n",[156,62383,62384,62388,62392,62397,62412,62416,62442,62446,62470,62496,62522,62526,62542,62546,62570,62586,62592,62596,62600,62604,62630,62640,62644,62672,62680,62686,62690,62714,62719,62738,62767,62771,62775,62787,62813,62823,62829,62833,62839,62843,62847,62851,62857,62861],{"__ignoreMap":13},[200,62385,62386],{"class":202,"line":203},[200,62387,32897],{"class":401},[200,62389,62390],{"class":202,"line":14},[200,62391,528],{"emptyLinePlaceholder":20},[200,62393,62394],{"class":202,"line":255},[200,62395,62396],{"class":401},"\u002F\u002F Garder une trace des clients connectés\n",[200,62398,62399,62401,62404,62406,62408,62410],{"class":202,"line":272},[200,62400,443],{"class":379},[200,62402,62403],{"class":210}," connection ",[200,62405,449],{"class":214},[200,62407,13007],{"class":214},[200,62409,15421],{"class":223},[200,62411,466],{"class":210},[200,62413,62414],{"class":202,"line":283},[200,62415,528],{"emptyLinePlaceholder":20},[200,62417,62418,62420,62422,62424,62426,62428,62430,62432,62434,62436,62438,62440],{"class":202,"line":300},[200,62419,443],{"class":379},[200,62421,19846],{"class":210},[200,62423,449],{"class":214},[200,62425,60995],{"class":223},[200,62427,227],{"class":210},[200,62429,227],{"class":214},[200,62431,61077],{"class":386},[200,62433,549],{"class":214},[200,62435,10954],{"class":386},[200,62437,2104],{"class":214},[200,62439,393],{"class":379},[200,62441,280],{"class":214},[200,62443,62444],{"class":202,"line":310},[200,62445,61538],{"class":401},[200,62447,62448,62450,62452,62454,62456,62458,62460,62462,62464,62466,62468],{"class":202,"line":336},[200,62449,20098],{"class":206},[200,62451,1094],{"class":235},[200,62453,61077],{"class":210},[200,62455,215],{"class":214},[200,62457,2035],{"class":210},[200,62459,3298],{"class":214},[200,62461,242],{"class":214},[200,62463,61493],{"class":245},[200,62465,249],{"class":214},[200,62467,5282],{"class":235},[200,62469,230],{"class":214},[200,62471,62472,62474,62476,62478,62480,62482,62484,62486,62488,62490,62492,62494],{"class":202,"line":342},[200,62473,61567],{"class":210},[200,62475,215],{"class":214},[200,62477,61115],{"class":223},[200,62479,227],{"class":235},[200,62481,249],{"class":214},[200,62483,61122],{"class":245},[200,62485,249],{"class":214},[200,62487,549],{"class":214},[200,62489,242],{"class":214},[200,62491,61586],{"class":245},[200,62493,249],{"class":214},[200,62495,416],{"class":235},[200,62497,62498,62500,62502,62504,62506,62508,62510,62512,62514,62516,62518,62520],{"class":202,"line":365},[200,62499,61567],{"class":210},[200,62501,215],{"class":214},[200,62503,61115],{"class":223},[200,62505,227],{"class":235},[200,62507,249],{"class":214},[200,62509,61605],{"class":245},[200,62511,249],{"class":214},[200,62513,549],{"class":214},[200,62515,242],{"class":214},[200,62517,61614],{"class":245},[200,62519,249],{"class":214},[200,62521,416],{"class":235},[200,62523,62524],{"class":202,"line":371},[200,62525,528],{"emptyLinePlaceholder":20},[200,62527,62528,62531,62533,62536,62538,62540],{"class":202,"line":398},[200,62529,62530],{"class":210},"    connection",[200,62532,215],{"class":214},[200,62534,62535],{"class":223},"add",[200,62537,227],{"class":235},[200,62539,10949],{"class":210},[200,62541,416],{"class":235},[200,62543,62544],{"class":202,"line":405},[200,62545,528],{"emptyLinePlaceholder":20},[200,62547,62548,62550,62552,62554,62556,62558,62560,62562,62564,62566,62568],{"class":202,"line":410},[200,62549,61948],{"class":210},[200,62551,215],{"class":214},[200,62553,25410],{"class":223},[200,62555,227],{"class":235},[200,62557,249],{"class":214},[200,62559,22609],{"class":245},[200,62561,249],{"class":214},[200,62563,549],{"class":214},[200,62565,3729],{"class":214},[200,62567,393],{"class":379},[200,62569,280],{"class":214},[200,62571,62572,62575,62577,62580,62582,62584],{"class":202,"line":860},[200,62573,62574],{"class":210},"      connection",[200,62576,215],{"class":214},[200,62578,62579],{"class":223},"delete",[200,62581,227],{"class":235},[200,62583,10949],{"class":210},[200,62585,416],{"class":235},[200,62587,62588,62590],{"class":202,"line":889},[200,62589,2111],{"class":214},[200,62591,416],{"class":235},[200,62593,62594],{"class":202,"line":899},[200,62595,528],{"emptyLinePlaceholder":20},[200,62597,62598],{"class":202,"line":904},[200,62599,61898],{"class":401},[200,62601,62602],{"class":202,"line":930},[200,62603,4505],{"class":214},[200,62605,62606,62608,62610,62612,62614,62616,62618,62620,62622,62624,62626,62628],{"class":202,"line":3655},[200,62607,45720],{"class":206},[200,62609,45723],{"class":206},[200,62611,1094],{"class":235},[200,62613,61077],{"class":210},[200,62615,215],{"class":214},[200,62617,2035],{"class":210},[200,62619,3298],{"class":214},[200,62621,242],{"class":214},[200,62623,61923],{"class":245},[200,62625,249],{"class":214},[200,62627,5282],{"class":235},[200,62629,230],{"class":214},[200,62631,62632,62634,62636,62638],{"class":202,"line":3660},[200,62633,52178],{"class":379},[200,62635,40809],{"class":210},[200,62637,509],{"class":214},[200,62639,18888],{"class":214},[200,62641,62642],{"class":202,"line":4035},[200,62643,528],{"emptyLinePlaceholder":20},[200,62645,62646,62648,62650,62652,62654,62656,62658,62660,62662,62664,62666,62668,62670],{"class":202,"line":4490},[200,62647,61948],{"class":210},[200,62649,215],{"class":214},[200,62651,25410],{"class":223},[200,62653,227],{"class":235},[200,62655,249],{"class":214},[200,62657,12736],{"class":245},[200,62659,249],{"class":214},[200,62661,549],{"class":214},[200,62663,1094],{"class":214},[200,62665,3613],{"class":386},[200,62667,2104],{"class":214},[200,62669,393],{"class":379},[200,62671,280],{"class":214},[200,62673,62674,62676,62678],{"class":202,"line":4496},[200,62675,41853],{"class":210},[200,62677,61979],{"class":214},[200,62679,61982],{"class":210},[200,62681,62682,62684],{"class":202,"line":4502},[200,62683,2111],{"class":214},[200,62685,416],{"class":235},[200,62687,62688],{"class":202,"line":4508},[200,62689,528],{"emptyLinePlaceholder":20},[200,62691,62692,62694,62696,62698,62700,62702,62704,62706,62708,62710,62712],{"class":202,"line":5469},[200,62693,61948],{"class":210},[200,62695,215],{"class":214},[200,62697,25410],{"class":223},[200,62699,227],{"class":235},[200,62701,249],{"class":214},[200,62703,28570],{"class":245},[200,62705,249],{"class":214},[200,62707,549],{"class":214},[200,62709,3729],{"class":214},[200,62711,393],{"class":379},[200,62713,280],{"class":214},[200,62715,62716],{"class":202,"line":5474},[200,62717,62718],{"class":401},"      \u002F\u002F Diffuser le message à tous les clients connectés\n",[200,62720,62721,62724,62726,62728,62731,62733,62736],{"class":202,"line":13237},[200,62722,62723],{"class":206},"      for",[200,62725,1094],{"class":235},[200,62727,443],{"class":379},[200,62729,62730],{"class":210}," client",[200,62732,6457],{"class":214},[200,62734,62735],{"class":210}," connection",[200,62737,416],{"class":235},[200,62739,62740,62743,62745,62747,62749,62751,62754,62756,62758,62760,62763,62765],{"class":202,"line":13263},[200,62741,62742],{"class":210},"        client",[200,62744,215],{"class":214},[200,62746,61210],{"class":223},[200,62748,227],{"class":235},[200,62750,3333],{"class":214},[200,62752,62753],{"class":245},"data: ",[200,62755,4868],{"class":214},[200,62757,23914],{"class":210},[200,62759,413],{"class":214},[200,62761,62762],{"class":210},"\\n\\n",[200,62764,3333],{"class":214},[200,62766,416],{"class":235},[200,62768,62769],{"class":202,"line":13279},[200,62770,528],{"emptyLinePlaceholder":20},[200,62772,62773],{"class":202,"line":13323},[200,62774,62030],{"class":401},[200,62776,62777,62779,62781,62783,62785],{"class":202,"line":13329},[200,62778,62035],{"class":210},[200,62780,215],{"class":214},[200,62782,39221],{"class":210},[200,62784,509],{"class":214},[200,62786,62044],{"class":2060},[200,62788,62789,62791,62793,62795,62797,62799,62801,62803,62805,62807,62809,62811],{"class":202,"line":13334},[200,62790,62035],{"class":210},[200,62792,215],{"class":214},[200,62794,61115],{"class":223},[200,62796,227],{"class":235},[200,62798,249],{"class":214},[200,62800,62059],{"class":245},[200,62802,249],{"class":214},[200,62804,549],{"class":214},[200,62806,242],{"class":214},[200,62808,2898],{"class":245},[200,62810,249],{"class":214},[200,62812,416],{"class":235},[200,62814,62815,62817,62819,62821],{"class":202,"line":13360},[200,62816,62035],{"class":210},[200,62818,215],{"class":214},[200,62820,28570],{"class":223},[200,62822,466],{"class":235},[200,62824,62825,62827],{"class":202,"line":13365},[200,62826,2111],{"class":214},[200,62828,416],{"class":235},[200,62830,62831],{"class":202,"line":13385},[200,62832,4505],{"class":214},[200,62834,62835,62837],{"class":202,"line":13405},[200,62836,45720],{"class":206},[200,62838,280],{"class":214},[200,62840,62841],{"class":202,"line":13410},[200,62842,61642],{"class":401},[200,62844,62845],{"class":202,"line":13418},[200,62846,13678],{"class":401},[200,62848,62849],{"class":202,"line":13436},[200,62850,4505],{"class":214},[200,62852,62853,62855],{"class":202,"line":13466},[200,62854,413],{"class":214},[200,62856,416],{"class":210},[200,62858,62859],{"class":202,"line":13472},[200,62860,528],{"emptyLinePlaceholder":20},[200,62862,62863],{"class":202,"line":13477},[200,62864,32897],{"class":401},[35,62866,62867,62868,62870,62871,62874],{},"Lorsqu'un client se connecte à l'endpoint ",[156,62869,61493],{},", le serveur ajoute l'objet de réponse à l'ensemble ",[156,62872,62873],{},"connection",". Lorsque la connexion est fermée, l'objet de réponse est supprimé de l'ensemble. Facile !",[35,62876,62877,62878,62880],{},"Nous gardons cet objet pour pouvoir envoyer des données au client en utilisant la méthode ",[156,62879,61210],{},". C'est le cœur du système.",[35,62882,62883,62884,62886,62887,62889],{},"Lorsqu'un message est reçu sur l'endpoint ",[156,62885,61923],{},", le serveur parcourt la ",[156,62888,62873],{}," précédemment sauvegardée pour envoyer le message à chaque client.",[35,62891,62892,62893,62895,62896,62898,62899,62901,62902,62904,62905,62907,62908,215],{},"Nous pouvons observer le mot clé ",[156,62894,12736],{}," dans le message et les doubles ",[156,62897,6098],{}," à la fin. C'est le format des événements envoyés par le serveur. Le mot clé ",[156,62900,12736],{}," est utilisé pour envoyer des données au client, et le double ",[156,62903,6098],{}," est utilisé pour indiquer au client que le message est terminé. Le client n'exposera que le contenu après le mot clé ",[156,62906,12736],{}," et avant le double ",[156,62909,6098],{},[35,62911,62912,62913,62916,62917,215],{},"Par exemple, si le serveur envoie ",[156,62914,62915],{},"data: Bonjour\\n\\n",", le client verra ",[156,62918,62919],{},"Bonjour",[51,62921,62923],{"id":62922},"api-eventsource","API EventSource",[35,62925,62926,62927,215],{},"Notre serveur gère la connexion et diffuse des messages, mais le client n'est toujours pas capable de se connecter à l'endpoint SSE, ressource ",[156,62928,61493],{},[35,62930,62931,62932,62935],{},"Pour se connecter au serveur, nous utiliserons l'API ",[156,62933,62934],{},"EventSource",". Cette API est une API JavaScript native pour gérer les événements envoyés par le serveur, et elle est très simple.",[35,62937,62938],{},"Au début de notre fichier, nous devons avoir un petit script pour établir la connexion et gérer les messages :",[192,62940,62942],{"className":590,"code":62941,"language":592,"meta":13,"style":13},"\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n    \u003Cscript>\n      \u002F\u002F Se connecter au serveur\n      const eventSource = new EventSource(\"\u002Fevents\");\n\n      const ul = document.querySelector(\"ul\");\n\n      \u002F\u002F Écouter le message\n      eventSource.onmessage = (event) => {\n        \u002F\u002F Ajouter le message à la liste\n        const li = document.createElement(\"li\");\n        li.innerText = event.data;\n\n        ul.appendChild(li);\n      };\n    \u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cul>\u003C\u002Ful>\n\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Envoyer\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[156,62943,62944,62954,62972,62980,62998,63026,63042,63050,63055,63081,63085,63113,63117,63122,63143,63148,63175,63195,63199,63216,63221,63229,63237,63245,63257,63261,63289,63317,63343,63351,63359],{"__ignoreMap":13},[200,62945,62946,62948,62950,62952],{"class":202,"line":203},[200,62947,61347],{"class":214},[200,62949,61350],{"class":235},[200,62951,10290],{"class":379},[200,62953,647],{"class":214},[200,62955,62956,62958,62960,62962,62964,62966,62968,62970],{"class":202,"line":14},[200,62957,599],{"class":214},[200,62959,592],{"class":235},[200,62961,14594],{"class":379},[200,62963,449],{"class":214},[200,62965,610],{"class":214},[200,62967,14601],{"class":245},[200,62969,610],{"class":214},[200,62971,647],{"class":214},[200,62973,62974,62976,62978],{"class":202,"line":255},[200,62975,656],{"class":214},[200,62977,14578],{"class":235},[200,62979,647],{"class":214},[200,62981,62982,62984,62986,62988,62990,62992,62994,62996],{"class":202,"line":272},[200,62983,803],{"class":214},[200,62985,2030],{"class":235},[200,62987,14743],{"class":379},[200,62989,449],{"class":214},[200,62991,610],{"class":214},[200,62993,61395],{"class":245},[200,62995,610],{"class":214},[200,62997,21527],{"class":214},[200,62999,63000,63002,63004,63006,63008,63010,63012,63014,63016,63018,63020,63022,63024],{"class":202,"line":283},[200,63001,803],{"class":214},[200,63003,2030],{"class":235},[200,63005,700],{"class":379},[200,63007,449],{"class":214},[200,63009,610],{"class":214},[200,63011,14798],{"class":245},[200,63013,610],{"class":214},[200,63015,6053],{"class":379},[200,63017,449],{"class":214},[200,63019,610],{"class":214},[200,63021,61424],{"class":245},[200,63023,610],{"class":214},[200,63025,21527],{"class":214},[200,63027,63028,63030,63032,63034,63036,63038,63040],{"class":202,"line":300},[200,63029,803],{"class":214},[200,63031,3581],{"class":235},[200,63033,674],{"class":214},[200,63035,61439],{"class":210},[200,63037,680],{"class":214},[200,63039,3581],{"class":235},[200,63041,647],{"class":214},[200,63043,63044,63046,63048],{"class":202,"line":310},[200,63045,803],{"class":214},[200,63047,14896],{"class":235},[200,63049,647],{"class":214},[200,63051,63052],{"class":202,"line":336},[200,63053,63054],{"class":401},"      \u002F\u002F Se connecter au serveur\n",[200,63056,63057,63059,63062,63064,63066,63069,63071,63073,63075,63077,63079],{"class":202,"line":342},[200,63058,20136],{"class":379},[200,63060,63061],{"class":210}," eventSource ",[200,63063,449],{"class":214},[200,63065,13007],{"class":214},[200,63067,63068],{"class":223}," EventSource",[200,63070,227],{"class":210},[200,63072,610],{"class":214},[200,63074,61493],{"class":245},[200,63076,610],{"class":214},[200,63078,2104],{"class":210},[200,63080,5944],{"class":214},[200,63082,63083],{"class":202,"line":365},[200,63084,528],{"emptyLinePlaceholder":20},[200,63086,63087,63089,63092,63094,63096,63098,63101,63103,63105,63107,63109,63111],{"class":202,"line":371},[200,63088,20136],{"class":379},[200,63090,63091],{"class":210}," ul ",[200,63093,449],{"class":214},[200,63095,211],{"class":210},[200,63097,215],{"class":214},[200,63099,63100],{"class":223},"querySelector",[200,63102,227],{"class":210},[200,63104,610],{"class":214},[200,63106,1593],{"class":245},[200,63108,610],{"class":214},[200,63110,2104],{"class":210},[200,63112,5944],{"class":214},[200,63114,63115],{"class":202,"line":398},[200,63116,528],{"emptyLinePlaceholder":20},[200,63118,63119],{"class":202,"line":405},[200,63120,63121],{"class":401},"      \u002F\u002F Écouter le message\n",[200,63123,63124,63127,63129,63131,63133,63135,63137,63139,63141],{"class":202,"line":410},[200,63125,63126],{"class":210},"      eventSource",[200,63128,215],{"class":214},[200,63130,29138],{"class":223},[200,63132,509],{"class":214},[200,63134,1094],{"class":214},[200,63136,25426],{"class":386},[200,63138,2104],{"class":214},[200,63140,393],{"class":379},[200,63142,280],{"class":214},[200,63144,63145],{"class":202,"line":860},[200,63146,63147],{"class":401},"        \u002F\u002F Ajouter le message à la liste\n",[200,63149,63150,63152,63155,63157,63159,63161,63163,63165,63167,63169,63171,63173],{"class":202,"line":889},[200,63151,12999],{"class":379},[200,63153,63154],{"class":210}," li",[200,63156,509],{"class":214},[200,63158,211],{"class":210},[200,63160,215],{"class":214},[200,63162,15947],{"class":223},[200,63164,227],{"class":235},[200,63166,610],{"class":214},[200,63168,71],{"class":245},[200,63170,610],{"class":214},[200,63172,2104],{"class":235},[200,63174,5944],{"class":214},[200,63176,63177,63180,63182,63185,63187,63189,63191,63193],{"class":202,"line":899},[200,63178,63179],{"class":210},"        li",[200,63181,215],{"class":214},[200,63183,63184],{"class":210},"innerText",[200,63186,509],{"class":214},[200,63188,43463],{"class":210},[200,63190,215],{"class":214},[200,63192,12736],{"class":210},[200,63194,5944],{"class":214},[200,63196,63197],{"class":202,"line":904},[200,63198,528],{"emptyLinePlaceholder":20},[200,63200,63201,63204,63206,63208,63210,63212,63214],{"class":202,"line":930},[200,63202,63203],{"class":210},"        ul",[200,63205,215],{"class":214},[200,63207,16017],{"class":223},[200,63209,227],{"class":235},[200,63211,71],{"class":210},[200,63213,2104],{"class":235},[200,63215,5944],{"class":214},[200,63217,63218],{"class":202,"line":3655},[200,63219,63220],{"class":214},"      };\n",[200,63222,63223,63225,63227],{"class":202,"line":3660},[200,63224,15078],{"class":214},[200,63226,14896],{"class":235},[200,63228,647],{"class":214},[200,63230,63231,63233,63235],{"class":202,"line":4035},[200,63232,892],{"class":214},[200,63234,14578],{"class":235},[200,63236,647],{"class":214},[200,63238,63239,63241,63243],{"class":202,"line":4490},[200,63240,656],{"class":214},[200,63242,23914],{"class":235},[200,63244,647],{"class":214},[200,63246,63247,63249,63251,63253,63255],{"class":202,"line":4496},[200,63248,803],{"class":214},[200,63250,1593],{"class":235},[200,63252,10206],{"class":214},[200,63254,1593],{"class":235},[200,63256,647],{"class":214},[200,63258,63259],{"class":202,"line":4502},[200,63260,528],{"emptyLinePlaceholder":20},[200,63262,63263,63265,63267,63269,63271,63273,63275,63277,63279,63281,63283,63285,63287],{"class":202,"line":4508},[200,63264,803],{"class":214},[200,63266,602],{"class":235},[200,63268,40681],{"class":379},[200,63270,449],{"class":214},[200,63272,610],{"class":214},[200,63274,61923],{"class":245},[200,63276,610],{"class":214},[200,63278,6589],{"class":379},[200,63280,449],{"class":214},[200,63282,610],{"class":214},[200,63284,3793],{"class":245},[200,63286,610],{"class":214},[200,63288,647],{"class":214},[200,63290,63291,63293,63295,63297,63299,63301,63303,63305,63307,63309,63311,63313,63315],{"class":202,"line":5469},[200,63292,22071],{"class":214},[200,63294,355],{"class":235},[200,63296,321],{"class":379},[200,63298,449],{"class":214},[200,63300,610],{"class":214},[200,63302,697],{"class":245},[200,63304,610],{"class":214},[200,63306,700],{"class":379},[200,63308,449],{"class":214},[200,63310,610],{"class":214},[200,63312,29303],{"class":245},[200,63314,610],{"class":214},[200,63316,21527],{"class":214},[200,63318,63319,63321,63323,63325,63327,63329,63331,63333,63335,63337,63339,63341],{"class":202,"line":5474},[200,63320,22071],{"class":214},[200,63322,909],{"class":235},[200,63324,321],{"class":379},[200,63326,449],{"class":214},[200,63328,610],{"class":214},[200,63330,916],{"class":245},[200,63332,610],{"class":214},[200,63334,674],{"class":214},[200,63336,62326],{"class":210},[200,63338,680],{"class":214},[200,63340,909],{"class":235},[200,63342,647],{"class":214},[200,63344,63345,63347,63349],{"class":202,"line":13237},[200,63346,15078],{"class":214},[200,63348,602],{"class":235},[200,63350,647],{"class":214},[200,63352,63353,63355,63357],{"class":202,"line":13263},[200,63354,892],{"class":214},[200,63356,23914],{"class":235},[200,63358,647],{"class":214},[200,63360,63361,63363,63365],{"class":202,"line":13279},[200,63362,680],{"class":214},[200,63364,592],{"class":235},[200,63366,647],{"class":214},[35,63368,63369,63370,63372,63373,63375],{},"Le script commence par demander la ressource ",[156,63371,61493],{}," au serveur en utilisant l'API ",[156,63374,62934],{},". Cela gérera automatiquement la reconnexion.",[35,63377,63378,63379,63381],{},"Ensuite, nous enregistrons une méthode ",[156,63380,29138],{}," qui sera déclenchée chaque fois qu'un message est reçu par le client depuis le serveur.",[35,63383,63384,63385,63387],{},"Pour avoir quelque chose de plus semblable à un chat, nous allons ajouter une liste pour afficher les messages. Lorsqu'un message est reçu, nous créerons un élément ",[156,63386,71],{}," et l'ajouterons à la liste.",[35,63389,63390],{},"Maintenant, nous pouvons ouvrir deux onglets dans notre navigateur et envoyer des messages. Ils apparaîtront dans l'autre onglet. :magie:",[35,63392,63393],{},[1707,63394],{"alt":63395,"src":63396},"Chat Node SSE","\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fnode-sse-chat.gif",[51,63398,63400],{"id":63399},"derniers-mots","Derniers Mots",[35,63402,63403],{},"Même si c'est un exemple très simple, rappelez-vous que la logique de stockage des clients et de diffusion avec une boucle est la même qu'une application plus complexe.",[35,63405,63406],{},"Il y a deux choses à retenir avec les événements envoyés par le serveur :",[1593,63408,63409,63415],{},[71,63410,63411,63412,63414],{},"Utilisez l'API ",[156,63413,62934],{}," pour vous connecter au serveur",[71,63416,63417,63418,63420],{},"Répondez avec ",[156,63419,61793],{}," depuis le serveur",[35,63422,63423],{},"Rien de plus. C'est simple, c'est HTTP, et c'est puissant.",[35,63425,63426],{},"J'espère que cet article vous aidera à comprendre comment fonctionnent les événements envoyés par le serveur et vous donnera la confiance nécessaire pour les utiliser dans vos projets. C'est un outil très puissant pour construire des applications en temps réel sans la complexité des WebSockets.",[1170,63428,63429],{},"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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":13,"searchDepth":14,"depth":14,"links":63431},[63432,63433,63434,63435,63436,63437,63438,63439],{"id":60929,"depth":14,"text":60930},{"id":60936,"depth":14,"text":60937},{"id":61331,"depth":14,"text":61332},{"id":61483,"depth":14,"text":61484},{"id":61811,"depth":14,"text":61812},{"id":62359,"depth":14,"text":62360},{"id":62922,"depth":14,"text":62923},{"id":63399,"depth":14,"text":63400},"4949967d-cd6e-4cf8-b28e-a71db1aaf014","2024-04-03","Créer des applications Node.js en temps réel en utilisant les SSE pour une communication serveur-client efficace sans la complexité des WebSockets.",{},"\u002Ffr\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events","---\ncontentId: 4949967d-cd6e-4cf8-b28e-a71db1aaf014\ntitle: Créer des Applications Node.js en Temps Réel avec les SSE\ndescription: Créer des applications Node.js en temps réel en utilisant les SSE pour une communication serveur-client efficace sans la complexité des WebSockets.\ndate: 2024-04-03\n---\n\nJe suis convaincu que pour comprendre un concept ou une technologie, nous devons tous essayer **de cuisiner quelque chose avec** en utilisant le plus bas niveau possible. Cela ne concerne pas seulement la programmation, mais tout dans la vie.\n\nDans la suite de l'article [Temps réel sans WebSocket](.\u002Fsimplify-real-time-with-server-sent-events-over-websockets.md), je pense que construire une petite application client-serveur utilisant des événements sent par le serveur (SSE) est un bon moyen de comprendre comment cela fonctionne et de nous donner la confiance nécessaire pour l'utiliser dans nos projets.\n\nCette application client-serveur sera un petit chat où le client enverra un message au serveur et le serveur diffusera le message à tous les clients connectés. Pas de bibliothèque externe, pas de framework, juste Node.js.\n\n## Exigences\n\nPour suivre ce tutoriel, nous devons avoir Node.js installé sur notre machine. C'est tout.\n\n## Le Serveur\n\nCréez un fichier nommé `server.mjs`. Ce fichier contiendra le code du serveur pour effectuer les actions suivantes :\n\n- Servir le code côté client c'est-à-dire un simple fichier HTML\n- Recevoir des messages des clients\n- Accepter les connexions des événements envoyés par le serveur et diffuser des messages\n\nMais pour l'instant, commençons par servir le code côté client. Notre serveur sera construit en utilisant le module `http` natif de Node.js.\n\n```js\nimport { readFileSync } from 'node:fs'\nimport { createServer } from 'node:http'\nimport { join } from 'node:path'\n\nconst hostname = '127.0.0.1'\nconst port = 3000\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Servir le fichier index.html\n  res.statusCode = 200\n  res.setHeader('Content-Type', 'text\u002Fhtml')\n\n  const htmlFile = join(process.cwd(), 'index.html')\n  const html = readFileSync(htmlFile, 'utf-8')\n\n  res.write(html)\n  res.end()\n})\n\nserver.listen(port, hostname, () => {\n  console.log(`Serveur en cours d'exécution à http:\u002F\u002F${hostname}:${port}\u002F`)\n})\n```\n\nLorsqu'une requête atteint le serveur, il répondra avec le contenu du fichier `index.html` en le lisant sur le disque. Le serveur écoute sur le port `3000`.\n\nNous pouvons démarrer notre serveur avec la commande suivante :\n\n```sh\nnode server.mjs\n```\n\n## Le Client\n\nCréez un fichier nommé `index.html` à la racine du projet. Ce fichier contiendra le code côté client :\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\nOui, c'est un fichier HTML très simple, rien de fantaisiste pour l'instant.\n\n## Connexion des Événements Envoyés par le Serveur\n\nMaintenant, ajoutons la connexion des événements envoyés par le serveur au serveur. Pour gérer les SSE, nous devons répondre avec certains en-têtes pour garder la connexion ouverte et envoyer des données. Ainsi, le client demandera une ressource et le serveur répondra avec un flux.\n\nCette ressource sera `\u002Fevents`. Mettons à jour le code du serveur :\n\n```js\n\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    \u002F\u002F TODO: Gérer la connexion\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    res.statusCode = 200\n    res.setHeader('Content-Type', 'text\u002Fhtml')\n\n    const htmlFile = join(process.cwd(), 'index.html')\n    const html = readFileSync(htmlFile, 'utf-8')\n\n    res.write(html)\n    res.end()\n  }\n})\n\n\u002F\u002F ...\n```\n\nLorsque le client demande la ressource `\u002Fevents`, le serveur répondra avec les en-têtes `Content-Type: text\u002Fevent-stream` et `Cache-Control: no-cache`. Cela informe le client que la connexion est un flux, c'est-à-dire que le serveur enverra des données au client à tout moment. La connexion ne doit pas être mise en cache pour éviter des problèmes avec le flux.\nIl est important de noter que nous n'appelons jamais la fonction `res.end()`. Cela est dû au fait que **la connexion doit rester ouverte** pour permettre au serveur d'envoyer des données au client. La fonction `res.end()` ferme la connexion.\n\n## Logique de Chat\n\nC'est très simple et simplifié au maximum, mais la logique sous-jacente reste la même qu'une application plus complexe.\n\nDans notre serveur, nous aurons une logique pour recevoir des messages des clients.\n\n```js\n\u002F\u002F ...\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    \u002F\u002F ...\n\n    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F TODO: diffuser le message\n\n      \u002F\u002F Rediriger vers le fichier index.html\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    \u002F\u002F ...\n  }\n})\n```\n\nLorsque l'URL demandée est `\u002Fmessage`, le serveur écoutera les données envoyées par le client, qui sont le corps de la demande. Lorsque la demande est terminée, le serveur redirige le client vers le fichier `index.html`. C'est un comportement classique lorsque un formulaire est soumis dans une application rendue par le serveur.\n\nDans notre fichier `index.html`, ajoutons un formulaire pour envoyer des messages au serveur :\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Envoyer\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\n## Diffuser le Message\n\nLa fonctionnalité manquante est la diffusion du message. Comment diffusons-nous le message et qu'est-ce que cela signifie ?\n\nUne diffusion consiste à envoyer un message à tout le monde. Sinon, c'est unicast, lorsque un message est envoyé à une personne spécifique.\n\n![Diffusion vs Unicast](\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fbroadcast-vs-unicast.png)\n\nDans notre cas, le client A enverra un message au serveur (unicast). Ensuite, le serveur enverra ce message à chaque client (diffusion).\n\nLe unicast a été mis en œuvre dans la section précédente. Pour la diffusion, nous devons garder une trace des clients connectés afin de passer en revue lorsque un message est reçu.\n\n```js\n\u002F\u002F ...\n\n\u002F\u002F Garder une trace des clients connectés\nconst connection = new Set()\n\nconst server = createServer((req, res) => {\n  \u002F\u002F Point de terminaison `\u002Fevents` utilisé pour écouter les messages\n  if (req.url === '\u002Fevents') {\n    res.setHeader('Content-Type', 'text\u002Fevent-stream')\n    res.setHeader('Cache-Control', 'no-cache')\n\n    connection.add(res)\n\n    req.on('close', () => {\n      connection.delete(res)\n    })\n\n    \u002F\u002F Point de terminaison `\u002Fmessages` utilisé pour envoyer des messages\n  }\n  else if (req.url === '\u002Fmessage') {\n    let body = ''\n\n    req.on('data', (chunk) => {\n      body += chunk\n    })\n\n    req.on('end', () => {\n      \u002F\u002F Diffuser le message à tous les clients connectés\n      for (const client of connection)\n        client.write(`data: ${body}\\n\\n`)\n\n      \u002F\u002F Rediriger vers le fichier index.html\n      res.statusCode = 302\n      res.setHeader('Location', '\u002F')\n      res.end()\n    })\n  }\n  else {\n    \u002F\u002F Servir le fichier index.html\n    \u002F\u002F ...\n  }\n})\n\n\u002F\u002F ...\n```\n\nLorsqu'un client se connecte à l'endpoint `\u002Fevents`, le serveur ajoute l'objet de réponse à l'ensemble `connection`. Lorsque la connexion est fermée, l'objet de réponse est supprimé de l'ensemble. Facile !\n\nNous gardons cet objet pour pouvoir envoyer des données au client en utilisant la méthode `write`. C'est le cœur du système.\n\nLorsqu'un message est reçu sur l'endpoint `\u002Fmessage`, le serveur parcourt la `connection` précédemment sauvegardée pour envoyer le message à chaque client.\n\nNous pouvons observer le mot clé `data` dans le message et les doubles `\\n` à la fin. C'est le format des événements envoyés par le serveur. Le mot clé `data` est utilisé pour envoyer des données au client, et le double `\\n` est utilisé pour indiquer au client que le message est terminé. Le client n'exposera que le contenu après le mot clé `data` et avant le double `\\n`.\n\nPar exemple, si le serveur envoie `data: Bonjour\\n\\n`, le client verra `Bonjour`.\n\n## API EventSource\n\nNotre serveur gère la connexion et diffuse des messages, mais le client n'est toujours pas capable de se connecter à l'endpoint SSE, ressource `\u002Fevents`.\n\nPour se connecter au serveur, nous utiliserons l'API `EventSource`. Cette API est une API JavaScript native pour gérer les événements envoyés par le serveur, et elle est très simple.\n\nAu début de notre fichier, nous devons avoir un petit script pour établir la connexion et gérer les messages :\n\n```html\n\u003C!doctype html>\n\u003Chtml lang=\"en\">\n  \u003Chead>\n    \u003Cmeta charset=\"UTF-8\" \u002F>\n    \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F>\n    \u003Ctitle>Node SSE\u003C\u002Ftitle>\n    \u003Cscript>\n      \u002F\u002F Se connecter au serveur\n      const eventSource = new EventSource(\"\u002Fevents\");\n\n      const ul = document.querySelector(\"ul\");\n\n      \u002F\u002F Écouter le message\n      eventSource.onmessage = (event) => {\n        \u002F\u002F Ajouter le message à la liste\n        const li = document.createElement(\"li\");\n        li.innerText = event.data;\n\n        ul.appendChild(li);\n      };\n    \u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    \u003Cul>\u003C\u002Ful>\n\n    \u003Cform action=\"\u002Fmessage\" method=\"post\">\n      \u003Cinput type=\"text\" name=\"message\" \u002F>\n      \u003Cbutton type=\"submit\">Envoyer\u003C\u002Fbutton>\n    \u003C\u002Fform>\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n```\n\nLe script commence par demander la ressource `\u002Fevents` au serveur en utilisant l'API `EventSource`. Cela gérera automatiquement la reconnexion.\n\nEnsuite, nous enregistrons une méthode `onmessage` qui sera déclenchée chaque fois qu'un message est reçu par le client depuis le serveur.\n\nPour avoir quelque chose de plus semblable à un chat, nous allons ajouter une liste pour afficher les messages. Lorsqu'un message est reçu, nous créerons un élément `li` et l'ajouterons à la liste.\n\nMaintenant, nous pouvons ouvrir deux onglets dans notre navigateur et envoyer des messages. Ils apparaîtront dans l'autre onglet. :magie:\n\n![Chat Node SSE](\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events\u002Fnode-sse-chat.gif)\n\n## Derniers Mots\n\nMême si c'est un exemple très simple, rappelez-vous que la logique de stockage des clients et de diffusion avec une boucle est la même qu'une application plus complexe.\n\nIl y a deux choses à retenir avec les événements envoyés par le serveur :\n\n- Utilisez l'API `EventSource` pour vous connecter au serveur\n- Répondez avec `Content-Type: text\u002Fevent-stream` depuis le serveur\n\nRien de plus. C'est simple, c'est HTTP, et c'est puissant.\n\nJ'espère que cet article vous aidera à comprendre comment fonctionnent les événements envoyés par le serveur et vous donnera la confiance nécessaire pour les utiliser dans vos projets. C'est un outil très puissant pour construire des applications en temps réel sans la complexité des WebSockets.\n",{"title":60906,"description":63442},{"loc":63444,"images":63448},[63449,63450],{"loc":62372},{"loc":63396},"fr\u002Fposts\u002Fcreate-real-time-nodejs-apps-with-server-sent-events","RTM8CAC98MUlqDbURQuRWlbQQW4RlMmJQeESMOt7aa4",{"id":63454,"title":63455,"body":63456,"contentId":63732,"date":63733,"description":63734,"extension":18,"meta":63735,"navigation":20,"path":63736,"rawbody":63737,"robots":23,"schemaOrg":23,"seo":63738,"sitemap":63739,"stem":63744,"__hash__":63745},"posts\u002Ffr\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets.md","Simplifier le Temps réel avec SSE Plutôt que WebSocket",{"type":7,"value":63457,"toc":63723},[63458,63469,63474,63484,63488,63495,63509,63514,63518,63521,63546,63549,63554,63558,63565,63570,63575,63580,63586,63593,63600,63605,63609,63622,63637,63643,63647,63653,63659,63665,63671,63681,63686,63692],[35,63459,63460,63461,63464,63465,63468],{},"Lorsque j'ai commencé à développer pour le ",[6862,63462,63463],{},"web"," en 2018, j'étais fasciné par la fonctionnalité de ",[6862,63466,63467],{},"temps réel",". Imaginez, quelqu'un change quelque chose sur une page et le changement est instantanément visible pour tout le monde. C'était comme de la magie pour moi.",[35,63470,63471],{},[1216,63472,63473],{},"J'ai ensuite appris que cette magie a un coût en matière de complexité.",[35,63475,63476,63477,63479,63480,63483],{},"À cette époque, j'apprenais ",[6862,63478,33388],{},", et construire un simple ",[6862,63481,63482],{},"serveur web"," était déjà un défi.",[51,63485,63487],{"id":63486},"la-première-fois","La Première Fois",[35,63489,63490,63491,63494],{},"Plus tard, pour un projet scolaire, je devais construire un tableau de bord très simple pour gérer des ",[6862,63492,63493],{},"dispositifs IoT",", tout depuis le début. Le temps réel était une exigence pour diffuser l'état des dispositifs depuis le serveur vers chaque client connecté.",[35,63496,63497,63498,63501,63502,63505,63506],{},"J'ai fait des recherches sur Internet et chaque article de blog, chaque tutoriel parlait de ",[6862,63499,63500],{},"WebSocket",". Alors, peut-être que c'est la voie à suivre. Pour ce projet, j'ai appris et utilisé ",[6862,63503,63504],{},"Socket.IO",". Le projet est sur GitHub à ",[1599,63507,215],{"repo":63508},"barbapapazes\u002Fraspiot",[35,63510,63511],{},[1216,63512,63513],{},"Pendant la présentation, l'effet de démonstration m'a touché et tout ne fonctionnait pas comme prévu.",[51,63515,63517],{"id":63516},"avec-un-framework","Avec un Framework",[35,63519,63520],{},"J'ai également essayé de construire une application web pour les étudiants de mon école afin de gérer le mentoring entre étudiants. J'étais encore débutant mais les projets sont le meilleur moyen d'apprendre, donc je me suis lancé.",[35,63522,63523,63524,63531,63532,63535,63536,63538,63539,215],{},"J'ai utilisé ",[6862,63525,63526],{},[39,63527,63530],{"href":63528,"rel":63529},"https:\u002F\u002Ffeathersjs.com\u002F",[77],"Feathers",", l'API et le ",[6862,63533,63534],{},"framework d'application temps réel",". C'était probablement trop facile, alors j'ai décidé d'utiliser ",[6862,63537,17998],{}," pour le frontend avec ",[6862,63540,63541],{},[39,63542,63545],{"href":63543,"rel":63544},"https:\u002F\u002Fvuex.feathersjs.com\u002F",[77],"Feathers-Vuex",[35,63547,63548],{},"Je n'ai jamais rencontré quelque chose d'aussi difficile pour absolument aucune valeur ajoutée à mon projet. Rétrospectivement, je pense que je voulais du temps réel parce que c'était à la mode (clairement, ce n'est pas une bonne raison).",[35,63550,63551],{},[1216,63552,63553],{},"C'était tellement complexe que je ne l'ai jamais terminé.",[51,63555,63557],{"id":63556},"le-websocket-est-complexe","Le WebSocket est Complexe",[35,63559,63560,63561,63564],{},"Sur le web, nous utilisons un protocole appelé ",[6862,63562,63563],{},"HTTP",". La requête d'un navigateur vers un serveur utilise HTTP.",[35,63566,63567],{},[1707,63568],{"alt":63563,"src":63569},"\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fhttp.png",[35,63571,63572,63574],{},[6862,63573,63500],{}," n'est pas HTTP. WebSocket est un protocole à part entière. Il est très important de le savoir car cela a des conséquences sur la façon dont nous pouvons l'utiliser.",[35,63576,63577],{},[1707,63578],{"alt":63500,"src":63579},"\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fwebsocket.png",[35,63581,63582,63583,63585],{},"Tout d'abord, WebSocket n'est pas natif sur ",[6862,63584,33385],{},". Nous devons utiliser une bibliothèque externe pour l'utiliser. Cela pourrait devenir un gros problème si nous devons l'intégrer dans un projet complexe.",[35,63587,63588,63589,63592],{},"Deuxièmement, WebSocket ne gère pas la ",[6862,63590,63591],{},"reconnexion",". Si la connexion est perdue, il n'y a aucun mécanisme intégré pour se reconnecter. Nous devons le gérer nous-mêmes. Autrement dit, et malgré son support natif dans les navigateurs, nous devons l'utiliser avec une bibliothèque pour gérer la reconnexion. Cela commence à représenter beaucoup de complexité et de code à maintenir.",[35,63594,63595,63596,63599],{},"Enfin, WebSocket n'est pas HTTP, et dans votre serveur web, vous devrez gérer deux protocoles et créer un pont entre eux. La plus grande partie concerne ",[6862,63597,63598],{},"l'authentification et l'autorisation",". Cela pourrait entraîner beaucoup de logique à créer et à maintenir malgré le fait que nous pourrions déjà avoir une logique d'authentification pour HTTP. Cela implique également d'avoir une logique personnalisée côté client pour gérer l'authentification et l'autorisation. C'est parce que HTTP est sans état et WebSocket ne l'est pas.",[35,63601,63602],{},[1216,63603,63604],{},"Chose que j'aurais aimé entendre lorsque je commençais avec WebSocket, c'est que toutes les données n'ont pas besoin d'être en temps réel et que des choses plus simples sont meilleures que rien.",[51,63606,63608],{"id":63607},"lautre-voie","L'Autre Voie",[35,63610,63611,63612,63616,63617,63621],{},"La première fois que j'ai entendu parler de cette technologie, c'était lors d'un stream de ",[39,63613,60753],{"href":63614,"rel":63615},"https:\u002F\u002Fwww.twitch.tv\u002Fromainlanz",[77],". Il est un mainteneur de ",[39,63618,29841],{"href":63619,"rel":63620},"https:\u002F\u002Fadonisjs.com",[77]," et maîtrise le sujet.",[35,63623,63624,63625,63628,63629,63632,63633,63636],{},"Parfois, le temps réel peut avoir de la valeur pour un projet contenant des ",[6862,63626,63627],{},"graphes",", de la ",[6862,63630,63631],{},"visualisation de données",", des informations, des tâches asynchrones, ou même un ",[6862,63634,63635],{},"chat",". Mais voyez-vous la chose que toutes ces fonctionnalités ont en commun ?",[35,63638,63639,63640,215],{},"Le client n'envoie pas, ou peu, d'informations. De plus, le temps de requête, entre le client et le serveur, n'est pas critique. Alors pourquoi aurions-nous besoin d'une communication bidirectionnelle ? Et cela n'interopère pas bien avec HTTP ? En 2024, ",[1216,63641,63642],{},"je ne sais pas",[1929,63644,63646],{"id":63645},"événements-envoyés-par-le-serveur","Événements Envoyés par le Serveur",[35,63648,21121,63649,63652],{},[6862,63650,63651],{},"événements envoyés par le serveur"," (SSE) est une technologie qui permet à un serveur d'envoyer des événements à un client. C'est une communication unidirectionnelle du serveur vers le client.",[35,63654,63655],{},[1707,63656],{"alt":63657,"src":63658},"SSE","\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fsse.png",[35,63660,63661,63662,63664],{},"Le client demande une ressource en utilisant l'API ",[156,63663,62934],{}," et le serveur répond par un flux. C'est une connexion HTTP mais qui reste ouverte pour permettre au serveur d'envoyer des données au client. Simple, non ?",[35,63666,63667,63668,63670],{},"Bonus, cela gère automatiquement la ",[6862,63669,63591],{}," sans code supplémentaire.",[35,63672,63673,63674,13741,63677,63680],{},"Sur Internet, nous pouvons souvent lire que SSE est mauvais, le client ne peut pas envoyer de données au serveur. Mais ces personnes oublient-elles comment fonctionne Internet ? Oublient-elles ",[156,63675,63676],{},"\u003Cform>",[156,63678,63679],{},"XMLHttpRequest"," ? Bien sûr, le client peut toujours envoyer des données au serveur. Aujourd'hui, SSE n'a pas d'énormes inconvénients.",[35,63682,63683],{},[1216,63684,63685],{},"Un jour, nous construirons un chat simple en utilisant SSE et un formulaire pour envoyer des messages afin de démontrer à quel point le web peut être simple et agréable.",[35,63687,63688,63689,215],{},"En réalité, il existe une raison historique pour laquelle WebSocket était meilleur que SSE mais ce n'est plus le cas en 2024 grâce à ",[6862,63690,63691],{},"HTTP\u002F2",[7918,63693,63696,63701,63703,63709,63712],{"text":63694,"href":63695},"Utiliser les Événements Envoyés par le Serveur","https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FServer-sent_events\u002FUsing_server-sent_events",[35,63697,63698],{},[1216,63699,63700],{},"Pour un jeu, WebSocket pourrait être la meilleure option pour éviter la poignée de main entre le client et le serveur.",[51,63702,19349],{"id":19348},[35,63704,63705,63706],{},"Je pense que nous devrions toujours nous demander si nous avons réellement besoin d'une communication bidirectionnelle instantanée. Si nous n'en avons pas besoin, nous devrions utiliser SSE. C'est plus simple, c'est HTTP, et c'est puissant. ",[1216,63707,63708],{},"Pesez toujours les avantages et les inconvénients avant de choisir, même si c'est le choix le plus populaire.",[35,63710,63711],{},"Alors, allez-vous utiliser SSE dans votre prochain projet qui nécessite du temps réel ?",[35,63713,63714],{},[1216,63715,63716,63717,63722],{},"Il est possible d'utiliser SSE directement dans notre application puisque c'est juste HTTP. Il est également intéressant de savoir que ",[39,63718,63721],{"href":63719,"rel":63720},"https:\u002F\u002Fmercure.rocks\u002F",[77],"Mercure"," existe et peut être utile pour des projets plus complexes.",{"title":13,"searchDepth":14,"depth":14,"links":63724},[63725,63726,63727,63728,63731],{"id":63486,"depth":14,"text":63487},{"id":63516,"depth":14,"text":63517},{"id":63556,"depth":14,"text":63557},{"id":63607,"depth":14,"text":63608,"children":63729},[63730],{"id":63645,"depth":255,"text":63646},{"id":19348,"depth":14,"text":19349},"f92fd030-4838-4877-885a-892cbc12168b","2024-03-29","Découvrez de puissantes alternatives web en temps réel aux WebSockets en utilisant SSE pour des solutions plus simples et plus efficaces.",{},"\u002Ffr\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets","---\ncontentId: f92fd030-4838-4877-885a-892cbc12168b\ntitle: Simplifier le Temps réel avec SSE Plutôt que WebSocket\ndescription : Découvrez de puissantes alternatives web en temps réel aux WebSockets en utilisant SSE pour des solutions plus simples et plus efficaces.\ndate: 2024-03-29\n---\n\nLorsque j'ai commencé à développer pour le **web** en 2018, j'étais fasciné par la fonctionnalité de **temps réel**. Imaginez, quelqu'un change quelque chose sur une page et le changement est instantanément visible pour tout le monde. C'était comme de la magie pour moi.\n\n_J'ai ensuite appris que cette magie a un coût en matière de complexité._\n\nÀ cette époque, j'apprenais **Express**, et construire un simple **serveur web** était déjà un défi.\n\n## La Première Fois\n\nPlus tard, pour un projet scolaire, je devais construire un tableau de bord très simple pour gérer des **dispositifs IoT**, tout depuis le début. Le temps réel était une exigence pour diffuser l'état des dispositifs depuis le serveur vers chaque client connecté.\n\nJ'ai fait des recherches sur Internet et chaque article de blog, chaque tutoriel parlait de **WebSocket**. Alors, peut-être que c'est la voie à suivre. Pour ce projet, j'ai appris et utilisé **Socket.IO**. Le projet est sur GitHub à \u003CGitHubLink repo=\"barbapapazes\u002Fraspiot\" \u002F>.\n\n_Pendant la présentation, l'effet de démonstration m'a touché et tout ne fonctionnait pas comme prévu._\n\n## Avec un Framework\n\nJ'ai également essayé de construire une application web pour les étudiants de mon école afin de gérer le mentoring entre étudiants. J'étais encore débutant mais les projets sont le meilleur moyen d'apprendre, donc je me suis lancé.\n\nJ'ai utilisé **[Feathers](https:\u002F\u002Ffeathersjs.com\u002F)**, l'API et le **framework d'application temps réel**. C'était probablement trop facile, alors j'ai décidé d'utiliser **Vue** pour le frontend avec **[Feathers-Vuex](https:\u002F\u002Fvuex.feathersjs.com\u002F)**.\n\nJe n'ai jamais rencontré quelque chose d'aussi difficile pour absolument aucune valeur ajoutée à mon projet. Rétrospectivement, je pense que je voulais du temps réel parce que c'était à la mode (clairement, ce n'est pas une bonne raison).\n\n_C'était tellement complexe que je ne l'ai jamais terminé._\n\n## Le WebSocket est Complexe\n\nSur le web, nous utilisons un protocole appelé **HTTP**. La requête d'un navigateur vers un serveur utilise HTTP.\n\n![HTTP](\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fhttp.png)\n\n**WebSocket** n'est pas HTTP. WebSocket est un protocole à part entière. Il est très important de le savoir car cela a des conséquences sur la façon dont nous pouvons l'utiliser.\n\n![WebSocket](\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fwebsocket.png)\n\nTout d'abord, WebSocket n'est pas natif sur **Node.js**. Nous devons utiliser une bibliothèque externe pour l'utiliser. Cela pourrait devenir un gros problème si nous devons l'intégrer dans un projet complexe.\n\nDeuxièmement, WebSocket ne gère pas la **reconnexion**. Si la connexion est perdue, il n'y a aucun mécanisme intégré pour se reconnecter. Nous devons le gérer nous-mêmes. Autrement dit, et malgré son support natif dans les navigateurs, nous devons l'utiliser avec une bibliothèque pour gérer la reconnexion. Cela commence à représenter beaucoup de complexité et de code à maintenir.\n\nEnfin, WebSocket n'est pas HTTP, et dans votre serveur web, vous devrez gérer deux protocoles et créer un pont entre eux. La plus grande partie concerne **l'authentification et l'autorisation**. Cela pourrait entraîner beaucoup de logique à créer et à maintenir malgré le fait que nous pourrions déjà avoir une logique d'authentification pour HTTP. Cela implique également d'avoir une logique personnalisée côté client pour gérer l'authentification et l'autorisation. C'est parce que HTTP est sans état et WebSocket ne l'est pas.\n\n_Chose que j'aurais aimé entendre lorsque je commençais avec WebSocket, c'est que toutes les données n'ont pas besoin d'être en temps réel et que des choses plus simples sont meilleures que rien._\n\n## L'Autre Voie\n\nLa première fois que j'ai entendu parler de cette technologie, c'était lors d'un stream de [Romain Lanz](https:\u002F\u002Fwww.twitch.tv\u002Fromainlanz). Il est un mainteneur de [Adonis](https:\u002F\u002Fadonisjs.com) et maîtrise le sujet.\n\nParfois, le temps réel peut avoir de la valeur pour un projet contenant des **graphes**, de la **visualisation de données**, des informations, des tâches asynchrones, ou même un **chat**. Mais voyez-vous la chose que toutes ces fonctionnalités ont en commun ?\n\nLe client n'envoie pas, ou peu, d'informations. De plus, le temps de requête, entre le client et le serveur, n'est pas critique. Alors pourquoi aurions-nous besoin d'une communication bidirectionnelle ? Et cela n'interopère pas bien avec HTTP ? En 2024, _je ne sais pas_.\n\n### Événements Envoyés par le Serveur\n\nLes **événements envoyés par le serveur** (SSE) est une technologie qui permet à un serveur d'envoyer des événements à un client. C'est une communication unidirectionnelle du serveur vers le client.\n\n![SSE](\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets\u002Fsse.png)\n\nLe client demande une ressource en utilisant l'API `EventSource` et le serveur répond par un flux. C'est une connexion HTTP mais qui reste ouverte pour permettre au serveur d'envoyer des données au client. Simple, non ?\n\nBonus, cela gère automatiquement la **reconnexion** sans code supplémentaire.\n\nSur Internet, nous pouvons souvent lire que SSE est mauvais, le client ne peut pas envoyer de données au serveur. Mais ces personnes oublient-elles comment fonctionne Internet ? Oublient-elles `\u003Cform>` ou `XMLHttpRequest` ? Bien sûr, le client peut toujours envoyer des données au serveur. Aujourd'hui, SSE n'a pas d'énormes inconvénients.\n\n_Un jour, nous construirons un chat simple en utilisant SSE et un formulaire pour envoyer des messages afin de démontrer à quel point le web peut être simple et agréable._\n\nEn réalité, il existe une raison historique pour laquelle WebSocket était meilleur que SSE mais ce n'est plus le cas en 2024 grâce à **HTTP\u002F2**.\n\n\u003CCalloutReadMore text=\"Utiliser les Événements Envoyés par le Serveur\" href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FServer-sent_events\u002FUsing_server-sent_events\" \u002F>\n\n_Pour un jeu, WebSocket pourrait être la meilleure option pour éviter la poignée de main entre le client et le serveur._\n\n## Enfin\n\nJe pense que nous devrions toujours nous demander si nous avons réellement besoin d'une communication bidirectionnelle instantanée. Si nous n'en avons pas besoin, nous devrions utiliser SSE. C'est plus simple, c'est HTTP, et c'est puissant. _Pesez toujours les avantages et les inconvénients avant de choisir, même si c'est le choix le plus populaire._\n\nAlors, allez-vous utiliser SSE dans votre prochain projet qui nécessite du temps réel ?\n\n_Il est possible d'utiliser SSE directement dans notre application puisque c'est juste HTTP. Il est également intéressant de savoir que [Mercure](https:\u002F\u002Fmercure.rocks\u002F) existe et peut être utile pour des projets plus complexes._\n",{"title":63455,"description":63734},{"loc":63736,"images":63740},[63741,63742,63743],{"loc":63569},{"loc":63579},{"loc":63658},"fr\u002Fposts\u002Fsimplify-real-time-with-server-sent-events-over-websockets","QAcm0uXCerDyBQM-aBGq2391QKnmJpLeFy5wai8dXtU",{"id":63747,"title":63748,"body":63749,"contentId":30564,"date":63963,"description":63964,"extension":18,"meta":63965,"navigation":20,"path":63966,"rawbody":63967,"robots":23,"schemaOrg":23,"seo":63968,"sitemap":63969,"stem":63970,"__hash__":63971},"posts\u002Ffr\u002Fposts\u002Fvitepress-for-a-personal-website-is-that-possible.md","VitePress pour un Site Personnel, est-ce Possible ?",{"type":7,"value":63750,"toc":63955},[63751,63754,63758,63769,63783,63794,63798,63803,63806,63812,63815,63824,63827,63833,63838,63842,63845,63848,63851,63854,63860,63864,63867,63870,63873,63876,63884,63891,63897,63901,63904,63913,63918,63921,63925,63936,63943,63949,63952],[35,63752,63753],{},"Depuis de nombreuses années, j'essaie de créer un site personnel qui reflète mon style. Jusqu'à présent, je n'ai pas réussi. Peut-être que cet effort échouera également, mais je n'ai jamais été aussi sûr de mon choix. Laissez-moi vous expliquer pourquoi !",[51,63755,63757],{"id":63756},"le-site-personnel","Le site personnel",[35,63759,63760,63761],{},"À première vue, la création d'un site personnel semble simple. Si simple que c'était mon premier projet significatif il y a 5 ans, ",[1599,63762,63764,63765,215],{"repo":63763},"barbapapazes\u002Festeban-s-website",", avec Express.js tout en apprenant ",[39,63766,33385],{"href":63767,"rel":63768},"https:\u002F\u002Fnodejs.org",[77],[35,63770,63771,63772],{},"Par la suite, j'ai expérimenté avec Nuxt 2, ",[1599,63773,63775,63776],{"repo":63774},"barbapapazes\u002Festeban-soubiran.site\u002Ftree\u002Fv2",", puis Nuxt 3, ",[1599,63777,63779,63780],{"repo":63778},"barbapapazes\u002Festeban-soubiran.site",", mais il me manquait toujours quelque chose. Je n'étais jamais satisfait du résultat, et l'utiliser n'était pas agréable. ",[1216,63781,63782],{},"S'il n'y a pas de plaisir, il n'y a pas d'élan pour le maintenir. Alors, quel est l'intérêt d'avoir un site personnel ?",[35,63784,63785,63786,63789,63790,63793],{},"Je veux ",[6862,63787,63788],{},"être fier de mon site"," et le partager avec tout le monde, mais ces tentatives étaient clairement ",[6862,63791,63792],{},"l'opposé"," de ce que j'avais envisionné.",[51,63795,63797],{"id":63796},"histoire","Histoire",[35,63799,63800],{},[1216,63801,63802],{},"Créer un site personnel est plus compliqué que je ne le pensais initialement.",[35,63804,63805],{},"Tout au long du processus d'apprentissage et de création de sites Web, j'ai connu une croissance significative, tant dans mes besoins et mes compétences que dans mon appréciation pour différentes approches.",[35,63807,63808,63809],{},"Au départ, je visais à construire un site avec une base de données, un éditeur de markdown et une authentification, comme on le voit dans les tutoriels et de nombreux sites. Mais pourquoi cherchais-je quelque chose d'aussi complexe ? Pour apprendre et écrire des articles. Finalement, j'ai acquis beaucoup de connaissances mais je n'ai jamais publié d'articles, car déployer quelque chose de cette complexité était intimidant pour un débutant. ",[1216,63810,63811],{},"Et j'étais un débutant.",[35,63813,63814],{},"Pour le prochain site de blog, je me suis interrogé sur mon intention de créer un site personnel. C'est bénéfique pour le CV et cela sert de plateforme pour tester de nouvelles technologies. C'est exactement ce que j'ai fait. J'ai exploré Nuxt 2 quand j'apprenais à son sujet. Pour cette deuxième tentative, je n'avais jamais envisagé d'écrire des articles.",[35,63816,63817,63818,63823],{},"Finalement, je comptais écrire un tutoriel sur Adonis 5 mais je n'avais nulle part où le partager. J'ai fini par le publier sur ",[39,63819,63822],{"href":63820,"rel":63821},"https:\u002F\u002Fdev.to\u002Fbarbapapazes\u002Fobjectifs-et-mise-en-route-creer-un-blog-avec-adonis-20lg",[77],"dev.to",". Je n'étais pas satisfait. L'expérience était insatisfaisante, et je manquais d'une sauvegarde de mon travail.",[35,63825,63826],{},"Plusieurs mois plus tard, j'ai décidé de rénover mon site Web depuis zéro en utilisant Nuxt 3. Inclure une section d'articles était prévu, mais expérimenter de nouvelles fonctionnalités est resté l'objectif principal.",[35,63828,63829,63830,215],{},"Et aujourd'hui, je suis de retour, reconstruisant une fois de plus mon site personnel. Le temps passe, mais maintenant, ",[6862,63831,63832],{},"je sais exactement ce que je veux",[35,63834,63835],{},[1216,63836,63837],{},"Même si c'est le même projet, le contexte et mes besoins ont évolué au point que cela ressemble à un nouveau départ.",[51,63839,63841],{"id":63840},"besoins","Besoins",[35,63843,63844],{},"La première nécessité est un blog. Je veux la capacité de créer et de publier du contenu sans effort. Ma méthode préférée est d'écrire en markdown dans mon éditeur de texte. J'apprécie cette expérience grâce à des plugins qui améliorent mes compétences en écriture.",[35,63846,63847],{},"Ce markdown doit être personnalisable pour incorporer des composants uniques et une syntaxe étendue, similaire aux alertes GitHub.",[35,63849,63850],{},"Je désire un blog qui nécessite peu de maintenance. Moins il faut d'efforts pour publier un article, mieux c'est. Je ne veux pas mettre à jour continuellement mon site à cause de vulnérabilités de sécurité ou de mises à jour de packages. Moins de dépendances me laisse plus de temps pour me concentrer sur l'écriture.",[35,63852,63853],{},"Je veux générer un flux RSS et une image Open Graph pour chaque article. Je veux que cette procédure soit simple et automatique lors de la publication d'un article. L'outil doit être extensible pour les améliorations futures.",[35,63855,63856,63857,215],{},"Le point crucial est que ",[6862,63858,63859],{},"je veux me concentrer sur le contenu, pas sur le code",[51,63861,63863],{"id":63862},"outils","Outils",[35,63865,63866],{},"Je suis familier avec la création de sites Web en utilisant WordPress, Adonis, Astro, Nuxt et Vite avec Vue.",[35,63868,63869],{},"Compte tenu de mes besoins, WordPress et Adonis ne sont pas des choix optimaux. Je ne souhaite pas maintenir un serveur et une base de données. Bien qu'Adonis offre une méthode pour générer un site statique, il n'est pas documenté. Je ne veux pas consacrer de temps à cela.",[35,63871,63872],{},"Nuxt avec le plugin Content est une option viable, mais au cours de l'année passée, j'ai développé de nombreux sites avec cette pile. L'expérience développeur du plugin Content est insatisfaisante. Elle ralentit considérablement le serveur de développement (jusqu'à 30 secondes pour démarrer) et le temps de construction (jusqu'à 2 minutes), et nécessite trop de maintenance. Je ne souhaite plus l'utiliser pour ce type de projet.",[35,63874,63875],{},"Astro est un choix adapté. C'est rapide et prêt à l'emploi. La seule préoccupation est qu'il pourrait offrir trop de fonctionnalités. J'ai peur de perdre le fil, et configurer le module de contenu est quelque chose que je préfère éviter.",[35,63877,63878,63879,63883],{},"Créer un projet personnalisé avec Vite et Vue est une autre option solide, me permettant d'explorer les plugins de Vite. Cette technologie alimente ",[39,63880,63882],{"href":31082,"rel":63881},[77],"le site d'Anthony Fu",", fournissant un exemple fiable à suivre.",[35,63885,63886,63887,63890],{},"J'ai décidé de construire mon prochain site en utilisant ",[6862,63888,63889],{},"un projet personnalisé avec Vite et Vue",". Cela me permet d'avoir le plus de contrôle possible sur le projet, de le garder minimal et d'avoir tout l'écosystème Vue à ma disposition.",[35,63892,63893,63894],{},"C'est le plan. ",[1216,63895,63896],{},"Ou du moins, je le pensais.",[51,63898,63900],{"id":63899},"choix-final","Choix final",[35,63902,63903],{},"Trois jours après avoir choisi et commencé le développement de mon site, j'ai utilisé VitePress au travail pour documenter un ensemble de fonctions. C'était un plaisir à utiliser, mais le modèle par défaut est orienté documentation. Je ne voulais pas que mon site personnel ressemble à un site de documentation.",[35,63905,63906,63907,63912],{},"Deux jours plus tard, ",[39,63908,63911],{"href":63909,"rel":63910},"https:\u002F\u002Fx.com\u002Fyouyuxi\u002Fstatus\u002F1770837536509661339",[77],"Evan You a annoncé VitePress 1.0"," et a déclaré :",[938,63914,63915],{},[35,63916,63917],{},"Découvrez pourquoi nous le considérons comme l'une des meilleures façons de construire des sites statiques (pas seulement pour la documentation !)",[35,63919,63920],{},"Cela a piqué ma curiosité. J'ai lu l'intégralité de la documentation et j'ai réalisé qu'elle correspond parfaitement à mes besoins.",[51,63922,63924],{"id":63923},"la-réalité-derrière-vitepress","La réalité derrière VitePress",[35,63926,63927,63928,63931,63932,63935],{},"En essence, ",[6862,63929,63930],{},"VitePress est un outil qui traite des fichiers Markdown pour créer des routes et les rendre en HTML."," Rien de plus. L'aspect documentation n'est qu'un thème, une couche sur ce noyau puissant avec un routage basé sur des fichiers, un parsing markdown, des hooks, un chargement de données au moment de la construction et SSG. ",[6862,63933,63934],{},"Vous pouvez facilement créer votre propre thème"," sur ce noyau puissant et maintenable.",[35,63937,63938,63939,63942],{},"En résumé, je peux tirer parti de ce noyau robuste pour construire mon site personnel avec un thème personnalisé créé en ",[39,63940,18636],{"href":34319,"rel":63941},[77],". Idéal pour l'extensibilité et la personnalisation tout en ayant une base solide autour du contenu.",[35,63944,63945,63946],{},"C'est précisément ce que je prévois de faire. Essentiellement, ce noyau est ce que j'avais l'intention de créer avec la pile Vite et Vue. Avec VitePress, c'est une seule dépendance, prête à l'emploi, bien documentée, maintenue, et avec une communauté, me permettant de me concentrer sur le contenu dès le départ. ",[1216,63947,63948],{},"Créer un thème est simple car c'est purement esthétique. La fonctionnalité de base est déjà établie. Je peux commencer à écrire maintenant tout en prenant le temps de créer un thème et d'ajouter des fonctionnalités au besoin.",[35,63950,63951],{},"Je compte garder ce blog aussi minimal que possible, et avec VitePress, composer du contenu et déployer est maintenant facilité.",[35,63953,63954],{},"Je recommande vivement d'essayer VitePress, malgré son image de constructeur de documentation. C'est vraiment impressionnant et dépasse les attentes. 📖",{"title":13,"searchDepth":14,"depth":14,"links":63956},[63957,63958,63959,63960,63961,63962],{"id":63756,"depth":14,"text":63757},{"id":63796,"depth":14,"text":63797},{"id":63840,"depth":14,"text":63841},{"id":63862,"depth":14,"text":63863},{"id":63899,"depth":14,"text":63900},{"id":63923,"depth":14,"text":63924},"2024-03-22","J'ai choisi VitePress pour créer mon site personnel. Vous le connaissez peut-être comme un outil de documentation, mais c'est bien plus que cela.",{},"\u002Ffr\u002Fposts\u002Fvitepress-for-a-personal-website-is-that-possible","---\ncontentId: a06fbfe1-6283-4eed-b3ec-e1d454613b9a\ntitle: VitePress pour un Site Personnel, est-ce Possible ?\ndescription: J'ai choisi VitePress pour créer mon site personnel. Vous le connaissez peut-être comme un outil de documentation, mais c'est bien plus que cela.\ndate: 2024-03-22\n---\n\nDepuis de nombreuses années, j'essaie de créer un site personnel qui reflète mon style. Jusqu'à présent, je n'ai pas réussi. Peut-être que cet effort échouera également, mais je n'ai jamais été aussi sûr de mon choix. Laissez-moi vous expliquer pourquoi !\n\n## Le site personnel\n\nÀ première vue, la création d'un site personnel semble simple. Si simple que c'était mon premier projet significatif il y a 5 ans, \u003CGitHubLink repo=\"barbapapazes\u002Festeban-s-website\" \u002F>, avec Express.js tout en apprenant [Node.js](https:\u002F\u002Fnodejs.org).\n\nPar la suite, j'ai expérimenté avec Nuxt 2, \u003CGitHubLink repo=\"barbapapazes\u002Festeban-soubiran.site\u002Ftree\u002Fv2\" \u002F>, puis Nuxt 3, \u003CGitHubLink repo=\"barbapapazes\u002Festeban-soubiran.site\" \u002F>, mais il me manquait toujours quelque chose. Je n'étais jamais satisfait du résultat, et l'utiliser n'était pas agréable. _S'il n'y a pas de plaisir, il n'y a pas d'élan pour le maintenir. Alors, quel est l'intérêt d'avoir un site personnel ?_\n\nJe veux **être fier de mon site** et le partager avec tout le monde, mais ces tentatives étaient clairement **l'opposé** de ce que j'avais envisionné.\n\n## Histoire\n\n_Créer un site personnel est plus compliqué que je ne le pensais initialement._\n\nTout au long du processus d'apprentissage et de création de sites Web, j'ai connu une croissance significative, tant dans mes besoins et mes compétences que dans mon appréciation pour différentes approches.\n\nAu départ, je visais à construire un site avec une base de données, un éditeur de markdown et une authentification, comme on le voit dans les tutoriels et de nombreux sites. Mais pourquoi cherchais-je quelque chose d'aussi complexe ? Pour apprendre et écrire des articles. Finalement, j'ai acquis beaucoup de connaissances mais je n'ai jamais publié d'articles, car déployer quelque chose de cette complexité était intimidant pour un débutant. _Et j'étais un débutant._\n\nPour le prochain site de blog, je me suis interrogé sur mon intention de créer un site personnel. C'est bénéfique pour le CV et cela sert de plateforme pour tester de nouvelles technologies. C'est exactement ce que j'ai fait. J'ai exploré Nuxt 2 quand j'apprenais à son sujet. Pour cette deuxième tentative, je n'avais jamais envisagé d'écrire des articles.\n\nFinalement, je comptais écrire un tutoriel sur Adonis 5 mais je n'avais nulle part où le partager. J'ai fini par le publier sur [dev.to](https:\u002F\u002Fdev.to\u002Fbarbapapazes\u002Fobjectifs-et-mise-en-route-creer-un-blog-avec-adonis-20lg). Je n'étais pas satisfait. L'expérience était insatisfaisante, et je manquais d'une sauvegarde de mon travail.\n\nPlusieurs mois plus tard, j'ai décidé de rénover mon site Web depuis zéro en utilisant Nuxt 3. Inclure une section d'articles était prévu, mais expérimenter de nouvelles fonctionnalités est resté l'objectif principal.\n\nEt aujourd'hui, je suis de retour, reconstruisant une fois de plus mon site personnel. Le temps passe, mais maintenant, **je sais exactement ce que je veux**.\n\n_Même si c'est le même projet, le contexte et mes besoins ont évolué au point que cela ressemble à un nouveau départ._\n\n## Besoins\n\nLa première nécessité est un blog. Je veux la capacité de créer et de publier du contenu sans effort. Ma méthode préférée est d'écrire en markdown dans mon éditeur de texte. J'apprécie cette expérience grâce à des plugins qui améliorent mes compétences en écriture.\n\nCe markdown doit être personnalisable pour incorporer des composants uniques et une syntaxe étendue, similaire aux alertes GitHub.\n\nJe désire un blog qui nécessite peu de maintenance. Moins il faut d'efforts pour publier un article, mieux c'est. Je ne veux pas mettre à jour continuellement mon site à cause de vulnérabilités de sécurité ou de mises à jour de packages. Moins de dépendances me laisse plus de temps pour me concentrer sur l'écriture.\n\nJe veux générer un flux RSS et une image Open Graph pour chaque article. Je veux que cette procédure soit simple et automatique lors de la publication d'un article. L'outil doit être extensible pour les améliorations futures.\n\nLe point crucial est que **je veux me concentrer sur le contenu, pas sur le code**.\n\n## Outils\n\nJe suis familier avec la création de sites Web en utilisant WordPress, Adonis, Astro, Nuxt et Vite avec Vue.\n\nCompte tenu de mes besoins, WordPress et Adonis ne sont pas des choix optimaux. Je ne souhaite pas maintenir un serveur et une base de données. Bien qu'Adonis offre une méthode pour générer un site statique, il n'est pas documenté. Je ne veux pas consacrer de temps à cela.\n\nNuxt avec le plugin Content est une option viable, mais au cours de l'année passée, j'ai développé de nombreux sites avec cette pile. L'expérience développeur du plugin Content est insatisfaisante. Elle ralentit considérablement le serveur de développement (jusqu'à 30 secondes pour démarrer) et le temps de construction (jusqu'à 2 minutes), et nécessite trop de maintenance. Je ne souhaite plus l'utiliser pour ce type de projet.\n\nAstro est un choix adapté. C'est rapide et prêt à l'emploi. La seule préoccupation est qu'il pourrait offrir trop de fonctionnalités. J'ai peur de perdre le fil, et configurer le module de contenu est quelque chose que je préfère éviter.\n\nCréer un projet personnalisé avec Vite et Vue est une autre option solide, me permettant d'explorer les plugins de Vite. Cette technologie alimente [le site d'Anthony Fu](https:\u002F\u002Fantfu.me), fournissant un exemple fiable à suivre.\n\nJ'ai décidé de construire mon prochain site en utilisant **un projet personnalisé avec Vite et Vue**. Cela me permet d'avoir le plus de contrôle possible sur le projet, de le garder minimal et d'avoir tout l'écosystème Vue à ma disposition.\n\nC'est le plan. _Ou du moins, je le pensais._\n\n## Choix final\n\nTrois jours après avoir choisi et commencé le développement de mon site, j'ai utilisé VitePress au travail pour documenter un ensemble de fonctions. C'était un plaisir à utiliser, mais le modèle par défaut est orienté documentation. Je ne voulais pas que mon site personnel ressemble à un site de documentation.\n\nDeux jours plus tard, [Evan You a annoncé VitePress 1.0](https:\u002F\u002Fx.com\u002Fyouyuxi\u002Fstatus\u002F1770837536509661339) et a déclaré :\n\n> Découvrez pourquoi nous le considérons comme l'une des meilleures façons de construire des sites statiques (pas seulement pour la documentation !)\n\nCela a piqué ma curiosité. J'ai lu l'intégralité de la documentation et j'ai réalisé qu'elle correspond parfaitement à mes besoins.\n\n## La réalité derrière VitePress\n\nEn essence, **VitePress est un outil qui traite des fichiers Markdown pour créer des routes et les rendre en HTML.** Rien de plus. L'aspect documentation n'est qu'un thème, une couche sur ce noyau puissant avec un routage basé sur des fichiers, un parsing markdown, des hooks, un chargement de données au moment de la construction et SSG. **Vous pouvez facilement créer votre propre thème** sur ce noyau puissant et maintenable.\n\nEn résumé, je peux tirer parti de ce noyau robuste pour construire mon site personnel avec un thème personnalisé créé en [Vue.js](https:\u002F\u002Fvuejs.org). Idéal pour l'extensibilité et la personnalisation tout en ayant une base solide autour du contenu.\n\nC'est précisément ce que je prévois de faire. Essentiellement, ce noyau est ce que j'avais l'intention de créer avec la pile Vite et Vue. Avec VitePress, c'est une seule dépendance, prête à l'emploi, bien documentée, maintenue, et avec une communauté, me permettant de me concentrer sur le contenu dès le départ. _Créer un thème est simple car c'est purement esthétique. La fonctionnalité de base est déjà établie. Je peux commencer à écrire maintenant tout en prenant le temps de créer un thème et d'ajouter des fonctionnalités au besoin._\n\nJe compte garder ce blog aussi minimal que possible, et avec VitePress, composer du contenu et déployer est maintenant facilité.\n\nJe recommande vivement d'essayer VitePress, malgré son image de constructeur de documentation. C'est vraiment impressionnant et dépasse les attentes. 📖\n",{"title":63748,"description":63964},{"loc":63966},"fr\u002Fposts\u002Fvitepress-for-a-personal-website-is-that-possible","VUoUcY30zNfJSWBOdsyVI2_tjeT2xF5yu_XZvqySPmY",{"id":63973,"title":63974,"body":63975,"contentId":30570,"date":66776,"description":66777,"extension":18,"meta":66778,"navigation":20,"path":66779,"rawbody":66780,"robots":23,"schemaOrg":23,"seo":66781,"sitemap":66782,"stem":66783,"__hash__":66784},"posts\u002Ffr\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages.md","Créer un raccourcisseur d'URL avec Nitro et Cloudflare Pages",{"type":7,"value":63976,"toc":66769},[63977,63996,64002,64011,64023,64036,64039,64096,64100,64106,64124,64131,64148,64155,64168,64176,64180,64183,64200,64204,64221,64639,64648,64659,64665,64883,64892,64896,64902,64916,64934,65466,65478,65490,65500,65514,66766],[35,63978,63979,63980,35453,63983,63989,63990,215],{},"Dans cet article, nous allons développer un ",[6862,63981,63982],{},"raccourcisseur d'URL",[39,63984,63987],{"href":63985,"rel":63986},"https:\u002F\u002Fnitro.unjs.io",[77],[6862,63988,36573],{}," et le déployer sur ",[39,63991,63994],{"href":63992,"rel":63993},"https:\u002F\u002Fpages.cloudflare.com\u002F",[77],[6862,63995,38535],{},[35,63997,63998,63999],{},"Le code source est accessible sur ",[1599,64000,215],{"repo":64001},"barbapapazes\u002Furl-shortener",[35,64003,64004,64006,64007,64010],{},[6862,64005,36573],{}," représente une nouvelle génération de ",[6862,64008,64009],{},"kit d'outils serveur",". Il nous permet de construire des serveurs web avec toutes les fonctionnalités nécessaires et de les déployer à notre convenance.",[35,64012,64013,64015,64016,64022],{},[6862,64014,38535],{}," est une plateforme conçue pour construire et héberger des sites web sur le edge. Elle prend en charge des services comme ",[39,64017,64019],{"href":38548,"rel":64018},[77],[6862,64020,64021],{},"KV"," pour créer des applications complètes et à état.",[35,64024,64025,64026,64028,64029,64031,64032,64035],{},"Notre projet est un ",[6862,64027,63982],{}," simple qui facilite la conversion d'une longue URL en une version raccourcie. Nous allons utiliser le ",[6862,64030,38550],{}," pour stocker les URL et le ",[6862,64033,64034],{},"serveur Nitro"," pour gérer les requêtes.",[35,64037,64038],{},"Nous allons utiliser :",[1593,64040,64041,64057,64070,64086],{},[71,64042,64043,64050,64051,64053,64054,215],{},[39,64044,64047],{"href":64045,"rel":64046},"https:\u002F\u002Funstorage.unjs.io",[77],[6862,64048,64049],{},"unstorage"," pour rationaliser le processus de développement en abstraire la couche ",[6862,64052,64021],{},", éliminant ainsi le besoin du ",[6862,64055,64056],{},"Cloudflare Wrangler CLI",[71,64058,64059,64066,64067,64069],{},[39,64060,64063],{"href":64061,"rel":64062},"https:\u002F\u002Fohash.unjs.io",[77],[6862,64064,64065],{},"ohash"," pour générer un ",[6862,64068,52842],{}," à partir de l'URL afin de prévenir les collisions.",[71,64071,64072,64079,64080,35453,64083,215],{},[39,64073,64076],{"href":64074,"rel":64075},"https:\u002F\u002Fnanojsx.io\u002F",[77],[6862,64077,64078],{},"nanojsx"," pour construire les ",[6862,64081,64082],{},"pages HTML",[6862,64084,64085],{},"TSX",[71,64087,64088,64095],{},[39,64089,64092],{"href":64090,"rel":64091},"https:\u002F\u002Fpicocss.com",[77],[6862,64093,64094],{},"pico.css"," pour styliser l'application.",[51,64097,64099],{"id":64098},"initialisation-du-projet","Initialisation du Projet",[35,64101,64102,64103,2186],{},"Tout d'abord, créez un nouveau ",[6862,64104,64105],{},"projet Nitro",[192,64107,64109],{"className":2166,"code":64108,"language":2168,"meta":13,"style":13},"npx giget@latest nitro url-shortener\n",[156,64110,64111],{"__ignoreMap":13},[200,64112,64113,64115,64118,64121],{"class":202,"line":203},[200,64114,30995],{"class":1364},[200,64116,64117],{"class":245}," giget@latest",[200,64119,64120],{"class":245}," nitro",[200,64122,64123],{"class":245}," url-shortener\n",[35,64125,64126,64127,64130],{},"Ensuite, naviguez jusqu'au projet et installez les ",[6862,64128,64129],{},"dépendances"," requises :",[192,64132,64134],{"className":2166,"code":64133,"language":2168,"meta":13,"style":13},"cd url-shortener\nnpm install\n",[156,64135,64136,64142],{"__ignoreMap":13},[200,64137,64138,64140],{"class":202,"line":203},[200,64139,36044],{"class":223},[200,64141,64123],{"class":245},[200,64143,64144,64146],{"class":202,"line":14},[200,64145,36084],{"class":1364},[200,64147,35925],{"class":245},[35,64149,64150,64151,64154],{},"Démarrez le ",[6862,64152,64153],{},"serveur de développement"," pour voir la page par défaut de Nitro :",[192,64156,64158],{"className":2166,"code":64157,"language":2168,"meta":13,"style":13},"npm run dev\n",[156,64159,64160],{"__ignoreMap":13},[200,64161,64162,64164,64166],{"class":202,"line":203},[200,64163,36084],{"class":1364},[200,64165,32462],{"class":245},[200,64167,13704],{"class":245},[35,64169,64170,64171,64175],{},"Ouvrez votre navigateur et visitez ",[39,64172,64173],{"href":64173,"rel":64174},"http:\u002F\u002Flocalhost:3000",[77]," pour vérifier la fonctionnalité.",[51,64177,64179],{"id":64178},"construction-du-raccourcisseur-durl","Construction du Raccourcisseur d'URL",[35,64181,64182],{},"Dans un premier temps, installez les paquets nécessaires :",[192,64184,64186],{"className":2166,"code":64185,"language":2168,"meta":13,"style":13},"npm install ohash nano-jsx\n",[156,64187,64188],{"__ignoreMap":13},[200,64189,64190,64192,64194,64197],{"class":202,"line":203},[200,64191,36084],{"class":1364},[200,64193,1368],{"class":245},[200,64195,64196],{"class":245}," ohash",[200,64198,64199],{"class":245}," nano-jsx\n",[1929,64201,64203],{"id":64202},"générer-une-url-raccourcie","Générer une URL Raccourcie",[35,64205,64206,64207,64209,64210,2873,64213,64216,64217,64220],{},"Créez une ",[6862,64208,56503],{}," nommée ",[156,64211,64212],{},"index.get.tsx",[156,64214,64215],{},"server\u002Froutes",". Cela servira de ",[6862,64218,64219],{},"page d'accueil"," de notre raccourcisseur d'URL où les utilisateurs peuvent générer une URL raccourcie à partir d'une longue.",[192,64222,64227],{"className":64223,"code":64224,"filename":64225,"language":64226,"meta":13,"style":13},"language-tsx shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { h, Helmet, renderSSR } from 'nano-jsx' \u002F\u002F le `h` est crucial ici\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineLazyEventHandler(() => {\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>Raccourcisseur d'URL avec Nitro\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Raccourcir une URL\u003C\u002Fh2>\n        \u003Cform action=\"\u002Fcreate\" method=\"POST\">\n          \u003Cinput type=\"url\" name=\"url\" placeholder=\"URL à raccourcir\" autocomplete=\"off\" \u002F>\n          \u003Cbutton type=\"submit\">Créer\u003C\u002Fbutton>\n        \u003C\u002Fform>\n      \u003C\u002Fdiv>\n    )\n  }\n  const app = renderSSR(\u003CApp \u002F>)\n  const { body, head } = Helmet.SSR(app)\n\n  const page = withTemplate({\n    body,\n    head,\n  })\n\n  return defineEventHandler(() => {\n    return page\n  })\n})\n","server\u002Froutes\u002Findex.get.tsx","tsx",[156,64228,64229,64262,64282,64286,64303,64318,64325,64333,64342,64359,64367,64384,64413,64464,64491,64499,64507,64511,64515,64536,64565,64569,64583,64589,64596,64602,64606,64620,64627,64633],{"__ignoreMap":13},[200,64230,64231,64233,64235,64238,64240,64243,64245,64248,64250,64252,64254,64257,64259],{"class":202,"line":203},[200,64232,972],{"class":206},[200,64234,318],{"class":214},[200,64236,64237],{"class":210}," h",[200,64239,549],{"class":214},[200,64241,64242],{"class":210}," Helmet",[200,64244,549],{"class":214},[200,64246,64247],{"class":210}," renderSSR",[200,64249,980],{"class":214},[200,64251,983],{"class":206},[200,64253,242],{"class":214},[200,64255,64256],{"class":245},"nano-jsx",[200,64258,249],{"class":214},[200,64260,64261],{"class":401}," \u002F\u002F le `h` est crucial ici\n",[200,64263,64264,64266,64268,64271,64273,64275,64277,64280],{"class":202,"line":14},[200,64265,972],{"class":206},[200,64267,318],{"class":214},[200,64269,64270],{"class":210}," withTemplate",[200,64272,980],{"class":214},[200,64274,983],{"class":206},[200,64276,242],{"class":214},[200,64278,64279],{"class":245},"..\u002Fresources\u002Ftemplate",[200,64281,991],{"class":214},[200,64283,64284],{"class":202,"line":255},[200,64285,528],{"emptyLinePlaceholder":20},[200,64287,64288,64290,64292,64295,64297,64299,64301],{"class":202,"line":272},[200,64289,1390],{"class":206},[200,64291,1393],{"class":206},[200,64293,64294],{"class":223}," defineLazyEventHandler",[200,64296,227],{"class":210},[200,64298,1549],{"class":214},[200,64300,393],{"class":379},[200,64302,280],{"class":214},[200,64304,64305,64307,64310,64312,64314,64316],{"class":202,"line":283},[200,64306,3738],{"class":379},[200,64308,64309],{"class":210}," App",[200,64311,509],{"class":214},[200,64313,3729],{"class":214},[200,64315,393],{"class":379},[200,64317,280],{"class":214},[200,64319,64320,64322],{"class":202,"line":300},[200,64321,3311],{"class":206},[200,64323,64324],{"class":235}," (\n",[200,64326,64327,64329,64331],{"class":202,"line":310},[200,64328,22071],{"class":214},[200,64330,11400],{"class":235},[200,64332,647],{"class":214},[200,64334,64335,64337,64340],{"class":202,"line":336},[200,64336,49441],{"class":214},[200,64338,64339],{"class":1364},"Helmet",[200,64341,647],{"class":214},[200,64343,64344,64346,64348,64350,64353,64355,64357],{"class":202,"line":342},[200,64345,49468],{"class":214},[200,64347,3581],{"class":235},[200,64349,674],{"class":214},[200,64351,64352],{"class":210},"Raccourcisseur d'URL avec Nitro",[200,64354,680],{"class":214},[200,64356,3581],{"class":235},[200,64358,647],{"class":214},[200,64360,64361,64363,64365],{"class":202,"line":365},[200,64362,49549],{"class":214},[200,64364,64339],{"class":1364},[200,64366,647],{"class":214},[200,64368,64369,64371,64373,64375,64378,64380,64382],{"class":202,"line":371},[200,64370,49441],{"class":214},[200,64372,51],{"class":235},[200,64374,674],{"class":214},[200,64376,64377],{"class":210},"Raccourcir une URL",[200,64379,680],{"class":214},[200,64381,51],{"class":235},[200,64383,647],{"class":214},[200,64385,64386,64388,64390,64392,64394,64396,64399,64401,64403,64405,64407,64409,64411],{"class":202,"line":398},[200,64387,49441],{"class":214},[200,64389,602],{"class":235},[200,64391,40681],{"class":379},[200,64393,449],{"class":214},[200,64395,610],{"class":214},[200,64397,64398],{"class":245},"\u002Fcreate",[200,64400,610],{"class":214},[200,64402,6589],{"class":379},[200,64404,449],{"class":214},[200,64406,610],{"class":214},[200,64408,18779],{"class":245},[200,64410,610],{"class":214},[200,64412,647],{"class":214},[200,64414,64415,64417,64419,64421,64423,64425,64427,64429,64431,64433,64435,64437,64439,64441,64443,64445,64448,64450,64453,64455,64457,64460,64462],{"class":202,"line":405},[200,64416,49468],{"class":214},[200,64418,355],{"class":235},[200,64420,321],{"class":379},[200,64422,449],{"class":214},[200,64424,610],{"class":214},[200,64426,2035],{"class":245},[200,64428,610],{"class":214},[200,64430,700],{"class":379},[200,64432,449],{"class":214},[200,64434,610],{"class":214},[200,64436,2035],{"class":245},[200,64438,610],{"class":214},[200,64440,40711],{"class":379},[200,64442,449],{"class":214},[200,64444,610],{"class":214},[200,64446,64447],{"class":245},"URL à raccourcir",[200,64449,610],{"class":214},[200,64451,64452],{"class":379}," autocomplete",[200,64454,449],{"class":214},[200,64456,610],{"class":214},[200,64458,64459],{"class":245},"off",[200,64461,610],{"class":214},[200,64463,21527],{"class":214},[200,64465,64466,64468,64470,64472,64474,64476,64478,64480,64482,64485,64487,64489],{"class":202,"line":410},[200,64467,49468],{"class":214},[200,64469,909],{"class":235},[200,64471,321],{"class":379},[200,64473,449],{"class":214},[200,64475,610],{"class":214},[200,64477,916],{"class":245},[200,64479,610],{"class":214},[200,64481,674],{"class":214},[200,64483,64484],{"class":210},"Créer",[200,64486,680],{"class":214},[200,64488,909],{"class":235},[200,64490,647],{"class":214},[200,64492,64493,64495,64497],{"class":202,"line":860},[200,64494,49549],{"class":214},[200,64496,602],{"class":235},[200,64498,647],{"class":214},[200,64500,64501,64503,64505],{"class":202,"line":889},[200,64502,43803],{"class":214},[200,64504,11400],{"class":235},[200,64506,647],{"class":214},[200,64508,64509],{"class":202,"line":899},[200,64510,3869],{"class":235},[200,64512,64513],{"class":202,"line":904},[200,64514,4505],{"class":214},[200,64516,64517,64519,64521,64523,64525,64527,64529,64532,64534],{"class":202,"line":930},[200,64518,3738],{"class":379},[200,64520,14334],{"class":210},[200,64522,509],{"class":214},[200,64524,64247],{"class":223},[200,64526,227],{"class":235},[200,64528,599],{"class":214},[200,64530,64531],{"class":1364},"App",[200,64533,22099],{"class":214},[200,64535,416],{"class":235},[200,64537,64538,64540,64542,64544,64546,64549,64551,64553,64555,64557,64559,64561,64563],{"class":202,"line":3655},[200,64539,3738],{"class":379},[200,64541,318],{"class":214},[200,64543,40809],{"class":210},[200,64545,549],{"class":214},[200,64547,64548],{"class":210}," head",[200,64550,980],{"class":214},[200,64552,509],{"class":214},[200,64554,64242],{"class":210},[200,64556,215],{"class":214},[200,64558,14496],{"class":223},[200,64560,227],{"class":235},[200,64562,26785],{"class":210},[200,64564,416],{"class":235},[200,64566,64567],{"class":202,"line":3660},[200,64568,528],{"emptyLinePlaceholder":20},[200,64570,64571,64573,64575,64577,64579,64581],{"class":202,"line":4035},[200,64572,3738],{"class":379},[200,64574,3272],{"class":210},[200,64576,509],{"class":214},[200,64578,64270],{"class":223},[200,64580,227],{"class":235},[200,64582,230],{"class":214},[200,64584,64585,64587],{"class":202,"line":4490},[200,64586,18788],{"class":210},[200,64588,252],{"class":214},[200,64590,64591,64594],{"class":202,"line":4496},[200,64592,64593],{"class":210},"    head",[200,64595,252],{"class":214},[200,64597,64598,64600],{"class":202,"line":4502},[200,64599,5095],{"class":214},[200,64601,416],{"class":235},[200,64603,64604],{"class":202,"line":4508},[200,64605,528],{"emptyLinePlaceholder":20},[200,64607,64608,64610,64612,64614,64616,64618],{"class":202,"line":5469},[200,64609,3758],{"class":206},[200,64611,38723],{"class":223},[200,64613,227],{"class":235},[200,64615,1549],{"class":214},[200,64617,393],{"class":379},[200,64619,280],{"class":214},[200,64621,64622,64624],{"class":202,"line":5474},[200,64623,3311],{"class":206},[200,64625,64626],{"class":210}," page\n",[200,64628,64629,64631],{"class":202,"line":13237},[200,64630,5095],{"class":214},[200,64632,416],{"class":235},[200,64634,64635,64637],{"class":202,"line":13263},[200,64636,413],{"class":214},[200,64638,416],{"class":210},[35,64640,64641,64642,64645,64646,215],{},"Cette route présentera un formulaire permettant aux utilisateurs de saisir une URL à raccourcir. Lors de la soumission du formulaire, une ",[6862,64643,64644],{},"requête POST"," est envoyée à la route ",[156,64647,64398],{},[35,64649,64650,64651,64654,64655,64658],{},"Un ",[6862,64652,64653],{},"gestionnaire d'événements paresseux"," est utilisé pour générer la vue une seule fois, lorsque la requête atteint le serveur. La réponse est ensuite ",[6862,64656,64657],{},"mise en cache en mémoire"," et réutilisée pour les requêtes futures, réduisant ainsi la charge computationnelle.",[35,64660,15736,64661,64664],{},[156,64662,64663],{},"withTemplate"," sert d'utilitaire que nous devons construire.",[192,64666,64669],{"className":1380,"code":64667,"filename":64668,"language":1383,"meta":13,"style":13},"interface LayoutProps {\n  body: string\n  head: string[]\n}\n\nexport function withTemplate(props: LayoutProps) {\n  const { head, body } = props\n\n  return \u002F* html *\u002F`\u003Chtml>\n      \u003Chead>\n       \u003Clink\n          rel=\"stylesheet\"\n          href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@picocss\u002Fpico@2\u002Fcss\u002Fpico.min.css\"\n        \u002F>\n        ${head.join('\\n')}\n      \u003C\u002Fhead>\n      \u003Cbody>\n        \u003Cheader class=\"container\">\n          \u003Ch1>\n            \u003Ca href=\"\u002F\">Raccourcisseur d'URL avec Nitro\u003C\u002Fa>\n          \u003C\u002Fh1>\n        \u003C\u002Fheader>\n        \u003Cmain class=\"container\">\n          ${body}\n        \u003C\u002Fmain>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>`\n}\n","server\u002Fresources\u002Ftemplate.tsx",[156,64670,64671,64681,64690,64702,64706,64710,64730,64749,64753,64765,64770,64775,64780,64785,64790,64813,64818,64823,64828,64833,64838,64843,64848,64853,64862,64867,64872,64879],{"__ignoreMap":13},[200,64672,64673,64676,64679],{"class":202,"line":203},[200,64674,64675],{"class":379},"interface",[200,64677,64678],{"class":1364}," LayoutProps",[200,64680,280],{"class":214},[200,64682,64683,64686,64688],{"class":202,"line":14},[200,64684,64685],{"class":235},"  body",[200,64687,239],{"class":214},[200,64689,23708],{"class":1364},[200,64691,64692,64695,64697,64699],{"class":202,"line":255},[200,64693,64694],{"class":235},"  head",[200,64696,239],{"class":214},[200,64698,22752],{"class":1364},[200,64700,64701],{"class":210},"[]\n",[200,64703,64704],{"class":202,"line":272},[200,64705,1129],{"class":214},[200,64707,64708],{"class":202,"line":283},[200,64709,528],{"emptyLinePlaceholder":20},[200,64711,64712,64714,64716,64718,64720,64722,64724,64726,64728],{"class":202,"line":300},[200,64713,1390],{"class":206},[200,64715,5002],{"class":379},[200,64717,64270],{"class":223},[200,64719,227],{"class":214},[200,64721,23874],{"class":386},[200,64723,239],{"class":214},[200,64725,64678],{"class":1364},[200,64727,2104],{"class":214},[200,64729,280],{"class":214},[200,64731,64732,64734,64736,64738,64740,64742,64744,64746],{"class":202,"line":310},[200,64733,3738],{"class":379},[200,64735,318],{"class":214},[200,64737,64548],{"class":210},[200,64739,549],{"class":214},[200,64741,40809],{"class":210},[200,64743,980],{"class":214},[200,64745,509],{"class":214},[200,64747,64748],{"class":210}," props\n",[200,64750,64751],{"class":202,"line":336},[200,64752,528],{"emptyLinePlaceholder":20},[200,64754,64755,64757,64760,64762],{"class":202,"line":342},[200,64756,3758],{"class":206},[200,64758,64759],{"class":401}," \u002F* html *\u002F",[200,64761,3333],{"class":214},[200,64763,64764],{"class":245},"\u003Chtml>\n",[200,64766,64767],{"class":202,"line":365},[200,64768,64769],{"class":245},"      \u003Chead>\n",[200,64771,64772],{"class":202,"line":371},[200,64773,64774],{"class":245},"       \u003Clink\n",[200,64776,64777],{"class":202,"line":398},[200,64778,64779],{"class":245},"          rel=\"stylesheet\"\n",[200,64781,64782],{"class":202,"line":405},[200,64783,64784],{"class":245},"          href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@picocss\u002Fpico@2\u002Fcss\u002Fpico.min.css\"\n",[200,64786,64787],{"class":202,"line":410},[200,64788,64789],{"class":245},"        \u002F>\n",[200,64791,64792,64795,64797,64799,64801,64803,64805,64807,64809,64811],{"class":202,"line":860},[200,64793,64794],{"class":214},"        ${",[200,64796,14578],{"class":210},[200,64798,215],{"class":214},[200,64800,6202],{"class":223},[200,64802,227],{"class":210},[200,64804,249],{"class":214},[200,64806,6098],{"class":210},[200,64808,249],{"class":214},[200,64810,2104],{"class":210},[200,64812,1129],{"class":214},[200,64814,64815],{"class":202,"line":889},[200,64816,64817],{"class":245},"      \u003C\u002Fhead>\n",[200,64819,64820],{"class":202,"line":899},[200,64821,64822],{"class":245},"      \u003Cbody>\n",[200,64824,64825],{"class":202,"line":904},[200,64826,64827],{"class":245},"        \u003Cheader class=\"container\">\n",[200,64829,64830],{"class":202,"line":930},[200,64831,64832],{"class":245},"          \u003Ch1>\n",[200,64834,64835],{"class":202,"line":3655},[200,64836,64837],{"class":245},"            \u003Ca href=\"\u002F\">Raccourcisseur d'URL avec Nitro\u003C\u002Fa>\n",[200,64839,64840],{"class":202,"line":3660},[200,64841,64842],{"class":245},"          \u003C\u002Fh1>\n",[200,64844,64845],{"class":202,"line":4035},[200,64846,64847],{"class":245},"        \u003C\u002Fheader>\n",[200,64849,64850],{"class":202,"line":4490},[200,64851,64852],{"class":245},"        \u003Cmain class=\"container\">\n",[200,64854,64855,64858,64860],{"class":202,"line":4496},[200,64856,64857],{"class":214},"          ${",[200,64859,23914],{"class":210},[200,64861,1129],{"class":214},[200,64863,64864],{"class":202,"line":4502},[200,64865,64866],{"class":245},"        \u003C\u002Fmain>\n",[200,64868,64869],{"class":202,"line":4508},[200,64870,64871],{"class":245},"      \u003C\u002Fbody>\n",[200,64873,64874,64877],{"class":202,"line":5469},[200,64875,64876],{"class":245},"    \u003C\u002Fhtml>",[200,64878,12772],{"class":214},[200,64880,64881],{"class":202,"line":5474},[200,64882,1129],{"class":214},[35,64884,64885,64886,64889,64890,215],{},"Ceci est un simple ",[6862,64887,64888],{},"template de chaîne"," où nous incorporons le contenu de l'en-tête et du corps générés par ",[6862,64891,64256],{},[1929,64893,64895],{"id":64894},"stockage-de-lurl","Stockage de l'URL",[35,64897,64898,64899,64901],{},"Avant de procéder, installez ",[156,64900,19813],{}," pour valider le corps de la requête.",[192,64903,64905],{"className":2166,"code":64904,"language":2168,"meta":13,"style":13},"npm install zod\n",[156,64906,64907],{"__ignoreMap":13},[200,64908,64909,64911,64913],{"class":202,"line":203},[200,64910,36084],{"class":1364},[200,64912,1368],{"class":245},[200,64914,64915],{"class":245}," zod\n",[35,64917,64918,64919,64921,64922,64924,64925,64927,64928,64931,64932,215],{},"Créez la route ",[156,64920,64398],{}," pour gérer la ",[6862,64923,64644],{}," et stocker l'URL dans le ",[6862,64926,64021],{},". Cette route est étiquetée ",[156,64929,64930],{},"create.post.tsx"," et se trouve dans le répertoire ",[156,64933,64215],{},[192,64935,64938],{"className":64223,"code":64936,"filename":64937,"language":64226,"meta":13,"style":13},"import { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod' \u002F\u002F le `h` est essentiel ici\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, z.object({\n    url: z.string().url(),\n  }).parse)\n\n  const requestURL = getRequestURL(event)\n  const id = hash(body.url)\n  const shortenURL = new URL(`\u002F${id}`, requestURL).href\n\n  await useStorage('data').setItem(id, body.url)\n\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>Créé\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Créé et prêt\u003C\u002Fh2>\n        \u003Cinput\n          type=\"text\"\n          value={shortenURL}\n          autofocus\n        \u002F>\n      \u003C\u002Fdiv>\n    )\n  }\n\n  const app = renderSSR(\u003CApp \u002F>)\n  const { body: nanoBody, head } = Helmet.SSR(app)\n\n  return withTemplate({\n    body: nanoBody,\n    head,\n  })\n})\n","server\u002Froutes\u002Fcreate.post.tsx",[156,64939,64940,64966,64984,65005,65023,65027,65049,65077,65099,65111,65115,65132,65152,65188,65192,65228,65232,65246,65252,65260,65268,65285,65293,65310,65317,65329,65342,65347,65351,65359,65363,65367,65371,65391,65424,65428,65438,65448,65454,65460],{"__ignoreMap":13},[200,64941,64942,64944,64946,64948,64950,64952,64954,64956,64958,64960,64962,64964],{"class":202,"line":203},[200,64943,972],{"class":206},[200,64945,318],{"class":214},[200,64947,64237],{"class":210},[200,64949,549],{"class":214},[200,64951,64242],{"class":210},[200,64953,549],{"class":214},[200,64955,64247],{"class":210},[200,64957,980],{"class":214},[200,64959,983],{"class":206},[200,64961,242],{"class":214},[200,64963,64256],{"class":245},[200,64965,991],{"class":214},[200,64967,64968,64970,64972,64974,64976,64978,64980,64982],{"class":202,"line":14},[200,64969,972],{"class":206},[200,64971,318],{"class":214},[200,64973,56790],{"class":210},[200,64975,980],{"class":214},[200,64977,983],{"class":206},[200,64979,242],{"class":214},[200,64981,64065],{"class":245},[200,64983,991],{"class":214},[200,64985,64986,64988,64990,64992,64994,64996,64998,65000,65002],{"class":202,"line":255},[200,64987,972],{"class":206},[200,64989,318],{"class":214},[200,64991,3180],{"class":210},[200,64993,980],{"class":214},[200,64995,983],{"class":206},[200,64997,242],{"class":214},[200,64999,19813],{"class":245},[200,65001,249],{"class":214},[200,65003,65004],{"class":401}," \u002F\u002F le `h` est essentiel ici\n",[200,65006,65007,65009,65011,65013,65015,65017,65019,65021],{"class":202,"line":272},[200,65008,972],{"class":206},[200,65010,318],{"class":214},[200,65012,64270],{"class":210},[200,65014,980],{"class":214},[200,65016,983],{"class":206},[200,65018,242],{"class":214},[200,65020,64279],{"class":245},[200,65022,991],{"class":214},[200,65024,65025],{"class":202,"line":283},[200,65026,528],{"emptyLinePlaceholder":20},[200,65028,65029,65031,65033,65035,65037,65039,65041,65043,65045,65047],{"class":202,"line":300},[200,65030,1390],{"class":206},[200,65032,1393],{"class":206},[200,65034,38723],{"class":223},[200,65036,227],{"class":210},[200,65038,3726],{"class":379},[200,65040,1094],{"class":214},[200,65042,25426],{"class":386},[200,65044,2104],{"class":214},[200,65046,393],{"class":379},[200,65048,280],{"class":214},[200,65050,65051,65053,65055,65057,65059,65061,65063,65065,65067,65069,65071,65073,65075],{"class":202,"line":310},[200,65052,3738],{"class":379},[200,65054,40809],{"class":210},[200,65056,509],{"class":214},[200,65058,452],{"class":206},[200,65060,40951],{"class":223},[200,65062,227],{"class":235},[200,65064,25426],{"class":210},[200,65066,549],{"class":214},[200,65068,3180],{"class":210},[200,65070,215],{"class":214},[200,65072,293],{"class":223},[200,65074,227],{"class":235},[200,65076,230],{"class":214},[200,65078,65079,65081,65083,65085,65087,65089,65091,65093,65095,65097],{"class":202,"line":336},[200,65080,5207],{"class":235},[200,65082,239],{"class":214},[200,65084,3180],{"class":210},[200,65086,215],{"class":214},[200,65088,328],{"class":223},[200,65090,1549],{"class":235},[200,65092,215],{"class":214},[200,65094,2035],{"class":223},[200,65096,1549],{"class":235},[200,65098,252],{"class":214},[200,65100,65101,65103,65105,65107,65109],{"class":202,"line":342},[200,65102,5095],{"class":214},[200,65104,2104],{"class":235},[200,65106,215],{"class":214},[200,65108,40994],{"class":210},[200,65110,416],{"class":235},[200,65112,65113],{"class":202,"line":365},[200,65114,528],{"emptyLinePlaceholder":20},[200,65116,65117,65119,65122,65124,65126,65128,65130],{"class":202,"line":371},[200,65118,3738],{"class":379},[200,65120,65121],{"class":210}," requestURL",[200,65123,509],{"class":214},[200,65125,46636],{"class":223},[200,65127,227],{"class":235},[200,65129,25426],{"class":210},[200,65131,416],{"class":235},[200,65133,65134,65136,65138,65140,65142,65144,65146,65148,65150],{"class":202,"line":398},[200,65135,3738],{"class":379},[200,65137,3239],{"class":210},[200,65139,509],{"class":214},[200,65141,56790],{"class":223},[200,65143,227],{"class":235},[200,65145,23914],{"class":210},[200,65147,215],{"class":214},[200,65149,2035],{"class":210},[200,65151,416],{"class":235},[200,65153,65154,65156,65159,65161,65163,65166,65168,65170,65172,65174,65176,65178,65180,65182,65184,65186],{"class":202,"line":405},[200,65155,3738],{"class":379},[200,65157,65158],{"class":210}," shortenURL",[200,65160,509],{"class":214},[200,65162,13007],{"class":214},[200,65164,65165],{"class":223}," URL",[200,65167,227],{"class":235},[200,65169,3333],{"class":214},[200,65171,2898],{"class":245},[200,65173,4868],{"class":214},[200,65175,3295],{"class":210},[200,65177,23045],{"class":214},[200,65179,549],{"class":214},[200,65181,65121],{"class":210},[200,65183,2104],{"class":235},[200,65185,215],{"class":214},[200,65187,46647],{"class":210},[200,65189,65190],{"class":202,"line":410},[200,65191,528],{"emptyLinePlaceholder":20},[200,65193,65194,65196,65199,65201,65203,65205,65207,65209,65211,65214,65216,65218,65220,65222,65224,65226],{"class":202,"line":860},[200,65195,6177],{"class":206},[200,65197,65198],{"class":223}," useStorage",[200,65200,227],{"class":235},[200,65202,249],{"class":214},[200,65204,12736],{"class":245},[200,65206,249],{"class":214},[200,65208,2104],{"class":235},[200,65210,215],{"class":214},[200,65212,65213],{"class":223},"setItem",[200,65215,227],{"class":235},[200,65217,3295],{"class":210},[200,65219,549],{"class":214},[200,65221,40809],{"class":210},[200,65223,215],{"class":214},[200,65225,2035],{"class":210},[200,65227,416],{"class":235},[200,65229,65230],{"class":202,"line":889},[200,65231,528],{"emptyLinePlaceholder":20},[200,65233,65234,65236,65238,65240,65242,65244],{"class":202,"line":899},[200,65235,3738],{"class":379},[200,65237,64309],{"class":210},[200,65239,509],{"class":214},[200,65241,3729],{"class":214},[200,65243,393],{"class":379},[200,65245,280],{"class":214},[200,65247,65248,65250],{"class":202,"line":904},[200,65249,3311],{"class":206},[200,65251,64324],{"class":235},[200,65253,65254,65256,65258],{"class":202,"line":930},[200,65255,22071],{"class":214},[200,65257,11400],{"class":235},[200,65259,647],{"class":214},[200,65261,65262,65264,65266],{"class":202,"line":3655},[200,65263,49441],{"class":214},[200,65265,64339],{"class":1364},[200,65267,647],{"class":214},[200,65269,65270,65272,65274,65276,65279,65281,65283],{"class":202,"line":3660},[200,65271,49468],{"class":214},[200,65273,3581],{"class":235},[200,65275,674],{"class":214},[200,65277,65278],{"class":210},"Créé",[200,65280,680],{"class":214},[200,65282,3581],{"class":235},[200,65284,647],{"class":214},[200,65286,65287,65289,65291],{"class":202,"line":4035},[200,65288,49549],{"class":214},[200,65290,64339],{"class":1364},[200,65292,647],{"class":214},[200,65294,65295,65297,65299,65301,65304,65306,65308],{"class":202,"line":4490},[200,65296,49441],{"class":214},[200,65298,51],{"class":235},[200,65300,674],{"class":214},[200,65302,65303],{"class":210},"Créé et prêt",[200,65305,680],{"class":214},[200,65307,51],{"class":235},[200,65309,647],{"class":214},[200,65311,65312,65314],{"class":202,"line":4496},[200,65313,49441],{"class":214},[200,65315,65316],{"class":235},"input\n",[200,65318,65319,65321,65323,65325,65327],{"class":202,"line":4502},[200,65320,4929],{"class":379},[200,65322,449],{"class":214},[200,65324,610],{"class":214},[200,65326,697],{"class":245},[200,65328,616],{"class":214},[200,65330,65331,65334,65337,65340],{"class":202,"line":4508},[200,65332,65333],{"class":379},"          value",[200,65335,65336],{"class":214},"={",[200,65338,65339],{"class":210},"shortenURL",[200,65341,1129],{"class":214},[200,65343,65344],{"class":202,"line":5469},[200,65345,65346],{"class":379},"          autofocus\n",[200,65348,65349],{"class":202,"line":5474},[200,65350,64789],{"class":214},[200,65352,65353,65355,65357],{"class":202,"line":13237},[200,65354,43803],{"class":214},[200,65356,11400],{"class":235},[200,65358,647],{"class":214},[200,65360,65361],{"class":202,"line":13263},[200,65362,3869],{"class":235},[200,65364,65365],{"class":202,"line":13279},[200,65366,4505],{"class":214},[200,65368,65369],{"class":202,"line":13323},[200,65370,528],{"emptyLinePlaceholder":20},[200,65372,65373,65375,65377,65379,65381,65383,65385,65387,65389],{"class":202,"line":13329},[200,65374,3738],{"class":379},[200,65376,14334],{"class":210},[200,65378,509],{"class":214},[200,65380,64247],{"class":223},[200,65382,227],{"class":235},[200,65384,599],{"class":214},[200,65386,64531],{"class":1364},[200,65388,22099],{"class":214},[200,65390,416],{"class":235},[200,65392,65393,65395,65397,65399,65401,65404,65406,65408,65410,65412,65414,65416,65418,65420,65422],{"class":202,"line":13334},[200,65394,3738],{"class":379},[200,65396,318],{"class":214},[200,65398,40809],{"class":235},[200,65400,239],{"class":214},[200,65402,65403],{"class":210}," nanoBody",[200,65405,549],{"class":214},[200,65407,64548],{"class":210},[200,65409,980],{"class":214},[200,65411,509],{"class":214},[200,65413,64242],{"class":210},[200,65415,215],{"class":214},[200,65417,14496],{"class":223},[200,65419,227],{"class":235},[200,65421,26785],{"class":210},[200,65423,416],{"class":235},[200,65425,65426],{"class":202,"line":13360},[200,65427,528],{"emptyLinePlaceholder":20},[200,65429,65430,65432,65434,65436],{"class":202,"line":13365},[200,65431,3758],{"class":206},[200,65433,64270],{"class":223},[200,65435,227],{"class":235},[200,65437,230],{"class":214},[200,65439,65440,65442,65444,65446],{"class":202,"line":13385},[200,65441,18788],{"class":235},[200,65443,239],{"class":214},[200,65445,65403],{"class":210},[200,65447,252],{"class":214},[200,65449,65450,65452],{"class":202,"line":13405},[200,65451,64593],{"class":210},[200,65453,252],{"class":214},[200,65455,65456,65458],{"class":202,"line":13410},[200,65457,5095],{"class":214},[200,65459,416],{"class":235},[200,65461,65462,65464],{"class":202,"line":13418},[200,65463,413],{"class":214},[200,65465,416],{"class":210},[35,65467,65468,65469,24175,65471,65474,65475,65477],{},"Dans ce segment, nous utilisons la fonction ",[156,65470,40889],{},[6862,65472,65473],{},"valider le corps de la requête",". Cela assure que le champ ",[156,65476,2035],{}," est une URL légitime, sinon une erreur est générée.",[35,65479,65480,65481,65484,65485,17891,65488,215],{},"Nous récupérons l'",[6862,65482,65483],{},"URL de la requête"," en utilisant la fonction ",[156,65486,65487],{},"getRequestURL",[156,65489,1929],{},[35,65491,19439,65492,65494,65495,17891,65497,65499],{},[6862,65493,52842],{}," est créé à partir de l'URL du corps en utilisant la fonction ",[156,65496,52842],{},[6862,65498,64065],{},", garantissant une génération de hash cohérente pour éviter les collisions.",[35,65501,65502,65503,65484,65505,17891,65508,65510,65511,65513],{},"L'URL est stockée dans le ",[6862,65504,64021],{},[156,65506,65507],{},"useStorage",[6862,65509,64049],{},", employant le namespace ",[156,65512,12736],{}," pour stocker les URL, préconfiguré pour notre utilisation.",[7918,65515,65518,65521,65528,65625,65647],{"text":65516,"href":65517},"Stockage KV","https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fstorage#usage",[35,65519,65520],{},"À la fin de ce gestionnaire d'événements, une route est renvoyée avec l'URL raccourcie que les utilisateurs peuvent copier et utiliser.",[35,65522,65523,65524,65527],{},"Pour le développement, tout fonctionne correctement, mais nous devons mettre à jour cette configuration, ",[156,65525,65526],{},"nitro.config.ts",", pour l'environnement de production.",[192,65529,65531],{"className":1380,"code":65530,"filename":65526,"language":1383,"meta":13,"style":13},"export default defineNitroConfig({\n  srcDir: 'server',\n  $production: {\n    storage: { data: { driver: 'cloudflare-kv-binding', binding: 'url-shortener' } },\n  },\n})\n",[156,65532,65533,65546,65561,65570,65615,65619],{"__ignoreMap":13},[200,65534,65535,65537,65539,65542,65544],{"class":202,"line":203},[200,65536,1390],{"class":206},[200,65538,1393],{"class":206},[200,65540,65541],{"class":223}," defineNitroConfig",[200,65543,227],{"class":210},[200,65545,230],{"class":214},[200,65547,65548,65551,65553,65555,65557,65559],{"class":202,"line":14},[200,65549,65550],{"class":235},"  srcDir",[200,65552,239],{"class":214},[200,65554,242],{"class":214},[200,65556,3123],{"class":245},[200,65558,249],{"class":214},[200,65560,252],{"class":214},[200,65562,65563,65566,65568],{"class":202,"line":255},[200,65564,65565],{"class":235},"  $production",[200,65567,239],{"class":214},[200,65569,280],{"class":214},[200,65571,65572,65575,65577,65579,65581,65583,65585,65588,65590,65592,65595,65597,65599,65602,65604,65606,65609,65611,65613],{"class":202,"line":272},[200,65573,65574],{"class":235},"    storage",[200,65576,239],{"class":214},[200,65578,318],{"class":214},[200,65580,5056],{"class":235},[200,65582,239],{"class":214},[200,65584,318],{"class":214},[200,65586,65587],{"class":235}," driver",[200,65589,239],{"class":214},[200,65591,242],{"class":214},[200,65593,65594],{"class":245},"cloudflare-kv-binding",[200,65596,249],{"class":214},[200,65598,549],{"class":214},[200,65600,65601],{"class":235}," binding",[200,65603,239],{"class":214},[200,65605,242],{"class":214},[200,65607,65608],{"class":245},"url-shortener",[200,65610,249],{"class":214},[200,65612,980],{"class":214},[200,65614,333],{"class":214},[200,65616,65617],{"class":202,"line":283},[200,65618,368],{"class":214},[200,65620,65621,65623],{"class":202,"line":300},[200,65622,413],{"class":214},[200,65624,416],{"class":210},[35,65626,65627,65628,65630,65631,65633,65634,65636,65637,65640,65641,65644,65645,215],{},"Dans cette configuration, nous définissons le namespace ",[156,65629,12736],{},", comme dans le développement, afin d'utiliser le driver ",[6862,65632,65594],{}," et le binding ",[156,65635,65608],{},", spécifié sous la clé ",[156,65638,65639],{},"$production",". Cela indique que la configuration s'applique uniquement à l'",[6862,65642,65643],{},"environnement de production"," pour accéder au ",[6862,65646,38550],{},[7918,65648,65651,65655,65664,65849,65868,65872,65882],{"text":65649,"href":65650},"Configuration spécifique à l'environnement","https:\u002F\u002Fgithub.com\u002Funjs\u002Fc12#environment-specific-configuration",[1929,65652,65654],{"id":65653},"redirection-vers-lurl-originale","Redirection vers l'URL Originale",[35,65656,65657,65658,65661,65662,215],{},"Enfin, nous devons établir une route pour gérer les URL raccourcies et rediriger les utilisateurs vers l'URL principale. Cette route est nommée ",[156,65659,65660],{},"[id].get.ts"," et est située dans le répertoire ",[156,65663,64215],{},[192,65665,65668],{"className":1380,"code":65666,"filename":65667,"language":1383,"meta":13,"style":13},"export default defineEventHandler(async (event) => {\n  const id = getRouterParam(event, 'short')\n\n  const value = await useStorage\u003Cstring>('data').getItem(id)\n\n  if (!value) {\n    throw createError({\n      statusCode: 404,\n      statusMessage: 'Non trouvé',\n    })\n  }\n\n  return sendRedirect(event, value)\n})\n","server\u002Froutes\u002F[id].get.ts",[156,65669,65670,65692,65718,65722,65760,65764,65778,65788,65798,65813,65819,65823,65827,65843],{"__ignoreMap":13},[200,65671,65672,65674,65676,65678,65680,65682,65684,65686,65688,65690],{"class":202,"line":203},[200,65673,1390],{"class":206},[200,65675,1393],{"class":206},[200,65677,38723],{"class":223},[200,65679,227],{"class":210},[200,65681,3726],{"class":379},[200,65683,1094],{"class":214},[200,65685,25426],{"class":386},[200,65687,2104],{"class":214},[200,65689,393],{"class":379},[200,65691,280],{"class":214},[200,65693,65694,65696,65698,65700,65703,65705,65707,65709,65711,65714,65716],{"class":202,"line":14},[200,65695,3738],{"class":379},[200,65697,3239],{"class":210},[200,65699,509],{"class":214},[200,65701,65702],{"class":223}," getRouterParam",[200,65704,227],{"class":235},[200,65706,25426],{"class":210},[200,65708,549],{"class":214},[200,65710,242],{"class":214},[200,65712,65713],{"class":245},"short",[200,65715,249],{"class":214},[200,65717,416],{"class":235},[200,65719,65720],{"class":202,"line":255},[200,65721,528],{"emptyLinePlaceholder":20},[200,65723,65724,65726,65728,65730,65732,65734,65736,65738,65740,65742,65744,65746,65748,65750,65752,65754,65756,65758],{"class":202,"line":272},[200,65725,3738],{"class":379},[200,65727,809],{"class":210},[200,65729,509],{"class":214},[200,65731,452],{"class":206},[200,65733,65198],{"class":223},[200,65735,599],{"class":214},[200,65737,328],{"class":1364},[200,65739,674],{"class":214},[200,65741,227],{"class":235},[200,65743,249],{"class":214},[200,65745,12736],{"class":245},[200,65747,249],{"class":214},[200,65749,2104],{"class":235},[200,65751,215],{"class":214},[200,65753,14971],{"class":223},[200,65755,227],{"class":235},[200,65757,3295],{"class":210},[200,65759,416],{"class":235},[200,65761,65762],{"class":202,"line":283},[200,65763,528],{"emptyLinePlaceholder":20},[200,65765,65766,65768,65770,65772,65774,65776],{"class":202,"line":300},[200,65767,20098],{"class":206},[200,65769,1094],{"class":235},[200,65771,11852],{"class":214},[200,65773,15840],{"class":210},[200,65775,5282],{"class":235},[200,65777,230],{"class":214},[200,65779,65780,65782,65784,65786],{"class":202,"line":310},[200,65781,39200],{"class":206},[200,65783,39203],{"class":223},[200,65785,227],{"class":235},[200,65787,230],{"class":214},[200,65789,65790,65792,65794,65796],{"class":202,"line":336},[200,65791,39212],{"class":235},[200,65793,239],{"class":214},[200,65795,40347],{"class":2060},[200,65797,252],{"class":214},[200,65799,65800,65803,65805,65807,65809,65811],{"class":202,"line":342},[200,65801,65802],{"class":235},"      statusMessage",[200,65804,239],{"class":214},[200,65806,242],{"class":214},[200,65808,40360],{"class":245},[200,65810,249],{"class":214},[200,65812,252],{"class":214},[200,65814,65815,65817],{"class":202,"line":365},[200,65816,2111],{"class":214},[200,65818,416],{"class":235},[200,65820,65821],{"class":202,"line":371},[200,65822,4505],{"class":214},[200,65824,65825],{"class":202,"line":398},[200,65826,528],{"emptyLinePlaceholder":20},[200,65828,65829,65831,65833,65835,65837,65839,65841],{"class":202,"line":405},[200,65830,3758],{"class":206},[200,65832,46075],{"class":223},[200,65834,227],{"class":235},[200,65836,25426],{"class":210},[200,65838,549],{"class":214},[200,65840,809],{"class":210},[200,65842,416],{"class":235},[200,65844,65845,65847],{"class":202,"line":410},[200,65846,413],{"class":214},[200,65848,416],{"class":210},[35,65850,65851,65852,65854,65855,65857,65858,26733,65861,65863,65864,65867],{},"Ici, nous obtenons simplement l'",[156,65853,3295],{}," à partir des paramètres du routeur et utilisons ",[156,65856,65507],{}," pour récupérer l'",[6862,65859,65860],{},"URL",[6862,65862,64021],{},". Si l'URL n'existe pas, une ",[6862,65865,65866],{},"erreur 404"," est générée. Sinon, l'utilisateur est redirigé vers l'URL originale.",[51,65869,65871],{"id":65870},"déployer-le-raccourcisseur-durl","Déployer le Raccourcisseur d'URL",[35,65873,65874,65875,65878,65879,215],{},"Félicitations ! Nous avons réussi à créer un raccourcisseur d'URL de base en utilisant Nitro. La prochaine étape consiste à ",[6862,65876,65877],{},"le déployer"," sur Cloudflare Pages. Rassurez-vous, c'est simple grâce aux ",[6862,65880,65881],{},"fournisseurs de déploiement sans configuration",[7918,65883,65886,65893,65910,65915,65932,65946,65953,65957,65967,66701,66708,66731,66733,66746,66754],{"text":65884,"href":65885},"Fournisseurs Zero Config","https:\u002F\u002Fnitro.unjs.io\u002Fdeploy#zero-config-providers",[938,65887,65888],{},[35,65889,65890,65892],{},[200,65891,29427],{},"\nUn compte Cloudflare et un dépôt GitHub sont nécessaires pour continuer avec le déploiement.\nTout d'abord, connectez-vous au tableau de bord Cloudflare et sélectionnez un compte. Naviguez vers Accueil du compte, choisissez Workers & Pages, et créez une nouvelle application en utilisant le bouton en haut à droite. Sélectionnez l'onglet Pages et suivez les instructions pour établir une connexion GitHub.",[35,65894,65895,65896,65899,65900,65902,65903,10703,65906,65909],{},"Sélectionnez le dépôt, ajoutez la commande de construction ",[156,65897,65898],{},"npm run build",", spécifiez le répertoire de sortie ",[156,65901,5154],{},", incluez une variable d'environnement ",[156,65904,65905],{},"NODE_ENV",[156,65907,65908],{},"production",", puis enregistrez et déployez.",[35,65911,65912,65913,215],{},"Maintenant, attendons que le déploiement soit terminé. Une fois terminé, vous pouvez accéder au raccourcisseur d'URL en utilisant l'URL fournie par ",[6862,65914,38535],{},[35,65916,65917,65920,65921,65924,65925,65928,65929,65931],{},[6862,65918,65919],{},"Cependant",", il se peut qu'il ne fonctionne pas adéquatement initialement, car un ",[6862,65922,65923],{},"namespace KV"," n'a pas été lié. Cela nécessite un lien manuel. Allez dans les paramètres du projet, sélectionnez les fonctions et faites défiler jusqu'à ce que vous trouviez ",[6862,65926,65927],{},"liaisons de namespace KV",". Ajoutez une liaison avec le nom ",[156,65930,65608],{}," et choisissez le namespace désiré.",[938,65933,65934],{},[35,65935,65936,65938,65939,65941,65942,65945],{},[200,65937,944],{},"\nCréer un ",[6862,65940,65923],{}," est réalisable dans la section ",[6862,65943,65944],{},"Workers & Pages",".\nRedéployez votre application pour tenir compte de la nouvelle liaison de namespace KV. Une fois terminé, le raccourcisseur d'URL sera pleinement fonctionnel. ✨",[938,65947,65948],{},[35,65949,65950,65952],{},[200,65951,944],{},"\nSi vous n'avez pas l'intention de maintenir le projet, n'oubliez pas de le supprimer du tableau de bord Cloudflare Pages afin d'éviter des dépenses inutiles.",[51,65954,65956],{"id":65955},"améliorations-et-considérations-futures","Améliorations et Considérations Futures",[35,65958,65959,65960,65963,65964,65966],{},"Pour améliorer la sécurité, envisagez d'ajouter une simple ",[6862,65961,65962],{},"protection CSRF"," en utilisant la syntaxe des objets gestionnaires. Modifiez le fichier ",[156,65965,64930],{}," pour inclure un gestionnaire avant la requête pour la vérification de l'en-tête d'origine.",[192,65968,65970],{"className":64223,"code":65969,"filename":64937,"language":64226,"meta":13,"style":13},"import { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod'\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineEventHandler({\n  onBeforeResponse: async (event) => {\n    const requestURL = getRequestURL(event).origin\n    const origin = getRequestHeader(event, 'origin')\n\n    if (!origin) {\n      throw createError({\n        statusCode: 400,\n        statusMessage: 'Mauvaise requête',\n      })\n    }\n\n    if (origin !== requestURL) {\n      throw createError({\n        statusCode: 403,\n        statusMessage: 'Interdit',\n      })\n    }\n  },\n  handler: async (event) => {\n    const body = await readValidatedBody(event, z.object({\n      url: z.string().url(),\n    }).parse)\n\n    const requestURL = getRequestURL(event)\n    const id = hash(body.url)\n    const shortenURL = new URL(`\u002F${id}`, requestURL).href\n\n    await useStorage('data').setItem(id, body.url)\n\n    const App = () => {\n      return (\n        \u003Cdiv>\n          \u003CHelmet>\n            \u003Ctitle>Créé\u003C\u002Ftitle>\n          \u003C\u002FHelmet>\n          \u003Ch2>Créé et prêt\u003C\u002Fh2>\n          \u003Cinput\n            type=\"text\"\n            value={shortenURL}\n            autofocus\n          \u002F>\n        \u003C\u002Fdiv>\n      )\n    }\n\n    const app = renderSSR(\u003CApp \u002F>)\n    const { body: nanoBody, head } = Helmet.SSR(app)\n\n    return withTemplate({\n      body: nanoBody,\n      head,\n    })\n  },\n})\n",[156,65971,65972,65998,66016,66034,66052,66056,66068,66087,66108,66135,66139,66153,66163,66174,66190,66196,66200,66204,66220,66230,66240,66254,66260,66264,66268,66287,66315,66337,66349,66353,66369,66389,66423,66427,66461,66465,66479,66485,66493,66501,66518,66526,66542,66548,66561,66572,66577,66582,66590,66594,66598,66602,66622,66654,66658,66668,66678,66685,66691,66695],{"__ignoreMap":13},[200,65973,65974,65976,65978,65980,65982,65984,65986,65988,65990,65992,65994,65996],{"class":202,"line":203},[200,65975,972],{"class":206},[200,65977,318],{"class":214},[200,65979,64237],{"class":210},[200,65981,549],{"class":214},[200,65983,64242],{"class":210},[200,65985,549],{"class":214},[200,65987,64247],{"class":210},[200,65989,980],{"class":214},[200,65991,983],{"class":206},[200,65993,242],{"class":214},[200,65995,64256],{"class":245},[200,65997,991],{"class":214},[200,65999,66000,66002,66004,66006,66008,66010,66012,66014],{"class":202,"line":14},[200,66001,972],{"class":206},[200,66003,318],{"class":214},[200,66005,56790],{"class":210},[200,66007,980],{"class":214},[200,66009,983],{"class":206},[200,66011,242],{"class":214},[200,66013,64065],{"class":245},[200,66015,991],{"class":214},[200,66017,66018,66020,66022,66024,66026,66028,66030,66032],{"class":202,"line":255},[200,66019,972],{"class":206},[200,66021,318],{"class":214},[200,66023,3180],{"class":210},[200,66025,980],{"class":214},[200,66027,983],{"class":206},[200,66029,242],{"class":214},[200,66031,19813],{"class":245},[200,66033,991],{"class":214},[200,66035,66036,66038,66040,66042,66044,66046,66048,66050],{"class":202,"line":272},[200,66037,972],{"class":206},[200,66039,318],{"class":214},[200,66041,64270],{"class":210},[200,66043,980],{"class":214},[200,66045,983],{"class":206},[200,66047,242],{"class":214},[200,66049,64279],{"class":245},[200,66051,991],{"class":214},[200,66053,66054],{"class":202,"line":283},[200,66055,528],{"emptyLinePlaceholder":20},[200,66057,66058,66060,66062,66064,66066],{"class":202,"line":300},[200,66059,1390],{"class":206},[200,66061,1393],{"class":206},[200,66063,38723],{"class":223},[200,66065,227],{"class":210},[200,66067,230],{"class":214},[200,66069,66070,66073,66075,66077,66079,66081,66083,66085],{"class":202,"line":310},[200,66071,66072],{"class":223},"  onBeforeResponse",[200,66074,239],{"class":214},[200,66076,380],{"class":379},[200,66078,1094],{"class":214},[200,66080,25426],{"class":386},[200,66082,2104],{"class":214},[200,66084,393],{"class":379},[200,66086,280],{"class":214},[200,66088,66089,66091,66093,66095,66097,66099,66101,66103,66105],{"class":202,"line":336},[200,66090,2014],{"class":379},[200,66092,65121],{"class":210},[200,66094,509],{"class":214},[200,66096,46636],{"class":223},[200,66098,227],{"class":235},[200,66100,25426],{"class":210},[200,66102,2104],{"class":235},[200,66104,215],{"class":214},[200,66106,66107],{"class":210},"origin\n",[200,66109,66110,66112,66115,66117,66120,66122,66124,66126,66128,66131,66133],{"class":202,"line":342},[200,66111,2014],{"class":379},[200,66113,66114],{"class":210}," origin",[200,66116,509],{"class":214},[200,66118,66119],{"class":223}," getRequestHeader",[200,66121,227],{"class":235},[200,66123,25426],{"class":210},[200,66125,549],{"class":214},[200,66127,242],{"class":214},[200,66129,66130],{"class":245},"origin",[200,66132,249],{"class":214},[200,66134,416],{"class":235},[200,66136,66137],{"class":202,"line":365},[200,66138,528],{"emptyLinePlaceholder":20},[200,66140,66141,66143,66145,66147,66149,66151],{"class":202,"line":371},[200,66142,5267],{"class":206},[200,66144,1094],{"class":235},[200,66146,11852],{"class":214},[200,66148,66130],{"class":210},[200,66150,5282],{"class":235},[200,66152,230],{"class":214},[200,66154,66155,66157,66159,66161],{"class":202,"line":398},[200,66156,20250],{"class":206},[200,66158,39203],{"class":223},[200,66160,227],{"class":235},[200,66162,230],{"class":214},[200,66164,66165,66168,66170,66172],{"class":202,"line":405},[200,66166,66167],{"class":235},"        statusCode",[200,66169,239],{"class":214},[200,66171,41198],{"class":2060},[200,66173,252],{"class":214},[200,66175,66176,66179,66181,66183,66186,66188],{"class":202,"line":410},[200,66177,66178],{"class":235},"        statusMessage",[200,66180,239],{"class":214},[200,66182,242],{"class":214},[200,66184,66185],{"class":245},"Mauvaise requête",[200,66187,249],{"class":214},[200,66189,252],{"class":214},[200,66191,66192,66194],{"class":202,"line":860},[200,66193,2647],{"class":214},[200,66195,416],{"class":235},[200,66197,66198],{"class":202,"line":889},[200,66199,4977],{"class":214},[200,66201,66202],{"class":202,"line":899},[200,66203,528],{"emptyLinePlaceholder":20},[200,66205,66206,66208,66210,66212,66214,66216,66218],{"class":202,"line":904},[200,66207,5267],{"class":206},[200,66209,1094],{"class":235},[200,66211,66130],{"class":210},[200,66213,13398],{"class":214},[200,66215,65121],{"class":210},[200,66217,5282],{"class":235},[200,66219,230],{"class":214},[200,66221,66222,66224,66226,66228],{"class":202,"line":930},[200,66223,20250],{"class":206},[200,66225,39203],{"class":223},[200,66227,227],{"class":235},[200,66229,230],{"class":214},[200,66231,66232,66234,66236,66238],{"class":202,"line":3655},[200,66233,66167],{"class":235},[200,66235,239],{"class":214},[200,66237,39226],{"class":2060},[200,66239,252],{"class":214},[200,66241,66242,66244,66246,66248,66250,66252],{"class":202,"line":3660},[200,66243,66178],{"class":235},[200,66245,239],{"class":214},[200,66247,242],{"class":214},[200,66249,39727],{"class":245},[200,66251,249],{"class":214},[200,66253,252],{"class":214},[200,66255,66256,66258],{"class":202,"line":4035},[200,66257,2647],{"class":214},[200,66259,416],{"class":235},[200,66261,66262],{"class":202,"line":4490},[200,66263,4977],{"class":214},[200,66265,66266],{"class":202,"line":4496},[200,66267,368],{"class":214},[200,66269,66270,66273,66275,66277,66279,66281,66283,66285],{"class":202,"line":4502},[200,66271,66272],{"class":223},"  handler",[200,66274,239],{"class":214},[200,66276,380],{"class":379},[200,66278,1094],{"class":214},[200,66280,25426],{"class":386},[200,66282,2104],{"class":214},[200,66284,393],{"class":379},[200,66286,280],{"class":214},[200,66288,66289,66291,66293,66295,66297,66299,66301,66303,66305,66307,66309,66311,66313],{"class":202,"line":4508},[200,66290,2014],{"class":379},[200,66292,40809],{"class":210},[200,66294,509],{"class":214},[200,66296,452],{"class":206},[200,66298,40951],{"class":223},[200,66300,227],{"class":235},[200,66302,25426],{"class":210},[200,66304,549],{"class":214},[200,66306,3180],{"class":210},[200,66308,215],{"class":214},[200,66310,293],{"class":223},[200,66312,227],{"class":235},[200,66314,230],{"class":214},[200,66316,66317,66319,66321,66323,66325,66327,66329,66331,66333,66335],{"class":202,"line":5469},[200,66318,3620],{"class":235},[200,66320,239],{"class":214},[200,66322,3180],{"class":210},[200,66324,215],{"class":214},[200,66326,328],{"class":223},[200,66328,1549],{"class":235},[200,66330,215],{"class":214},[200,66332,2035],{"class":223},[200,66334,1549],{"class":235},[200,66336,252],{"class":214},[200,66338,66339,66341,66343,66345,66347],{"class":202,"line":5474},[200,66340,2111],{"class":214},[200,66342,2104],{"class":235},[200,66344,215],{"class":214},[200,66346,40994],{"class":210},[200,66348,416],{"class":235},[200,66350,66351],{"class":202,"line":13237},[200,66352,528],{"emptyLinePlaceholder":20},[200,66354,66355,66357,66359,66361,66363,66365,66367],{"class":202,"line":13263},[200,66356,2014],{"class":379},[200,66358,65121],{"class":210},[200,66360,509],{"class":214},[200,66362,46636],{"class":223},[200,66364,227],{"class":235},[200,66366,25426],{"class":210},[200,66368,416],{"class":235},[200,66370,66371,66373,66375,66377,66379,66381,66383,66385,66387],{"class":202,"line":13279},[200,66372,2014],{"class":379},[200,66374,3239],{"class":210},[200,66376,509],{"class":214},[200,66378,56790],{"class":223},[200,66380,227],{"class":235},[200,66382,23914],{"class":210},[200,66384,215],{"class":214},[200,66386,2035],{"class":210},[200,66388,416],{"class":235},[200,66390,66391,66393,66395,66397,66399,66401,66403,66405,66407,66409,66411,66413,66415,66417,66419,66421],{"class":202,"line":13323},[200,66392,2014],{"class":379},[200,66394,65158],{"class":210},[200,66396,509],{"class":214},[200,66398,13007],{"class":214},[200,66400,65165],{"class":223},[200,66402,227],{"class":235},[200,66404,3333],{"class":214},[200,66406,2898],{"class":245},[200,66408,4868],{"class":214},[200,66410,3295],{"class":210},[200,66412,23045],{"class":214},[200,66414,549],{"class":214},[200,66416,65121],{"class":210},[200,66418,2104],{"class":235},[200,66420,215],{"class":214},[200,66422,46647],{"class":210},[200,66424,66425],{"class":202,"line":13329},[200,66426,528],{"emptyLinePlaceholder":20},[200,66428,66429,66431,66433,66435,66437,66439,66441,66443,66445,66447,66449,66451,66453,66455,66457,66459],{"class":202,"line":13334},[200,66430,19231],{"class":206},[200,66432,65198],{"class":223},[200,66434,227],{"class":235},[200,66436,249],{"class":214},[200,66438,12736],{"class":245},[200,66440,249],{"class":214},[200,66442,2104],{"class":235},[200,66444,215],{"class":214},[200,66446,65213],{"class":223},[200,66448,227],{"class":235},[200,66450,3295],{"class":210},[200,66452,549],{"class":214},[200,66454,40809],{"class":210},[200,66456,215],{"class":214},[200,66458,2035],{"class":210},[200,66460,416],{"class":235},[200,66462,66463],{"class":202,"line":13360},[200,66464,528],{"emptyLinePlaceholder":20},[200,66466,66467,66469,66471,66473,66475,66477],{"class":202,"line":13365},[200,66468,2014],{"class":379},[200,66470,64309],{"class":210},[200,66472,509],{"class":214},[200,66474,3729],{"class":214},[200,66476,393],{"class":379},[200,66478,280],{"class":214},[200,66480,66481,66483],{"class":202,"line":13385},[200,66482,11730],{"class":206},[200,66484,64324],{"class":235},[200,66486,66487,66489,66491],{"class":202,"line":13405},[200,66488,49441],{"class":214},[200,66490,11400],{"class":235},[200,66492,647],{"class":214},[200,66494,66495,66497,66499],{"class":202,"line":13410},[200,66496,49468],{"class":214},[200,66498,64339],{"class":1364},[200,66500,647],{"class":214},[200,66502,66503,66506,66508,66510,66512,66514,66516],{"class":202,"line":13418},[200,66504,66505],{"class":214},"            \u003C",[200,66507,3581],{"class":235},[200,66509,674],{"class":214},[200,66511,65278],{"class":210},[200,66513,680],{"class":214},[200,66515,3581],{"class":235},[200,66517,647],{"class":214},[200,66519,66520,66522,66524],{"class":202,"line":13436},[200,66521,49540],{"class":214},[200,66523,64339],{"class":1364},[200,66525,647],{"class":214},[200,66527,66528,66530,66532,66534,66536,66538,66540],{"class":202,"line":13466},[200,66529,49468],{"class":214},[200,66531,51],{"class":235},[200,66533,674],{"class":214},[200,66535,65303],{"class":210},[200,66537,680],{"class":214},[200,66539,51],{"class":235},[200,66541,647],{"class":214},[200,66543,66544,66546],{"class":202,"line":13472},[200,66545,49468],{"class":214},[200,66547,65316],{"class":235},[200,66549,66550,66553,66555,66557,66559],{"class":202,"line":13477},[200,66551,66552],{"class":379},"            type",[200,66554,449],{"class":214},[200,66556,610],{"class":214},[200,66558,697],{"class":245},[200,66560,616],{"class":214},[200,66562,66563,66566,66568,66570],{"class":202,"line":13482},[200,66564,66565],{"class":379},"            value",[200,66567,65336],{"class":214},[200,66569,65339],{"class":210},[200,66571,1129],{"class":214},[200,66573,66574],{"class":202,"line":13497},[200,66575,66576],{"class":379},"            autofocus\n",[200,66578,66579],{"class":202,"line":13508},[200,66580,66581],{"class":214},"          \u002F>\n",[200,66583,66584,66586,66588],{"class":202,"line":13513},[200,66585,49549],{"class":214},[200,66587,11400],{"class":235},[200,66589,647],{"class":214},[200,66591,66592],{"class":202,"line":13518},[200,66593,46766],{"class":235},[200,66595,66596],{"class":202,"line":20247},[200,66597,4977],{"class":214},[200,66599,66600],{"class":202,"line":20269},[200,66601,528],{"emptyLinePlaceholder":20},[200,66603,66604,66606,66608,66610,66612,66614,66616,66618,66620],{"class":202,"line":20274},[200,66605,2014],{"class":379},[200,66607,14334],{"class":210},[200,66609,509],{"class":214},[200,66611,64247],{"class":223},[200,66613,227],{"class":235},[200,66615,599],{"class":214},[200,66617,64531],{"class":1364},[200,66619,22099],{"class":214},[200,66621,416],{"class":235},[200,66623,66624,66626,66628,66630,66632,66634,66636,66638,66640,66642,66644,66646,66648,66650,66652],{"class":202,"line":20279},[200,66625,2014],{"class":379},[200,66627,318],{"class":214},[200,66629,40809],{"class":235},[200,66631,239],{"class":214},[200,66633,65403],{"class":210},[200,66635,549],{"class":214},[200,66637,64548],{"class":210},[200,66639,980],{"class":214},[200,66641,509],{"class":214},[200,66643,64242],{"class":210},[200,66645,215],{"class":214},[200,66647,14496],{"class":223},[200,66649,227],{"class":235},[200,66651,26785],{"class":210},[200,66653,416],{"class":235},[200,66655,66656],{"class":202,"line":20284},[200,66657,528],{"emptyLinePlaceholder":20},[200,66659,66660,66662,66664,66666],{"class":202,"line":20305},[200,66661,3311],{"class":206},[200,66663,64270],{"class":223},[200,66665,227],{"class":235},[200,66667,230],{"class":214},[200,66669,66670,66672,66674,66676],{"class":202,"line":20312},[200,66671,41853],{"class":235},[200,66673,239],{"class":214},[200,66675,65403],{"class":210},[200,66677,252],{"class":214},[200,66679,66680,66683],{"class":202,"line":20317},[200,66681,66682],{"class":210},"      head",[200,66684,252],{"class":214},[200,66686,66687,66689],{"class":202,"line":20333},[200,66688,2111],{"class":214},[200,66690,416],{"class":235},[200,66692,66693],{"class":202,"line":46974},[200,66694,368],{"class":214},[200,66696,66697,66699],{"class":202,"line":46979},[200,66698,413],{"class":214},[200,66700,416],{"class":210},[35,66702,66703,66704,66707],{},"Nous mettons en place un gestionnaire avant la requête pour vérifier l'",[6862,66705,66706],{},"en-tête d'origine",". S'il y a un désaccord, une erreur est générée ; sinon, la requête passe au gestionnaire principal.",[938,66709,66710],{},[35,66711,66712,66714,66715,66717,66718,66723,66724,66726,66727,66730],{},[200,66713,944],{},"\nPour des informations complètes sur la ",[6862,66716,65962],{},", consultez la ",[39,66719,66722],{"href":66720,"rel":66721},"https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FCross-Site_Request_Forgery_Prevention_Cheat_Sheet.html",[77],"Fiche de Prévention contre les Attaques CSRF"," par OWASP. Valider l'",[6862,66725,66706],{}," est une approche de défense basique pour ",[6862,66728,66729],{},"CSRF",". Cependant, il est conseillé de mettre en œuvre un mécanisme de protection plus robuste, tel qu'un token.",[51,66732,24146],{"id":24145},[35,66734,66735,66736,1655,66738,66740,66741,66743,66744,215],{},"Développer avec ",[6862,66737,36573],{},[6862,66739,38535],{}," est simple et offre une expérience de développeur exceptionnelle grâce à la possibilité d'utiliser ",[6862,66742,64085],{}," et un stockage de développement, éliminant le besoin du ",[6862,66745,64056],{},[35,66747,66748,66749,1655,66751,66753],{},"Profitez de votre parcours de développement avec ",[6862,66750,36573],{},[6862,66752,38535],{}," ! 🚀",[938,66755,66756],{},[35,66757,66758,66760,66761,215],{},[200,66759,944],{},"\nLe raccourcisseur d'URL est inspiré par ",[39,66762,66765],{"href":66763,"rel":66764},"https:\u002F\u002Fgithub.com\u002Fyusukebe\u002Furl-shortener",[77],"le Raccourcisseur d'URL de Yusuke Wada",[1170,66767,66768],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}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}",{"title":13,"searchDepth":14,"depth":14,"links":66770},[66771,66772],{"id":64098,"depth":14,"text":64099},{"id":64178,"depth":14,"text":64179,"children":66773},[66774,66775],{"id":64202,"depth":255,"text":64203},{"id":64894,"depth":255,"text":64895},"2024-03-19","Apprenez à créer un raccourcisseur d'URL en utilisant Nitro et à le déployer sur Cloudflare Pages, permettant un développement dans l'edge sans effort.",{},"\u002Ffr\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages","---\ncontentId: aa8454be-94ba-4d46-8be0-8cf703c0bfed\ntitle: Créer un raccourcisseur d'URL avec Nitro et Cloudflare Pages\ndescription: Apprenez à créer un raccourcisseur d'URL en utilisant Nitro et à le déployer sur Cloudflare Pages, permettant un développement dans l'edge sans effort.\ndate: 2024-03-19\n---\n\nDans cet article, nous allons développer un **raccourcisseur d'URL** en utilisant [**Nitro**](https:\u002F\u002Fnitro.unjs.io) et le déployer sur [**Cloudflare Pages**](https:\u002F\u002Fpages.cloudflare.com\u002F).\n\nLe code source est accessible sur \u003CGitHubLink repo=\"barbapapazes\u002Furl-shortener\" \u002F>.\n\n**Nitro** représente une nouvelle génération de **kit d'outils serveur**. Il nous permet de construire des serveurs web avec toutes les fonctionnalités nécessaires et de les déployer à notre convenance.\n\n**Cloudflare Pages** est une plateforme conçue pour construire et héberger des sites web sur le edge. Elle prend en charge des services comme [**KV**](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fkv\u002F) pour créer des applications complètes et à état.\n\nNotre projet est un **raccourcisseur d'URL** simple qui facilite la conversion d'une longue URL en une version raccourcie. Nous allons utiliser le **Cloudflare KV** pour stocker les URL et le **serveur Nitro** pour gérer les requêtes.\n\nNous allons utiliser :\n\n- [**unstorage**](https:\u002F\u002Funstorage.unjs.io) pour rationaliser le processus de développement en abstraire la couche **KV**, éliminant ainsi le besoin du **Cloudflare Wrangler CLI**.\n- [**ohash**](https:\u002F\u002Fohash.unjs.io) pour générer un **hash** à partir de l'URL afin de prévenir les collisions.\n- [**nanojsx**](https:\u002F\u002Fnanojsx.io\u002F) pour construire les **pages HTML** en utilisant **TSX**.\n- [**pico.css**](https:\u002F\u002Fpicocss.com) pour styliser l'application.\n\n## Initialisation du Projet\n\nTout d'abord, créez un nouveau **projet Nitro** :\n\n```bash\nnpx giget@latest nitro url-shortener\n```\n\nEnsuite, naviguez jusqu'au projet et installez les **dépendances** requises :\n\n```bash\ncd url-shortener\nnpm install\n```\n\nDémarrez le **serveur de développement** pour voir la page par défaut de Nitro :\n\n```bash\nnpm run dev\n```\n\nOuvrez votre navigateur et visitez [http:\u002F\u002Flocalhost:3000](http:\u002F\u002Flocalhost:3000) pour vérifier la fonctionnalité.\n\n## Construction du Raccourcisseur d'URL\n\nDans un premier temps, installez les paquets nécessaires :\n\n```bash\nnpm install ohash nano-jsx\n```\n\n### Générer une URL Raccourcie\n\nCréez une **route** nommée `index.get.tsx` dans le répertoire `server\u002Froutes`. Cela servira de **page d'accueil** de notre raccourcisseur d'URL où les utilisateurs peuvent générer une URL raccourcie à partir d'une longue.\n\n```tsx [server\u002Froutes\u002Findex.get.tsx]\nimport { h, Helmet, renderSSR } from 'nano-jsx' \u002F\u002F le `h` est crucial ici\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineLazyEventHandler(() => {\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>Raccourcisseur d'URL avec Nitro\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Raccourcir une URL\u003C\u002Fh2>\n        \u003Cform action=\"\u002Fcreate\" method=\"POST\">\n          \u003Cinput type=\"url\" name=\"url\" placeholder=\"URL à raccourcir\" autocomplete=\"off\" \u002F>\n          \u003Cbutton type=\"submit\">Créer\u003C\u002Fbutton>\n        \u003C\u002Fform>\n      \u003C\u002Fdiv>\n    )\n  }\n  const app = renderSSR(\u003CApp \u002F>)\n  const { body, head } = Helmet.SSR(app)\n\n  const page = withTemplate({\n    body,\n    head,\n  })\n\n  return defineEventHandler(() => {\n    return page\n  })\n})\n```\n\nCette route présentera un formulaire permettant aux utilisateurs de saisir une URL à raccourcir. Lors de la soumission du formulaire, une **requête POST** est envoyée à la route `\u002Fcreate`.\n\nUn **gestionnaire d'événements paresseux** est utilisé pour générer la vue une seule fois, lorsque la requête atteint le serveur. La réponse est ensuite **mise en cache en mémoire** et réutilisée pour les requêtes futures, réduisant ainsi la charge computationnelle.\n\nLa fonction `withTemplate` sert d'utilitaire que nous devons construire.\n\n```ts [server\u002Fresources\u002Ftemplate.tsx]\ninterface LayoutProps {\n  body: string\n  head: string[]\n}\n\nexport function withTemplate(props: LayoutProps) {\n  const { head, body } = props\n\n  return \u002F* html *\u002F`\u003Chtml>\n      \u003Chead>\n       \u003Clink\n          rel=\"stylesheet\"\n          href=\"https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002F@picocss\u002Fpico@2\u002Fcss\u002Fpico.min.css\"\n        \u002F>\n        ${head.join('\\n')}\n      \u003C\u002Fhead>\n      \u003Cbody>\n        \u003Cheader class=\"container\">\n          \u003Ch1>\n            \u003Ca href=\"\u002F\">Raccourcisseur d'URL avec Nitro\u003C\u002Fa>\n          \u003C\u002Fh1>\n        \u003C\u002Fheader>\n        \u003Cmain class=\"container\">\n          ${body}\n        \u003C\u002Fmain>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>`\n}\n```\n\nCeci est un simple **template de chaîne** où nous incorporons le contenu de l'en-tête et du corps générés par **nano-jsx**.\n\n### Stockage de l'URL\n\nAvant de procéder, installez `zod` pour valider le corps de la requête.\n\n```bash\nnpm install zod\n```\n\nCréez la route `\u002Fcreate` pour gérer la **requête POST** et stocker l'URL dans le **KV**. Cette route est étiquetée `create.post.tsx` et se trouve dans le répertoire `server\u002Froutes`.\n\n```tsx [server\u002Froutes\u002Fcreate.post.tsx]\nimport { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod' \u002F\u002F le `h` est essentiel ici\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineEventHandler(async (event) => {\n  const body = await readValidatedBody(event, z.object({\n    url: z.string().url(),\n  }).parse)\n\n  const requestURL = getRequestURL(event)\n  const id = hash(body.url)\n  const shortenURL = new URL(`\u002F${id}`, requestURL).href\n\n  await useStorage('data').setItem(id, body.url)\n\n  const App = () => {\n    return (\n      \u003Cdiv>\n        \u003CHelmet>\n          \u003Ctitle>Créé\u003C\u002Ftitle>\n        \u003C\u002FHelmet>\n        \u003Ch2>Créé et prêt\u003C\u002Fh2>\n        \u003Cinput\n          type=\"text\"\n          value={shortenURL}\n          autofocus\n        \u002F>\n      \u003C\u002Fdiv>\n    )\n  }\n\n  const app = renderSSR(\u003CApp \u002F>)\n  const { body: nanoBody, head } = Helmet.SSR(app)\n\n  return withTemplate({\n    body: nanoBody,\n    head,\n  })\n})\n```\n\nDans ce segment, nous utilisons la fonction `readValidatedBody` pour **valider le corps de la requête**. Cela assure que le champ `url` est une URL légitime, sinon une erreur est générée.\n\nNous récupérons l'**URL de la requête** en utilisant la fonction `getRequestURL` de `h3`.\n\nLe **hash** est créé à partir de l'URL du corps en utilisant la fonction `hash` de **ohash**, garantissant une génération de hash cohérente pour éviter les collisions.\n\nL'URL est stockée dans le **KV** en utilisant la fonction `useStorage` de **unstorage**, employant le namespace `data` pour stocker les URL, préconfiguré pour notre utilisation.\n\n\u003CCalloutReadMore text=\"Stockage KV\" href=\"https:\u002F\u002Fnitro.unjs.io\u002Fguide\u002Fstorage#usage\" \u002F>\n\nÀ la fin de ce gestionnaire d'événements, une route est renvoyée avec l'URL raccourcie que les utilisateurs peuvent copier et utiliser.\n\nPour le développement, tout fonctionne correctement, mais nous devons mettre à jour cette configuration, `nitro.config.ts`, pour l'environnement de production.\n\n```ts [nitro.config.ts]\nexport default defineNitroConfig({\n  srcDir: 'server',\n  $production: {\n    storage: { data: { driver: 'cloudflare-kv-binding', binding: 'url-shortener' } },\n  },\n})\n```\n\nDans cette configuration, nous définissons le namespace `data`, comme dans le développement, afin d'utiliser le driver **cloudflare-kv-binding** et le binding `url-shortener`, spécifié sous la clé `$production`. Cela indique que la configuration s'applique uniquement à l'**environnement de production** pour accéder au **Cloudflare KV**.\n\n\u003CCalloutReadMore text=\"Configuration spécifique à l'environnement\" href=\"https:\u002F\u002Fgithub.com\u002Funjs\u002Fc12#environment-specific-configuration\" \u002F>\n\n### Redirection vers l'URL Originale\n\nEnfin, nous devons établir une route pour gérer les URL raccourcies et rediriger les utilisateurs vers l'URL principale. Cette route est nommée `[id].get.ts` et est située dans le répertoire `server\u002Froutes`.\n\n```ts [server\u002Froutes\u002F[id].get.ts]\nexport default defineEventHandler(async (event) => {\n  const id = getRouterParam(event, 'short')\n\n  const value = await useStorage\u003Cstring>('data').getItem(id)\n\n  if (!value) {\n    throw createError({\n      statusCode: 404,\n      statusMessage: 'Non trouvé',\n    })\n  }\n\n  return sendRedirect(event, value)\n})\n```\n\nIci, nous obtenons simplement l'`id` à partir des paramètres du routeur et utilisons `useStorage` pour récupérer l'**URL** du **KV**. Si l'URL n'existe pas, une **erreur 404** est générée. Sinon, l'utilisateur est redirigé vers l'URL originale.\n\n## Déployer le Raccourcisseur d'URL\n\nFélicitations ! Nous avons réussi à créer un raccourcisseur d'URL de base en utilisant Nitro. La prochaine étape consiste à **le déployer** sur Cloudflare Pages. Rassurez-vous, c'est simple grâce aux **fournisseurs de déploiement sans configuration**.\n\n\u003CCalloutReadMore text=\"Fournisseurs Zero Config\" href=\"https:\u002F\u002Fnitro.unjs.io\u002Fdeploy#zero-config-providers\" \u002F>\n\n> [!WARNING]\n> Un compte Cloudflare et un dépôt GitHub sont nécessaires pour continuer avec le déploiement.\nTout d'abord, connectez-vous au tableau de bord Cloudflare et sélectionnez un compte. Naviguez vers Accueil du compte, choisissez Workers & Pages, et créez une nouvelle application en utilisant le bouton en haut à droite. Sélectionnez l'onglet Pages et suivez les instructions pour établir une connexion GitHub.\n\nSélectionnez le dépôt, ajoutez la commande de construction `npm run build`, spécifiez le répertoire de sortie `dist`, incluez une variable d'environnement `NODE_ENV` avec la valeur `production`, puis enregistrez et déployez.\n\nMaintenant, attendons que le déploiement soit terminé. Une fois terminé, vous pouvez accéder au raccourcisseur d'URL en utilisant l'URL fournie par **Cloudflare Pages**.\n\n**Cependant**, il se peut qu'il ne fonctionne pas adéquatement initialement, car un **namespace KV** n'a pas été lié. Cela nécessite un lien manuel. Allez dans les paramètres du projet, sélectionnez les fonctions et faites défiler jusqu'à ce que vous trouviez **liaisons de namespace KV**. Ajoutez une liaison avec le nom `url-shortener` et choisissez le namespace désiré.\n\n> [!NOTE]\n> Créer un **namespace KV** est réalisable dans la section **Workers & Pages**.\nRedéployez votre application pour tenir compte de la nouvelle liaison de namespace KV. Une fois terminé, le raccourcisseur d'URL sera pleinement fonctionnel. :sparkles:\n\n> [!NOTE]\n> Si vous n'avez pas l'intention de maintenir le projet, n'oubliez pas de le supprimer du tableau de bord Cloudflare Pages afin d'éviter des dépenses inutiles.\n\n## Améliorations et Considérations Futures\n\nPour améliorer la sécurité, envisagez d'ajouter une simple **protection CSRF** en utilisant la syntaxe des objets gestionnaires. Modifiez le fichier `create.post.tsx` pour inclure un gestionnaire avant la requête pour la vérification de l'en-tête d'origine.\n\n```tsx [server\u002Froutes\u002Fcreate.post.tsx]\nimport { h, Helmet, renderSSR } from 'nano-jsx'\nimport { hash } from 'ohash'\nimport { z } from 'zod'\nimport { withTemplate } from '..\u002Fresources\u002Ftemplate'\n\nexport default defineEventHandler({\n  onBeforeResponse: async (event) => {\n    const requestURL = getRequestURL(event).origin\n    const origin = getRequestHeader(event, 'origin')\n\n    if (!origin) {\n      throw createError({\n        statusCode: 400,\n        statusMessage: 'Mauvaise requête',\n      })\n    }\n\n    if (origin !== requestURL) {\n      throw createError({\n        statusCode: 403,\n        statusMessage: 'Interdit',\n      })\n    }\n  },\n  handler: async (event) => {\n    const body = await readValidatedBody(event, z.object({\n      url: z.string().url(),\n    }).parse)\n\n    const requestURL = getRequestURL(event)\n    const id = hash(body.url)\n    const shortenURL = new URL(`\u002F${id}`, requestURL).href\n\n    await useStorage('data').setItem(id, body.url)\n\n    const App = () => {\n      return (\n        \u003Cdiv>\n          \u003CHelmet>\n            \u003Ctitle>Créé\u003C\u002Ftitle>\n          \u003C\u002FHelmet>\n          \u003Ch2>Créé et prêt\u003C\u002Fh2>\n          \u003Cinput\n            type=\"text\"\n            value={shortenURL}\n            autofocus\n          \u002F>\n        \u003C\u002Fdiv>\n      )\n    }\n\n    const app = renderSSR(\u003CApp \u002F>)\n    const { body: nanoBody, head } = Helmet.SSR(app)\n\n    return withTemplate({\n      body: nanoBody,\n      head,\n    })\n  },\n})\n```\n\nNous mettons en place un gestionnaire avant la requête pour vérifier l'**en-tête d'origine**. S'il y a un désaccord, une erreur est générée ; sinon, la requête passe au gestionnaire principal.\n\n> [!NOTE]\n> Pour des informations complètes sur la **protection CSRF**, consultez la [Fiche de Prévention contre les Attaques CSRF](https:\u002F\u002Fcheatsheetseries.owasp.org\u002Fcheatsheets\u002FCross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) par OWASP. Valider l'**en-tête d'origine** est une approche de défense basique pour **CSRF**. Cependant, il est conseillé de mettre en œuvre un mécanisme de protection plus robuste, tel qu'un token.\n\n## Conclusion\n\nDévelopper avec **Nitro** et **Cloudflare Pages** est simple et offre une expérience de développeur exceptionnelle grâce à la possibilité d'utiliser **TSX** et un stockage de développement, éliminant le besoin du **Cloudflare Wrangler CLI**.\n\nProfitez de votre parcours de développement avec **Nitro** et **Cloudflare Pages** ! 🚀\n\n> [!NOTE]\n> Le raccourcisseur d'URL est inspiré par [le Raccourcisseur d'URL de Yusuke Wada](https:\u002F\u002Fgithub.com\u002Fyusukebe\u002Furl-shortener).\n",{"title":63974,"description":66777},{"loc":66779},"fr\u002Fposts\u002Fbuild-a-url-shortener-with-nitro-on-cloudflare-pages","C_B2fnQfNM7IHG-Sccv50NWq-LKu-LWbjenWAuB37RE",[66786,67673,67984,68132,69035,71297,71364,72202,72668,73160,73537,73975,74122,75534,77698,77922,79804,80143,80363,80568,80700,80770,80990,81285,81944,82822,83116,83479,86026,89880,91158,91476,93377,93643,93761,93901,94051,95706,95960,96943,97098,97215,97456,99127,104094,117234,117536,117666,120013,120201,120342],{"id":30,"title":31,"body":66787,"contentId":1180,"date":1181,"description":1182,"extension":18,"meta":67670,"navigation":20,"path":1184,"rawbody":1185,"robots":23,"schemaOrg":23,"seo":67671,"sitemap":67672,"stem":1188,"__hash__":1189},{"type":7,"value":66788,"toc":67663},[66789,66793,66795,66797,66799,66801,66803,66805,66807,66819,66821,66823,66825,66833,66835,66837,66842,66850,66852,66857,66859,66861,66863,66865,66867,66872,66878,66880,67038,67048,67074,67083,67089,67171,67173,67475,67484,67486,67491,67635,67644,67646,67648,67650,67652,67654,67656,67661],[35,66790,37,66791,43],{},[39,66792,42],{"href":41},[35,66794,46],{},[35,66796,49],{},[51,66798,54],{"id":53},[35,66800,57],{},[35,66802,60],{},[35,66804,63],{},[35,66806,66],{},[68,66808,66809,66814],{},[71,66810,66811,79],{},[39,66812,78],{"href":75,"rel":66813},[77],[71,66815,66816,87],{},[39,66817,86],{"href":84,"rel":66818},[77],[35,66820,90],{},[51,66822,94],{"id":93},[35,66824,97],{},[35,66826,100,66827,106,66830,112],{},[39,66828,105],{"href":103,"rel":66829},[77],[39,66831,111],{"href":109,"rel":66832},[77],[35,66834,115],{},[51,66836,119],{"id":118},[35,66838,122,66839,128],{},[39,66840,127],{"href":125,"rel":66841},[77],[130,66843,132,66844,132,66848],{},[134,66845,136,66846,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,66847],{"src":140,"type":141},[143,66849,145],{},[35,66851,148],{},[35,66853,151,66854,157],{},[39,66855,156],{"href":154,"rel":66856},[77],[35,66858,160],{},[35,66860,163],{},[35,66862,166],{},[51,66864,170],{"id":169},[35,66866,173],{},[35,66868,66869,179],{},[39,66870,78],{"href":75,"rel":66871},[77],[68,66873,66874,66876],{},[71,66875,184],{},[71,66877,187],{},[35,66879,190],{},[192,66881,66882],{"className":194,"code":195,"language":196,"meta":13,"style":13},[156,66883,66884,66902,66916,66930,66938,66952,66960,66980,66984,67002,67006,67024,67028,67032],{"__ignoreMap":13},[200,66885,66886,66888,66890,66892,66894,66896,66898,66900],{"class":202,"line":203},[200,66887,207],{"class":206},[200,66889,211],{"class":210},[200,66891,215],{"class":214},[200,66893,218],{"class":210},[200,66895,215],{"class":214},[200,66897,224],{"class":223},[200,66899,227],{"class":210},[200,66901,230],{"class":214},[200,66903,66904,66906,66908,66910,66912,66914],{"class":202,"line":14},[200,66905,236],{"class":235},[200,66907,239],{"class":214},[200,66909,242],{"class":214},[200,66911,246],{"class":245},[200,66913,249],{"class":214},[200,66915,252],{"class":214},[200,66917,66918,66920,66922,66924,66926,66928],{"class":202,"line":255},[200,66919,258],{"class":235},[200,66921,239],{"class":214},[200,66923,242],{"class":214},[200,66925,265],{"class":245},[200,66927,249],{"class":214},[200,66929,252],{"class":214},[200,66931,66932,66934,66936],{"class":202,"line":272},[200,66933,275],{"class":235},[200,66935,239],{"class":214},[200,66937,280],{"class":214},[200,66939,66940,66942,66944,66946,66948,66950],{"class":202,"line":283},[200,66941,286],{"class":235},[200,66943,239],{"class":214},[200,66945,242],{"class":214},[200,66947,293],{"class":245},[200,66949,249],{"class":214},[200,66951,252],{"class":214},[200,66953,66954,66956,66958],{"class":202,"line":300},[200,66955,303],{"class":235},[200,66957,239],{"class":214},[200,66959,280],{"class":214},[200,66961,66962,66964,66966,66968,66970,66972,66974,66976,66978],{"class":202,"line":310},[200,66963,313],{"class":235},[200,66965,239],{"class":214},[200,66967,318],{"class":214},[200,66969,321],{"class":235},[200,66971,239],{"class":214},[200,66973,242],{"class":214},[200,66975,328],{"class":245},[200,66977,249],{"class":214},[200,66979,333],{"class":214},[200,66981,66982],{"class":202,"line":336},[200,66983,339],{"class":214},[200,66985,66986,66988,66990,66992,66994,66996,66998,67000],{"class":202,"line":342},[200,66987,345],{"class":235},[200,66989,239],{"class":214},[200,66991,350],{"class":210},[200,66993,249],{"class":214},[200,66995,355],{"class":245},[200,66997,249],{"class":214},[200,66999,360],{"class":210},[200,67001,252],{"class":214},[200,67003,67004],{"class":202,"line":365},[200,67005,368],{"class":214},[200,67007,67008,67010,67012,67014,67016,67018,67020,67022],{"class":202,"line":371},[200,67009,374],{"class":223},[200,67011,239],{"class":214},[200,67013,380],{"class":379},[200,67015,383],{"class":214},[200,67017,387],{"class":386},[200,67019,390],{"class":214},[200,67021,393],{"class":379},[200,67023,280],{"class":214},[200,67025,67026],{"class":202,"line":398},[200,67027,402],{"class":401},[200,67029,67030],{"class":202,"line":405},[200,67031,368],{"class":214},[200,67033,67034,67036],{"class":202,"line":410},[200,67035,413],{"class":214},[200,67037,416],{"class":210},[35,67039,419,67040,423,67042,426,67044,429,67046,433],{},[156,67041,422],{},[156,67043,218],{},[156,67045,218],{},[156,67047,432],{},[192,67049,67050],{"className":194,"code":436,"language":196,"meta":13,"style":13},[156,67051,67052],{"__ignoreMap":13},[200,67053,67054,67056,67058,67060,67062,67064,67066,67068,67070,67072],{"class":202,"line":203},[200,67055,443],{"class":379},[200,67057,446],{"class":210},[200,67059,449],{"class":214},[200,67061,452],{"class":206},[200,67063,211],{"class":210},[200,67065,215],{"class":214},[200,67067,218],{"class":210},[200,67069,215],{"class":214},[200,67071,463],{"class":223},[200,67073,466],{"class":210},[35,67075,469,67076,473,67078,481],{},[156,67077,472],{},[39,67079,67081],{"href":476,"rel":67080},[77],[156,67082,480],{},[35,67084,484,67085,488,67087,215],{},[156,67086,487],{},[156,67088,218],{},[192,67090,67091],{"className":194,"code":493,"language":196,"meta":13,"style":13},[156,67092,67093,67119,67123,67151,67165],{"__ignoreMap":13},[200,67094,67095,67097,67099,67101,67103,67105,67107,67109,67111,67113,67115,67117],{"class":202,"line":203},[200,67096,443],{"class":379},[200,67098,350],{"class":214},[200,67100,504],{"class":210},[200,67102,360],{"class":214},[200,67104,509],{"class":214},[200,67106,452],{"class":206},[200,67108,211],{"class":210},[200,67110,215],{"class":214},[200,67112,218],{"class":210},[200,67114,215],{"class":214},[200,67116,463],{"class":223},[200,67118,466],{"class":210},[200,67120,67121],{"class":202,"line":14},[200,67122,528],{"emptyLinePlaceholder":20},[200,67124,67125,67127,67129,67131,67133,67135,67137,67139,67141,67143,67145,67147,67149],{"class":202,"line":255},[200,67126,207],{"class":206},[200,67128,211],{"class":210},[200,67130,215],{"class":214},[200,67132,218],{"class":210},[200,67134,215],{"class":214},[200,67136,543],{"class":223},[200,67138,546],{"class":210},[200,67140,549],{"class":214},[200,67142,552],{"class":210},[200,67144,215],{"class":214},[200,67146,557],{"class":223},[200,67148,227],{"class":210},[200,67150,230],{"class":214},[200,67152,67153,67155,67157,67159,67161,67163],{"class":202,"line":272},[200,67154,566],{"class":235},[200,67156,239],{"class":214},[200,67158,242],{"class":214},[200,67160,573],{"class":245},[200,67162,249],{"class":214},[200,67164,252],{"class":214},[200,67166,67167,67169],{"class":202,"line":283},[200,67168,413],{"class":214},[200,67170,584],{"class":210},[35,67172,587],{},[192,67174,67175],{"className":590,"code":591,"language":592,"meta":13,"style":13},[156,67176,67177,67193,67205,67219,67223,67249,67269,67273,67299,67319,67323,67341,67355,67381,67407,67433,67441,67445,67467],{"__ignoreMap":13},[200,67178,67179,67181,67183,67185,67187,67189,67191],{"class":202,"line":203},[200,67180,599],{"class":214},[200,67182,602],{"class":235},[200,67184,605],{"class":379},[200,67186,449],{"class":214},[200,67188,610],{"class":214},[200,67190,613],{"class":245},[200,67192,616],{"class":214},[200,67194,67195,67197,67199,67201,67203],{"class":202,"line":14},[200,67196,621],{"class":379},[200,67198,449],{"class":214},[200,67200,610],{"class":214},[200,67202,628],{"class":245},[200,67204,616],{"class":214},[200,67206,67207,67209,67211,67213,67215,67217],{"class":202,"line":255},[200,67208,635],{"class":379},[200,67210,449],{"class":214},[200,67212,610],{"class":214},[200,67214,642],{"class":245},[200,67216,610],{"class":214},[200,67218,647],{"class":214},[200,67220,67221],{"class":202,"line":272},[200,67222,528],{"emptyLinePlaceholder":20},[200,67224,67225,67227,67229,67231,67233,67235,67237,67239,67241,67243,67245,67247],{"class":202,"line":283},[200,67226,656],{"class":214},[200,67228,659],{"class":235},[200,67230,662],{"class":379},[200,67232,449],{"class":214},[200,67234,610],{"class":214},[200,67236,669],{"class":245},[200,67238,610],{"class":214},[200,67240,674],{"class":214},[200,67242,677],{"class":210},[200,67244,680],{"class":214},[200,67246,659],{"class":235},[200,67248,647],{"class":214},[200,67250,67251,67253,67255,67257,67259,67261,67263,67265,67267],{"class":202,"line":300},[200,67252,656],{"class":214},[200,67254,355],{"class":235},[200,67256,321],{"class":379},[200,67258,449],{"class":214},[200,67260,697],{"class":245},[200,67262,700],{"class":379},[200,67264,449],{"class":214},[200,67266,669],{"class":245},[200,67268,647],{"class":214},[200,67270,67271],{"class":202,"line":310},[200,67272,528],{"emptyLinePlaceholder":20},[200,67274,67275,67277,67279,67281,67283,67285,67287,67289,67291,67293,67295,67297],{"class":202,"line":336},[200,67276,656],{"class":214},[200,67278,659],{"class":235},[200,67280,662],{"class":379},[200,67282,449],{"class":214},[200,67284,610],{"class":214},[200,67286,725],{"class":245},[200,67288,610],{"class":214},[200,67290,674],{"class":214},[200,67292,732],{"class":210},[200,67294,680],{"class":214},[200,67296,659],{"class":235},[200,67298,647],{"class":214},[200,67300,67301,67303,67305,67307,67309,67311,67313,67315,67317],{"class":202,"line":342},[200,67302,656],{"class":214},[200,67304,355],{"class":235},[200,67306,321],{"class":379},[200,67308,449],{"class":214},[200,67310,697],{"class":245},[200,67312,700],{"class":379},[200,67314,449],{"class":214},[200,67316,725],{"class":245},[200,67318,647],{"class":214},[200,67320,67321],{"class":202,"line":365},[200,67322,528],{"emptyLinePlaceholder":20},[200,67324,67325,67327,67329,67331,67333,67335,67337,67339],{"class":202,"line":371},[200,67326,656],{"class":214},[200,67328,769],{"class":235},[200,67330,700],{"class":379},[200,67332,449],{"class":214},[200,67334,610],{"class":214},[200,67336,769],{"class":245},[200,67338,610],{"class":214},[200,67340,782],{"class":379},[200,67342,67343,67345,67347,67349,67351,67353],{"class":202,"line":398},[200,67344,787],{"class":379},[200,67346,449],{"class":214},[200,67348,610],{"class":214},[200,67350,794],{"class":245},[200,67352,610],{"class":214},[200,67354,647],{"class":214},[200,67356,67357,67359,67361,67363,67365,67367,67369,67371,67373,67375,67377,67379],{"class":202,"line":405},[200,67358,803],{"class":214},[200,67360,806],{"class":235},[200,67362,809],{"class":379},[200,67364,449],{"class":214},[200,67366,610],{"class":214},[200,67368,816],{"class":245},[200,67370,610],{"class":214},[200,67372,674],{"class":214},[200,67374,823],{"class":210},[200,67376,680],{"class":214},[200,67378,806],{"class":235},[200,67380,647],{"class":214},[200,67382,67383,67385,67387,67389,67391,67393,67395,67397,67399,67401,67403,67405],{"class":202,"line":410},[200,67384,803],{"class":214},[200,67386,806],{"class":235},[200,67388,809],{"class":379},[200,67390,449],{"class":214},[200,67392,610],{"class":214},[200,67394,844],{"class":245},[200,67396,610],{"class":214},[200,67398,674],{"class":214},[200,67400,851],{"class":210},[200,67402,680],{"class":214},[200,67404,806],{"class":235},[200,67406,647],{"class":214},[200,67408,67409,67411,67413,67415,67417,67419,67421,67423,67425,67427,67429,67431],{"class":202,"line":860},[200,67410,803],{"class":214},[200,67412,806],{"class":235},[200,67414,809],{"class":379},[200,67416,449],{"class":214},[200,67418,610],{"class":214},[200,67420,873],{"class":245},[200,67422,610],{"class":214},[200,67424,674],{"class":214},[200,67426,880],{"class":210},[200,67428,680],{"class":214},[200,67430,806],{"class":235},[200,67432,647],{"class":214},[200,67434,67435,67437,67439],{"class":202,"line":889},[200,67436,892],{"class":214},[200,67438,769],{"class":235},[200,67440,647],{"class":214},[200,67442,67443],{"class":202,"line":899},[200,67444,528],{"emptyLinePlaceholder":20},[200,67446,67447,67449,67451,67453,67455,67457,67459,67461,67463,67465],{"class":202,"line":904},[200,67448,656],{"class":214},[200,67450,909],{"class":235},[200,67452,321],{"class":379},[200,67454,449],{"class":214},[200,67456,916],{"class":245},[200,67458,674],{"class":214},[200,67460,921],{"class":210},[200,67462,680],{"class":214},[200,67464,909],{"class":235},[200,67466,647],{"class":214},[200,67468,67469,67471,67473],{"class":202,"line":930},[200,67470,680],{"class":214},[200,67472,602],{"class":235},[200,67474,647],{"class":214},[938,67476,67477],{},[35,67478,67479,945,67481,215],{},[200,67480,944],{},[39,67482,950],{"href":948,"rel":67483},[77],[35,67485,953],{},[35,67487,956,67488,962],{},[39,67489,961],{"href":959,"rel":67490},[77],[192,67492,67493],{"className":194,"code":965,"language":196,"meta":13,"style":13},[156,67494,67495,67513,67531,67535,67547,67551,67569,67575,67589,67595,67599,67617,67631],{"__ignoreMap":13},[200,67496,67497,67499,67501,67503,67505,67507,67509,67511],{"class":202,"line":203},[200,67498,972],{"class":206},[200,67500,318],{"class":214},[200,67502,977],{"class":210},[200,67504,980],{"class":214},[200,67506,983],{"class":206},[200,67508,242],{"class":214},[200,67510,988],{"class":245},[200,67512,991],{"class":214},[200,67514,67515,67517,67519,67521,67523,67525,67527,67529],{"class":202,"line":14},[200,67516,972],{"class":206},[200,67518,318],{"class":214},[200,67520,1000],{"class":210},[200,67522,980],{"class":214},[200,67524,983],{"class":206},[200,67526,242],{"class":214},[200,67528,1009],{"class":245},[200,67530,991],{"class":214},[200,67532,67533],{"class":202,"line":255},[200,67534,528],{"emptyLinePlaceholder":20},[200,67536,67537,67539,67541,67543,67545],{"class":202,"line":272},[200,67538,443],{"class":379},[200,67540,1022],{"class":210},[200,67542,449],{"class":214},[200,67544,977],{"class":223},[200,67546,466],{"class":210},[200,67548,67549],{"class":202,"line":283},[200,67550,528],{"emptyLinePlaceholder":20},[200,67552,67553,67555,67557,67559,67561,67563,67565,67567],{"class":202,"line":300},[200,67554,443],{"class":379},[200,67556,318],{"class":214},[200,67558,1041],{"class":210},[200,67560,413],{"class":214},[200,67562,509],{"class":214},[200,67564,1000],{"class":223},[200,67566,227],{"class":210},[200,67568,230],{"class":214},[200,67570,67571,67573],{"class":202,"line":310},[200,67572,1056],{"class":210},[200,67574,252],{"class":214},[200,67576,67577,67579,67581,67583,67585,67587],{"class":202,"line":336},[200,67578,1063],{"class":235},[200,67580,239],{"class":214},[200,67582,242],{"class":214},[200,67584,1070],{"class":245},[200,67586,249],{"class":214},[200,67588,252],{"class":214},[200,67590,67591,67593],{"class":202,"line":342},[200,67592,413],{"class":214},[200,67594,416],{"class":210},[200,67596,67597],{"class":202,"line":365},[200,67598,528],{"emptyLinePlaceholder":20},[200,67600,67601,67603,67605,67607,67609,67611,67613,67615],{"class":202,"line":371},[200,67602,1089],{"class":206},[200,67604,452],{"class":206},[200,67606,1094],{"class":210},[200,67608,443],{"class":379},[200,67610,1099],{"class":210},[200,67612,1102],{"class":214},[200,67614,1105],{"class":210},[200,67616,230],{"class":214},[200,67618,67619,67621,67623,67625,67627,67629],{"class":202,"line":398},[200,67620,1112],{"class":210},[200,67622,215],{"class":214},[200,67624,1117],{"class":223},[200,67626,227],{"class":235},[200,67628,1122],{"class":210},[200,67630,416],{"class":235},[200,67632,67633],{"class":202,"line":405},[200,67634,1129],{"class":214},[938,67636,67637],{},[35,67638,67639,1136,67641,215],{},[200,67640,944],{},[39,67642,1140],{"href":103,"rel":67643},[77],[51,67645,1144],{"id":1143},[35,67647,1147],{},[35,67649,1150],{},[35,67651,1153],{},[35,67653,1156],{},[1158,67655],{},[35,67657,1162,67658,1168],{},[39,67659,1167],{"href":1165,"rel":67660},[77],[1170,67662,1172],{},{"title":13,"searchDepth":14,"depth":14,"links":67664},[67665,67666,67667,67668,67669],{"id":53,"depth":14,"text":54},{"id":93,"depth":14,"text":94},{"id":118,"depth":14,"text":119},{"id":169,"depth":14,"text":170},{"id":1143,"depth":14,"text":1144},{},{"title":31,"description":1182},{"loc":1184},{"id":1191,"title":1192,"body":67674,"contentId":1623,"date":1624,"description":1625,"extension":18,"meta":67981,"navigation":20,"path":1627,"rawbody":1628,"robots":23,"schemaOrg":23,"seo":67982,"sitemap":67983,"stem":1631,"__hash__":1632},{"type":7,"value":67675,"toc":67975},[67676,67678,67680,67682,67684,67686,67688,67692,67694,67696,67698,67705,67707,67712,67714,67718,67720,67724,67726,67728,67730,67732,67734,67736,67738,67745,67752,67754,67759,67761,67763,67765,67771,67773,67787,67789,67835,67837,67843,67845,67847,67861,67863,67943,67947,67949,67957,67959,67969,67973],[35,67677,1197],{},[35,67679,1200],{},[35,67681,1203],{},[35,67683,1206],{},[35,67685,1209],{},[35,67687,1212],{},[35,67689,67690],{},[1216,67691,1218],{},[51,67693,1222],{"id":1221},[35,67695,1225],{},[35,67697,1228],{},[35,67699,1231,67700],{},[1216,67701,1234,67702],{},[39,67703,1239],{"href":1237,"rel":67704},[77],[35,67706,1242],{},[35,67708,1245,67709,1251],{},[39,67710,1250],{"href":1248,"rel":67711},[77],[35,67713,1254],{},[35,67715,1257,67716,1261],{},[156,67717,1260],{},[35,67719,1264],{},[35,67721,1267,67722,1271],{},[1216,67723,1270],{},[35,67725,1274],{},[35,67727,1277],{},[51,67729,1281],{"id":1280},[35,67731,1284],{},[35,67733,1287],{},[35,67735,1290],{},[35,67737,1293],{},[35,67739,1296,67740,1302,67743,1306],{},[39,67741,1301],{"href":1299,"rel":67742},[77],[156,67744,1305],{},[35,67746,1309,67747,1314,67750,1318],{},[39,67748,1270],{"href":1312,"rel":67749},[77],[156,67751,1317],{},[35,67753,1321],{},[35,67755,1324,67756,215],{},[39,67757,1329],{"href":1327,"rel":67758},[77],[35,67760,1332],{},[51,67762,1336],{"id":1335},[35,67764,1339],{},[35,67766,1342,67767,1345,67769,215],{},[156,67768,1270],{},[156,67770,1348],{},[35,67772,1351],{},[192,67774,67775],{"className":1354,"code":1355,"filename":1356,"language":1357,"meta":13,"style":13},[156,67776,67777],{"__ignoreMap":13},[200,67778,67779,67781,67783,67785],{"class":202,"line":203},[200,67780,1365],{"class":1364},[200,67782,1368],{"class":245},[200,67784,1371],{"class":245},[200,67786,1374],{"class":245},[35,67788,1377],{},[192,67790,67791],{"className":1380,"code":1381,"filename":1382,"language":1383,"meta":13,"style":13},[156,67792,67793,67805,67813,67823,67829],{"__ignoreMap":13},[200,67794,67795,67797,67799,67801,67803],{"class":202,"line":203},[200,67796,1390],{"class":206},[200,67798,1393],{"class":206},[200,67800,1396],{"class":223},[200,67802,227],{"class":210},[200,67804,230],{"class":214},[200,67806,67807,67809,67811],{"class":202,"line":14},[200,67808,1405],{"class":235},[200,67810,239],{"class":214},[200,67812,1410],{"class":210},[200,67814,67815,67817,67819,67821],{"class":202,"line":255},[200,67816,1415],{"class":214},[200,67818,1418],{"class":245},[200,67820,249],{"class":214},[200,67822,252],{"class":214},[200,67824,67825,67827],{"class":202,"line":272},[200,67826,1427],{"class":210},[200,67828,252],{"class":214},[200,67830,67831,67833],{"class":202,"line":283},[200,67832,413],{"class":214},[200,67834,416],{"class":210},[35,67836,1438],{},[35,67838,1441,67839,1445,67841,1448],{},[156,67840,1444],{},[156,67842,1317],{},[35,67844,1451],{},[35,67846,1454],{},[192,67848,67849],{"className":1354,"code":1457,"filename":1356,"language":1357,"meta":13,"style":13},[156,67850,67851],{"__ignoreMap":13},[200,67852,67853,67855,67857,67859],{"class":202,"line":203},[200,67854,1365],{"class":1364},[200,67856,1368],{"class":245},[200,67858,1371],{"class":245},[200,67860,1470],{"class":245},[35,67862,1473],{},[192,67864,67865],{"className":1380,"code":1476,"filename":1477,"language":1383,"meta":13,"style":13},[156,67866,67867,67881,67899,67903,67915,67923,67931,67937],{"__ignoreMap":13},[200,67868,67869,67871,67873,67875,67877,67879],{"class":202,"line":203},[200,67870,972],{"class":206},[200,67872,1486],{"class":210},[200,67874,1489],{"class":206},[200,67876,242],{"class":214},[200,67878,1494],{"class":245},[200,67880,991],{"class":214},[200,67882,67883,67885,67887,67889,67891,67893,67895,67897],{"class":202,"line":14},[200,67884,972],{"class":206},[200,67886,318],{"class":214},[200,67888,1505],{"class":210},[200,67890,980],{"class":214},[200,67892,983],{"class":206},[200,67894,242],{"class":214},[200,67896,1514],{"class":245},[200,67898,991],{"class":214},[200,67900,67901],{"class":202,"line":255},[200,67902,528],{"emptyLinePlaceholder":20},[200,67904,67905,67907,67909,67911,67913],{"class":202,"line":272},[200,67906,1390],{"class":206},[200,67908,1393],{"class":206},[200,67910,1505],{"class":223},[200,67912,227],{"class":210},[200,67914,230],{"class":214},[200,67916,67917,67919,67921],{"class":202,"line":283},[200,67918,1537],{"class":235},[200,67920,239],{"class":214},[200,67922,1410],{"class":210},[200,67924,67925,67927,67929],{"class":202,"line":300},[200,67926,1546],{"class":223},[200,67928,1549],{"class":210},[200,67930,252],{"class":214},[200,67932,67933,67935],{"class":202,"line":310},[200,67934,1427],{"class":210},[200,67936,252],{"class":214},[200,67938,67939,67941],{"class":202,"line":336},[200,67940,413],{"class":214},[200,67942,416],{"class":210},[35,67944,1566,67945,1569],{},[156,67946,1348],{},[51,67948,1573],{"id":1572},[35,67950,1576,67951,1582,67954,1588],{},[39,67952,1581],{"href":1579,"rel":67953},[77],[39,67955,1587],{"href":1585,"rel":67956},[77],[35,67958,1591],{},[1593,67960,67961,67965],{},[71,67962,1597,67963],{},[1599,67964],{"repo":1601},[71,67966,1604,67967],{},[1599,67968],{"repo":1607},[35,67970,1610,67971],{},[1216,67972,1613],{},[1170,67974,1616],{},{"title":13,"searchDepth":14,"depth":14,"links":67976},[67977,67978,67979,67980],{"id":1221,"depth":14,"text":1222},{"id":1280,"depth":14,"text":1281},{"id":1335,"depth":14,"text":1336},{"id":1572,"depth":14,"text":1573},{},{"title":1192,"description":1625},{"loc":1627},{"id":1634,"title":1635,"body":67985,"contentId":1852,"date":1624,"description":1853,"extension":18,"meta":68122,"navigation":20,"path":1855,"rawbody":1856,"robots":23,"schemaOrg":23,"seo":68123,"sitemap":68124,"stem":1866,"__hash__":1867},{"type":7,"value":67986,"toc":68117},[67987,67989,67991,67993,68001,68003,68005,68007,68009,68011,68013,68023,68025,68027,68029,68033,68035,68037,68039,68041,68046,68048,68050,68052,68054,68061,68065,68070,68074,68078,68083,68087,68091,68093,68095,68097,68099,68101,68103,68105,68109,68111,68113,68115],[35,67988,1640],{},[35,67990,1643],{},[35,67992,1646],{},[35,67994,1649,67995,1655,67998,1661],{},[39,67996,1654],{"href":1652,"rel":67997},[77],[39,67999,1660],{"href":1658,"rel":68000},[77],[35,68002,1664],{},[51,68004,1668],{"id":1667},[35,68006,1671],{},[35,68008,1674],{},[35,68010,1677],{},[35,68012,1680],{},[1593,68014,68015,68017,68019,68021],{},[71,68016,1685],{},[71,68018,1688],{},[71,68020,1691],{},[71,68022,1694],{},[35,68024,1697],{},[35,68026,1700],{},[35,68028,1703],{},[35,68030,68031],{},[1707,68032],{"alt":1709,"src":1710},[51,68034,1714],{"id":1713},[35,68036,1717],{},[35,68038,1720],{},[35,68040,1723],{},[35,68042,1726,68043,1732],{},[39,68044,1731],{"href":1729,"rel":68045},[77],[35,68047,1735],{},[35,68049,1738],{},[35,68051,1741],{},[35,68053,1744],{},[35,68055,1747,68056,1753,68059,215],{},[39,68057,1752],{"href":1750,"rel":68058},[77],[39,68060,1757],{"href":1756},[35,68062,68063],{},[1707,68064],{"alt":1762,"src":1763},[35,68066,1766,68067,1772],{},[39,68068,1771],{"href":1769,"rel":68069},[77],[35,68071,68072],{},[1707,68073],{"alt":1777,"src":1778},[35,68075,68076],{},[1707,68077],{"alt":1783,"src":1784},[35,68079,1787,68080,1793],{},[39,68081,1792],{"href":1790,"rel":68082},[77],[35,68084,68085],{},[1707,68086],{"alt":1798,"src":1799},[35,68088,68089],{},[1707,68090],{"alt":1804,"src":1805},[51,68092,1809],{"id":1808},[35,68094,1812],{},[35,68096,1815],{},[35,68098,1818],{},[35,68100,1821],{},[35,68102,1824],{},[35,68104,1827],{},[35,68106,1830,68107,1835],{},[39,68108,1834],{"href":1833},[35,68110,1838],{},[35,68112,1841],{},[1158,68114],{},[35,68116,1846],{},{"title":13,"searchDepth":14,"depth":14,"links":68118},[68119,68120,68121],{"id":1667,"depth":14,"text":1668},{"id":1713,"depth":14,"text":1714},{"id":1808,"depth":14,"text":1809},{},{"title":1635,"description":1853},{"loc":1855,"images":68125},[68126,68127,68128,68129,68130,68131],{"loc":1710},{"loc":1763},{"loc":1778},{"loc":1784},{"loc":1799},{"loc":1805},{"id":1869,"title":1870,"body":68133,"contentId":3009,"date":3010,"description":3011,"extension":18,"meta":69032,"navigation":20,"path":3013,"rawbody":3014,"robots":23,"schemaOrg":23,"seo":69033,"sitemap":69034,"stem":3017,"__hash__":3018},{"type":7,"value":68134,"toc":69021},[68135,68142,68144,68152,68154,68158,68160,68162,68164,68166,68168,68170,68172,68176,68336,68342,68344,68352,68360,68362,68374,68378,68478,68480,68487,68489,68491,68499,68503,68505,68510,68512,68524,68528,68606,68608,68610,68612,68617,68621,68623,68625,68627,68629,68631,68638,68642,68644,68656,68660,68782,68787,68789,68801,68805,68891,68899,68901,68909,68911,68916,68918,68926,68936,68941,68943,68955,68963,68965,68967,69019],[35,68136,1875,68137,1881,68140],{},[39,68138,1880],{"href":1878,"rel":68139},[77],[1216,68141,1884],{},[51,68143,1888],{"id":1887},[35,68145,1891,68146,1897,68149,215],{},[39,68147,1896],{"href":1894,"rel":68148},[77],[39,68150,1902],{"href":1900,"rel":68151},[77],[35,68153,1905],{},[35,68155,1908,68156],{},[1216,68157,1911],{},[51,68159,1915],{"id":1914},[35,68161,1918],{},[35,68163,1921],{},[35,68165,1924],{},[35,68167,1927],{},[1929,68169,1932],{"id":1931},[35,68171,1935],{},[35,68173,1938,68174,1942],{},[156,68175,1941],{},[192,68177,68178],{"className":1380,"code":1945,"language":1383,"meta":13,"style":13},[156,68179,68180,68206,68210,68228,68236,68260,68264,68272,68282,68296,68320,68326,68330],{"__ignoreMap":13},[200,68181,68182,68184,68186,68188,68190,68192,68194,68196,68198,68200,68202,68204],{"class":202,"line":203},[200,68183,972],{"class":206},[200,68185,318],{"class":214},[200,68187,1956],{"class":210},[200,68189,549],{"class":214},[200,68191,1961],{"class":210},[200,68193,549],{"class":214},[200,68195,1966],{"class":210},[200,68197,980],{"class":214},[200,68199,983],{"class":206},[200,68201,242],{"class":214},[200,68203,1975],{"class":245},[200,68205,991],{"class":214},[200,68207,68208],{"class":202,"line":14},[200,68209,528],{"emptyLinePlaceholder":20},[200,68211,68212,68214,68216,68218,68220,68222,68224,68226],{"class":202,"line":255},[200,68213,1390],{"class":206},[200,68215,1393],{"class":206},[200,68217,1966],{"class":223},[200,68219,599],{"class":214},[200,68221,1994],{"class":1364},[200,68223,674],{"class":214},[200,68225,227],{"class":210},[200,68227,230],{"class":214},[200,68229,68230,68232,68234],{"class":202,"line":272},[200,68231,2005],{"class":235},[200,68233,1549],{"class":214},[200,68235,280],{"class":214},[200,68237,68238,68240,68242,68244,68246,68248,68250,68252,68254,68256,68258],{"class":202,"line":283},[200,68239,2014],{"class":379},[200,68241,2017],{"class":210},[200,68243,509],{"class":214},[200,68245,1961],{"class":223},[200,68247,227],{"class":235},[200,68249,972],{"class":206},[200,68251,215],{"class":214},[200,68253,2030],{"class":210},[200,68255,215],{"class":214},[200,68257,2035],{"class":210},[200,68259,416],{"class":235},[200,68261,68262],{"class":202,"line":300},[200,68263,528],{"emptyLinePlaceholder":20},[200,68265,68266,68268,68270],{"class":202,"line":310},[200,68267,2046],{"class":223},[200,68269,227],{"class":235},[200,68271,230],{"class":214},[200,68273,68274,68276,68278,68280],{"class":202,"line":336},[200,68275,2055],{"class":235},[200,68277,239],{"class":214},[200,68279,2061],{"class":2060},[200,68281,252],{"class":214},[200,68283,68284,68286,68288,68290,68292,68294],{"class":202,"line":342},[200,68285,2068],{"class":235},[200,68287,239],{"class":214},[200,68289,242],{"class":214},[200,68291,1941],{"class":245},[200,68293,249],{"class":214},[200,68295,252],{"class":214},[200,68297,68298,68300,68302,68304,68306,68308,68310,68312,68314,68316,68318],{"class":202,"line":365},[200,68299,2083],{"class":235},[200,68301,239],{"class":214},[200,68303,2017],{"class":210},[200,68305,215],{"class":214},[200,68307,2092],{"class":223},[200,68309,227],{"class":235},[200,68311,249],{"class":214},[200,68313,2099],{"class":245},[200,68315,249],{"class":214},[200,68317,2104],{"class":235},[200,68319,252],{"class":214},[200,68321,68322,68324],{"class":202,"line":371},[200,68323,2111],{"class":214},[200,68325,416],{"class":235},[200,68327,68328],{"class":202,"line":398},[200,68329,368],{"class":214},[200,68331,68332,68334],{"class":202,"line":405},[200,68333,413],{"class":214},[200,68335,416],{"class":210},[35,68337,2126,68338,2129,68340,2133],{},[156,68339,1941],{},[156,68341,2132],{},[35,68343,2136],{},[130,68345,132,68346,132,68350],{},[134,68347,136,68348,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,68349],{"src":2143,"type":141},[143,68351,2146],{},[35,68353,2149,68354,2155,68357,215],{},[39,68355,2154],{"href":2152,"rel":68356},[77],[39,68358,2160],{"href":2158,"rel":68359},[77],[35,68361,2163],{},[192,68363,68364],{"className":2166,"code":2167,"language":2168,"meta":13,"style":13},[156,68365,68366],{"__ignoreMap":13},[200,68367,68368,68370,68372],{"class":202,"line":203},[200,68369,1365],{"class":1364},[200,68371,2177],{"class":245},[200,68373,2180],{"class":245},[35,68375,2183,68376,2186],{},[156,68377,1382],{},[192,68379,68380],{"className":1380,"code":2189,"language":1383,"meta":13,"style":13},[156,68381,68382,68400,68404,68416,68434,68442,68450,68464,68468,68472],{"__ignoreMap":13},[200,68383,68384,68386,68388,68390,68392,68394,68396,68398],{"class":202,"line":203},[200,68385,972],{"class":206},[200,68387,318],{"class":214},[200,68389,1396],{"class":210},[200,68391,980],{"class":214},[200,68393,983],{"class":206},[200,68395,242],{"class":214},[200,68397,2208],{"class":245},[200,68399,991],{"class":214},[200,68401,68402],{"class":202,"line":14},[200,68403,528],{"emptyLinePlaceholder":20},[200,68405,68406,68408,68410,68412,68414],{"class":202,"line":255},[200,68407,1390],{"class":206},[200,68409,1393],{"class":206},[200,68411,1396],{"class":223},[200,68413,227],{"class":210},[200,68415,230],{"class":214},[200,68417,68418,68420,68422,68424,68426,68428,68430,68432],{"class":202,"line":272},[200,68419,1405],{"class":235},[200,68421,239],{"class":214},[200,68423,350],{"class":210},[200,68425,249],{"class":214},[200,68427,2239],{"class":245},[200,68429,249],{"class":214},[200,68431,360],{"class":210},[200,68433,252],{"class":214},[200,68435,68436,68438,68440],{"class":202,"line":283},[200,68437,2250],{"class":235},[200,68439,239],{"class":214},[200,68441,280],{"class":214},[200,68443,68444,68446,68448],{"class":202,"line":300},[200,68445,2259],{"class":235},[200,68447,239],{"class":214},[200,68449,280],{"class":214},[200,68451,68452,68454,68456,68458,68460,68462],{"class":202,"line":310},[200,68453,2268],{"class":235},[200,68455,239],{"class":214},[200,68457,242],{"class":214},[200,68459,2275],{"class":245},[200,68461,249],{"class":214},[200,68463,252],{"class":214},[200,68465,68466],{"class":202,"line":336},[200,68467,339],{"class":214},[200,68469,68470],{"class":202,"line":342},[200,68471,368],{"class":214},[200,68473,68474,68476],{"class":202,"line":365},[200,68475,413],{"class":214},[200,68477,416],{"class":210},[35,68479,2296],{},[35,68481,2299,68482,2305,68485],{},[39,68483,2304],{"href":2302,"rel":68484},[77],[1216,68486,2308],{},[51,68488,2312],{"id":2311},[35,68490,2315],{},[68,68492,68493,68495,68497],{},[71,68494,2320],{},[71,68496,2323],{},[71,68498,2326],{},[35,68500,2329,68501,2333],{},[156,68502,2332],{},[35,68504,2336],{},[35,68506,2339,68507,2186],{},[39,68508,2344],{"href":2342,"rel":68509},[77],[35,68511,2347],{},[192,68513,68514],{"className":2166,"code":2350,"language":2168,"meta":13,"style":13},[156,68515,68516],{"__ignoreMap":13},[200,68517,68518,68520,68522],{"class":202,"line":203},[200,68519,1365],{"class":1364},[200,68521,2177],{"class":245},[200,68523,2361],{"class":245},[35,68525,2364,68526,2186],{},[156,68527,1382],{},[192,68529,68530],{"className":1380,"code":2369,"language":1383,"meta":13,"style":13},[156,68531,68532,68544,68552,68562,68572,68578,68586,68596,68600],{"__ignoreMap":13},[200,68533,68534,68536,68538,68540,68542],{"class":202,"line":203},[200,68535,1390],{"class":206},[200,68537,1393],{"class":206},[200,68539,1396],{"class":223},[200,68541,227],{"class":210},[200,68543,230],{"class":214},[200,68545,68546,68548,68550],{"class":202,"line":14},[200,68547,1405],{"class":235},[200,68549,239],{"class":214},[200,68551,1410],{"class":210},[200,68553,68554,68556,68558,68560],{"class":202,"line":255},[200,68555,1415],{"class":214},[200,68557,2398],{"class":245},[200,68559,249],{"class":214},[200,68561,252],{"class":214},[200,68563,68564,68566,68568,68570],{"class":202,"line":272},[200,68565,1415],{"class":214},[200,68567,2409],{"class":245},[200,68569,249],{"class":214},[200,68571,252],{"class":214},[200,68573,68574,68576],{"class":202,"line":283},[200,68575,1427],{"class":210},[200,68577,252],{"class":214},[200,68579,68580,68582,68584],{"class":202,"line":300},[200,68581,2424],{"class":235},[200,68583,239],{"class":214},[200,68585,280],{"class":214},[200,68587,68588,68590,68592,68594],{"class":202,"line":310},[200,68589,2433],{"class":235},[200,68591,239],{"class":214},[200,68593,2439],{"class":2438},[200,68595,252],{"class":214},[200,68597,68598],{"class":202,"line":336},[200,68599,368],{"class":214},[200,68601,68602,68604],{"class":202,"line":342},[200,68603,413],{"class":214},[200,68605,416],{"class":210},[35,68607,2454],{},[35,68609,2457],{},[51,68611,2461],{"id":2460},[35,68613,2464,68614,2470],{},[39,68615,2469],{"href":2467,"rel":68616},[77],[35,68618,2473,68619,2477],{},[156,68620,2476],{},[35,68622,2480],{},[35,68624,2483],{},[1929,68626,2487],{"id":2486},[35,68628,2490],{},[35,68630,2493],{},[35,68632,2496,68633,2502,68636],{},[39,68634,2501],{"href":2499,"rel":68635},[77],[1599,68637,215],{"repo":2505},[35,68639,2508,68640,2512],{},[156,68641,2511],{},[35,68643,2515],{},[192,68645,68646],{"className":2166,"code":2518,"language":2168,"meta":13,"style":13},[156,68647,68648],{"__ignoreMap":13},[200,68649,68650,68652,68654],{"class":202,"line":203},[200,68651,1365],{"class":1364},[200,68653,2177],{"class":245},[200,68655,2529],{"class":245},[35,68657,2532,68658,2186],{},[156,68659,2535],{},[192,68661,68662],{"className":1380,"code":2538,"language":1383,"meta":13,"style":13},[156,68663,68664,68668,68686,68704,68708,68720,68728,68736,68744,68758,68766,68772,68776],{"__ignoreMap":13},[200,68665,68666],{"class":202,"line":203},[200,68667,2545],{"class":401},[200,68669,68670,68672,68674,68676,68678,68680,68682,68684],{"class":202,"line":14},[200,68671,972],{"class":206},[200,68673,318],{"class":214},[200,68675,1505],{"class":210},[200,68677,980],{"class":214},[200,68679,983],{"class":206},[200,68681,242],{"class":214},[200,68683,2562],{"class":245},[200,68685,991],{"class":214},[200,68687,68688,68690,68692,68694,68696,68698,68700,68702],{"class":202,"line":255},[200,68689,972],{"class":206},[200,68691,318],{"class":214},[200,68693,2573],{"class":210},[200,68695,980],{"class":214},[200,68697,983],{"class":206},[200,68699,242],{"class":214},[200,68701,2582],{"class":245},[200,68703,991],{"class":214},[200,68705,68706],{"class":202,"line":272},[200,68707,528],{"emptyLinePlaceholder":20},[200,68709,68710,68712,68714,68716,68718],{"class":202,"line":283},[200,68711,1390],{"class":206},[200,68713,1393],{"class":206},[200,68715,1505],{"class":223},[200,68717,227],{"class":210},[200,68719,230],{"class":214},[200,68721,68722,68724,68726],{"class":202,"line":300},[200,68723,2605],{"class":235},[200,68725,239],{"class":214},[200,68727,280],{"class":214},[200,68729,68730,68732,68734],{"class":202,"line":310},[200,68731,2614],{"class":235},[200,68733,239],{"class":214},[200,68735,1410],{"class":210},[200,68737,68738,68740,68742],{"class":202,"line":336},[200,68739,2623],{"class":223},[200,68741,227],{"class":210},[200,68743,230],{"class":214},[200,68745,68746,68748,68750,68752,68754,68756],{"class":202,"line":342},[200,68747,2632],{"class":235},[200,68749,239],{"class":214},[200,68751,242],{"class":214},[200,68753,2275],{"class":245},[200,68755,249],{"class":214},[200,68757,252],{"class":214},[200,68759,68760,68762,68764],{"class":202,"line":365},[200,68761,2647],{"class":214},[200,68763,2104],{"class":210},[200,68765,252],{"class":214},[200,68767,68768,68770],{"class":202,"line":371},[200,68769,2656],{"class":210},[200,68771,252],{"class":214},[200,68773,68774],{"class":202,"line":398},[200,68775,368],{"class":214},[200,68777,68778,68780],{"class":202,"line":405},[200,68779,413],{"class":214},[200,68781,416],{"class":210},[35,68783,2671,68784,2675],{},[39,68785,2344],{"href":2342,"rel":68786},[77],[35,68788,2347],{},[192,68790,68791],{"className":2166,"code":2350,"language":2168,"meta":13,"style":13},[156,68792,68793],{"__ignoreMap":13},[200,68794,68795,68797,68799],{"class":202,"line":203},[200,68796,1365],{"class":1364},[200,68798,2177],{"class":245},[200,68800,2361],{"class":245},[35,68802,2692,68803,2186],{},[156,68804,2535],{},[192,68806,68807],{"className":1380,"code":2697,"language":1383,"meta":13,"style":13},[156,68808,68809,68827,68845,68849,68861,68885],{"__ignoreMap":13},[200,68810,68811,68813,68815,68817,68819,68821,68823,68825],{"class":202,"line":203},[200,68812,972],{"class":206},[200,68814,318],{"class":214},[200,68816,2708],{"class":210},[200,68818,980],{"class":214},[200,68820,983],{"class":206},[200,68822,242],{"class":214},[200,68824,2717],{"class":245},[200,68826,991],{"class":214},[200,68828,68829,68831,68833,68835,68837,68839,68841,68843],{"class":202,"line":14},[200,68830,972],{"class":206},[200,68832,318],{"class":214},[200,68834,1505],{"class":210},[200,68836,980],{"class":214},[200,68838,983],{"class":206},[200,68840,242],{"class":214},[200,68842,2562],{"class":245},[200,68844,991],{"class":214},[200,68846,68847],{"class":202,"line":255},[200,68848,528],{"emptyLinePlaceholder":20},[200,68850,68851,68853,68855,68857,68859],{"class":202,"line":272},[200,68852,1390],{"class":206},[200,68854,1393],{"class":206},[200,68856,1505],{"class":223},[200,68858,227],{"class":210},[200,68860,230],{"class":214},[200,68862,68863,68865,68867,68869,68871,68873,68875,68877,68879,68881,68883],{"class":202,"line":283},[200,68864,2758],{"class":235},[200,68866,239],{"class":214},[200,68868,2708],{"class":223},[200,68870,227],{"class":210},[200,68872,2767],{"class":214},[200,68874,2770],{"class":235},[200,68876,239],{"class":214},[200,68878,2439],{"class":2438},[200,68880,980],{"class":214},[200,68882,2104],{"class":210},[200,68884,252],{"class":214},[200,68886,68887,68889],{"class":202,"line":300},[200,68888,413],{"class":214},[200,68890,416],{"class":210},[130,68892,132,68893,132,68897],{},[134,68894,136,68895,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,68896],{"src":2793,"type":141},[143,68898,2796],{},[35,68900,2799],{},[35,68902,2802,68903,2155,68906,215],{},[39,68904,2807],{"href":2805,"rel":68905},[77],[39,68907,2160],{"href":2810,"rel":68908},[77],[51,68910,2815],{"id":2814},[35,68912,2818,68913,2824],{},[39,68914,2823],{"href":2821,"rel":68915},[77],[35,68917,2827],{},[68,68919,68920,68922,68924],{},[71,68921,2832],{},[71,68923,2323],{},[71,68925,2837],{},[35,68927,2840,68928,2846,68931,106,68934],{},[39,68929,2845],{"href":2843,"rel":68930},[77],[39,68932,2851],{"href":2849,"rel":68933},[77],[1216,68935,2854],{},[35,68937,2857,68938,2863],{},[39,68939,2862],{"href":2860,"rel":68940},[77],[35,68942,2866],{},[35,68944,2869,68945,2873,68947,2877,68949,2881,68951,2885,68953,215],{},[156,68946,2872],{},[156,68948,2876],{},[156,68950,2880],{},[156,68952,2884],{},[156,68954,2888],{},[35,68956,2891,68957,2895,68959,2899,68961,2903],{},[156,68958,2894],{},[156,68960,2898],{},[156,68962,2902],{},[35,68964,2906],{},[35,68966,2909],{},[2911,68968,68969,68971],{"title":2913,"href":2914},[35,68970,2917],{},[2911,68972,68973,68981,68986,68988,68990,68997,69005,69007,69009,69011,69015,69017],{"title":2920,"href":2921},[35,68974,2924,68975,1655,68978,2935],{},[39,68976,2929],{"href":2927,"rel":68977},[77],[39,68979,2934],{"href":2932,"rel":68980},[77],[35,68982,2938,68983,2943],{},[39,68984,2942],{"href":2342,"rel":68985},[77],[35,68987,2946],{},[35,68989,2949],{},[35,68991,2952,68992,2957,68995,2961],{},[39,68993,2956],{"href":2914,"rel":68994},[77],[156,68996,2960],{},[35,68998,2964,68999,2968,69002,215],{},[39,69000,2807],{"href":2805,"rel":69001},[77],[39,69003,2154],{"href":2152,"rel":69004},[77],[35,69006,2974],{},[35,69008,2977],{},[51,69010,2981],{"id":2980},[35,69012,2984,69013,215],{},[39,69014,2988],{"href":2987},[35,69016,2991],{},[35,69018,2994],{},[1170,69020,2997],{},{"title":13,"searchDepth":14,"depth":14,"links":69022},[69023,69024,69027,69028,69031],{"id":1887,"depth":14,"text":1888},{"id":1914,"depth":14,"text":1915,"children":69025},[69026],{"id":1931,"depth":255,"text":1932},{"id":2311,"depth":14,"text":2312},{"id":2460,"depth":14,"text":2461,"children":69029},[69030],{"id":2486,"depth":255,"text":2487},{"id":2814,"depth":14,"text":2815},{},{"title":1870,"description":3011},{"loc":3013},{"id":3020,"title":3021,"body":69036,"contentId":5631,"date":3010,"description":5632,"extension":18,"meta":71294,"navigation":20,"path":5634,"rawbody":5635,"robots":23,"schemaOrg":23,"seo":71295,"sitemap":71296,"stem":5638,"__hash__":5639},{"type":7,"value":69037,"toc":71288},[69038,69040,69050,69054,69056,69058,69060,69062,69074,69076,69078,69086,69090,69096,69298,69308,69310,69318,69322,69329,69585,69595,69597,69605,69609,69611,69613,69617,69624,69626,69910,69918,69920,70084,70094,70102,70104,70106,70118,70120,70122,70126,70128,70478,71286],[35,69039,3026],{},[1593,69041,69042,69044,69046,69048],{},[71,69043,3031],{},[71,69045,3034],{},[71,69047,3037],{},[71,69049,3040],{},[35,69051,3043,69052],{},[3045,69053,3048],{"id":3047},[35,69055,3051],{},[51,69057,3055],{"id":3054},[35,69059,3058],{},[35,69061,3061],{},[68,69063,69064,69066,69068,69070,69072],{},[71,69065,3066],{},[71,69067,3069],{},[71,69069,3072],{},[71,69071,3075],{},[71,69073,3078],{},[35,69075,3081],{},[51,69077,3085],{"id":3084},[35,69079,3088,69080,3092,69082,1655,69084,3099],{},[156,69081,3091],{},[156,69083,3095],{},[156,69085,3098],{},[938,69087,69088],{},[35,69089,3104],{},[35,69091,3107,69092,3110,69094,3113],{},[156,69093,3091],{},[156,69095,3091],{},[192,69097,69098],{"className":194,"code":3116,"language":196,"meta":13,"style":13},[156,69099,69100,69110,69120,69124,69138,69146,69190,69194,69198,69212,69226,69230,69262,69266,69290,69294],{"__ignoreMap":13},[200,69101,69102,69104,69106,69108],{"class":202,"line":203},[200,69103,3123],{"class":210},[200,69105,215],{"class":214},[200,69107,224],{"class":223},[200,69109,3130],{"class":210},[200,69111,69112,69114,69116,69118],{"class":202,"line":14},[200,69113,3135],{"class":214},[200,69115,3091],{"class":245},[200,69117,249],{"class":214},[200,69119,252],{"class":214},[200,69121,69122],{"class":202,"line":255},[200,69123,3146],{"class":214},[200,69125,69126,69128,69130,69132,69134,69136],{"class":202,"line":272},[200,69127,3151],{"class":235},[200,69129,239],{"class":214},[200,69131,242],{"class":214},[200,69133,573],{"class":245},[200,69135,249],{"class":214},[200,69137,252],{"class":214},[200,69139,69140,69142,69144],{"class":202,"line":283},[200,69141,3166],{"class":235},[200,69143,239],{"class":214},[200,69145,280],{"class":214},[200,69147,69148,69150,69152,69154,69156,69158,69160,69162,69164,69166,69168,69170,69172,69174,69176,69178,69180,69182,69184,69186,69188],{"class":202,"line":300},[200,69149,3175],{"class":235},[200,69151,239],{"class":214},[200,69153,3180],{"class":210},[200,69155,215],{"class":214},[200,69157,328],{"class":223},[200,69159,1549],{"class":210},[200,69161,215],{"class":214},[200,69163,3191],{"class":223},[200,69165,1549],{"class":210},[200,69167,215],{"class":214},[200,69169,3198],{"class":223},[200,69171,227],{"class":210},[200,69173,3203],{"class":2060},[200,69175,2104],{"class":210},[200,69177,215],{"class":214},[200,69179,3210],{"class":223},[200,69181,227],{"class":210},[200,69183,249],{"class":214},[200,69185,3217],{"class":245},[200,69187,249],{"class":214},[200,69189,416],{"class":210},[200,69191,69192],{"class":202,"line":310},[200,69193,339],{"class":214},[200,69195,69196],{"class":202,"line":336},[200,69197,368],{"class":214},[200,69199,69200,69202,69204,69206,69208,69210],{"class":202,"line":342},[200,69201,3234],{"class":379},[200,69203,383],{"class":214},[200,69205,3239],{"class":386},[200,69207,390],{"class":214},[200,69209,393],{"class":379},[200,69211,280],{"class":214},[200,69213,69214,69216,69218,69220,69222,69224],{"class":202,"line":365},[200,69215,2014],{"class":379},[200,69217,3252],{"class":210},[200,69219,509],{"class":214},[200,69221,452],{"class":206},[200,69223,3259],{"class":223},[200,69225,466],{"class":235},[200,69227,69228],{"class":202,"line":371},[200,69229,528],{"emptyLinePlaceholder":20},[200,69231,69232,69234,69236,69238,69240,69242,69244,69246,69248,69250,69252,69254,69256,69258,69260],{"class":202,"line":398},[200,69233,2014],{"class":379},[200,69235,3272],{"class":210},[200,69237,509],{"class":214},[200,69239,3252],{"class":210},[200,69241,215],{"class":214},[200,69243,3281],{"class":223},[200,69245,227],{"class":235},[200,69247,3286],{"class":386},[200,69249,393],{"class":379},[200,69251,3272],{"class":210},[200,69253,215],{"class":214},[200,69255,3295],{"class":210},[200,69257,3298],{"class":214},[200,69259,3239],{"class":210},[200,69261,416],{"class":235},[200,69263,69264],{"class":202,"line":405},[200,69265,528],{"emptyLinePlaceholder":20},[200,69267,69268,69270,69272,69274,69276,69278,69280,69282,69284,69286,69288],{"class":202,"line":410},[200,69269,3311],{"class":206},[200,69271,3314],{"class":223},[200,69273,227],{"class":235},[200,69275,3319],{"class":214},[200,69277,3286],{"class":210},[200,69279,215],{"class":214},[200,69281,2035],{"class":210},[200,69283,413],{"class":214},[200,69285,3330],{"class":245},[200,69287,3333],{"class":214},[200,69289,416],{"class":235},[200,69291,69292],{"class":202,"line":860},[200,69293,368],{"class":214},[200,69295,69296],{"class":202,"line":889},[200,69297,416],{"class":210},[938,69299,69300],{},[35,69301,69302,3350,69304,3353,69306],{},[200,69303,944],{},[156,69305,3091],{},[1599,69307,215],{"repo":3356},[35,69309,3359],{},[130,69311,132,69312,132,69316],{},[134,69313,136,69314,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,69315],{"src":3366,"type":141},[143,69317,3369],{},[938,69319,69320],{},[35,69321,3374],{},[35,69323,3377,69324,3380,69326,215],{},[156,69325,3095],{},[39,69327,3385],{"href":3383,"rel":69328},[77],[192,69330,69331],{"className":194,"code":3388,"language":196,"meta":13,"style":13},[156,69332,69333,69343,69353,69357,69371,69379,69423,69427,69431,69445,69463,69467,69487,69501,69515,69529,69543,69557,69571,69577,69581],{"__ignoreMap":13},[200,69334,69335,69337,69339,69341],{"class":202,"line":203},[200,69336,3123],{"class":210},[200,69338,215],{"class":214},[200,69340,224],{"class":223},[200,69342,3130],{"class":210},[200,69344,69345,69347,69349,69351],{"class":202,"line":14},[200,69346,3135],{"class":214},[200,69348,3095],{"class":245},[200,69350,249],{"class":214},[200,69352,252],{"class":214},[200,69354,69355],{"class":202,"line":255},[200,69356,3146],{"class":214},[200,69358,69359,69361,69363,69365,69367,69369],{"class":202,"line":272},[200,69360,3151],{"class":235},[200,69362,239],{"class":214},[200,69364,242],{"class":214},[200,69366,573],{"class":245},[200,69368,249],{"class":214},[200,69370,252],{"class":214},[200,69372,69373,69375,69377],{"class":202,"line":283},[200,69374,3166],{"class":235},[200,69376,239],{"class":214},[200,69378,280],{"class":214},[200,69380,69381,69383,69385,69387,69389,69391,69393,69395,69397,69399,69401,69403,69405,69407,69409,69411,69413,69415,69417,69419,69421],{"class":202,"line":300},[200,69382,3441],{"class":235},[200,69384,239],{"class":214},[200,69386,3180],{"class":210},[200,69388,215],{"class":214},[200,69390,328],{"class":223},[200,69392,1549],{"class":210},[200,69394,215],{"class":214},[200,69396,3191],{"class":223},[200,69398,1549],{"class":210},[200,69400,215],{"class":214},[200,69402,3198],{"class":223},[200,69404,227],{"class":210},[200,69406,3203],{"class":2060},[200,69408,2104],{"class":210},[200,69410,215],{"class":214},[200,69412,3210],{"class":223},[200,69414,227],{"class":210},[200,69416,249],{"class":214},[200,69418,3478],{"class":245},[200,69420,249],{"class":214},[200,69422,416],{"class":210},[200,69424,69425],{"class":202,"line":310},[200,69426,339],{"class":214},[200,69428,69429],{"class":202,"line":336},[200,69430,368],{"class":214},[200,69432,69433,69435,69437,69439,69441,69443],{"class":202,"line":342},[200,69434,3234],{"class":379},[200,69436,383],{"class":214},[200,69438,3499],{"class":386},[200,69440,390],{"class":214},[200,69442,393],{"class":379},[200,69444,280],{"class":214},[200,69446,69447,69449,69451,69453,69455,69457,69459,69461],{"class":202,"line":365},[200,69448,2014],{"class":379},[200,69450,3512],{"class":210},[200,69452,509],{"class":214},[200,69454,452],{"class":206},[200,69456,3519],{"class":223},[200,69458,227],{"class":235},[200,69460,3524],{"class":210},[200,69462,416],{"class":235},[200,69464,69465],{"class":202,"line":371},[200,69466,528],{"emptyLinePlaceholder":20},[200,69468,69469,69471,69473,69475,69477,69479,69481,69483,69485],{"class":202,"line":398},[200,69470,3311],{"class":206},[200,69472,3512],{"class":210},[200,69474,215],{"class":214},[200,69476,3541],{"class":223},[200,69478,227],{"class":235},[200,69480,3546],{"class":386},[200,69482,393],{"class":379},[200,69484,1094],{"class":235},[200,69486,230],{"class":214},[200,69488,69489,69491,69493,69495,69497,69499],{"class":202,"line":405},[200,69490,3175],{"class":235},[200,69492,239],{"class":214},[200,69494,3561],{"class":210},[200,69496,215],{"class":214},[200,69498,3295],{"class":210},[200,69500,252],{"class":214},[200,69502,69503,69505,69507,69509,69511,69513],{"class":202,"line":410},[200,69504,3572],{"class":235},[200,69506,239],{"class":214},[200,69508,3561],{"class":210},[200,69510,215],{"class":214},[200,69512,3581],{"class":210},[200,69514,252],{"class":214},[200,69516,69517,69519,69521,69523,69525,69527],{"class":202,"line":860},[200,69518,3588],{"class":235},[200,69520,239],{"class":214},[200,69522,3561],{"class":210},[200,69524,215],{"class":214},[200,69526,3597],{"class":210},[200,69528,252],{"class":214},[200,69530,69531,69533,69535,69537,69539,69541],{"class":202,"line":889},[200,69532,3604],{"class":235},[200,69534,239],{"class":214},[200,69536,3561],{"class":210},[200,69538,215],{"class":214},[200,69540,3613],{"class":210},[200,69542,252],{"class":214},[200,69544,69545,69547,69549,69551,69553,69555],{"class":202,"line":899},[200,69546,3620],{"class":235},[200,69548,239],{"class":214},[200,69550,3561],{"class":210},[200,69552,215],{"class":214},[200,69554,2035],{"class":210},[200,69556,252],{"class":214},[200,69558,69559,69561,69563,69565,69567,69569],{"class":202,"line":904},[200,69560,3635],{"class":235},[200,69562,239],{"class":214},[200,69564,3561],{"class":210},[200,69566,215],{"class":214},[200,69568,3644],{"class":210},[200,69570,252],{"class":214},[200,69572,69573,69575],{"class":202,"line":930},[200,69574,2111],{"class":214},[200,69576,584],{"class":235},[200,69578,69579],{"class":202,"line":3655},[200,69580,368],{"class":214},[200,69582,69583],{"class":202,"line":3660},[200,69584,416],{"class":210},[938,69586,69587],{},[35,69588,69589,3350,69591,3353,69593],{},[200,69590,944],{},[156,69592,3095],{},[1599,69594,215],{"repo":3356},[35,69596,3675],{},[130,69598,132,69599,132,69603],{},[134,69600,136,69601,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,69602],{"src":3682,"type":141},[143,69604,3037],{},[938,69606,69607],{},[35,69608,3689],{},[35,69610,3692],{},[35,69612,3695],{},[35,69614,3698,69615,3701],{},[156,69616,3098],{},[35,69618,3704,69619,3707,69621,3713],{},[156,69620,156],{},[39,69622,3712],{"href":3710,"rel":69623},[77],[35,69625,3716],{},[192,69627,69628],{"className":194,"code":3719,"language":196,"meta":13,"style":13},[156,69629,69630,69640,69654,69658,69668,69696,69708,69732,69740,69754,69758,69778,69814,69818,69834,69870,69876,69882,69888,69894,69900,69906],{"__ignoreMap":13},[200,69631,69632,69634,69636,69638],{"class":202,"line":203},[200,69633,3726],{"class":379},[200,69635,3729],{"class":214},[200,69637,393],{"class":379},[200,69639,280],{"class":214},[200,69641,69642,69644,69646,69648,69650,69652],{"class":202,"line":14},[200,69643,3738],{"class":379},[200,69645,3499],{"class":210},[200,69647,509],{"class":214},[200,69649,242],{"class":214},[200,69651,3747],{"class":245},[200,69653,991],{"class":214},[200,69655,69656],{"class":202,"line":255},[200,69657,528],{"emptyLinePlaceholder":20},[200,69659,69660,69662,69664,69666],{"class":202,"line":272},[200,69661,3758],{"class":206},[200,69663,3252],{"class":210},[200,69665,215],{"class":214},[200,69667,3765],{"class":210},[200,69669,69670,69672,69674,69676,69678,69680,69682,69684,69686,69688,69690,69692,69694],{"class":202,"line":283},[200,69671,3770],{"class":214},[200,69673,3773],{"class":223},[200,69675,227],{"class":235},[200,69677,3286],{"class":386},[200,69679,393],{"class":379},[200,69681,3272],{"class":210},[200,69683,215],{"class":214},[200,69685,3786],{"class":210},[200,69687,3298],{"class":214},[200,69689,242],{"class":214},[200,69691,3793],{"class":245},[200,69693,249],{"class":214},[200,69695,416],{"class":235},[200,69697,69698,69700,69702,69704,69706],{"class":202,"line":300},[200,69699,3770],{"class":214},[200,69701,3773],{"class":223},[200,69703,227],{"class":235},[200,69705,3286],{"class":386},[200,69707,3810],{"class":379},[200,69709,69710,69712,69714,69716,69718,69720,69722,69724,69726,69728,69730],{"class":202,"line":310},[200,69711,3815],{"class":214},[200,69713,3286],{"class":210},[200,69715,215],{"class":214},[200,69717,3581],{"class":210},[200,69719,413],{"class":214},[200,69721,3826],{"class":214},[200,69723,3286],{"class":210},[200,69725,215],{"class":214},[200,69727,3833],{"class":210},[200,69729,3836],{"class":214},[200,69731,3839],{"class":214},[200,69733,69734,69736,69738],{"class":202,"line":336},[200,69735,3844],{"class":214},[200,69737,3847],{"class":223},[200,69739,466],{"class":235},[200,69741,69742,69744,69746,69748,69750,69752],{"class":202,"line":342},[200,69743,3844],{"class":214},[200,69745,3856],{"class":223},[200,69747,227],{"class":235},[200,69749,3524],{"class":210},[200,69751,2104],{"class":235},[200,69753,252],{"class":214},[200,69755,69756],{"class":202,"line":365},[200,69757,3869],{"class":235},[200,69759,69760,69762,69764,69766,69768,69770,69772,69774,69776],{"class":202,"line":371},[200,69761,3770],{"class":214},[200,69763,3876],{"class":223},[200,69765,227],{"class":235},[200,69767,227],{"class":214},[200,69769,39],{"class":386},[200,69771,549],{"class":214},[200,69773,3887],{"class":386},[200,69775,2104],{"class":214},[200,69777,3810],{"class":379},[200,69779,69780,69782,69784,69786,69788,69790,69792,69794,69796,69798,69800,69802,69804,69806,69808,69810,69812],{"class":202,"line":398},[200,69781,3896],{"class":235},[200,69783,3899],{"class":210},[200,69785,215],{"class":214},[200,69787,3644],{"class":210},[200,69789,3906],{"class":214},[200,69791,3909],{"class":214},[200,69793,2104],{"class":235},[200,69795,215],{"class":214},[200,69797,3916],{"class":223},[200,69799,227],{"class":235},[200,69801,39],{"class":210},[200,69803,215],{"class":214},[200,69805,3644],{"class":210},[200,69807,3906],{"class":214},[200,69809,3909],{"class":214},[200,69811,2104],{"class":235},[200,69813,252],{"class":214},[200,69815,69816],{"class":202,"line":405},[200,69817,3869],{"class":235},[200,69819,69820,69822,69824,69826,69828,69830,69832],{"class":202,"line":410},[200,69821,3770],{"class":214},[200,69823,3943],{"class":223},[200,69825,227],{"class":235},[200,69827,3948],{"class":2060},[200,69829,549],{"class":214},[200,69831,3953],{"class":2060},[200,69833,416],{"class":235},[200,69835,69836,69838,69840,69842,69844,69846,69848,69850,69852,69854,69856,69858,69860,69862,69864,69866,69868],{"class":202,"line":860},[200,69837,3770],{"class":214},[200,69839,3541],{"class":223},[200,69841,227],{"class":235},[200,69843,3966],{"class":214},[200,69845,3239],{"class":386},[200,69847,549],{"class":214},[200,69849,3973],{"class":386},[200,69851,549],{"class":214},[200,69853,3978],{"class":386},[200,69855,549],{"class":214},[200,69857,3983],{"class":386},[200,69859,549],{"class":214},[200,69861,3988],{"class":386},[200,69863,390],{"class":214},[200,69865,393],{"class":379},[200,69867,1094],{"class":235},[200,69869,230],{"class":214},[200,69871,69872,69874],{"class":202,"line":889},[200,69873,3175],{"class":210},[200,69875,252],{"class":214},[200,69877,69878,69880],{"class":202,"line":899},[200,69879,3572],{"class":210},[200,69881,252],{"class":214},[200,69883,69884,69886],{"class":202,"line":904},[200,69885,3588],{"class":210},[200,69887,252],{"class":214},[200,69889,69890,69892],{"class":202,"line":930},[200,69891,3635],{"class":210},[200,69893,252],{"class":214},[200,69895,69896,69898],{"class":202,"line":3655},[200,69897,3620],{"class":210},[200,69899,252],{"class":214},[200,69901,69902,69904],{"class":202,"line":3660},[200,69903,2111],{"class":214},[200,69905,584],{"class":235},[200,69907,69908],{"class":202,"line":4035},[200,69909,1129],{"class":214},[938,69911,69912],{},[35,69913,69914,4044,69916,215],{},[200,69915,944],{},[39,69917,4048],{"href":4047},[35,69919,4051],{},[192,69921,69922],{"className":194,"code":4054,"language":196,"meta":13,"style":13},[156,69923,69924,69934,69944,69948,69962,69970,70026,70030,70034,70048,70066,70070,70076,70080],{"__ignoreMap":13},[200,69925,69926,69928,69930,69932],{"class":202,"line":203},[200,69927,3123],{"class":210},[200,69929,215],{"class":214},[200,69931,224],{"class":223},[200,69933,3130],{"class":210},[200,69935,69936,69938,69940,69942],{"class":202,"line":14},[200,69937,3135],{"class":214},[200,69939,3098],{"class":245},[200,69941,249],{"class":214},[200,69943,252],{"class":214},[200,69945,69946],{"class":202,"line":255},[200,69947,3146],{"class":214},[200,69949,69950,69952,69954,69956,69958,69960],{"class":202,"line":272},[200,69951,3151],{"class":235},[200,69953,239],{"class":214},[200,69955,242],{"class":214},[200,69957,573],{"class":245},[200,69959,249],{"class":214},[200,69961,252],{"class":214},[200,69963,69964,69966,69968],{"class":202,"line":283},[200,69965,3166],{"class":235},[200,69967,239],{"class":214},[200,69969,280],{"class":214},[200,69971,69972,69974,69976,69978,69980,69982,69984,69986,69988,69990,69992,69994,69996,69998,70000,70002,70004,70006,70008,70010,70012,70014,70016,70018,70020,70022,70024],{"class":202,"line":300},[200,69973,4107],{"class":235},[200,69975,239],{"class":214},[200,69977,3180],{"class":210},[200,69979,215],{"class":214},[200,69981,328],{"class":223},[200,69983,1549],{"class":210},[200,69985,215],{"class":214},[200,69987,3191],{"class":223},[200,69989,1549],{"class":210},[200,69991,215],{"class":214},[200,69993,3198],{"class":223},[200,69995,227],{"class":210},[200,69997,3203],{"class":2060},[200,69999,2104],{"class":210},[200,70001,215],{"class":214},[200,70003,4138],{"class":223},[200,70005,227],{"class":210},[200,70007,4143],{"class":2060},[200,70009,2104],{"class":210},[200,70011,215],{"class":214},[200,70013,3210],{"class":223},[200,70015,227],{"class":210},[200,70017,249],{"class":214},[200,70019,4156],{"class":245},[200,70021,249],{"class":214},[200,70023,2104],{"class":210},[200,70025,252],{"class":214},[200,70027,70028],{"class":202,"line":310},[200,70029,339],{"class":214},[200,70031,70032],{"class":202,"line":336},[200,70033,368],{"class":214},[200,70035,70036,70038,70040,70042,70044,70046],{"class":202,"line":342},[200,70037,3234],{"class":379},[200,70039,383],{"class":214},[200,70041,4179],{"class":386},[200,70043,390],{"class":214},[200,70045,393],{"class":379},[200,70047,280],{"class":214},[200,70049,70050,70052,70054,70056,70058,70060,70062,70064],{"class":202,"line":365},[200,70051,2014],{"class":379},[200,70053,3561],{"class":210},[200,70055,509],{"class":214},[200,70057,452],{"class":206},[200,70059,4198],{"class":223},[200,70061,227],{"class":235},[200,70063,156],{"class":210},[200,70065,416],{"class":235},[200,70067,70068],{"class":202,"line":371},[200,70069,528],{"emptyLinePlaceholder":20},[200,70071,70072,70074],{"class":202,"line":398},[200,70073,3311],{"class":206},[200,70075,4215],{"class":210},[200,70077,70078],{"class":202,"line":405},[200,70079,368],{"class":214},[200,70081,70082],{"class":202,"line":410},[200,70083,416],{"class":210},[938,70085,70086],{},[35,70087,70088,3350,70090,3353,70092],{},[200,70089,944],{},[156,70091,3098],{},[1599,70093,215],{"repo":3356},[130,70095,132,70096,132,70100],{},[134,70097,136,70098,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,70099],{"src":4240,"type":141},[143,70101,3040],{},[35,70103,4245],{},[35,70105,4248],{},[1593,70107,70108,70110,70112,70114,70116],{},[71,70109,4253],{},[71,70111,4256],{},[71,70113,4259],{},[71,70115,4262],{},[71,70117,4265],{},[35,70119,4268],{},[51,70121,4272],{"id":4271},[35,70123,4275,70124],{},[3045,70125,4279],{"id":4278},[35,70127,4282],{},[1593,70129,70130,70142,70154,70166,70178,70190,70202,70314,70418,70466],{},[71,70131,70132,70134],{},[156,70133,4289],{},[192,70135,70136],{"className":4292,"code":4293,"language":4294,"meta":13,"style":13},[156,70137,70138],{"__ignoreMap":13},[200,70139,70140],{"class":202,"line":203},[200,70141,4293],{},[71,70143,70144,70146],{},[156,70145,4305],{},[192,70147,70148],{"className":4292,"code":4308,"language":4294,"meta":13,"style":13},[156,70149,70150],{"__ignoreMap":13},[200,70151,70152],{"class":202,"line":203},[200,70153,4308],{},[71,70155,70156,70158],{},[156,70157,3098],{},[192,70159,70160],{"className":4292,"code":4321,"language":4294,"meta":13,"style":13},[156,70161,70162],{"__ignoreMap":13},[200,70163,70164],{"class":202,"line":203},[200,70165,4321],{},[71,70167,70168,70170],{},[156,70169,4332],{},[192,70171,70172],{"className":4292,"code":4335,"language":4294,"meta":13,"style":13},[156,70173,70174],{"__ignoreMap":13},[200,70175,70176],{"class":202,"line":203},[200,70177,4335],{},[71,70179,70180,70182],{},[156,70181,4346],{},[192,70183,70184],{"className":4292,"code":4349,"language":4294,"meta":13,"style":13},[156,70185,70186],{"__ignoreMap":13},[200,70187,70188],{"class":202,"line":203},[200,70189,4349],{},[71,70191,70192,70194],{},[156,70193,4360],{},[192,70195,70196],{"className":4292,"code":4363,"language":4294,"meta":13,"style":13},[156,70197,70198],{"__ignoreMap":13},[200,70199,70200],{"class":202,"line":203},[200,70201,4363],{},[71,70203,70204,70206],{},[156,70205,4374],{},[192,70207,70208],{"className":4292,"code":4377,"language":4294,"meta":13,"style":13},[156,70209,70210,70214,70218,70222,70226,70230,70234,70238,70242,70246,70250,70254,70258,70262,70266,70270,70274,70278,70282,70286,70290,70294,70298,70302,70306,70310],{"__ignoreMap":13},[200,70211,70212],{"class":202,"line":203},[200,70213,4384],{},[200,70215,70216],{"class":202,"line":14},[200,70217,4389],{},[200,70219,70220],{"class":202,"line":255},[200,70221,4394],{},[200,70223,70224],{"class":202,"line":272},[200,70225,4399],{},[200,70227,70228],{"class":202,"line":283},[200,70229,4404],{},[200,70231,70232],{"class":202,"line":300},[200,70233,4409],{},[200,70235,70236],{"class":202,"line":310},[200,70237,4414],{},[200,70239,70240],{"class":202,"line":336},[200,70241,4419],{},[200,70243,70244],{"class":202,"line":342},[200,70245,4424],{},[200,70247,70248],{"class":202,"line":365},[200,70249,528],{"emptyLinePlaceholder":20},[200,70251,70252],{"class":202,"line":371},[200,70253,4433],{},[200,70255,70256],{"class":202,"line":398},[200,70257,4438],{},[200,70259,70260],{"class":202,"line":405},[200,70261,3146],{},[200,70263,70264],{"class":202,"line":410},[200,70265,4447],{},[200,70267,70268],{"class":202,"line":860},[200,70269,4452],{},[200,70271,70272],{"class":202,"line":889},[200,70273,4457],{},[200,70275,70276],{"class":202,"line":899},[200,70277,4462],{},[200,70279,70280],{"class":202,"line":904},[200,70281,4467],{},[200,70283,70284],{"class":202,"line":930},[200,70285,4472],{},[200,70287,70288],{"class":202,"line":3655},[200,70289,4477],{},[200,70291,70292],{"class":202,"line":3660},[200,70293,4482],{},[200,70295,70296],{"class":202,"line":4035},[200,70297,4487],{},[200,70299,70300],{"class":202,"line":4490},[200,70301,4493],{},[200,70303,70304],{"class":202,"line":4496},[200,70305,4499],{},[200,70307,70308],{"class":202,"line":4502},[200,70309,4505],{},[200,70311,70312],{"class":202,"line":4508},[200,70313,4511],{},[71,70315,70316,70318],{},[156,70317,4516],{},[192,70319,70320],{"className":4292,"code":4519,"language":4294,"meta":13,"style":13},[156,70321,70322,70326,70330,70334,70338,70342,70346,70350,70354,70358,70362,70366,70370,70374,70378,70382,70386,70390,70394,70398,70402,70406,70410,70414],{"__ignoreMap":13},[200,70323,70324],{"class":202,"line":203},[200,70325,4526],{},[200,70327,70328],{"class":202,"line":14},[200,70329,4531],{},[200,70331,70332],{"class":202,"line":255},[200,70333,4536],{},[200,70335,70336],{"class":202,"line":272},[200,70337,4541],{},[200,70339,70340],{"class":202,"line":283},[200,70341,4546],{},[200,70343,70344],{"class":202,"line":300},[200,70345,4551],{},[200,70347,70348],{"class":202,"line":310},[200,70349,4556],{},[200,70351,70352],{"class":202,"line":336},[200,70353,4561],{},[200,70355,70356],{"class":202,"line":342},[200,70357,4566],{},[200,70359,70360],{"class":202,"line":365},[200,70361,4571],{},[200,70363,70364],{"class":202,"line":371},[200,70365,528],{"emptyLinePlaceholder":20},[200,70367,70368],{"class":202,"line":398},[200,70369,4433],{},[200,70371,70372],{"class":202,"line":405},[200,70373,4438],{},[200,70375,70376],{"class":202,"line":410},[200,70377,3146],{},[200,70379,70380],{"class":202,"line":860},[200,70381,4592],{},[200,70383,70384],{"class":202,"line":889},[200,70385,4597],{},[200,70387,70388],{"class":202,"line":899},[200,70389,4602],{},[200,70391,70392],{"class":202,"line":904},[200,70393,4607],{},[200,70395,70396],{"class":202,"line":930},[200,70397,4612],{},[200,70399,70400],{"class":202,"line":3655},[200,70401,4617],{},[200,70403,70404],{"class":202,"line":3660},[200,70405,4622],{},[200,70407,70408],{"class":202,"line":4035},[200,70409,4627],{},[200,70411,70412],{"class":202,"line":4490},[200,70413,4505],{},[200,70415,70416],{"class":202,"line":4496},[200,70417,4511],{},[71,70419,70420,70422],{},[156,70421,4640],{},[192,70423,70424],{"className":4292,"code":4643,"language":4294,"meta":13,"style":13},[156,70425,70426,70430,70434,70438,70442,70446,70450,70454,70458,70462],{"__ignoreMap":13},[200,70427,70428],{"class":202,"line":203},[200,70429,4650],{},[200,70431,70432],{"class":202,"line":14},[200,70433,4655],{},[200,70435,70436],{"class":202,"line":255},[200,70437,4660],{},[200,70439,70440],{"class":202,"line":272},[200,70441,4665],{},[200,70443,70444],{"class":202,"line":283},[200,70445,528],{"emptyLinePlaceholder":20},[200,70447,70448],{"class":202,"line":300},[200,70449,4433],{},[200,70451,70452],{"class":202,"line":310},[200,70453,4438],{},[200,70455,70456],{"class":202,"line":336},[200,70457,4682],{},[200,70459,70460],{"class":202,"line":342},[200,70461,4687],{},[200,70463,70464],{"class":202,"line":365},[200,70465,4511],{},[71,70467,70468,70470],{},[156,70469,3091],{},[192,70471,70472],{"className":4292,"code":4698,"language":4294,"meta":13,"style":13},[156,70473,70474],{"__ignoreMap":13},[200,70475,70476],{"class":202,"line":203},[200,70477,4698],{},[1599,70479,70480,70482,70486,70494,70728,70730,70872,70876,70880,71184,71186,71192,71194,71196,71202,71204,71214,71225,71229,71231,71233,71235,71237,71244,71254,71256,71258,71272,71274,71276,71278,71280,71282],{"repo":4707},[35,70481,4710],{},[35,70483,70484,4717],{},[39,70485,4716],{"href":4715},[35,70487,4720,70488,4723,70490,4727,70492,4731],{},[156,70489,3098],{},[156,70491,4726],{},[156,70493,4730],{},[192,70495,70496],{"className":194,"code":4734,"language":196,"meta":13,"style":13},[156,70497,70498,70510,70520,70530,70534,70584,70588,70602,70634,70648,70654,70660,70668,70672,70686,70706,70710,70716,70720,70724],{"__ignoreMap":13},[200,70499,70500,70502,70504,70506,70508],{"class":202,"line":203},[200,70501,4741],{"class":214},[200,70503,3123],{"class":210},[200,70505,215],{"class":214},[200,70507,504],{"class":223},[200,70509,3130],{"class":210},[200,70511,70512,70514,70516,70518],{"class":202,"line":14},[200,70513,3135],{"class":214},[200,70515,3098],{"class":245},[200,70517,249],{"class":214},[200,70519,252],{"class":214},[200,70521,70522,70524,70526,70528],{"class":202,"line":255},[200,70523,3135],{"class":214},[200,70525,573],{"class":245},[200,70527,249],{"class":214},[200,70529,252],{"class":214},[200,70531,70532],{"class":202,"line":272},[200,70533,3146],{"class":214},[200,70535,70536,70538,70540,70542,70544,70546,70548,70550,70552,70554,70556,70558,70560,70562,70564,70566,70568,70570,70572,70574,70576,70578,70580,70582],{"class":202,"line":283},[200,70537,4778],{"class":235},[200,70539,239],{"class":214},[200,70541,3180],{"class":210},[200,70543,215],{"class":214},[200,70545,328],{"class":223},[200,70547,1549],{"class":210},[200,70549,215],{"class":214},[200,70551,3198],{"class":223},[200,70553,227],{"class":210},[200,70555,4797],{"class":2060},[200,70557,2104],{"class":210},[200,70559,215],{"class":214},[200,70561,4138],{"class":223},[200,70563,227],{"class":210},[200,70565,4797],{"class":2060},[200,70567,2104],{"class":210},[200,70569,215],{"class":214},[200,70571,3210],{"class":223},[200,70573,227],{"class":210},[200,70575,249],{"class":214},[200,70577,4820],{"class":245},[200,70579,249],{"class":214},[200,70581,2104],{"class":210},[200,70583,252],{"class":214},[200,70585,70586],{"class":202,"line":300},[200,70587,368],{"class":214},[200,70589,70590,70592,70594,70596,70598,70600],{"class":202,"line":310},[200,70591,3234],{"class":379},[200,70593,383],{"class":214},[200,70595,4839],{"class":386},[200,70597,390],{"class":214},[200,70599,393],{"class":379},[200,70601,280],{"class":214},[200,70603,70604,70606,70608,70610,70612,70614,70616,70618,70620,70622,70624,70626,70628,70630,70632],{"class":202,"line":336},[200,70605,2014],{"class":379},[200,70607,3561],{"class":210},[200,70609,509],{"class":214},[200,70611,452],{"class":206},[200,70613,4858],{"class":223},[200,70615,227],{"class":235},[200,70617,3333],{"class":214},[200,70619,4865],{"class":245},[200,70621,4868],{"class":214},[200,70623,4871],{"class":210},[200,70625,413],{"class":214},[200,70627,4876],{"class":245},[200,70629,3333],{"class":214},[200,70631,549],{"class":214},[200,70633,280],{"class":214},[200,70635,70636,70638,70640,70642,70644,70646],{"class":202,"line":342},[200,70637,4887],{"class":235},[200,70639,239],{"class":214},[200,70641,4892],{"class":210},[200,70643,215],{"class":214},[200,70645,4897],{"class":210},[200,70647,252],{"class":214},[200,70649,70650,70652],{"class":202,"line":365},[200,70651,2111],{"class":214},[200,70653,416],{"class":235},[200,70655,70656,70658],{"class":202,"line":371},[200,70657,3311],{"class":206},[200,70659,280],{"class":214},[200,70661,70662,70664,70666],{"class":202,"line":398},[200,70663,3604],{"class":235},[200,70665,239],{"class":214},[200,70667,1410],{"class":235},[200,70669,70670],{"class":202,"line":405},[200,70671,4924],{"class":214},[200,70673,70674,70676,70678,70680,70682,70684],{"class":202,"line":410},[200,70675,4929],{"class":235},[200,70677,239],{"class":214},[200,70679,242],{"class":214},[200,70681,697],{"class":245},[200,70683,249],{"class":214},[200,70685,252],{"class":214},[200,70687,70688,70690,70692,70694,70696,70698,70700,70702,70704],{"class":202,"line":860},[200,70689,4944],{"class":235},[200,70691,239],{"class":214},[200,70693,552],{"class":210},[200,70695,215],{"class":214},[200,70697,557],{"class":223},[200,70699,227],{"class":235},[200,70701,3546],{"class":210},[200,70703,2104],{"class":235},[200,70705,252],{"class":214},[200,70707,70708],{"class":202,"line":889},[200,70709,4965],{"class":214},[200,70711,70712,70714],{"class":202,"line":899},[200,70713,4970],{"class":235},[200,70715,252],{"class":214},[200,70717,70718],{"class":202,"line":904},[200,70719,4977],{"class":214},[200,70721,70722],{"class":202,"line":930},[200,70723,368],{"class":214},[200,70725,70726],{"class":202,"line":3655},[200,70727,416],{"class":210},[35,70729,4988],{},[192,70731,70732],{"className":194,"code":4991,"language":196,"meta":13,"style":13},[156,70733,70734,70756,70780,70792,70804,70818,70830,70834,70840,70854,70864,70868],{"__ignoreMap":13},[200,70735,70736,70738,70740,70742,70744,70746,70748,70750,70752,70754],{"class":202,"line":203},[200,70737,1390],{"class":206},[200,70739,380],{"class":379},[200,70741,5002],{"class":379},[200,70743,5005],{"class":223},[200,70745,5008],{"class":214},[200,70747,5011],{"class":1364},[200,70749,599],{"class":214},[200,70751,5016],{"class":1364},[200,70753,674],{"class":214},[200,70755,280],{"class":214},[200,70757,70758,70760,70762,70764,70766,70768,70770,70772,70774,70776,70778],{"class":202,"line":14},[200,70759,3738],{"class":379},[200,70761,3252],{"class":210},[200,70763,509],{"class":214},[200,70765,452],{"class":206},[200,70767,5033],{"class":223},[200,70769,227],{"class":235},[200,70771,249],{"class":214},[200,70773,5040],{"class":245},[200,70775,249],{"class":214},[200,70777,549],{"class":214},[200,70779,280],{"class":214},[200,70781,70782,70784,70786,70788,70790],{"class":202,"line":255},[200,70783,5051],{"class":223},[200,70785,239],{"class":214},[200,70787,5056],{"class":386},[200,70789,393],{"class":379},[200,70791,5061],{"class":210},[200,70793,70794,70796,70798,70800,70802],{"class":202,"line":272},[200,70795,5066],{"class":214},[200,70797,3773],{"class":223},[200,70799,227],{"class":235},[200,70801,5073],{"class":210},[200,70803,416],{"class":235},[200,70805,70806,70808,70810,70812,70814,70816],{"class":202,"line":283},[200,70807,5066],{"class":214},[200,70809,3541],{"class":223},[200,70811,227],{"class":235},[200,70813,5086],{"class":210},[200,70815,2104],{"class":235},[200,70817,252],{"class":214},[200,70819,70820,70822,70824,70826,70828],{"class":202,"line":300},[200,70821,5095],{"class":214},[200,70823,2104],{"class":235},[200,70825,215],{"class":214},[200,70827,5102],{"class":223},[200,70829,466],{"class":235},[200,70831,70832],{"class":202,"line":310},[200,70833,528],{"emptyLinePlaceholder":20},[200,70835,70836,70838],{"class":202,"line":336},[200,70837,3758],{"class":206},[200,70839,280],{"class":214},[200,70841,70842,70844,70846,70848,70850,70852],{"class":202,"line":342},[200,70843,5119],{"class":235},[200,70845,239],{"class":214},[200,70847,242],{"class":214},[200,70849,4726],{"class":245},[200,70851,249],{"class":214},[200,70853,252],{"class":214},[200,70855,70856,70858,70860,70862],{"class":202,"line":365},[200,70857,5134],{"class":235},[200,70859,239],{"class":214},[200,70861,3252],{"class":210},[200,70863,252],{"class":214},[200,70865,70866],{"class":202,"line":371},[200,70867,4505],{"class":214},[200,70869,70870],{"class":202,"line":398},[200,70871,1129],{"class":214},[35,70873,5151,70874,5155],{},[156,70875,5154],{},[35,70877,5158,70878,5161],{},[156,70879,3091],{},[192,70881,70882],{"className":194,"code":5164,"language":196,"meta":13,"style":13},[156,70883,70884,70896,70906,70916,70920,70960,70964,70978,70998,71010,71014,71018,71038,71050,71054,71058,71090,71104,71110,71116,71124,71128,71142,71162,71166,71172,71176,71180],{"__ignoreMap":13},[200,70885,70886,70888,70890,70892,70894],{"class":202,"line":203},[200,70887,4741],{"class":214},[200,70889,3123],{"class":210},[200,70891,215],{"class":214},[200,70893,504],{"class":223},[200,70895,3130],{"class":210},[200,70897,70898,70900,70902,70904],{"class":202,"line":14},[200,70899,3135],{"class":214},[200,70901,3091],{"class":245},[200,70903,249],{"class":214},[200,70905,252],{"class":214},[200,70907,70908,70910,70912,70914],{"class":202,"line":255},[200,70909,3135],{"class":214},[200,70911,573],{"class":245},[200,70913,249],{"class":214},[200,70915,252],{"class":214},[200,70917,70918],{"class":202,"line":272},[200,70919,3146],{"class":214},[200,70921,70922,70924,70926,70928,70930,70932,70934,70936,70938,70940,70942,70944,70946,70948,70950,70952,70954,70956,70958],{"class":202,"line":283},[200,70923,5207],{"class":235},[200,70925,239],{"class":214},[200,70927,3180],{"class":210},[200,70929,215],{"class":214},[200,70931,328],{"class":223},[200,70933,1549],{"class":210},[200,70935,215],{"class":214},[200,70937,3198],{"class":223},[200,70939,227],{"class":210},[200,70941,3203],{"class":2060},[200,70943,2104],{"class":210},[200,70945,215],{"class":214},[200,70947,3210],{"class":223},[200,70949,227],{"class":210},[200,70951,249],{"class":214},[200,70953,5238],{"class":245},[200,70955,249],{"class":214},[200,70957,2104],{"class":210},[200,70959,252],{"class":214},[200,70961,70962],{"class":202,"line":300},[200,70963,368],{"class":214},[200,70965,70966,70968,70970,70972,70974,70976],{"class":202,"line":310},[200,70967,3234],{"class":379},[200,70969,383],{"class":214},[200,70971,3988],{"class":386},[200,70973,390],{"class":214},[200,70975,393],{"class":379},[200,70977,280],{"class":214},[200,70979,70980,70982,70984,70986,70988,70990,70992,70994,70996],{"class":202,"line":336},[200,70981,5267],{"class":206},[200,70983,1094],{"class":235},[200,70985,2035],{"class":210},[200,70987,3298],{"class":214},[200,70989,242],{"class":214},[200,70991,2898],{"class":245},[200,70993,249],{"class":214},[200,70995,5282],{"class":235},[200,70997,230],{"class":214},[200,70999,71000,71002,71004,71006,71008],{"class":202,"line":342},[200,71001,3620],{"class":210},[200,71003,509],{"class":214},[200,71005,242],{"class":214},[200,71007,5295],{"class":245},[200,71009,991],{"class":214},[200,71011,71012],{"class":202,"line":365},[200,71013,4977],{"class":214},[200,71015,71016],{"class":202,"line":371},[200,71017,528],{"emptyLinePlaceholder":20},[200,71019,71020,71022,71024,71026,71028,71030,71032,71034,71036],{"class":202,"line":398},[200,71021,5267],{"class":206},[200,71023,1094],{"class":235},[200,71025,2035],{"class":210},[200,71027,3298],{"class":214},[200,71029,242],{"class":214},[200,71031,5320],{"class":245},[200,71033,249],{"class":214},[200,71035,5282],{"class":235},[200,71037,230],{"class":214},[200,71039,71040,71042,71044,71046,71048],{"class":202,"line":405},[200,71041,3620],{"class":210},[200,71043,509],{"class":214},[200,71045,242],{"class":214},[200,71047,5337],{"class":245},[200,71049,991],{"class":214},[200,71051,71052],{"class":202,"line":410},[200,71053,4977],{"class":214},[200,71055,71056],{"class":202,"line":860},[200,71057,528],{"emptyLinePlaceholder":20},[200,71059,71060,71062,71064,71066,71068,71070,71072,71074,71076,71078,71080,71082,71084,71086,71088],{"class":202,"line":889},[200,71061,2014],{"class":379},[200,71063,3561],{"class":210},[200,71065,509],{"class":214},[200,71067,452],{"class":206},[200,71069,4858],{"class":223},[200,71071,227],{"class":235},[200,71073,3333],{"class":214},[200,71075,5366],{"class":245},[200,71077,4868],{"class":214},[200,71079,2035],{"class":210},[200,71081,413],{"class":214},[200,71083,3330],{"class":245},[200,71085,3333],{"class":214},[200,71087,549],{"class":214},[200,71089,280],{"class":214},[200,71091,71092,71094,71096,71098,71100,71102],{"class":202,"line":899},[200,71093,4887],{"class":235},[200,71095,239],{"class":214},[200,71097,4892],{"class":210},[200,71099,215],{"class":214},[200,71101,4897],{"class":210},[200,71103,252],{"class":214},[200,71105,71106,71108],{"class":202,"line":904},[200,71107,2111],{"class":214},[200,71109,416],{"class":235},[200,71111,71112,71114],{"class":202,"line":930},[200,71113,3311],{"class":206},[200,71115,280],{"class":214},[200,71117,71118,71120,71122],{"class":202,"line":3655},[200,71119,3604],{"class":235},[200,71121,239],{"class":214},[200,71123,1410],{"class":235},[200,71125,71126],{"class":202,"line":3660},[200,71127,4924],{"class":214},[200,71129,71130,71132,71134,71136,71138,71140],{"class":202,"line":4035},[200,71131,4929],{"class":235},[200,71133,239],{"class":214},[200,71135,242],{"class":214},[200,71137,697],{"class":245},[200,71139,249],{"class":214},[200,71141,252],{"class":214},[200,71143,71144,71146,71148,71150,71152,71154,71156,71158,71160],{"class":202,"line":4490},[200,71145,4944],{"class":235},[200,71147,239],{"class":214},[200,71149,552],{"class":210},[200,71151,215],{"class":214},[200,71153,557],{"class":223},[200,71155,227],{"class":235},[200,71157,3546],{"class":210},[200,71159,2104],{"class":235},[200,71161,252],{"class":214},[200,71163,71164],{"class":202,"line":4496},[200,71165,4965],{"class":214},[200,71167,71168,71170],{"class":202,"line":4502},[200,71169,4970],{"class":235},[200,71171,252],{"class":214},[200,71173,71174],{"class":202,"line":4508},[200,71175,4977],{"class":214},[200,71177,71178],{"class":202,"line":5469},[200,71179,368],{"class":214},[200,71181,71182],{"class":202,"line":5474},[200,71183,416],{"class":210},[35,71185,5479],{},[68,71187,71188,71190],{},[71,71189,5484],{},[71,71191,5487],{},[35,71193,5490],{},[35,71195,5493],{},[68,71197,71198,71200],{},[71,71199,5498],{},[71,71201,5501],{},[35,71203,5504],{},[68,71205,71206,71210],{},[71,71207,71208,5511],{},[156,71209,3091],{},[71,71211,71212,215],{},[156,71213,5516],{},[35,71215,5519,71216,5522,71218,5525,71220,5529,71223,5532],{},[156,71217,3091],{},[156,71219,5516],{},[39,71221,3385],{"href":3383,"rel":71222},[77],[156,71224,3091],{},[35,71226,5535,71227,5538],{},[156,71228,4289],{},[35,71230,5541],{},[35,71232,5544],{},[35,71234,5547],{},[35,71236,5550],{},[35,71238,5553,71239,5559,71242,215],{},[39,71240,5558],{"href":5556,"rel":71241},[77],[156,71243,5562],{},[35,71245,5565,71246,5569,71248,1655,71250,5569,71252,5576],{},[156,71247,5568],{},[156,71249,3091],{},[156,71251,5562],{},[156,71253,3098],{},[51,71255,5580],{"id":5579},[35,71257,5583],{},[1593,71259,71260,71264,71268],{},[71,71261,71262,5590],{},[156,71263,3091],{},[71,71265,71266,5595],{},[156,71267,3095],{},[71,71269,71270,5600],{},[156,71271,3098],{},[35,71273,5603],{},[35,71275,5606],{},[35,71277,5609],{},[35,71279,5612],{},[1158,71281],{},[35,71283,5617,71284,5621],{},[156,71285,5620],{},[1170,71287,5624],{},{"title":13,"searchDepth":14,"depth":14,"links":71289},[71290,71291,71292,71293],{"id":3054,"depth":14,"text":3055},{"id":3084,"depth":14,"text":3085},{"id":4271,"depth":14,"text":4272},{"id":5579,"depth":14,"text":5580},{},{"title":3021,"description":5632},{"loc":5634},{"id":5641,"title":5642,"body":71298,"contentId":5742,"date":5743,"description":5744,"extension":18,"meta":71361,"navigation":20,"path":5746,"rawbody":5747,"robots":23,"schemaOrg":23,"seo":71362,"sitemap":71363,"stem":5750,"__hash__":5751},{"type":7,"value":71299,"toc":71357},[71300,71302,71304,71306,71311,71315,71317,71319,71321,71323,71325,71327,71329,71334,71339,71347,71349,71351,71353,71355],[35,71301,5647],{},[35,71303,5650],{},[51,71305,5654],{"id":5653},[35,71307,5657,71308,5663],{},[39,71309,5662],{"href":5660,"rel":71310},[77],[35,71312,5666,71313],{},[3045,71314,5670],{"id":5669},[35,71316,5673],{},[35,71318,5676],{},[35,71320,5679],{},[35,71322,5682],{},[35,71324,5685],{},[51,71326,5689],{"id":5688},[35,71328,5692],{},[35,71330,5695,71331,5701],{},[39,71332,5700],{"href":5698,"rel":71333},[77],[35,71335,5704,71336,5710],{},[39,71337,5709],{"href":5707,"rel":71338},[77],[35,71340,5713,71341,5719,71344,5724],{},[39,71342,5718],{"href":5716,"rel":71343},[77],[39,71345,5723],{"href":1327,"rel":71346},[77],[1158,71348],{},[35,71350,5729],{},[1158,71352],{},[35,71354,5734],{},[35,71356,5737],{},{"title":13,"searchDepth":14,"depth":14,"links":71358},[71359,71360],{"id":5653,"depth":14,"text":5654},{"id":5688,"depth":14,"text":5689},{},{"title":5642,"description":5744},{"loc":5746},{"id":5753,"title":5754,"body":71365,"contentId":6793,"date":6794,"description":6795,"extension":18,"meta":72199,"navigation":20,"path":6797,"rawbody":6798,"robots":23,"schemaOrg":23,"seo":72200,"sitemap":72201,"stem":6801,"__hash__":6802},{"type":7,"value":71366,"toc":72196},[71367,71369,71371,71373,71375,71377,71379,71384,71396,71398,72194],[35,71368,5759],{},[35,71370,5762],{},[35,71372,5765],{},[35,71374,5768],{},[51,71376,5772],{"id":5771},[35,71378,5775],{},[35,71380,5778,71381,5784],{},[39,71382,5783],{"href":5781,"rel":71383},[77],[1593,71385,71386,71388,71390,71392,71394],{},[71,71387,5789],{},[71,71389,5792],{},[71,71391,5795],{},[71,71393,5798],{},[71,71395,5801],{},[35,71397,5804],{},[5806,71399,71400,71402],{},[35,71401,5810],{},[5812,71403,71404,71406],{},[35,71405,5816],{},[5818,71407,71408,71410,71412,71414,71416,71418,71420,71422,71424,71429,71435,71437,71439,71441,71443,71449,71451],{},[35,71409,5822],{},[35,71411,5825],{},[35,71413,5828],{},[35,71415,5831],{},[35,71417,5834],{},[51,71419,5838],{"id":5837},[35,71421,5841],{},[35,71423,5844],{},[35,71425,5847,71426,215],{},[39,71427,5851],{"href":5556,"rel":71428},[77],[938,71430,71431],{},[35,71432,5856,71433,5859],{},[200,71434,573],{},[35,71436,5862],{},[35,71438,5865],{},[35,71440,5868],{},[35,71442,5871],{},[68,71444,71445,71447],{},[71,71446,5876],{},[71,71448,5879],{},[35,71450,5882],{},[5884,71452,71453,71455,71457,71459,71461,71463,71567,71569,71739,71743],{},[35,71454,5888],{},[35,71456,5891],{},[35,71458,5894],{},[35,71460,5897],{},[35,71462,5900],{},[192,71464,71465],{"className":5903,"code":5904,"language":18,"meta":13,"style":13},[156,71466,71467,71471,71475,71479,71483,71487,71499,71511,71515,71519,71523,71527,71531,71535,71539,71543,71547,71551,71555,71559,71563],{"__ignoreMap":13},[200,71468,71469],{"class":202,"line":203},[200,71470,5911],{"class":210},[200,71472,71473],{"class":202,"line":14},[200,71474,528],{"emptyLinePlaceholder":20},[200,71476,71477],{"class":202,"line":255},[200,71478,5920],{"class":210},[200,71480,71481],{"class":202,"line":272},[200,71482,5925],{"class":210},[200,71484,71485],{"class":202,"line":283},[200,71486,5930],{"class":210},[200,71488,71489,71491,71493,71495,71497],{"class":202,"line":300},[200,71490,5935],{"class":210},[200,71492,599],{"class":214},[200,71494,328],{"class":235},[200,71496,674],{"class":214},[200,71498,5944],{"class":210},[200,71500,71501,71503,71505,71507,71509],{"class":202,"line":310},[200,71502,5949],{"class":210},[200,71504,599],{"class":214},[200,71506,5954],{"class":235},[200,71508,674],{"class":214},[200,71510,5944],{"class":210},[200,71512,71513],{"class":202,"line":336},[200,71514,1129],{"class":210},[200,71516,71517],{"class":202,"line":342},[200,71518,528],{"emptyLinePlaceholder":20},[200,71520,71521],{"class":202,"line":365},[200,71522,5971],{"class":210},[200,71524,71525],{"class":202,"line":371},[200,71526,528],{"emptyLinePlaceholder":20},[200,71528,71529],{"class":202,"line":398},[200,71530,5980],{"class":210},[200,71532,71533],{"class":202,"line":405},[200,71534,528],{"emptyLinePlaceholder":20},[200,71536,71537],{"class":202,"line":410},[200,71538,5989],{"class":210},[200,71540,71541],{"class":202,"line":860},[200,71542,528],{"emptyLinePlaceholder":20},[200,71544,71545],{"class":202,"line":889},[200,71546,5998],{"class":210},[200,71548,71549],{"class":202,"line":899},[200,71550,6003],{"class":210},[200,71552,71553],{"class":202,"line":904},[200,71554,6008],{"class":210},[200,71556,71557],{"class":202,"line":930},[200,71558,6013],{"class":210},[200,71560,71561],{"class":202,"line":3655},[200,71562,6018],{"class":210},[200,71564,71565],{"class":202,"line":3660},[200,71566,6023],{"class":210},[35,71568,6026],{},[192,71570,71571],{"className":1380,"code":6029,"language":1383,"meta":13,"style":13},[156,71572,71573,71577,71587,71613,71699,71735],{"__ignoreMap":13},[200,71574,71575],{"class":202,"line":203},[200,71576,6036],{"class":401},[200,71578,71579,71581,71583,71585],{"class":202,"line":14},[200,71580,3726],{"class":379},[200,71582,3729],{"class":214},[200,71584,393],{"class":379},[200,71586,280],{"class":214},[200,71588,71589,71591,71593,71595,71597,71599,71601,71603,71605,71607,71609,71611],{"class":202,"line":255},[200,71590,3738],{"class":379},[200,71592,6053],{"class":210},[200,71594,509],{"class":214},[200,71596,452],{"class":206},[200,71598,6060],{"class":210},[200,71600,215],{"class":214},[200,71602,6065],{"class":223},[200,71604,227],{"class":235},[200,71606,249],{"class":214},[200,71608,2872],{"class":245},[200,71610,249],{"class":214},[200,71612,416],{"class":235},[200,71614,71615,71617,71619,71621,71623,71625,71627,71629,71631,71633,71635,71637,71639,71641,71643,71645,71647,71649,71651,71653,71655,71657,71659,71661,71663,71665,71667,71669,71671,71673,71675,71677,71679,71681,71683,71685,71687,71689,71691,71693,71695,71697],{"class":202,"line":272},[200,71616,3738],{"class":379},[200,71618,6082],{"class":210},[200,71620,509],{"class":214},[200,71622,6053],{"class":210},[200,71624,215],{"class":214},[200,71626,6091],{"class":223},[200,71628,227],{"class":235},[200,71630,249],{"class":214},[200,71632,6098],{"class":210},[200,71634,249],{"class":214},[200,71636,2104],{"class":235},[200,71638,215],{"class":214},[200,71640,3773],{"class":223},[200,71642,227],{"class":235},[200,71644,202],{"class":386},[200,71646,393],{"class":379},[200,71648,6115],{"class":210},[200,71650,215],{"class":214},[200,71652,6120],{"class":223},[200,71654,227],{"class":235},[200,71656,249],{"class":214},[200,71658,2902],{"class":245},[200,71660,249],{"class":214},[200,71662,6131],{"class":235},[200,71664,215],{"class":214},[200,71666,3541],{"class":223},[200,71668,227],{"class":235},[200,71670,202],{"class":386},[200,71672,393],{"class":379},[200,71674,6115],{"class":210},[200,71676,215],{"class":214},[200,71678,6148],{"class":223},[200,71680,227],{"class":235},[200,71682,2898],{"class":214},[200,71684,6155],{"class":206},[200,71686,2902],{"class":245},[200,71688,6160],{"class":214},[200,71690,6163],{"class":245},[200,71692,6166],{"class":214},[200,71694,549],{"class":214},[200,71696,3909],{"class":214},[200,71698,584],{"class":235},[200,71700,71701,71703,71705,71707,71709,71711,71713,71715,71717,71719,71721,71723,71725,71727,71729,71731,71733],{"class":202,"line":283},[200,71702,6177],{"class":206},[200,71704,6060],{"class":210},[200,71706,215],{"class":214},[200,71708,6184],{"class":223},[200,71710,227],{"class":235},[200,71712,249],{"class":214},[200,71714,6191],{"class":245},[200,71716,249],{"class":214},[200,71718,549],{"class":214},[200,71720,6082],{"class":210},[200,71722,215],{"class":214},[200,71724,6202],{"class":223},[200,71726,227],{"class":235},[200,71728,249],{"class":214},[200,71730,6098],{"class":210},[200,71732,249],{"class":214},[200,71734,584],{"class":235},[200,71736,71737],{"class":202,"line":300},[200,71738,1129],{"class":214},[35,71740,6219,71741,6223],{},[156,71742,6222],{},[6225,71744,71745,71747,71749,71751,71756,71758,71762,71764,71766,71768,71770,71772,71774,71830,71832,71834,71836,71838,71840,71842,71844,71846,71851,71855,71857,71859,71861,71867,71871,71873,71877,72083,72085,72089,72163,72165,72172,72174,72176,72178,72180,72188,72190,72192],{},[35,71746,6229],{},[51,71748,6233],{"id":6232},[35,71750,6236],{},[35,71752,6239,71753,6245],{},[39,71754,6244],{"href":6242,"rel":71755},[77],[35,71757,6248],{},[35,71759,6251,71760],{},[1216,71761,6254],{},[1929,71763,6258],{"id":6257},[35,71765,6261],{},[35,71767,6264],{},[1929,71769,6268],{"id":6267},[35,71771,6271],{},[35,71773,6274],{},[192,71775,71776],{"className":1380,"code":6277,"language":1383,"meta":13,"style":13},[156,71777,71778],{"__ignoreMap":13},[200,71779,71780,71782,71784,71786,71788,71790,71792,71794,71796,71798,71800,71802,71804,71806,71808,71810,71812,71814,71816,71818,71820,71822,71824,71826,71828],{"class":202,"line":203},[200,71781,1549],{"class":214},[200,71783,393],{"class":379},[200,71785,6053],{"class":210},[200,71787,215],{"class":214},[200,71789,6091],{"class":223},[200,71791,227],{"class":210},[200,71793,249],{"class":214},[200,71795,2902],{"class":245},[200,71797,249],{"class":214},[200,71799,6302],{"class":210},[200,71801,3203],{"class":2060},[200,71803,360],{"class":210},[200,71805,215],{"class":214},[200,71807,6091],{"class":223},[200,71809,227],{"class":210},[200,71811,249],{"class":214},[200,71813,6317],{"class":245},[200,71815,249],{"class":214},[200,71817,6302],{"class":210},[200,71819,3948],{"class":2060},[200,71821,360],{"class":210},[200,71823,215],{"class":214},[200,71825,3191],{"class":223},[200,71827,6332],{"class":210},[200,71829,6335],{"class":401},[35,71831,6338],{},[35,71833,6341],{},[35,71835,6344],{},[35,71837,6347],{},[51,71839,6351],{"id":6350},[35,71841,6354],{},[35,71843,6357],{},[35,71845,6360],{},[35,71847,6363,71848,6369],{},[39,71849,6368],{"href":6366,"rel":71850},[77],[938,71852,71853],{},[35,71854,6374],{},[35,71856,6377],{},[1929,71858,6381],{"id":6380},[35,71860,6384],{},[68,71862,71863,71865],{},[71,71864,6389],{},[71,71866,6392],{},[35,71868,6395,71869,6399],{},[156,71870,6398],{},[35,71872,6402],{},[68,71874,71875],{},[71,71876,6407],{},[192,71878,71879],{"className":1380,"code":6410,"language":1383,"meta":13,"style":13},[156,71880,71881,71891,71901,71939,71973,72017,72061,72065,72069,72073,72079],{"__ignoreMap":13},[200,71882,71883,71885,71887,71889],{"class":202,"line":203},[200,71884,3726],{"class":379},[200,71886,3729],{"class":214},[200,71888,393],{"class":379},[200,71890,280],{"class":214},[200,71892,71893,71895,71897,71899],{"class":202,"line":14},[200,71894,3738],{"class":379},[200,71896,3512],{"class":210},[200,71898,509],{"class":214},[200,71900,6433],{"class":235},[200,71902,71903,71905,71907,71909,71911,71913,71915,71917,71919,71921,71923,71925,71927,71929,71931,71933,71935,71937],{"class":202,"line":255},[200,71904,6438],{"class":206},[200,71906,1094],{"class":235},[200,71908,443],{"class":379},[200,71910,350],{"class":214},[200,71912,6447],{"class":210},[200,71914,549],{"class":214},[200,71916,6452],{"class":210},[200,71918,360],{"class":214},[200,71920,6457],{"class":214},[200,71922,6460],{"class":210},[200,71924,215],{"class":214},[200,71926,6465],{"class":223},[200,71928,227],{"class":235},[200,71930,6470],{"class":210},[200,71932,215],{"class":214},[200,71934,6475],{"class":210},[200,71936,6478],{"class":235},[200,71938,230],{"class":214},[200,71940,71941,71943,71945,71947,71949,71951,71953,71955,71957,71959,71961,71963,71965,71967,71969,71971],{"class":202,"line":272},[200,71942,6485],{"class":206},[200,71944,1094],{"class":235},[200,71946,443],{"class":379},[200,71948,350],{"class":214},[200,71950,6494],{"class":210},[200,71952,549],{"class":214},[200,71954,6499],{"class":210},[200,71956,360],{"class":214},[200,71958,6457],{"class":214},[200,71960,6460],{"class":210},[200,71962,215],{"class":214},[200,71964,6465],{"class":223},[200,71966,227],{"class":235},[200,71968,6514],{"class":210},[200,71970,6478],{"class":235},[200,71972,230],{"class":214},[200,71974,71975,71977,71979,71981,71983,71985,71987,71989,71991,71993,71995,71997,71999,72001,72003,72005,72007,72009,72011,72013,72015],{"class":202,"line":283},[200,71976,6523],{"class":206},[200,71978,1094],{"class":235},[200,71980,6528],{"class":210},[200,71982,215],{"class":214},[200,71984,6533],{"class":210},[200,71986,6536],{"class":214},[200,71988,6539],{"class":223},[200,71990,227],{"class":235},[200,71992,6544],{"class":386},[200,71994,393],{"class":379},[200,71996,6549],{"class":210},[200,71998,215],{"class":214},[200,72000,3847],{"class":223},[200,72002,6332],{"class":235},[200,72004,6558],{"class":214},[200,72006,242],{"class":214},[200,72008,6563],{"class":245},[200,72010,249],{"class":214},[200,72012,6478],{"class":235},[200,72014,2767],{"class":214},[200,72016,6572],{"class":401},[200,72018,72019,72021,72023,72025,72027,72029,72031,72033,72035,72037,72039,72041,72043,72045,72047,72049,72051,72053,72055,72057,72059],{"class":202,"line":300},[200,72020,6577],{"class":210},[200,72022,215],{"class":214},[200,72024,6582],{"class":223},[200,72026,227],{"class":235},[200,72028,2767],{"class":214},[200,72030,6589],{"class":235},[200,72032,239],{"class":214},[200,72034,6589],{"class":210},[200,72036,215],{"class":214},[200,72038,6598],{"class":223},[200,72040,1549],{"class":235},[200,72042,549],{"class":214},[200,72044,6605],{"class":210},[200,72046,549],{"class":214},[200,72048,6610],{"class":235},[200,72050,239],{"class":214},[200,72052,6499],{"class":210},[200,72054,215],{"class":214},[200,72056,6619],{"class":210},[200,72058,980],{"class":214},[200,72060,416],{"class":235},[200,72062,72063],{"class":202,"line":310},[200,72064,4493],{"class":214},[200,72066,72067],{"class":202,"line":336},[200,72068,4977],{"class":214},[200,72070,72071],{"class":202,"line":342},[200,72072,4505],{"class":214},[200,72074,72075,72077],{"class":202,"line":365},[200,72076,3758],{"class":206},[200,72078,6642],{"class":210},[200,72080,72081],{"class":202,"line":371},[200,72082,1129],{"class":214},[35,72084,6649],{},[68,72086,72087],{"start":14},[71,72088,6654],{},[192,72090,72091],{"className":1380,"code":6657,"language":1383,"meta":13,"style":13},[156,72092,72093,72111,72125,72141,72157],{"__ignoreMap":13},[200,72094,72095,72097,72099,72101,72103,72105,72107,72109],{"class":202,"line":203},[200,72096,3726],{"class":379},[200,72098,3729],{"class":214},[200,72100,393],{"class":379},[200,72102,6670],{"class":210},[200,72104,215],{"class":214},[200,72106,6675],{"class":223},[200,72108,227],{"class":210},[200,72110,230],{"class":214},[200,72112,72113,72115,72117,72119,72121,72123],{"class":202,"line":14},[200,72114,6684],{"class":235},[200,72116,239],{"class":214},[200,72118,242],{"class":214},[200,72120,6691],{"class":245},[200,72122,249],{"class":214},[200,72124,252],{"class":214},[200,72126,72127,72129,72131,72133,72135,72137,72139],{"class":202,"line":255},[200,72128,6700],{"class":235},[200,72130,239],{"class":214},[200,72132,242],{"class":214},[200,72134,6707],{"class":245},[200,72136,249],{"class":214},[200,72138,549],{"class":214},[200,72140,6714],{"class":401},[200,72142,72143,72145,72147,72149,72151,72153,72155],{"class":202,"line":272},[200,72144,6719],{"class":235},[200,72146,239],{"class":214},[200,72148,318],{"class":214},[200,72150,6726],{"class":235},[200,72152,239],{"class":214},[200,72154,6731],{"class":2060},[200,72156,333],{"class":214},[200,72158,72159,72161],{"class":202,"line":283},[200,72160,413],{"class":214},[200,72162,416],{"class":210},[35,72164,6742],{},[35,72166,6745,72167,6749,72169,6755],{},[156,72168,6748],{},[39,72170,6754],{"href":6752,"rel":72171},[77],[35,72173,6758],{},[35,72175,6761],{},[51,72177,6765],{"id":6764},[35,72179,6768],{},[130,72181,132,72182,132,72186],{},[134,72183,136,72184,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,72185],{"src":6775,"type":141},[143,72187,6778],{},[1158,72189],{},[35,72191,6783],{},[35,72193,6786],{},[1170,72195,6789],{},{"title":13,"searchDepth":14,"depth":14,"links":72197},[72198],{"id":5771,"depth":14,"text":5772},{},{"title":5754,"description":6795},{"loc":6797},{"id":6804,"title":6805,"body":72203,"contentId":7464,"date":7465,"description":7466,"extension":18,"meta":72654,"navigation":20,"path":7468,"rawbody":7469,"robots":23,"schemaOrg":23,"seo":72655,"sitemap":72656,"stem":7483,"__hash__":7484},{"type":7,"value":72204,"toc":72647},[72205,72207,72209,72213,72215,72217,72221,72223,72225,72230,72232,72234,72432,72436,72440,72442],[35,72206,6810],{},[35,72208,6813],{},[35,72210,72211],{},[1707,72212],{"alt":6818,"src":6819},[35,72214,6822],{},[35,72216,6825],{},[35,72218,72219],{},[1216,72220,6830],{},[51,72222,6834],{"id":6833},[35,72224,6837],{},[35,72226,6840,72227,215],{},[39,72228,6845],{"href":6843,"rel":72229},[77],[35,72231,6848],{},[35,72233,6851],{},[68,72235,72236,72306,72376],{},[71,72237,6856,72238],{},[1593,72239,72240,72247,72254,72261,72268,72275,72282,72292,72299],{},[71,72241,72242,6865,72244],{},[6862,72243,6864],{},[39,72245,6870],{"href":6868,"rel":72246},[77],[71,72248,72249,6865,72251],{},[6862,72250,6875],{},[39,72252,6880],{"href":6878,"rel":72253},[77],[71,72255,72256,6865,72258],{},[6862,72257,6885],{},[39,72259,6890],{"href":6888,"rel":72260},[77],[71,72262,72263,6865,72265],{},[6862,72264,6895],{},[39,72266,6900],{"href":6898,"rel":72267},[77],[71,72269,72270,6865,72272],{},[6862,72271,6905],{},[39,72273,6910],{"href":6908,"rel":72274},[77],[71,72276,72277,6865,72279],{},[6862,72278,6915],{},[39,72280,6920],{"href":6918,"rel":72281},[77],[71,72283,72284,6865,72286,1655,72289],{},[6862,72285,6925],{},[39,72287,6930],{"href":6928,"rel":72288},[77],[39,72290,6935],{"href":6933,"rel":72291},[77],[71,72293,72294,6865,72296],{},[6862,72295,6940],{},[39,72297,6945],{"href":6943,"rel":72298},[77],[71,72300,72301,6865,72303],{},[6862,72302,6950],{},[39,72304,6955],{"href":6953,"rel":72305},[77],[71,72307,6958,72308],{},[1593,72309,72310,72317,72324,72331,72338,72345,72352,72362,72369],{},[71,72311,72312,6865,72314],{},[6862,72313,6965],{},[39,72315,6970],{"href":6968,"rel":72316},[77],[71,72318,72319,6865,72321],{},[6862,72320,6975],{},[39,72322,6980],{"href":6978,"rel":72323},[77],[71,72325,72326,6865,72328],{},[6862,72327,6985],{},[39,72329,6990],{"href":6988,"rel":72330},[77],[71,72332,72333,6865,72335],{},[6862,72334,6995],{},[39,72336,7000],{"href":6998,"rel":72337},[77],[71,72339,72340,6865,72342],{},[6862,72341,7005],{},[39,72343,7010],{"href":7008,"rel":72344},[77],[71,72346,72347,6865,72349],{},[6862,72348,7015],{},[39,72350,7020],{"href":7018,"rel":72351},[77],[71,72353,72354,6865,72356,1655,72359],{},[6862,72355,7025],{},[39,72357,7030],{"href":7028,"rel":72358},[77],[39,72360,7035],{"href":7033,"rel":72361},[77],[71,72363,72364,6865,72366],{},[6862,72365,7040],{},[39,72367,7045],{"href":7043,"rel":72368},[77],[71,72370,72371,6865,72373],{},[6862,72372,7050],{},[39,72374,7055],{"href":7053,"rel":72375},[77],[71,72377,7058,72378],{},[1593,72379,72380,72387,72394,72401,72408,72418,72425],{},[71,72381,72382,6865,72384],{},[6862,72383,7065],{},[39,72385,7070],{"href":7068,"rel":72386},[77],[71,72388,72389,6865,72391],{},[6862,72390,7075],{},[39,72392,7080],{"href":7078,"rel":72393},[77],[71,72395,72396,6865,72398],{},[6862,72397,7085],{},[39,72399,7090],{"href":7088,"rel":72400},[77],[71,72402,72403,6865,72405],{},[6862,72404,7095],{},[39,72406,7100],{"href":7098,"rel":72407},[77],[71,72409,72410,6865,72412,1655,72415],{},[6862,72411,7105],{},[39,72413,7110],{"href":7108,"rel":72414},[77],[39,72416,7115],{"href":7113,"rel":72417},[77],[71,72419,72420,6865,72422],{},[6862,72421,7120],{},[39,72423,7125],{"href":7123,"rel":72424},[77],[71,72426,72427,6865,72429],{},[6862,72428,7130],{},[39,72430,7135],{"href":7133,"rel":72431},[77],[35,72433,72434],{},[1216,72435,7140],{},[35,72437,7143,72438],{},[1216,72439,7146],{},[35,72441,7149],{},[7151,72443,72444,72446,72448,72489,72491,72493,72497,72499,72503,72507,72514,72519,72521,72523,72527,72529,72533,72535,72567,72569,72571,72575,72579,72583,72585,72589,72603,72607,72620,72633,72635,72639,72641,72643,72645],{"w-full":13,"icon":7153,"text":7154,"to":7155,"target":7156},[51,72445,7160],{"id":7159},[35,72447,7163],{},[68,72449,72450,72463,72476],{},[71,72451,72452,6865,72454,72457,7176,72459,72461],{},[6862,72453,7120],{},[39,72455,7125],{"href":7123,"rel":72456},[77],[7174,72458],{},[7174,72460],{},[1707,72462],{"alt":7181,"src":7182},[71,72464,72465,6865,72467,72470,7192,72472,72474],{},[6862,72466,6915],{},[39,72468,6920],{"href":6918,"rel":72469},[77],[7174,72471],{},[7174,72473],{},[1707,72475],{"alt":7197,"src":7198},[71,72477,72478,6865,72480,72483,7208,72485,72487],{},[6862,72479,6985],{},[39,72481,6990],{"href":6988,"rel":72482},[77],[7174,72484],{},[7174,72486],{},[1707,72488],{"alt":7213,"src":7214},[51,72490,7218],{"id":7217},[35,72492,7221],{},[35,72494,72495],{},[1707,72496],{"alt":7226,"src":7227},[35,72498,7230],{},[35,72500,72501],{},[1707,72502],{"alt":7235,"src":7236},[35,72504,72505],{},[1707,72506],{"alt":7241,"src":7242},[35,72508,7245,72509,7250,72511,215],{},[39,72510,7249],{"href":7248},[39,72512,7255],{"href":7253,"rel":72513},[77],[35,72515,7258,72516,215],{},[39,72517,7263],{"href":7261,"rel":72518},[77],[51,72520,7267],{"id":7266},[35,72522,7270],{},[35,72524,7273,72525],{},[1216,72526,7276],{},[35,72528,7279],{},[35,72530,7282,72531],{},[1216,72532,7285],{},[35,72534,7288],{},[1593,72536,72537,72539,72541,72543,72545,72547,72549,72551,72553,72555,72557,72559,72561,72563],{},[71,72538,7293],{},[71,72540,7296],{},[71,72542,7299],{},[71,72544,7302],{},[71,72546,7305],{},[71,72548,7308],{},[71,72550,7311],{},[71,72552,7314],{},[71,72554,7317],{},[71,72556,7320],{},[71,72558,7323],{},[71,72560,7326],{},[71,72562,7329],{},[71,72564,7332,72565],{},[1216,72566,7335],{},[35,72568,7338],{},[35,72570,7341],{},[35,72572,72573],{},[1707,72574],{"alt":7346,"src":7347},[35,72576,72577],{},[1707,72578],{"alt":7352,"src":7353},[35,72580,72581],{},[1707,72582],{"alt":7358,"src":7359},[51,72584,7363],{"id":7362},[35,72586,7366,72587],{},[1216,72588,7369],{},[35,72590,7372,72591,3092,72594,1655,72597,106,72600,7391],{},[39,72592,1792],{"href":1790,"rel":72593},[77],[39,72595,7380],{"href":7378,"rel":72596},[77],[39,72598,7385],{"href":7383,"rel":72599},[77],[39,72601,7390],{"href":7388,"rel":72602},[77],[35,72604,7394,72605],{},[1216,72606,7397],{},[35,72608,7400,72609,3092,72612,1655,72615,7410,72618],{},[39,72610,1792],{"href":1790,"rel":72611},[77],[39,72613,7380],{"href":7378,"rel":72614},[77],[39,72616,7385],{"href":7383,"rel":72617},[77],[1216,72619,7413],{},[35,72621,7416,72622,7422,72625,7428,72628,7434,72631],{},[39,72623,7421],{"href":7419,"rel":72624},[77],[39,72626,7427],{"href":7425,"rel":72627},[77],[39,72629,7433],{"href":7431,"rel":72630},[77],[1216,72632,7437],{},[35,72634,7440],{},[35,72636,7443,72637],{},[1216,72638,7446],{},[1158,72640],{},[35,72642,7451],{},[1158,72644],{},[35,72646,7456],{},{"title":13,"searchDepth":14,"depth":14,"links":72648},[72649,72650,72651,72652,72653],{"id":6833,"depth":14,"text":6834},{"id":7159,"depth":14,"text":7160},{"id":7217,"depth":14,"text":7218},{"id":7266,"depth":14,"text":7267},{"id":7362,"depth":14,"text":7363},{},{"title":6805,"description":7466},{"loc":7468,"images":72657},[72658,72659,72660,72661,72662,72663,72664,72665,72666,72667],{"loc":6819},{"loc":7182},{"loc":7198},{"loc":7214},{"loc":7227},{"loc":7236},{"loc":7242},{"loc":7347},{"loc":7353},{"loc":7359},{"id":7486,"title":7487,"body":72669,"contentId":8164,"date":8165,"description":8166,"extension":18,"meta":73142,"navigation":20,"path":8168,"rawbody":8169,"robots":23,"schemaOrg":23,"seo":73143,"sitemap":73144,"stem":8187,"__hash__":8188},{"type":7,"value":72670,"toc":73132},[72671,72678,72680,72684,72686,72697,72701,72714,72716,72718,72723,72730,72737,72739,72741,72745,72747,72749,72797,72801,72803,72807,72809,72852,72856,72861,72871,72882,72886,72890,72894,72896,72898,72938,72945,72947,72951,72953,72957,72959,72964,72966,72970,72974,72976,72978],[35,72672,7492,72673,106,72676,7502],{},[39,72674,7497],{"href":7495,"rel":72675},[77],[39,72677,7501],{"href":7500},[35,72679,7505],{},[35,72681,72682],{},[1707,72683],{"alt":7510,"src":7511},[35,72685,7514],{},[35,72687,7517,72688,3092,72691,1655,72694,215],{},[39,72689,1792],{"href":1790,"rel":72690},[77],[39,72692,7380],{"href":7378,"rel":72693},[77],[39,72695,7385],{"href":7383,"rel":72696},[77],[35,72698,72699],{},[1707,72700],{"alt":7531,"src":7532},[35,72702,7535,72703,1655,72706,7542,72709,7546,72712],{},[39,72704,1792],{"href":1790,"rel":72705},[77],[39,72707,7380],{"href":7378,"rel":72708},[77],[39,72710,7385],{"href":7383,"rel":72711},[77],[1216,72713,7549],{},[35,72715,7552],{},[35,72717,7555],{},[35,72719,7558,72720,7563],{},[39,72721,6845],{"href":7561,"rel":72722},[77],[35,72724,72725,7569,72728],{},[39,72726,6845],{"href":7561,"rel":72727},[77],[1216,72729,7572],{},[35,72731,7575,72732],{},[1216,72733,7578,72734,7582],{},[39,72735,6845],{"href":7561,"rel":72736},[77],[51,72738,7249],{"id":7585},[35,72740,7588],{},[35,72742,7591,72743],{},[1216,72744,7594],{},[35,72746,7597],{},[35,72748,7600],{},[1593,72750,72751,72758,72765,72772,72779,72786,72793],{},[71,72752,72753,6865,72755],{},[6862,72754,7607],{},[39,72756,7612],{"href":7610,"rel":72757},[77],[71,72759,72760,6865,72762],{},[6862,72761,7617],{},[39,72763,7045],{"href":7043,"rel":72764},[77],[71,72766,72767,6865,72769],{},[6862,72768,7625],{},[39,72770,7630],{"href":7628,"rel":72771},[77],[71,72773,72774,6865,72776],{},[6862,72775,7635],{},[39,72777,7640],{"href":7638,"rel":72778},[77],[71,72780,72781,6865,72783],{},[6862,72782,7645],{},[39,72784,7650],{"href":7648,"rel":72785},[77],[71,72787,72788,6865,72790],{},[6862,72789,7655],{},[39,72791,7660],{"href":7658,"rel":72792},[77],[71,72794,72795,7666],{},[6862,72796,7665],{},[35,72798,72799],{},[1707,72800],{"alt":7671,"src":7672},[35,72802,7675],{},[35,72804,7678,72805],{},[1216,72806,7681],{},[35,72808,7684],{},[1593,72810,72811,72818,72825,72835,72845],{},[71,72812,72813,6865,72815],{},[6862,72814,7691],{},[39,72816,7696],{"href":7694,"rel":72817},[77],[71,72819,72820,6865,72822],{},[6862,72821,7701],{},[39,72823,7706],{"href":7704,"rel":72824},[77],[71,72826,72827,6865,72829,1655,72832],{},[6862,72828,7711],{},[39,72830,7715],{"href":7561,"rel":72831},[77],[39,72833,7045],{"href":7043,"rel":72834},[77],[71,72836,72837,6865,72839,1655,72842],{},[6862,72838,7723],{},[39,72840,7727],{"href":1790,"rel":72841},[77],[39,72843,7731],{"href":7378,"rel":72844},[77],[71,72846,72847,6865,72849],{},[6862,72848,7736],{},[39,72850,7741],{"href":7739,"rel":72851},[77],[35,72853,7744,72854],{},[1216,72855,7747],{},[35,72857,7750,72858,7754],{},[39,72859,7385],{"href":7383,"rel":72860},[77],[35,72862,7757,72863,1655,72866,7764,72869],{},[39,72864,1792],{"href":1790,"rel":72865},[77],[39,72867,7380],{"href":7378,"rel":72868},[77],[1216,72870,7767],{},[35,72872,7770,72873,3092,72876,1655,72879,215],{},[39,72874,1792],{"href":1790,"rel":72875},[77],[39,72877,7380],{"href":7378,"rel":72878},[77],[39,72880,7385],{"href":7383,"rel":72881},[77],[35,72883,72884],{},[1707,72885],{"alt":7784,"src":7785},[35,72887,72888],{},[1707,72889],{"alt":7790,"src":7791},[35,72891,7794,72892],{},[1216,72893,7797],{},[51,72895,7801],{"id":7800},[35,72897,7804],{},[68,72899,72900,72922],{},[71,72901,72902,6865,72904,1655,72907,72910,7819,72912,1655,72915,7826,72918,72920],{},[6862,72903,7723],{},[39,72905,7727],{"href":1790,"rel":72906},[77],[39,72908,7731],{"href":7378,"rel":72909},[77],[7174,72911],{},[39,72913,1792],{"href":1790,"rel":72914},[77],[39,72916,7380],{"href":7378,"rel":72917},[77],[7174,72919],{},[1707,72921],{"alt":7831,"src":7832},[71,72923,72924,6865,72926,72929,7842,72931,7847,72934,72936],{},[6862,72925,7645],{},[39,72927,7650],{"href":7648,"rel":72928},[77],[7174,72930],{},[39,72932,7846],{"href":7648,"rel":72933},[77],[7174,72935],{},[1707,72937],{"alt":7852,"src":7853},[35,72939,7856,72940,7860,72943],{},[39,72941,7385],{"href":7383,"rel":72942},[77],[1216,72944,7863],{},[51,72946,7867],{"id":7866},[35,72948,7870,72949],{},[1216,72950,7873],{},[1929,72952,7877],{"id":7876},[35,72954,72955],{},[1707,72956],{"alt":7882,"src":7883},[35,72958,7886],{},[35,72960,7889,72961,7895],{},[39,72962,7894],{"href":7892,"rel":72963},[77],[35,72965,7898],{},[35,72967,7901,72968],{},[1216,72969,7904],{},[35,72971,72972],{},[1707,72973],{"alt":7909,"src":7910},[35,72975,7913],{},[35,72977,7916],{},[7918,72979,72980,72982,72989,72991,72997,72999,73001,73003,73007,73011,73015,73020,73022,73024,73028,73033,73041,73045,73047,73052,73054,73056,73064,73066,73073,73077,73079,73083,73087,73089,73103,73111,73122,73124,73128,73130],{"href":7920,"text":7921},[1929,72981,7925],{"id":7924},[35,72983,7928,72984,7932,72987],{},[39,72985,6845],{"href":7561,"rel":72986},[77],[1216,72988,7935],{},[35,72990,7938],{},[35,72992,7941,72993],{},[1216,72994,7944,72995,7949],{},[39,72996,7948],{"href":7947},[35,72998,7952],{},[35,73000,7955],{},[35,73002,7958],{},[35,73004,7961,73005],{},[1216,73006,7964],{},[35,73008,7967,73009],{},[1216,73010,7970],{},[35,73012,7973,73013],{},[1216,73014,7976],{},[35,73016,7979,73017,7983],{},[39,73018,6845],{"href":7561,"rel":73019},[77],[35,73021,7986],{},[35,73023,7989],{},[35,73025,73026],{},[1707,73027],{"alt":7994,"src":7995},[35,73029,7998,73030,8002],{},[39,73031,6845],{"href":7561,"rel":73032},[77],[35,73034,8005,73035,8011,73038,215],{},[39,73036,8010],{"href":8008,"rel":73037},[77],[39,73039,8016],{"href":8014,"rel":73040},[77],[35,73042,73043],{},[1707,73044],{"alt":8021,"src":8022},[35,73046,8025],{},[35,73048,8028,73049,215],{},[39,73050,8033],{"href":8031,"rel":73051},[77],[35,73053,8036],{},[1929,73055,8040],{"id":8039},[35,73057,8043,73058,8048,73061,215],{},[39,73059,8040],{"href":8046,"rel":73060},[77],[39,73062,8053],{"href":8051,"rel":73063},[77],[35,73065,8056],{},[35,73067,8059,73068,8065,73071],{},[39,73069,8064],{"href":8062,"rel":73070},[77],[1216,73072,8068],{},[35,73074,73075],{},[1707,73076],{"alt":8073,"src":8074},[51,73078,5580],{"id":5579},[35,73080,8079,73081],{},[1216,73082,8082],{},[35,73084,73085],{},[1707,73086],{"alt":8087,"src":8088},[35,73088,8091],{},[35,73090,8094,73091,3092,73094,1655,73097,106,73100,8107],{},[39,73092,7385],{"href":7383,"rel":73093},[77],[39,73095,7380],{"href":7378,"rel":73096},[77],[39,73098,1792],{"href":1790,"rel":73099},[77],[39,73101,7390],{"href":7388,"rel":73102},[77],[35,73104,8110,73105,8116,73108,8121],{},[39,73106,8115],{"href":8113,"rel":73107},[77],[39,73109,8120],{"href":7419,"rel":73110},[77],[35,73112,8124,73113,8130,73116,1655,73119,8139],{},[39,73114,8129],{"href":8127,"rel":73115},[77],[39,73117,8135],{"href":8133,"rel":73118},[77],[39,73120,6920],{"href":6918,"rel":73121},[77],[35,73123,8142],{},[35,73125,73126],{},[1707,73127],{"alt":8147,"src":8148},[1158,73129],{},[35,73131,8153],{},{"title":13,"searchDepth":14,"depth":14,"links":73133},[73134,73135,73136,73141],{"id":7585,"depth":14,"text":7249},{"id":7800,"depth":14,"text":7801},{"id":7866,"depth":14,"text":7867,"children":73137},[73138,73139,73140],{"id":7876,"depth":255,"text":7877},{"id":7924,"depth":255,"text":7925},{"id":8039,"depth":255,"text":8040},{"id":5579,"depth":14,"text":5580},{},{"title":7487,"description":8166},{"loc":8168,"images":73145},[73146,73147,73148,73149,73150,73151,73152,73153,73154,73155,73156,73157,73158,73159],{"loc":7511},{"loc":7532},{"loc":7672},{"loc":7785},{"loc":7791},{"loc":7832},{"loc":7853},{"loc":7883},{"loc":7910},{"loc":7995},{"loc":8022},{"loc":8074},{"loc":8088},{"loc":8148},{"id":8190,"title":1757,"body":73161,"contentId":8759,"date":8760,"description":8761,"extension":18,"meta":73524,"navigation":20,"path":8763,"rawbody":8764,"robots":23,"schemaOrg":23,"seo":73525,"sitemap":73526,"stem":8777,"__hash__":8778},{"type":7,"value":73162,"toc":73516},[73163,73167,73172,73174,73179,73181,73183,73185,73189,73193,73195,73197,73199,73201,73203,73205,73207,73209,73211,73215,73219,73230,73232,73236,73240,73244,73248,73250,73252,73256,73258,73260,73264,73268,73272,73276,73281,73285,73292,73294,73298,73300,73306,73310,73312,73314,73318,73320,73322,73324,73326,73328,73330,73332,73334,73336,73338,73340,73342,73344,73350,73352],[35,73164,8195,73165],{},[1216,73166,8198],{},[35,73168,8201,73169,8207],{},[39,73170,8206],{"href":8204,"rel":73171},[77],[51,73173,8211],{"id":8210},[35,73175,8214,73176,8220],{},[39,73177,8219],{"href":8217,"rel":73178},[77],[35,73180,8223],{},[35,73182,8226],{},[35,73184,8229],{},[35,73186,8232,73187,215],{},[6862,73188,8235],{},[35,73190,8238,73191,215],{},[6862,73192,8241],{},[35,73194,8244],{},[51,73196,8248],{"id":8247},[35,73198,8251],{},[35,73200,8254],{},[35,73202,8257],{},[35,73204,8260],{},[35,73206,8263],{},[35,73208,8266],{},[51,73210,8270],{"id":8269},[35,73212,73213],{},[1216,73214,8275],{},[35,73216,8278,73217,8282],{},[6862,73218,8281],{},[35,73220,8285,73221,8291,73224,1655,73227,8302],{},[39,73222,8290],{"href":8288,"rel":73223},[77],[39,73225,8296],{"href":8294,"rel":73226},[77],[39,73228,8301],{"href":8299,"rel":73229},[77],[35,73231,8305],{},[35,73233,73234],{},[1707,73235],{"alt":8310,"src":8311},[35,73237,73238],{},[1707,73239],{"alt":8316,"src":8317},[35,73241,8320,73242],{},[1216,73243,8323],{},[35,73245,73246],{},[1707,73247],{"alt":8328,"src":8329},[35,73249,8332],{},[35,73251,8335],{},[35,73253,73254],{},[1707,73255],{"alt":8340,"src":8341},[35,73257,8344],{},[35,73259,8347],{},[35,73261,8350,73262],{},[1216,73263,8353],{},[35,73265,8356,73266],{},[1216,73267,8359],{},[35,73269,8362,73270],{},[1216,73271,8365],{},[35,73273,73274],{},[1707,73275],{"alt":8370,"src":8371},[35,73277,8374,73278,8380],{},[39,73279,8379],{"href":8377,"rel":73280},[77],[35,73282,73283],{},[1707,73284],{"alt":8385,"src":8386},[35,73286,73287],{},[1216,73288,8391,73289,8397],{},[39,73290,8396],{"href":8394,"rel":73291},[77],[35,73293,8400],{},[35,73295,8403,73296],{},[1216,73297,8406],{},[35,73299,8409],{},[35,73301,73302,8415,73304],{},[6862,73303,8414],{},[1216,73305,8418],{},[35,73307,73308],{},[1707,73309],{"alt":8423,"src":8424},[35,73311,8427],{},[35,73313,8430],{},[35,73315,73316],{},[1707,73317],{"alt":8435,"src":8436},[35,73319,8439],{},[51,73321,8443],{"id":8442},[35,73323,8446],{},[51,73325,8450],{"id":8449},[35,73327,8453],{},[35,73329,8456],{},[35,73331,8459],{},[35,73333,8462],{},[35,73335,8465],{},[35,73337,8468],{},[51,73339,8472],{"id":8471},[35,73341,8475],{},[35,73343,8478],{},[68,73345,73346,73348],{},[71,73347,8483],{},[71,73349,8486],{},[35,73351,8489],{},[8491,73353,73354,73356],{},[35,73355,8495],{},[8497,73357,73358,73360,73366,73368,73370,73372,73374,73378,73382,73384,73388,73393,73397,73399,73403,73405,73423,73425,73429],{"title":8499,":steps":8500,":relations":8501,":ui":8502},[35,73359,8505],{},[35,73361,8508,73362,106,73364],{},[6862,73363,8511],{},[1216,73365,8514],{},[1929,73367,8518],{"id":8517},[35,73369,8521],{},[35,73371,8524],{},[35,73373,8527],{},[35,73375,8530,73376,8534],{},[156,73377,8533],{},[35,73379,73380],{},[1707,73381],{"alt":8539,"src":8540},[35,73383,8543],{},[35,73385,73386],{},[1216,73387,8548],{},[35,73389,8551,73390,8557],{},[39,73391,8556],{"href":8554,"rel":73392},[77],[35,73394,8560,73395],{},[1216,73396,8563],{},[35,73398,8566],{},[35,73400,8569,73401,8573],{},[6862,73402,8572],{},[35,73404,8576],{},[68,73406,73407,73411,73415,73419],{},[71,73408,73409,8584],{},[6862,73410,8583],{},[71,73412,73413,8590],{},[6862,73414,8589],{},[71,73416,73417,8596],{},[6862,73418,8595],{},[71,73420,73421,8602],{},[6862,73422,8601],{},[35,73424,8605],{},[35,73426,8608,73427,8612],{},[6862,73428,8611],{},[8497,73430,73431,73433,73435,73437,73441,73443,73445,73447,73449,73451],{"title":8615,":steps":8616,":relations":8617},[35,73432,8620],{},[35,73434,8623],{},[35,73436,8626],{},[35,73438,8629,73439,8633],{},[1216,73440,8632],{},[35,73442,8636],{},[35,73444,8639],{},[35,73446,8642],{},[35,73448,8645],{},[35,73450,8648],{},[8497,73452,73453,73457,73459,73461,73463,73472,73476,73478,73480,73482,73484,73486,73488,73492,73494,73496,73498,73500,73502,73504,73506,73508,73510,73512,73514],{"title":8651,":steps":8652,":relations":8653,":ui":8654},[35,73454,8657,73455],{},[1599,73456],{"repo":8660},[1929,73458,8664],{"id":8663},[35,73460,8667],{},[35,73462,8670],{},[68,73464,73465,73470],{},[71,73466,8675,73467,8681],{},[39,73468,8680],{"href":8678,"rel":73469},[77],[71,73471,8684],{},[35,73473,8687,73474],{},[1216,73475,8690],{},[35,73477,8693],{},[35,73479,8696],{},[1929,73481,8700],{"id":8699},[35,73483,8703],{},[35,73485,8706],{},[35,73487,8709],{},[35,73489,73490],{},[1216,73491,8714],{},[51,73493,8718],{"id":8717},[35,73495,8721],{},[35,73497,8724],{},[35,73499,8727],{},[35,73501,8730],{},[35,73503,8733],{},[35,73505,8736],{},[35,73507,8739],{},[1158,73509],{},[35,73511,8744],{},[35,73513,8747],{},[35,73515,8750],{},{"title":13,"searchDepth":14,"depth":14,"links":73517},[73518,73519,73520,73521,73522,73523],{"id":8210,"depth":14,"text":8211},{"id":8247,"depth":14,"text":8248},{"id":8269,"depth":14,"text":8270},{"id":8442,"depth":14,"text":8443},{"id":8449,"depth":14,"text":8450},{"id":8471,"depth":14,"text":8472},{},{"title":1757,"description":8761},{"loc":8763,"images":73527},[73528,73529,73530,73531,73532,73533,73534,73535,73536],{"loc":8311},{"loc":8317},{"loc":8329},{"loc":8341},{"loc":8371},{"loc":8386},{"loc":8424},{"loc":8436},{"loc":8540},{"id":8780,"title":8781,"body":73538,"contentId":9412,"date":9413,"description":9414,"extension":18,"meta":73958,"navigation":20,"path":9416,"rawbody":9417,"robots":23,"schemaOrg":23,"seo":73959,"sitemap":73960,"stem":9434,"__hash__":9435},{"type":7,"value":73539,"toc":73950},[73540,73542,73544,73551,73557,73561,73563,73589,73591,73593,73597,73608,73610,73616,73620,73624,73626,73630,73632,73636,73640,73644,73646,73648,73652,73654,73658,73662,73664,73668,73672,73676,73678,73684,73688,73694,73698,73700,73714,73718,73722,73726,73730,73732,73736,73778,73780,73784,73788,73790,73796,73803,73805,73812,73818,73823,73831,73833,73835,73837,73839,73869,73871,73893,73895,73909,73911,73913,73915,73917,73921,73926,73930,73932,73934,73936,73938,73940,73942,73944,73946],[35,73541,8786],{},[35,73543,8789],{},[35,73545,8792,73546],{},[3045,73547,1655,73548,8801],{"id":8795},[39,73549,8800],{"href":8798,"rel":73550},[77],[68,73552,73553,73555],{},[71,73554,8806],{},[71,73556,8809],{},[35,73558,73559],{},[1707,73560],{"alt":8814,"src":8815},[35,73562,8818],{},[1593,73564,73565,73567,73569,73571,73573,73575,73577,73579,73581,73583,73585,73587],{},[71,73566,8823],{},[71,73568,8826],{},[71,73570,8829],{},[71,73572,8832],{},[71,73574,8835],{},[71,73576,8838],{},[71,73578,8841],{},[71,73580,8844],{},[71,73582,8847],{},[71,73584,8850],{},[71,73586,8853],{},[71,73588,8856],{},[35,73590,8859],{},[51,73592,8863],{"id":8862},[35,73594,8866,73595],{},[1216,73596,8869],{},[35,73598,8872,73599,8878,73602,8882,73604,8886,73606],{},[39,73600,8877],{"href":8875,"rel":73601},[77],[1216,73603,8881],{},[1216,73605,8885],{},[1216,73607,8889],{},[35,73609,8892],{},[1593,73611,73612],{},[71,73613,8897,73614],{},[1216,73615,8900],{},[35,73617,73618],{},[1707,73619],{"alt":8905,"src":8906},[1593,73621,73622],{},[71,73623,8911],{},[35,73625,8914],{},[35,73627,73628],{},[1707,73629],{"alt":8919,"src":8920},[51,73631,8924],{"id":8923},[35,73633,8927,73634,215],{},[39,73635,8931],{"href":8930},[35,73637,8934,73638],{},[1216,73639,8937],{},[35,73641,8940,73642],{},[1216,73643,8943],{},[35,73645,8946],{},[35,73647,8949],{},[35,73649,73650],{},[1216,73651,8954],{},[51,73653,8958],{"id":8957},[35,73655,8961,73656,8966],{},[39,73657,8965],{"href":8964},[938,73659,73660],{},[35,73661,8971],{},[35,73663,8974],{},[35,73665,8977,73666],{},[1216,73667,8980],{},[35,73669,8983,73670,8988],{},[39,73671,8987],{"href":8986},[35,73673,73674],{},[1707,73675],{"alt":8993,"src":8994},[35,73677,8997],{},[35,73679,9000,73680,9003,73682,9007],{},[39,73681,7497],{"href":7500},[1216,73683,9006],{},[35,73685,73686],{},[1707,73687],{"alt":9012,"src":9013},[35,73689,9016,73690,9021,73692],{},[39,73691,9020],{"href":9019},[1216,73693,9024],{},[35,73695,73696],{},[1707,73697],{"alt":9029,"src":9030},[35,73699,9033],{},[1593,73701,73702,73706,73710,73712],{},[71,73703,73704,9042],{},[39,73705,9041],{"href":9040},[71,73707,73708,9049],{},[39,73709,9048],{"href":9047},[71,73711,9052],{},[71,73713,9055],{},[35,73715,73716],{},[1707,73717],{"alt":9060,"src":9061},[35,73719,73720],{},[1707,73721],{"alt":9066,"src":9067},[35,73723,73724],{},[1707,73725],{"alt":9072,"src":9073},[35,73727,73728],{},[1707,73729],{"alt":9078,"src":9079},[35,73731,9082],{},[35,73733,9085,73734],{},[1216,73735,9088],{},[1593,73737,73738,73743,73748,73753,73758,73763,73768,73773],{},[71,73739,9093,73740],{},[39,73741,9098],{"href":9096,"rel":73742},[77],[71,73744,9101,73745],{},[39,73746,9106],{"href":9104,"rel":73747},[77],[71,73749,9109,73750],{},[39,73751,9114],{"href":9112,"rel":73752},[77],[71,73754,9117,73755],{},[39,73756,9122],{"href":9120,"rel":73757},[77],[71,73759,9125,73760],{},[39,73761,9122],{"href":9128,"rel":73762},[77],[71,73764,9132,73765],{},[39,73766,9137],{"href":9135,"rel":73767},[77],[71,73769,9140,73770],{},[39,73771,9137],{"href":9143,"rel":73772},[77],[71,73774,9147,73775],{},[39,73776,9152],{"href":9150,"rel":73777},[77],[35,73779,9155],{},[35,73781,73782],{},[1707,73783],{"alt":9160,"src":9161},[35,73785,73786],{},[1707,73787],{"alt":9166,"src":9167},[35,73789,9170],{},[35,73791,9173,73792,9178,73794],{},[39,73793,9177],{"href":9176},[1599,73795,215],{"repo":9181},[35,73797,73798],{},[1216,73799,9186,73800,215],{},[39,73801,9191],{"href":9189,"rel":73802},[77],[51,73804,9195],{"id":9194},[35,73806,9198,73807,9204,73810,9209],{},[39,73808,9203],{"href":9201,"rel":73809},[77],[39,73811,9208],{"href":9207},[35,73813,9212,73814],{},[1599,73815,9216,73816],{"repo":9215},[1599,73817,215],{"repo":9219},[35,73819,9222,73820,9226],{},[39,73821,7894],{"href":7892,"rel":73822},[77],[35,73824,9229,73825,9235,73828,215],{},[39,73826,9234],{"href":9232,"rel":73827},[77],[39,73829,9240],{"href":9238,"rel":73830},[77],[35,73832,9243],{},[51,73834,9247],{"id":9246},[35,73836,9250],{},[35,73838,9253],{},[1593,73840,73841,73845,73849,73853,73857,73861,73865],{},[71,73842,73843],{},[3045,73844],{"id":9260},[71,73846,73847],{},[3045,73848],{"id":9265},[71,73850,73851],{},[3045,73852],{"id":9270},[71,73854,73855],{},[3045,73856],{"id":9275},[71,73858,73859],{},[3045,73860],{"id":9280},[71,73862,73863],{},[3045,73864],{"id":9285},[71,73866,73867],{},[3045,73868],{"id":9290},[35,73870,9293],{},[1593,73872,73873,73877,73881,73885,73889],{},[71,73874,73875],{},[3045,73876],{"id":9300},[71,73878,73879],{},[3045,73880],{"id":9305},[71,73882,73883],{},[3045,73884],{"id":9310},[71,73886,73887],{},[3045,73888],{"id":9315},[71,73890,73891],{},[3045,73892],{"id":9320},[35,73894,9323],{},[1593,73896,73897,73901,73905],{},[71,73898,73899],{},[3045,73900],{"id":9330},[71,73902,73903],{},[3045,73904],{"id":9335},[71,73906,73907],{},[3045,73908],{"id":9340},[35,73910,9343],{},[35,73912,9346],{},[51,73914,9349],{"id":5579},[35,73916,9352],{},[35,73918,9355,73919],{},[1216,73920,9358],{},[35,73922,9361,73923,9367],{},[39,73924,9366],{"href":9364,"rel":73925},[77],[35,73927,9370,73928],{},[1216,73929,9373],{},[35,73931,9376],{},[35,73933,9379],{},[35,73935,9382],{},[35,73937,9385],{},[35,73939,9388],{},[35,73941,9391],{},[35,73943,9394],{},[35,73945,9397],{},[35,73947,73948],{},[1707,73949],{"alt":9402,"src":9403},{"title":13,"searchDepth":14,"depth":14,"links":73951},[73952,73953,73954,73955,73956,73957],{"id":8862,"depth":14,"text":8863},{"id":8923,"depth":14,"text":8924},{"id":8957,"depth":14,"text":8958},{"id":9194,"depth":14,"text":9195},{"id":9246,"depth":14,"text":9247},{"id":5579,"depth":14,"text":9349},{},{"title":8781,"description":9414},{"loc":9416,"images":73961},[73962,73963,73964,73965,73966,73967,73968,73969,73970,73971,73972,73973,73974],{"loc":8815},{"loc":8906},{"loc":8920},{"loc":8994},{"loc":9013},{"loc":9030},{"loc":9061},{"loc":9067},{"loc":9073},{"loc":9079},{"loc":9161},{"loc":9167},{"loc":9403},{"id":9437,"title":9438,"body":73976,"contentId":9656,"date":9657,"description":9658,"extension":18,"meta":74119,"navigation":20,"path":9660,"rawbody":9661,"robots":23,"schemaOrg":23,"seo":74120,"sitemap":74121,"stem":9664,"__hash__":9665},{"type":7,"value":73977,"toc":74113},[73978,73982,73984,73986,73990,73994,73996,74000,74002,74004,74006,74008,74012,74020,74022,74024,74026,74028,74032,74036,74040,74042,74044,74046,74050,74052,74054,74056,74058,74060,74062,74064,74066,74068,74073,74078,74083,74085,74087,74089,74093,74095,74099,74101,74103,74105,74107,74109,74111],[35,73979,9443,73980],{},[1216,73981,9446],{},[51,73983,9450],{"id":9449},[35,73985,9453],{},[35,73987,9456,73988],{},[1216,73989,9459],{},[35,73991,9462,73992],{},[1216,73993,9465],{},[35,73995,9468],{},[35,73997,9471,73998],{},[1216,73999,9474],{},[35,74001,9477],{},[35,74003,9480],{},[35,74005,9483],{},[51,74007,9487],{"id":9486},[35,74009,9490,74010,9494],{},[1216,74011,9493],{},[35,74013,9497,74014],{},[3045,74015,3092,74016],{"id":9260},[3045,74017,9502,74018],{"id":9280},[3045,74019,215],{"id":9505},[35,74021,9508],{},[35,74023,9511],{},[35,74025,9514],{},[35,74027,9517],{},[35,74029,9520,74030,9525],{},[39,74031,9524],{"href":9523},[35,74033,74034],{},[1216,74035,9530],{},[35,74037,9533,74038],{},[1216,74039,9536],{},[35,74041,9539],{},[35,74043,9542],{},[51,74045,9546],{"id":9545},[35,74047,9549,74048,9553],{},[39,74049,9552],{"href":9523},[35,74051,9556],{},[35,74053,9559],{},[35,74055,9562],{},[35,74057,9565],{},[35,74059,9568],{},[35,74061,9571],{},[35,74063,9574],{},[51,74065,9578],{"id":9577},[35,74067,9581],{},[35,74069,9584,74070,9590],{},[39,74071,9589],{"href":9587,"rel":74072},[77],[35,74074,9593,74075,9599],{},[39,74076,9598],{"href":9596,"rel":74077},[77],[35,74079,9602,74080,9607],{},[39,74081,8064],{"href":9605,"rel":74082},[77],[35,74084,9610],{},[35,74086,9613],{},[35,74088,9616],{},[35,74090,9619,74091],{},[1216,74092,9622],{},[1158,74094],{},[35,74096,9627,74097],{},[1216,74098,9630],{},[1158,74100],{},[35,74102,9635],{},[35,74104,9638],{},[35,74106,9641],{},[35,74108,9644],{},[1158,74110],{},[35,74112,9649],{},{"title":13,"searchDepth":14,"depth":14,"links":74114},[74115,74116,74117,74118],{"id":9449,"depth":14,"text":9450},{"id":9486,"depth":14,"text":9487},{"id":9545,"depth":14,"text":9546},{"id":9577,"depth":14,"text":9578},{},{"title":9438,"description":9658},{"loc":9660},{"id":9667,"title":9668,"body":74123,"contentId":9290,"date":11300,"description":11301,"extension":18,"meta":75531,"navigation":20,"path":11303,"rawbody":11304,"robots":23,"schemaOrg":23,"seo":75532,"sitemap":75533,"stem":11307,"__hash__":11308},{"type":7,"value":74124,"toc":75524},[74125,74129,74131,74133,74138,74143,74145,74147,74149,74163,75522],[35,74126,9673,74127],{},[3045,74128,9676],{"id":9285},[35,74130,9679],{},[1158,74132],{},[35,74134,9684,74135,9690],{},[39,74136,9689],{"href":9687,"rel":74137},[77],[35,74139,9693,74140,9699],{},[39,74141,9698],{"href":9696,"rel":74142},[77],[35,74144,9702],{},[51,74146,9706],{"id":9705},[35,74148,9709],{},[35,74150,9712,74151,9716,74153,9720,74155,9724,74157,9728,74159,9731,74161,9735],{},[156,74152,9715],{},[156,74154,9719],{},[156,74156,9723],{},[156,74158,9727],{},[156,74160,9727],{},[156,74162,9734],{},[7918,74164,74165,74167,74173,74354,74360,74362,74364,74373,74375,74380,74382,74386,74388,74392,74396,74398,74408,74410,74436,74438,74546,74548,74553,74555,74675,74677,74733,74735,74739,74745,74973,74977,74979,74985,74999,75005,75137,75139,75205,75207,75211,75313,75315,75437,75441,75475,75480,75484,75486,75488,75490,75514,75518,75520],{"text":9738,"href":9739},[35,74166,9742],{},[35,74168,9745,74169,9749,74171,215],{},[156,74170,9748],{},[156,74172,9752],{},[192,74174,74175],{"className":9755,"code":9756,"language":1383,"meta":13,"style":13},[156,74176,74177,74197,74221,74265,74281,74285,74312,74316,74344,74348],{"__ignoreMap":13},[200,74178,74179,74181,74183,74185,74187,74189,74191,74193,74195],{"class":202,"line":203},[200,74180,18],{"class":210},[200,74182,215],{"class":214},[200,74184,9767],{"class":223},[200,74186,227],{"class":210},[200,74188,227],{"class":214},[200,74190,18],{"class":386},[200,74192,2104],{"class":214},[200,74194,393],{"class":379},[200,74196,280],{"class":214},[200,74198,74199,74201,74203,74205,74207,74209,74211,74213,74215,74217,74219],{"class":202,"line":14},[200,74200,3738],{"class":379},[200,74202,9786],{"class":210},[200,74204,509],{"class":214},[200,74206,9791],{"class":210},[200,74208,215],{"class":214},[200,74210,9796],{"class":210},[200,74212,215],{"class":214},[200,74214,9801],{"class":210},[200,74216,215],{"class":214},[200,74218,9752],{"class":210},[200,74220,9808],{"class":214},[200,74222,74223,74225,74227,74229,74231,74233,74235,74237,74239,74241,74243,74245,74247,74249,74251,74253,74255,74257,74259,74261,74263],{"class":202,"line":255},[200,74224,9813],{"class":210},[200,74226,215],{"class":214},[200,74228,9796],{"class":210},[200,74230,215],{"class":214},[200,74232,9801],{"class":210},[200,74234,215],{"class":214},[200,74236,9752],{"class":223},[200,74238,509],{"class":214},[200,74240,1094],{"class":214},[200,74242,9832],{"class":386},[200,74244,549],{"class":214},[200,74246,9837],{"class":386},[200,74248,549],{"class":214},[200,74250,9842],{"class":386},[200,74252,549],{"class":214},[200,74254,4892],{"class":386},[200,74256,549],{"class":214},[200,74258,9851],{"class":386},[200,74260,2104],{"class":214},[200,74262,393],{"class":379},[200,74264,280],{"class":214},[200,74266,74267,74269,74271,74273,74275,74277,74279],{"class":202,"line":272},[200,74268,2014],{"class":379},[200,74270,9864],{"class":210},[200,74272,509],{"class":214},[200,74274,9869],{"class":210},[200,74276,9872],{"class":235},[200,74278,9875],{"class":210},[200,74280,4511],{"class":235},[200,74282,74283],{"class":202,"line":283},[200,74284,528],{"emptyLinePlaceholder":20},[200,74286,74288,74290,74292,74294,74296,74298,74300,74302,74304,74306,74308,74310],{"class":74287,"line":300},[202,9885],[200,74289,9888],{"class":210},[200,74291,215],{"class":214},[200,74293,9893],{"class":223},[200,74295,227],{"class":235},[200,74297,249],{"class":214},[200,74299,9900],{"class":245},[200,74301,249],{"class":214},[200,74303,549],{"class":214},[200,74305,242],{"class":214},[200,74307,9909],{"class":245},[200,74309,249],{"class":214},[200,74311,5282],{"class":235},[200,74313,74314],{"class":202,"line":310},[200,74315,528],{"emptyLinePlaceholder":20},[200,74317,74318,74320,74322,74324,74326,74328,74330,74332,74334,74336,74338,74340,74342],{"class":202,"line":336},[200,74319,3311],{"class":206},[200,74321,9786],{"class":223},[200,74323,227],{"class":235},[200,74325,9832],{"class":210},[200,74327,549],{"class":214},[200,74329,9837],{"class":210},[200,74331,549],{"class":214},[200,74333,9842],{"class":210},[200,74335,549],{"class":214},[200,74337,4892],{"class":210},[200,74339,549],{"class":214},[200,74341,9851],{"class":210},[200,74343,416],{"class":235},[200,74345,74346],{"class":202,"line":342},[200,74347,4505],{"class":214},[200,74349,74350,74352],{"class":202,"line":365},[200,74351,413],{"class":214},[200,74353,416],{"class":210},[35,74355,9958,74356,9962,74358,9966],{},[156,74357,9961],{},[156,74359,9965],{},[51,74361,9970],{"id":9969},[35,74363,9973],{},[938,74365,74366],{},[35,74367,74368,9980,74370,9986],{},[200,74369,944],{},[39,74371,9985],{"href":9983,"rel":74372},[77],[35,74374,9989],{},[35,74376,9992,74377,9998],{},[39,74378,9997],{"href":9995,"rel":74379},[77],[51,74381,10002],{"id":10001},[35,74383,10005,74384,10010],{},[39,74385,10009],{"href":10008},[35,74387,10013],{},[35,74389,10016,74390],{},[1599,74391,10020],{"repo":10019},[35,74393,10023,74394,10027],{},[156,74395,10026],{},[35,74397,10030],{},[192,74399,74400],{"className":1354,"code":10033,"language":1357,"meta":13,"style":13},[156,74401,74402],{"__ignoreMap":13},[200,74403,74404,74406],{"class":202,"line":203},[200,74405,10040],{"class":1364},[200,74407,10043],{"class":245},[35,74409,10046],{},[192,74411,74412],{"className":5903,"code":10049,"language":18,"meta":13,"style":13},[156,74413,74414,74420,74424],{"__ignoreMap":13},[200,74415,74416,74418],{"class":202,"line":203},[200,74417,10056],{"class":214},[200,74419,10059],{"class":1364},[200,74421,74422],{"class":202,"line":14},[200,74423,528],{"emptyLinePlaceholder":20},[200,74425,74426,74428,74430,74432,74434],{"class":202,"line":255},[200,74427,10068],{"class":214},[200,74429,10071],{"class":245},[200,74431,10074],{"class":214},[200,74433,10078],{"class":10077},[200,74435,416],{"class":214},[35,74437,10083],{},[192,74439,74440],{"className":590,"code":10086,"language":592,"meta":13,"style":13},[156,74441,74442,74458],{"__ignoreMap":13},[200,74443,74444,74446,74448,74450,74452,74454,74456],{"class":202,"line":203},[200,74445,599],{"class":214},[200,74447,10095],{"class":235},[200,74449,674],{"class":214},[200,74451,10100],{"class":210},[200,74453,680],{"class":214},[200,74455,10095],{"class":235},[200,74457,647],{"class":214},[200,74459,74460,74462,74464,74466,74468,74470,74472,74474,74476,74478,74480,74482,74484,74486,74488,74490,74492,74494,74496,74498,74500,74502,74504,74506,74508,74510,74512,74514,74516,74518,74520,74522,74524,74526,74528,74530,74532,74534,74536,74538,74540,74542,74544],{"class":202,"line":14},[200,74461,599],{"class":214},[200,74463,35],{"class":235},[200,74465,10115],{"class":214},[200,74467,1707],{"class":235},[200,74469,10120],{"class":379},[200,74471,449],{"class":214},[200,74473,610],{"class":214},[200,74475,10127],{"class":245},[200,74477,10130],{"class":210},[200,74479,10133],{"class":245},[200,74481,10130],{"class":210},[200,74483,10138],{"class":245},[200,74485,10130],{"class":210},[200,74487,10143],{"class":245},[200,74489,610],{"class":214},[200,74491,10148],{"class":379},[200,74493,449],{"class":214},[200,74495,610],{"class":214},[200,74497,10071],{"class":245},[200,74499,610],{"class":214},[200,74501,10159],{"class":379},[200,74503,449],{"class":214},[200,74505,610],{"class":214},[200,74507,9909],{"class":245},[200,74509,610],{"class":214},[200,74511,10170],{"class":379},[200,74513,449],{"class":214},[200,74515,610],{"class":214},[200,74517,10177],{"class":245},[200,74519,610],{"class":214},[200,74521,10182],{"class":379},[200,74523,449],{"class":214},[200,74525,610],{"class":214},[200,74527,10189],{"class":245},[200,74529,610],{"class":214},[200,74531,10194],{"class":379},[200,74533,449],{"class":214},[200,74535,610],{"class":214},[200,74537,10201],{"class":245},[200,74539,610],{"class":214},[200,74541,10206],{"class":214},[200,74543,35],{"class":235},[200,74545,647],{"class":214},[51,74547,10214],{"id":10213},[35,74549,10217,74550,215],{},[39,74551,10222],{"href":10220,"rel":74552},[77],[35,74554,10225],{},[192,74556,74557],{"className":1380,"code":10228,"language":1383,"meta":13,"style":13},[156,74558,74559,74577,74581,74593,74597,74609,74631,74635,74643,74647,74661,74665,74669],{"__ignoreMap":13},[200,74560,74561,74563,74565,74567,74569,74571,74573,74575],{"class":202,"line":203},[200,74562,972],{"class":206},[200,74564,318],{"class":214},[200,74566,10239],{"class":210},[200,74568,980],{"class":214},[200,74570,983],{"class":206},[200,74572,242],{"class":214},[200,74574,9997],{"class":245},[200,74576,991],{"class":214},[200,74578,74579],{"class":202,"line":14},[200,74580,528],{"emptyLinePlaceholder":20},[200,74582,74583,74585,74587,74589,74591],{"class":202,"line":255},[200,74584,443],{"class":379},[200,74586,10260],{"class":210},[200,74588,449],{"class":214},[200,74590,10239],{"class":223},[200,74592,466],{"class":210},[200,74594,74595],{"class":202,"line":272},[200,74596,528],{"emptyLinePlaceholder":20},[200,74598,74599,74601,74603,74605,74607],{"class":202,"line":283},[200,74600,3726],{"class":379},[200,74602,5002],{"class":379},[200,74604,10279],{"class":223},[200,74606,1549],{"class":214},[200,74608,280],{"class":214},[200,74610,74611,74613,74615,74617,74619,74621,74623,74625,74627,74629],{"class":202,"line":300},[200,74612,3738],{"class":379},[200,74614,10290],{"class":210},[200,74616,509],{"class":214},[200,74618,452],{"class":206},[200,74620,9791],{"class":210},[200,74622,215],{"class":214},[200,74624,10301],{"class":223},[200,74626,227],{"class":235},[200,74628,3333],{"class":214},[200,74630,10308],{"class":245},[200,74632,74633],{"class":202,"line":310},[200,74634,528],{"emptyLinePlaceholder":20},[200,74636,74637,74639,74641],{"class":202,"line":336},[200,74638,10317],{"class":245},[200,74640,3333],{"class":214},[200,74642,416],{"class":235},[200,74644,74645],{"class":202,"line":342},[200,74646,528],{"emptyLinePlaceholder":20},[200,74648,74649,74651,74653,74655,74657,74659],{"class":202,"line":365},[200,74650,1112],{"class":210},[200,74652,215],{"class":214},[200,74654,1117],{"class":223},[200,74656,227],{"class":235},[200,74658,592],{"class":210},[200,74660,416],{"class":235},[200,74662,74663],{"class":202,"line":371},[200,74664,1129],{"class":214},[200,74666,74667],{"class":202,"line":398},[200,74668,528],{"emptyLinePlaceholder":20},[200,74670,74671,74673],{"class":202,"line":405},[200,74672,10352],{"class":223},[200,74674,466],{"class":210},[35,74676,10357],{},[192,74678,74679],{"className":590,"code":10360,"language":592,"meta":13,"style":13},[156,74680,74681,74697],{"__ignoreMap":13},[200,74682,74683,74685,74687,74689,74691,74693,74695],{"class":202,"line":203},[200,74684,599],{"class":214},[200,74686,10095],{"class":235},[200,74688,674],{"class":214},[200,74690,10100],{"class":210},[200,74692,680],{"class":214},[200,74694,10095],{"class":235},[200,74696,647],{"class":214},[200,74698,74699,74701,74703,74705,74707,74709,74711,74713,74715,74717,74719,74721,74723,74725,74727,74729,74731],{"class":202,"line":14},[200,74700,599],{"class":214},[200,74702,35],{"class":235},[200,74704,10115],{"class":214},[200,74706,1707],{"class":235},[200,74708,10120],{"class":379},[200,74710,449],{"class":214},[200,74712,610],{"class":214},[200,74714,10078],{"class":245},[200,74716,610],{"class":214},[200,74718,10148],{"class":379},[200,74720,449],{"class":214},[200,74722,610],{"class":214},[200,74724,10071],{"class":245},[200,74726,610],{"class":214},[200,74728,10206],{"class":214},[200,74730,35],{"class":235},[200,74732,647],{"class":214},[35,74734,10417],{},[35,74736,10420,74737,10423],{},[156,74738,9752],{},[35,74740,10426,74741,10429,74743,215],{},[156,74742,9767],{},[156,74744,9752],{},[192,74746,74747],{"className":1380,"code":10434,"language":1383,"meta":13,"style":13},[156,74748,74749,74769,74787,74791,74803,74807,74831,74855,74901,74929,74933,74939,74943,74955,74959,74963,74967],{"__ignoreMap":13},[200,74750,74751,74753,74755,74757,74759,74761,74763,74765,74767],{"class":202,"line":203},[200,74752,972],{"class":206},[200,74754,321],{"class":206},[200,74756,318],{"class":214},[200,74758,10447],{"class":210},[200,74760,980],{"class":214},[200,74762,983],{"class":206},[200,74764,242],{"class":214},[200,74766,9997],{"class":245},[200,74768,991],{"class":214},[200,74770,74771,74773,74775,74777,74779,74781,74783,74785],{"class":202,"line":14},[200,74772,972],{"class":206},[200,74774,318],{"class":214},[200,74776,10239],{"class":210},[200,74778,980],{"class":214},[200,74780,983],{"class":206},[200,74782,242],{"class":214},[200,74784,9997],{"class":245},[200,74786,991],{"class":214},[200,74788,74789],{"class":202,"line":255},[200,74790,528],{"emptyLinePlaceholder":20},[200,74792,74793,74795,74797,74799,74801],{"class":202,"line":272},[200,74794,443],{"class":379},[200,74796,10260],{"class":210},[200,74798,449],{"class":214},[200,74800,10239],{"class":223},[200,74802,466],{"class":210},[200,74804,74805],{"class":202,"line":283},[200,74806,528],{"emptyLinePlaceholder":20},[200,74808,74809,74811,74813,74815,74817,74819,74821,74823,74825,74827,74829],{"class":202,"line":300},[200,74810,18],{"class":210},[200,74812,215],{"class":214},[200,74814,9767],{"class":223},[200,74816,227],{"class":210},[200,74818,227],{"class":214},[200,74820,18],{"class":386},[200,74822,239],{"class":214},[200,74824,10447],{"class":1364},[200,74826,2104],{"class":214},[200,74828,393],{"class":379},[200,74830,280],{"class":214},[200,74832,74833,74835,74837,74839,74841,74843,74845,74847,74849,74851,74853],{"class":202,"line":310},[200,74834,3738],{"class":379},[200,74836,9786],{"class":210},[200,74838,509],{"class":214},[200,74840,9791],{"class":210},[200,74842,215],{"class":214},[200,74844,9796],{"class":210},[200,74846,215],{"class":214},[200,74848,9801],{"class":210},[200,74850,215],{"class":214},[200,74852,9752],{"class":210},[200,74854,9808],{"class":214},[200,74856,74857,74859,74861,74863,74865,74867,74869,74871,74873,74875,74877,74879,74881,74883,74885,74887,74889,74891,74893,74895,74897,74899],{"class":202,"line":336},[200,74858,9813],{"class":210},[200,74860,215],{"class":214},[200,74862,9796],{"class":210},[200,74864,215],{"class":214},[200,74866,9801],{"class":210},[200,74868,215],{"class":214},[200,74870,9752],{"class":223},[200,74872,509],{"class":214},[200,74874,380],{"class":379},[200,74876,1094],{"class":214},[200,74878,9832],{"class":386},[200,74880,549],{"class":214},[200,74882,9837],{"class":386},[200,74884,549],{"class":214},[200,74886,9842],{"class":386},[200,74888,549],{"class":214},[200,74890,4892],{"class":386},[200,74892,549],{"class":214},[200,74894,9851],{"class":386},[200,74896,2104],{"class":214},[200,74898,393],{"class":379},[200,74900,280],{"class":214},[200,74902,74903,74905,74907,74909,74911,74913,74915,74917,74919,74921,74923,74925,74927],{"class":202,"line":342},[200,74904,3311],{"class":206},[200,74906,9786],{"class":223},[200,74908,227],{"class":235},[200,74910,9832],{"class":210},[200,74912,549],{"class":214},[200,74914,9837],{"class":210},[200,74916,549],{"class":214},[200,74918,9842],{"class":210},[200,74920,549],{"class":214},[200,74922,4892],{"class":210},[200,74924,549],{"class":214},[200,74926,9851],{"class":210},[200,74928,416],{"class":235},[200,74930,74931],{"class":202,"line":365},[200,74932,4505],{"class":214},[200,74934,74935,74937],{"class":202,"line":371},[200,74936,413],{"class":214},[200,74938,416],{"class":210},[200,74940,74941],{"class":202,"line":398},[200,74942,528],{"emptyLinePlaceholder":20},[200,74944,74945,74947,74949,74951,74953],{"class":202,"line":405},[200,74946,3726],{"class":379},[200,74948,5002],{"class":379},[200,74950,10279],{"class":223},[200,74952,1549],{"class":214},[200,74954,280],{"class":214},[200,74956,74957],{"class":202,"line":410},[200,74958,10648],{"class":401},[200,74960,74961],{"class":202,"line":860},[200,74962,1129],{"class":214},[200,74964,74965],{"class":202,"line":889},[200,74966,528],{"emptyLinePlaceholder":20},[200,74968,74969,74971],{"class":202,"line":899},[200,74970,10352],{"class":223},[200,74972,466],{"class":210},[35,74974,10665,74975,10668],{},[156,74976,9752],{},[35,74978,10671],{},[35,74980,10674,74981,10677,74983,10680],{},[156,74982,9832],{},[156,74984,9875],{},[192,74986,74987],{"className":1380,"code":10683,"language":1383,"meta":13,"style":13},[156,74988,74989],{"__ignoreMap":13},[200,74990,74991,74993,74995,74997],{"class":202,"line":203},[200,74992,443],{"class":379},[200,74994,10692],{"class":210},[200,74996,449],{"class":214},[200,74998,10697],{"class":210},[35,75000,10700,75001,10703,75003,215],{},[156,75002,9900],{},[156,75004,9909],{},[192,75006,75007],{"className":1380,"code":10708,"language":1383,"meta":13,"style":13},[156,75008,75009,75055,75071,75075,75101,75105,75133],{"__ignoreMap":13},[200,75010,75011,75013,75015,75017,75019,75021,75023,75025,75027,75029,75031,75033,75035,75037,75039,75041,75043,75045,75047,75049,75051,75053],{"class":202,"line":203},[200,75012,18],{"class":210},[200,75014,215],{"class":214},[200,75016,9796],{"class":210},[200,75018,215],{"class":214},[200,75020,9801],{"class":210},[200,75022,215],{"class":214},[200,75024,9752],{"class":223},[200,75026,509],{"class":214},[200,75028,380],{"class":379},[200,75030,1094],{"class":214},[200,75032,9832],{"class":386},[200,75034,549],{"class":214},[200,75036,9837],{"class":386},[200,75038,549],{"class":214},[200,75040,9842],{"class":386},[200,75042,549],{"class":214},[200,75044,4892],{"class":386},[200,75046,549],{"class":214},[200,75048,9851],{"class":386},[200,75050,2104],{"class":214},[200,75052,393],{"class":379},[200,75054,280],{"class":214},[200,75056,75057,75059,75061,75063,75065,75067,75069],{"class":202,"line":14},[200,75058,3738],{"class":379},[200,75060,9864],{"class":210},[200,75062,509],{"class":214},[200,75064,9869],{"class":210},[200,75066,9872],{"class":235},[200,75068,9875],{"class":210},[200,75070,4511],{"class":235},[200,75072,75073],{"class":202,"line":255},[200,75074,528],{"emptyLinePlaceholder":20},[200,75076,75077,75079,75081,75083,75085,75087,75089,75091,75093,75095,75097,75099],{"class":202,"line":272},[200,75078,10781],{"class":210},[200,75080,215],{"class":214},[200,75082,9893],{"class":223},[200,75084,227],{"class":235},[200,75086,249],{"class":214},[200,75088,9900],{"class":245},[200,75090,249],{"class":214},[200,75092,549],{"class":214},[200,75094,242],{"class":214},[200,75096,9909],{"class":245},[200,75098,249],{"class":214},[200,75100,416],{"class":235},[200,75102,75103],{"class":202,"line":283},[200,75104,528],{"emptyLinePlaceholder":20},[200,75106,75107,75109,75111,75113,75115,75117,75119,75121,75123,75125,75127,75129,75131],{"class":202,"line":300},[200,75108,3758],{"class":206},[200,75110,9786],{"class":223},[200,75112,227],{"class":235},[200,75114,9832],{"class":210},[200,75116,549],{"class":214},[200,75118,9837],{"class":210},[200,75120,549],{"class":214},[200,75122,9842],{"class":210},[200,75124,549],{"class":214},[200,75126,4892],{"class":210},[200,75128,549],{"class":214},[200,75130,9851],{"class":210},[200,75132,416],{"class":235},[200,75134,75135],{"class":202,"line":310},[200,75136,1129],{"class":214},[35,75138,10842],{},[192,75140,75141],{"className":590,"code":10845,"language":592,"meta":13,"style":13},[156,75142,75143,75159],{"__ignoreMap":13},[200,75144,75145,75147,75149,75151,75153,75155,75157],{"class":202,"line":203},[200,75146,599],{"class":214},[200,75148,10095],{"class":235},[200,75150,674],{"class":214},[200,75152,10100],{"class":210},[200,75154,680],{"class":214},[200,75156,10095],{"class":235},[200,75158,647],{"class":214},[200,75160,75161,75163,75165,75167,75169,75171,75173,75175,75177,75179,75181,75183,75185,75187,75189,75191,75193,75195,75197,75199,75201,75203],{"class":202,"line":14},[200,75162,599],{"class":214},[200,75164,35],{"class":235},[200,75166,10115],{"class":214},[200,75168,1707],{"class":235},[200,75170,10120],{"class":379},[200,75172,449],{"class":214},[200,75174,610],{"class":214},[200,75176,10078],{"class":245},[200,75178,610],{"class":214},[200,75180,10148],{"class":379},[200,75182,449],{"class":214},[200,75184,610],{"class":214},[200,75186,10071],{"class":245},[200,75188,610],{"class":214},[200,75190,10159],{"class":379},[200,75192,449],{"class":214},[200,75194,610],{"class":214},[200,75196,9909],{"class":245},[200,75198,610],{"class":214},[200,75200,10206],{"class":214},[200,75202,35],{"class":235},[200,75204,647],{"class":214},[35,75206,10912],{},[35,75208,10915,75209],{},[3045,75210,10918],{"id":9285},[192,75212,75213],{"className":1380,"code":10921,"language":1383,"meta":13,"style":13},[156,75214,75215,75247,75261,75265],{"__ignoreMap":13},[200,75216,75217,75219,75221,75223,75225,75227,75229,75231,75233,75235,75237,75239,75241,75243,75245],{"class":202,"line":203},[200,75218,443],{"class":379},[200,75220,10930],{"class":210},[200,75222,449],{"class":214},[200,75224,452],{"class":206},[200,75226,3314],{"class":223},[200,75228,10939],{"class":210},[200,75230,215],{"class":214},[200,75232,10944],{"class":223},[200,75234,227],{"class":210},[200,75236,10949],{"class":386},[200,75238,393],{"class":379},[200,75240,10954],{"class":210},[200,75242,215],{"class":214},[200,75244,10959],{"class":223},[200,75246,10962],{"class":210},[200,75248,75249,75251,75253,75255,75257,75259],{"class":202,"line":14},[200,75250,443],{"class":379},[200,75252,10969],{"class":210},[200,75254,449],{"class":214},[200,75256,452],{"class":206},[200,75258,10976],{"class":223},[200,75260,10979],{"class":210},[200,75262,75263],{"class":202,"line":255},[200,75264,528],{"emptyLinePlaceholder":20},[200,75266,75267,75269,75271,75273,75275,75277,75279,75281,75283,75285,75287,75289,75291,75293,75295,75297,75299,75301,75303,75305,75307,75309,75311],{"class":202,"line":272},[200,75268,443],{"class":379},[200,75270,10990],{"class":210},[200,75272,449],{"class":214},[200,75274,10995],{"class":223},[200,75276,10998],{"class":210},[200,75278,215],{"class":214},[200,75280,1489],{"class":223},[200,75282,11005],{"class":210},[200,75284,215],{"class":214},[200,75286,11010],{"class":210},[200,75288,549],{"class":214},[200,75290,5056],{"class":210},[200,75292,215],{"class":214},[200,75294,11019],{"class":210},[200,75296,549],{"class":214},[200,75298,5056],{"class":210},[200,75300,215],{"class":214},[200,75302,11028],{"class":210},[200,75304,549],{"class":214},[200,75306,11033],{"class":2060},[200,75308,549],{"class":214},[200,75310,11033],{"class":2060},[200,75312,416],{"class":210},[35,75314,11042],{},[192,75316,75317],{"className":1380,"code":11045,"language":1383,"meta":13,"style":13},[156,75318,75319,75337,75341,75371,75401],{"__ignoreMap":13},[200,75320,75321,75323,75325,75327,75329,75331,75333,75335],{"class":202,"line":203},[200,75322,972],{"class":206},[200,75324,318],{"class":214},[200,75326,11056],{"class":210},[200,75328,980],{"class":214},[200,75330,983],{"class":206},[200,75332,242],{"class":214},[200,75334,11065],{"class":245},[200,75336,991],{"class":214},[200,75338,75339],{"class":202,"line":14},[200,75340,528],{"emptyLinePlaceholder":20},[200,75342,75343,75345,75347,75349,75351,75353,75355,75357,75359,75361,75363,75365,75367,75369],{"class":202,"line":255},[200,75344,11076],{"class":210},[200,75346,215],{"class":214},[200,75348,9893],{"class":223},[200,75350,227],{"class":210},[200,75352,249],{"class":214},[200,75354,11019],{"class":245},[200,75356,249],{"class":214},[200,75358,549],{"class":214},[200,75360,5056],{"class":210},[200,75362,215],{"class":214},[200,75364,11019],{"class":210},[200,75366,215],{"class":214},[200,75368,11101],{"class":223},[200,75370,10962],{"class":210},[200,75372,75373,75375,75377,75379,75381,75383,75385,75387,75389,75391,75393,75395,75397,75399],{"class":202,"line":272},[200,75374,11076],{"class":210},[200,75376,215],{"class":214},[200,75378,9893],{"class":223},[200,75380,227],{"class":210},[200,75382,249],{"class":214},[200,75384,11028],{"class":245},[200,75386,249],{"class":214},[200,75388,549],{"class":214},[200,75390,5056],{"class":210},[200,75392,215],{"class":214},[200,75394,11028],{"class":210},[200,75396,215],{"class":214},[200,75398,11101],{"class":223},[200,75400,10962],{"class":210},[200,75402,75403,75405,75407,75409,75411,75413,75415,75417,75419,75421,75423,75425,75427,75429,75431,75433,75435],{"class":202,"line":283},[200,75404,11076],{"class":210},[200,75406,215],{"class":214},[200,75408,9893],{"class":223},[200,75410,227],{"class":210},[200,75412,249],{"class":214},[200,75414,1170],{"class":245},[200,75416,249],{"class":214},[200,75418,549],{"class":214},[200,75420,11154],{"class":214},[200,75422,11157],{"class":245},[200,75424,4868],{"class":214},[200,75426,11162],{"class":223},[200,75428,11165],{"class":210},[200,75430,413],{"class":214},[200,75432,11170],{"class":245},[200,75434,3333],{"class":214},[200,75436,416],{"class":210},[35,75438,11177,75439,11181],{},[156,75440,11180],{},[192,75442,75443],{"className":1380,"code":11184,"language":1383,"meta":13,"style":13},[156,75444,75445],{"__ignoreMap":13},[200,75446,75447,75449,75451,75453,75455,75457,75459,75461,75463,75465,75467,75469,75471,75473],{"class":202,"line":203},[200,75448,11076],{"class":210},[200,75450,215],{"class":214},[200,75452,9893],{"class":223},[200,75454,227],{"class":210},[200,75456,249],{"class":214},[200,75458,11180],{"class":245},[200,75460,249],{"class":214},[200,75462,549],{"class":214},[200,75464,11207],{"class":214},[200,75466,11180],{"class":210},[200,75468,413],{"class":214},[200,75470,11214],{"class":245},[200,75472,3333],{"class":214},[200,75474,416],{"class":210},[35,75476,11221,75477,11225],{},[39,75478,10222],{"href":10220,"rel":75479},[77],[35,75481,11228,75482,11232],{},[156,75483,11231],{},[51,75485,2461],{"id":2460},[35,75487,11237],{},[35,75489,11240],{},[68,75491,75492,75496,75502],{},[71,75493,75494,11248],{},[6862,75495,11247],{},[71,75497,75498,11254,75500],{},[6862,75499,11253],{},[1216,75501,11257],{},[71,75503,75504,11263,75506],{},[6862,75505,11262],{},[1216,75507,11266,75508,11272,75511,11278],{},[39,75509,11271],{"href":11269,"rel":75510},[77],[39,75512,11277],{"href":11275,"rel":75513},[77],[35,75515,75516],{},[1216,75517,11283],{},[35,75519,11286],{},[35,75521,11289],{},[1170,75523,11292],{},{"title":13,"searchDepth":14,"depth":14,"links":75525},[75526,75527,75528,75529,75530],{"id":9705,"depth":14,"text":9706},{"id":9969,"depth":14,"text":9970},{"id":10001,"depth":14,"text":10002},{"id":10213,"depth":14,"text":10214},{"id":2460,"depth":14,"text":2461},{},{"title":9668,"description":11301},{"loc":11303},{"id":11310,"title":11311,"body":75535,"contentId":9285,"date":13780,"description":13781,"extension":18,"meta":77693,"navigation":20,"path":13783,"rawbody":13784,"robots":23,"schemaOrg":23,"seo":77694,"sitemap":77695,"stem":13789,"__hash__":13790},{"type":7,"value":75536,"toc":77686},[75537,75539,75541,75545,75547,75549,75551,75553,75561,75565,75576,75578,75580,75584,75586,75588,75636,75638,75740,75742,75746,75752,75754,75756,75764,75770,75772,75780,75782,75784,75793,75797,75917,75919,75926,76057,76059,76234,76240,77684],[35,75538,11316],{},[35,75540,11319],{},[35,75542,11322,75543],{},[1216,75544,11325],{},[1158,75546],{},[35,75548,11330],{},[35,75550,11333],{},[35,75552,11336],{},[130,75554,132,75555,132,75559],{},[134,75556,136,75557,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,75558],{"src":11343,"type":141},[143,75560,11346],{},[35,75562,11349,75563],{},[1216,75564,11352],{},[938,75566,75567],{},[35,75568,75569,11359,75571,11364,75573,215],{},[200,75570,944],{},[39,75572,11363],{"href":11362},[39,75574,11369],{"href":11367,"rel":75575},[77],[51,75577,11373],{"id":11372},[35,75579,11376],{},[35,75581,11379,75582],{},[1216,75583,11382],{},[35,75585,11385],{},[35,75587,11388],{},[192,75589,75590],{"className":590,"code":11391,"language":592,"meta":13,"style":13},[156,75591,75592,75600,75628],{"__ignoreMap":13},[200,75593,75594,75596,75598],{"class":202,"line":203},[200,75595,599],{"class":214},[200,75597,11400],{"class":235},[200,75599,647],{"class":214},[200,75601,75602,75604,75606,75608,75610,75612,75614,75616,75618,75620,75622,75624,75626],{"class":202,"line":14},[200,75603,656],{"class":214},[200,75605,1707],{"class":235},[200,75607,10120],{"class":379},[200,75609,449],{"class":214},[200,75611,610],{"class":214},[200,75613,11417],{"class":245},[200,75615,610],{"class":214},[200,75617,10148],{"class":379},[200,75619,449],{"class":214},[200,75621,610],{"class":214},[200,75623,11428],{"class":245},[200,75625,610],{"class":214},[200,75627,647],{"class":214},[200,75629,75630,75632,75634],{"class":202,"line":255},[200,75631,680],{"class":214},[200,75633,11400],{"class":235},[200,75635,647],{"class":214},[35,75637,11443],{},[192,75639,75640],{"className":590,"code":11446,"language":592,"meta":13,"style":13},[156,75641,75642,75650,75656,75668,75680,75692,75704,75716,75728,75732],{"__ignoreMap":13},[200,75643,75644,75646,75648],{"class":202,"line":203},[200,75645,599],{"class":214},[200,75647,11400],{"class":235},[200,75649,647],{"class":214},[200,75651,75652,75654],{"class":202,"line":14},[200,75653,656],{"class":214},[200,75655,11463],{"class":235},[200,75657,75658,75660,75662,75664,75666],{"class":202,"line":255},[200,75659,11468],{"class":379},[200,75661,449],{"class":214},[200,75663,610],{"class":214},[200,75665,11475],{"class":245},[200,75667,616],{"class":214},[200,75669,75670,75672,75674,75676,75678],{"class":202,"line":272},[200,75671,11482],{"class":379},[200,75673,449],{"class":214},[200,75675,610],{"class":214},[200,75677,11428],{"class":245},[200,75679,616],{"class":214},[200,75681,75682,75684,75686,75688,75690],{"class":202,"line":283},[200,75683,11495],{"class":379},[200,75685,449],{"class":214},[200,75687,610],{"class":214},[200,75689,11502],{"class":245},[200,75691,616],{"class":214},[200,75693,75694,75696,75698,75700,75702],{"class":202,"line":300},[200,75695,11509],{"class":379},[200,75697,449],{"class":214},[200,75699,610],{"class":214},[200,75701,11516],{"class":245},[200,75703,616],{"class":214},[200,75705,75706,75708,75710,75712,75714],{"class":202,"line":310},[200,75707,11523],{"class":379},[200,75709,449],{"class":214},[200,75711,610],{"class":214},[200,75713,9909],{"class":245},[200,75715,616],{"class":214},[200,75717,75718,75720,75722,75724,75726],{"class":202,"line":336},[200,75719,11536],{"class":379},[200,75721,449],{"class":214},[200,75723,610],{"class":214},[200,75725,11543],{"class":245},[200,75727,616],{"class":214},[200,75729,75730],{"class":202,"line":342},[200,75731,11550],{"class":214},[200,75733,75734,75736,75738],{"class":202,"line":365},[200,75735,680],{"class":214},[200,75737,11400],{"class":235},[200,75739,647],{"class":214},[35,75741,11561],{},[35,75743,11564,75744],{},[1216,75745,11567],{},[938,75747,75748],{},[35,75749,75750,11574],{},[200,75751,944],{},[51,75753,11578],{"id":11577},[35,75755,11581],{},[35,75757,11584,75758,11588,75760,11591,75762,11594],{},[156,75759,11587],{},[156,75761,11180],{},[156,75763,11587],{},[938,75765,75766],{},[35,75767,75768,11601],{},[200,75769,944],{},[35,75771,11604],{},[35,75773,11607,75774,11613,75777,215],{},[39,75775,11612],{"href":11610,"rel":75776},[77],[39,75778,11618],{"href":11616,"rel":75779},[77],[35,75781,11621],{},[35,75783,11624],{},[938,75785,75786],{},[35,75787,75788,11631,75790,215],{},[200,75789,944],{},[39,75791,11636],{"href":11634,"rel":75792},[77],[35,75794,11639,75795,11642],{},[156,75796,1477],{},[192,75798,75799],{"className":1380,"code":11645,"filename":1477,"language":1383,"meta":13,"style":13},[156,75800,75801,75815,75833,75837,75849,75857,75865,75875,75881,75895,75899,75905,75911],{"__ignoreMap":13},[200,75802,75803,75805,75807,75809,75811,75813],{"class":202,"line":203},[200,75804,972],{"class":206},[200,75806,11654],{"class":210},[200,75808,1489],{"class":206},[200,75810,242],{"class":214},[200,75812,11661],{"class":245},[200,75814,991],{"class":214},[200,75816,75817,75819,75821,75823,75825,75827,75829,75831],{"class":202,"line":14},[200,75818,972],{"class":206},[200,75820,318],{"class":214},[200,75822,1505],{"class":210},[200,75824,980],{"class":214},[200,75826,983],{"class":206},[200,75828,242],{"class":214},[200,75830,1514],{"class":245},[200,75832,991],{"class":214},[200,75834,75835],{"class":202,"line":255},[200,75836,528],{"emptyLinePlaceholder":20},[200,75838,75839,75841,75843,75845,75847],{"class":202,"line":272},[200,75840,1390],{"class":206},[200,75842,1393],{"class":206},[200,75844,1505],{"class":223},[200,75846,227],{"class":210},[200,75848,230],{"class":214},[200,75850,75851,75853,75855],{"class":202,"line":283},[200,75852,1537],{"class":235},[200,75854,239],{"class":214},[200,75856,1410],{"class":210},[200,75858,75859,75861,75863],{"class":202,"line":300},[200,75860,11710],{"class":223},[200,75862,1549],{"class":210},[200,75864,252],{"class":214},[200,75866,75867,75869,75871,75873],{"class":202,"line":310},[200,75868,11719],{"class":210},[200,75870,1549],{"class":214},[200,75872,393],{"class":379},[200,75874,280],{"class":214},[200,75876,75877,75879],{"class":202,"line":336},[200,75878,11730],{"class":206},[200,75880,280],{"class":214},[200,75882,75883,75885,75887,75889,75891,75893],{"class":202,"line":342},[200,75884,11737],{"class":235},[200,75886,239],{"class":214},[200,75888,242],{"class":214},[200,75890,11612],{"class":245},[200,75892,249],{"class":214},[200,75894,252],{"class":214},[200,75896,75897],{"class":202,"line":365},[200,75898,4493],{"class":214},[200,75900,75901,75903],{"class":202,"line":371},[200,75902,2111],{"class":214},[200,75904,11758],{"class":210},[200,75906,75907,75909],{"class":202,"line":398},[200,75908,1427],{"class":210},[200,75910,252],{"class":214},[200,75912,75913,75915],{"class":202,"line":405},[200,75914,413],{"class":214},[200,75916,416],{"class":210},[35,75918,11773],{},[35,75920,11776,75921,11782,75924,11786],{},[39,75922,11781],{"href":11779,"rel":75923},[77],[156,75925,11785],{},[192,75927,75928],{"className":9755,"code":11789,"filename":1477,"language":1383,"meta":13,"style":13},[156,75929,75930,75940,75946,75960,75978,76003,76007,76011,76015,76019,76025,76031,76039,76043,76047,76051],{"__ignoreMap":13},[200,75931,75932,75934,75936,75938],{"class":202,"line":203},[200,75933,227],{"class":210},[200,75935,1549],{"class":214},[200,75937,393],{"class":379},[200,75939,280],{"class":214},[200,75941,75942,75944],{"class":202,"line":14},[200,75943,3758],{"class":206},[200,75945,280],{"class":214},[200,75947,75948,75950,75952,75954,75956,75958],{"class":202,"line":255},[200,75949,11812],{"class":235},[200,75951,239],{"class":214},[200,75953,242],{"class":214},[200,75955,11612],{"class":245},[200,75957,249],{"class":214},[200,75959,252],{"class":214},[200,75961,75962,75964,75966,75968,75970,75972,75974,75976],{"class":202,"line":272},[200,75963,11827],{"class":379},[200,75965,11830],{"class":235},[200,75967,227],{"class":214},[200,75969,156],{"class":386},[200,75971,549],{"class":214},[200,75973,3239],{"class":386},[200,75975,2104],{"class":214},[200,75977,280],{"class":214},[200,75979,75981,75983,75985,75987,75989,75991,75993,75995,75997,75999,76001],{"class":75980,"line":283},[202,9885],[200,75982,6523],{"class":206},[200,75984,1094],{"class":235},[200,75986,11852],{"class":214},[200,75988,3295],{"class":210},[200,75990,215],{"class":214},[200,75992,11859],{"class":223},[200,75994,227],{"class":235},[200,75996,249],{"class":214},[200,75998,11866],{"class":245},[200,76000,249],{"class":214},[200,76002,6478],{"class":235},[200,76004,76005],{"class":202,"line":300},[200,76006,11875],{"class":206},[200,76008,76009],{"class":202,"line":310},[200,76010,528],{"emptyLinePlaceholder":20},[200,76012,76013],{"class":202,"line":336},[200,76014,11884],{"class":401},[200,76016,76017],{"class":202,"line":342},[200,76018,528],{"emptyLinePlaceholder":20},[200,76020,76021,76023],{"class":202,"line":365},[200,76022,11730],{"class":206},[200,76024,280],{"class":214},[200,76026,76027,76029],{"class":202,"line":371},[200,76028,11899],{"class":210},[200,76030,252],{"class":214},[200,76032,76033,76035,76037],{"class":202,"line":398},[200,76034,11906],{"class":235},[200,76036,239],{"class":214},[200,76038,11911],{"class":214},[200,76040,76041],{"class":202,"line":405},[200,76042,4493],{"class":214},[200,76044,76045],{"class":202,"line":410},[200,76046,4977],{"class":214},[200,76048,76049],{"class":202,"line":860},[200,76050,4505],{"class":214},[200,76052,76053,76055],{"class":202,"line":889},[200,76054,413],{"class":214},[200,76056,11758],{"class":210},[35,76058,11932],{},[192,76060,76061],{"className":9755,"code":11935,"filename":1477,"language":1383,"meta":13,"style":13},[156,76062,76063,76073,76118,76122,76128,76142,76156,76164,76172,76191,76202,76206,76220,76224,76228],{"__ignoreMap":13},[200,76064,76065,76067,76069,76071],{"class":202,"line":203},[200,76066,227],{"class":210},[200,76068,1549],{"class":214},[200,76070,393],{"class":379},[200,76072,280],{"class":214},[200,76074,76076,76078,76080,76082,76084,76086,76088,76090,76092,76094,76096,76098,76100,76102,76104,76106,76108,76110,76112,76114,76116],{"class":76075,"line":14},[202,9885],[200,76077,3738],{"class":379},[200,76079,11955],{"class":210},[200,76081,509],{"class":214},[200,76083,11960],{"class":214},[200,76085,11963],{"class":245},[200,76087,11966],{"class":214},[200,76089,674],{"class":245},[200,76091,11971],{"class":214},[200,76093,11974],{"class":245},[200,76095,9872],{"class":214},[200,76097,11979],{"class":245},[200,76099,11982],{"class":214},[200,76101,11979],{"class":245},[200,76103,11987],{"class":214},[200,76105,11979],{"class":245},[200,76107,11992],{"class":214},[200,76109,674],{"class":245},[200,76111,11971],{"class":214},[200,76113,674],{"class":245},[200,76115,2898],{"class":214},[200,76117,12003],{"class":2060},[200,76119,76120],{"class":202,"line":255},[200,76121,528],{"emptyLinePlaceholder":20},[200,76123,76124,76126],{"class":202,"line":272},[200,76125,3758],{"class":206},[200,76127,280],{"class":214},[200,76129,76130,76132,76134,76136,76138,76140],{"class":202,"line":283},[200,76131,11812],{"class":235},[200,76133,239],{"class":214},[200,76135,242],{"class":214},[200,76137,11612],{"class":245},[200,76139,249],{"class":214},[200,76141,252],{"class":214},[200,76143,76144,76146,76148,76150,76152,76154],{"class":202,"line":300},[200,76145,12032],{"class":235},[200,76147,239],{"class":214},[200,76149,242],{"class":214},[200,76151,192],{"class":245},[200,76153,249],{"class":214},[200,76155,252],{"class":214},[200,76157,76158,76160,76162],{"class":202,"line":310},[200,76159,5051],{"class":235},[200,76161,239],{"class":214},[200,76163,280],{"class":214},[200,76165,76166,76168,76170],{"class":202,"line":336},[200,76167,12055],{"class":235},[200,76169,239],{"class":214},[200,76171,280],{"class":214},[200,76173,76175,76177,76179,76181,76183,76185,76187,76189],{"class":76174,"line":342},[202,9885],[200,76176,12065],{"class":235},[200,76178,239],{"class":214},[200,76180,11960],{"class":214},[200,76182,12072],{"class":210},[200,76184,12075],{"class":245},[200,76186,12078],{"class":206},[200,76188,2898],{"class":214},[200,76190,549],{"class":214},[200,76192,76194,76196,76198,76200],{"class":76193,"line":365},[202,9885],[200,76195,11899],{"class":235},[200,76197,239],{"class":214},[200,76199,11955],{"class":210},[200,76201,549],{"class":214},[200,76203,76204],{"class":202,"line":371},[200,76205,12098],{"class":214},[200,76207,76208,76210,76212,76214,76216,76218],{"class":202,"line":398},[200,76209,12103],{"class":379},[200,76211,12106],{"class":235},[200,76213,227],{"class":214},[200,76215,156],{"class":386},[200,76217,2104],{"class":214},[200,76219,12115],{"class":214},[200,76221,76222],{"class":202,"line":405},[200,76223,4977],{"class":214},[200,76225,76226],{"class":202,"line":410},[200,76227,4505],{"class":214},[200,76229,76230,76232],{"class":202,"line":860},[200,76231,413],{"class":214},[200,76233,11758],{"class":210},[35,76235,12132,76236,12135,76238,12138],{},[156,76237,3773],{},[156,76239,11587],{},[7918,76241,76242,76244,76254,76329,76333,76335,76339,76363,76365,76385,76387,76393,76419,76427,76489,76493,76563,76569,76693,76701,76807,76809,77489,77499,77503,77505,77513,77521,77523,77528,77542,77546,77630,77632,77642,77646,77650,77654,77656,77658,77660,77674,77676,77680,77682],{"text":12141,"href":12142},[35,76243,12145],{},[68,76245,76246,76250],{},[71,76247,12150,76248,215],{},[156,76249,12153],{},[71,76251,12156,76252,12160],{},[156,76253,12159],{},[192,76255,76256],{"className":9755,"code":12163,"filename":1477,"language":1383,"meta":13,"style":13},[156,76257,76258,76268,76274,76288,76303,76311,76315,76319,76323],{"__ignoreMap":13},[200,76259,76260,76262,76264,76266],{"class":202,"line":203},[200,76261,227],{"class":210},[200,76263,1549],{"class":214},[200,76265,393],{"class":379},[200,76267,280],{"class":214},[200,76269,76270,76272],{"class":202,"line":14},[200,76271,3758],{"class":206},[200,76273,280],{"class":214},[200,76275,76276,76278,76280,76282,76284,76286],{"class":202,"line":255},[200,76277,11812],{"class":235},[200,76279,239],{"class":214},[200,76281,242],{"class":214},[200,76283,11612],{"class":245},[200,76285,249],{"class":214},[200,76287,252],{"class":214},[200,76289,76291,76293,76295,76297,76299,76301],{"class":76290,"line":272},[202,9885],[200,76292,12032],{"class":235},[200,76294,239],{"class":214},[200,76296,242],{"class":214},[200,76298,192],{"class":245},[200,76300,249],{"class":214},[200,76302,549],{"class":214},[200,76304,76305,76307,76309],{"class":202,"line":283},[200,76306,5051],{"class":235},[200,76308,239],{"class":214},[200,76310,280],{"class":214},[200,76312,76313],{"class":202,"line":300},[200,76314,12223],{"class":401},[200,76316,76317],{"class":202,"line":310},[200,76318,4977],{"class":214},[200,76320,76321],{"class":202,"line":336},[200,76322,4505],{"class":214},[200,76324,76325,76327],{"class":202,"line":342},[200,76326,413],{"class":214},[200,76328,11758],{"class":210},[35,76330,12240,76331,215],{},[156,76332,11587],{},[51,76334,12246],{"id":12245},[35,76336,12249,76337,12252],{},[156,76338,11785],{},[68,76340,76341,76345,76351,76353,76357],{},[71,76342,12257,76343,215],{},[156,76344,11587],{},[71,76346,12262,76347,12265,76349,215],{},[156,76348,11587],{},[156,76350,11180],{},[71,76352,12270],{},[71,76354,12273,76355,12276],{},[156,76356,11612],{},[71,76358,12279,76359,12282,76361,12285],{},[156,76360,11587],{},[156,76362,11587],{},[35,76364,12288],{},[192,76366,76367],{"className":2166,"code":12291,"language":2168,"meta":13,"style":13},[156,76368,76369],{"__ignoreMap":13},[200,76370,76371,76373,76375,76377,76379,76381,76383],{"class":202,"line":203},[200,76372,1365],{"class":1364},[200,76374,1368],{"class":245},[200,76376,1371],{"class":245},[200,76378,12304],{"class":245},[200,76380,12307],{"class":245},[200,76382,12310],{"class":245},[200,76384,12313],{"class":245},[35,76386,12316],{},[68,76388,76389],{},[71,76390,12257,76391,215],{},[156,76392,11587],{},[192,76394,76395],{"className":1380,"code":12325,"filename":1477,"language":1383,"meta":13,"style":13},[156,76396,76397,76401],{"__ignoreMap":13},[200,76398,76399],{"class":202,"line":203},[200,76400,12332],{"class":401},[200,76402,76403,76405,76407,76409,76411,76413,76415,76417],{"class":202,"line":14},[200,76404,12337],{"class":379},[200,76406,12340],{"class":210},[200,76408,449],{"class":214},[200,76410,11955],{"class":210},[200,76412,215],{"class":214},[200,76414,12349],{"class":223},[200,76416,12352],{"class":210},[200,76418,12355],{"class":401},[68,76420,76421],{"start":14},[71,76422,12262,76423,12265,76425,215],{},[156,76424,11587],{},[156,76426,11180],{},[192,76428,76429],{"className":1380,"code":12366,"filename":1477,"language":1383,"meta":13,"style":13},[156,76430,76431,76437,76453,76457,76475],{"__ignoreMap":13},[200,76432,76433,76435],{"class":202,"line":203},[200,76434,12373],{"class":206},[200,76436,280],{"class":214},[200,76438,76439,76441,76443,76445,76447,76449,76451],{"class":202,"line":14},[200,76440,3738],{"class":379},[200,76442,12382],{"class":210},[200,76444,509],{"class":214},[200,76446,12387],{"class":210},[200,76448,9872],{"class":235},[200,76450,3203],{"class":2060},[200,76452,4511],{"class":235},[200,76454,76455],{"class":202,"line":255},[200,76456,528],{"emptyLinePlaceholder":20},[200,76458,76459,76461,76463,76465,76467,76469,76471,76473],{"class":202,"line":272},[200,76460,12402],{"class":210},[200,76462,509],{"class":214},[200,76464,11955],{"class":210},[200,76466,215],{"class":214},[200,76468,12349],{"class":223},[200,76470,227],{"class":235},[200,76472,156],{"class":210},[200,76474,416],{"class":235},[200,76476,76477,76479,76481,76483,76485,76487],{"class":202,"line":283},[200,76478,413],{"class":214},[200,76480,12423],{"class":206},[200,76482,12426],{"class":210},[200,76484,12429],{"class":214},[200,76486,12432],{"class":214},[200,76488,416],{"class":210},[68,76490,76491],{"start":255},[71,76492,12270],{},[192,76494,76495],{"className":1380,"code":12441,"filename":1477,"language":1383,"meta":13,"style":13},[156,76496,76497,76515,76533,76537],{"__ignoreMap":13},[200,76498,76499,76501,76503,76505,76507,76509,76511,76513],{"class":202,"line":203},[200,76500,972],{"class":206},[200,76502,318],{"class":214},[200,76504,12452],{"class":210},[200,76506,980],{"class":214},[200,76508,983],{"class":206},[200,76510,242],{"class":214},[200,76512,12461],{"class":245},[200,76514,991],{"class":214},[200,76516,76517,76519,76521,76523,76525,76527,76529,76531],{"class":202,"line":14},[200,76518,972],{"class":206},[200,76520,318],{"class":214},[200,76522,12472],{"class":210},[200,76524,980],{"class":214},[200,76526,983],{"class":206},[200,76528,242],{"class":214},[200,76530,12481],{"class":245},[200,76532,991],{"class":214},[200,76534,76535],{"class":202,"line":255},[200,76536,528],{"emptyLinePlaceholder":20},[200,76538,76539,76541,76543,76545,76547,76549,76551,76553,76555,76557,76559,76561],{"class":202,"line":272},[200,76540,443],{"class":379},[200,76542,10930],{"class":210},[200,76544,449],{"class":214},[200,76546,452],{"class":206},[200,76548,12452],{"class":223},[200,76550,227],{"class":210},[200,76552,6202],{"class":223},[200,76554,12506],{"class":210},[200,76556,215],{"class":214},[200,76558,12511],{"class":210},[200,76560,549],{"class":214},[200,76562,12516],{"class":210},[68,76564,76565],{"start":272},[71,76566,12273,76567,12276],{},[156,76568,11612],{},[192,76570,76571],{"className":1380,"code":12525,"filename":1477,"language":1383,"meta":13,"style":13},[156,76572,76573,76591,76609,76627,76631,76645],{"__ignoreMap":13},[200,76574,76575,76577,76579,76581,76583,76585,76587,76589],{"class":202,"line":203},[200,76576,972],{"class":206},[200,76578,318],{"class":214},[200,76580,10976],{"class":210},[200,76582,980],{"class":214},[200,76584,983],{"class":206},[200,76586,242],{"class":214},[200,76588,12544],{"class":245},[200,76590,991],{"class":214},[200,76592,76593,76595,76597,76599,76601,76603,76605,76607],{"class":202,"line":14},[200,76594,972],{"class":206},[200,76596,318],{"class":214},[200,76598,11056],{"class":210},[200,76600,980],{"class":214},[200,76602,983],{"class":206},[200,76604,242],{"class":214},[200,76606,11065],{"class":245},[200,76608,991],{"class":214},[200,76610,76611,76613,76615,76617,76619,76621,76623,76625],{"class":202,"line":255},[200,76612,972],{"class":206},[200,76614,318],{"class":214},[200,76616,10995],{"class":210},[200,76618,980],{"class":214},[200,76620,983],{"class":206},[200,76622,242],{"class":214},[200,76624,12581],{"class":245},[200,76626,991],{"class":214},[200,76628,76629],{"class":202,"line":272},[200,76630,528],{"emptyLinePlaceholder":20},[200,76632,76633,76635,76637,76639,76641,76643],{"class":202,"line":283},[200,76634,443],{"class":379},[200,76636,10969],{"class":210},[200,76638,449],{"class":214},[200,76640,452],{"class":206},[200,76642,10976],{"class":223},[200,76644,10979],{"class":210},[200,76646,76647,76649,76651,76653,76655,76657,76659,76661,76663,76665,76667,76669,76671,76673,76675,76677,76679,76681,76683,76685,76687,76689,76691],{"class":202,"line":300},[200,76648,443],{"class":379},[200,76650,10990],{"class":210},[200,76652,449],{"class":214},[200,76654,10995],{"class":223},[200,76656,10998],{"class":210},[200,76658,215],{"class":214},[200,76660,1489],{"class":223},[200,76662,11005],{"class":210},[200,76664,215],{"class":214},[200,76666,11010],{"class":210},[200,76668,549],{"class":214},[200,76670,5056],{"class":210},[200,76672,215],{"class":214},[200,76674,11019],{"class":210},[200,76676,549],{"class":214},[200,76678,5056],{"class":210},[200,76680,215],{"class":214},[200,76682,11028],{"class":210},[200,76684,549],{"class":214},[200,76686,11033],{"class":2060},[200,76688,549],{"class":214},[200,76690,11033],{"class":2060},[200,76692,416],{"class":210},[68,76694,76695],{"start":283},[71,76696,12279,76697,12282,76699,12285],{},[156,76698,11587],{},[156,76700,11587],{},[192,76702,76703],{"className":1380,"code":12660,"filename":1477,"language":1383,"meta":13,"style":13},[156,76704,76705,76723,76727,76747,76761,76803],{"__ignoreMap":13},[200,76706,76707,76709,76711,76713,76715,76717,76719,76721],{"class":202,"line":203},[200,76708,972],{"class":206},[200,76710,318],{"class":214},[200,76712,11056],{"class":210},[200,76714,980],{"class":214},[200,76716,983],{"class":206},[200,76718,242],{"class":214},[200,76720,11065],{"class":245},[200,76722,991],{"class":214},[200,76724,76725],{"class":202,"line":14},[200,76726,528],{"emptyLinePlaceholder":20},[200,76728,76729,76731,76733,76735,76737,76739,76741,76743,76745],{"class":202,"line":255},[200,76730,443],{"class":379},[200,76732,12691],{"class":210},[200,76734,449],{"class":214},[200,76736,12696],{"class":210},[200,76738,3948],{"class":2060},[200,76740,360],{"class":210},[200,76742,215],{"class":214},[200,76744,6148],{"class":223},[200,76746,3130],{"class":210},[200,76748,76749,76751,76753,76755,76757,76759],{"class":202,"line":272},[200,76750,12711],{"class":214},[200,76752,12714],{"class":245},[200,76754,227],{"class":214},[200,76756,6163],{"class":245},[200,76758,12721],{"class":214},[200,76760,252],{"class":214},[200,76762,76763,76765,76767,76769,76771,76773,76775,76777,76779,76781,76783,76785,76787,76789,76791,76793,76795,76797,76799,76801],{"class":202,"line":283},[200,76764,12728],{"class":214},[200,76766,12731],{"class":245},[200,76768,4868],{"class":214},[200,76770,12736],{"class":210},[200,76772,215],{"class":214},[200,76774,11019],{"class":210},[200,76776,413],{"class":214},[200,76778,12745],{"class":245},[200,76780,4868],{"class":214},[200,76782,12736],{"class":210},[200,76784,215],{"class":214},[200,76786,11028],{"class":210},[200,76788,413],{"class":214},[200,76790,12758],{"class":245},[200,76792,4868],{"class":214},[200,76794,11162],{"class":223},[200,76796,11165],{"class":210},[200,76798,413],{"class":214},[200,76800,12769],{"class":245},[200,76802,12772],{"class":214},[200,76804,76805],{"class":202,"line":300},[200,76806,416],{"class":210},[35,76808,12779],{},[192,76810,76811],{"className":1380,"code":12782,"filename":1477,"language":1383,"meta":13,"style":13},[156,76812,76813,76823,76833,76837,76881,76885,76891,76905,76919,76927,76935,76953,76997,77001,77019,77037,77041,77061,77065,77071,77089,77093,77123,77141,77195,77199,77213,77239,77243,77267,77281,77323,77327,77331,77353,77357,77375,77391,77395,77401,77417,77443,77447,77451,77455,77467,77475,77479,77483],{"__ignoreMap":13},[200,76814,76815,76817,76819,76821],{"class":202,"line":203},[200,76816,227],{"class":210},[200,76818,1549],{"class":214},[200,76820,393],{"class":379},[200,76822,280],{"class":214},[200,76824,76825,76827,76829,76831],{"class":202,"line":14},[200,76826,12799],{"class":379},[200,76828,12802],{"class":210},[200,76830,239],{"class":214},[200,76832,12807],{"class":1364},[200,76834,76835],{"class":202,"line":255},[200,76836,528],{"emptyLinePlaceholder":20},[200,76838,76839,76841,76843,76845,76847,76849,76851,76853,76855,76857,76859,76861,76863,76865,76867,76869,76871,76873,76875,76877,76879],{"class":202,"line":272},[200,76840,3738],{"class":379},[200,76842,11955],{"class":210},[200,76844,509],{"class":214},[200,76846,11960],{"class":214},[200,76848,11963],{"class":245},[200,76850,11966],{"class":214},[200,76852,674],{"class":245},[200,76854,11971],{"class":214},[200,76856,11974],{"class":245},[200,76858,9872],{"class":214},[200,76860,11979],{"class":245},[200,76862,11982],{"class":214},[200,76864,11979],{"class":245},[200,76866,11987],{"class":214},[200,76868,11979],{"class":245},[200,76870,11992],{"class":214},[200,76872,674],{"class":245},[200,76874,11971],{"class":214},[200,76876,674],{"class":245},[200,76878,2898],{"class":214},[200,76880,12856],{"class":2060},[200,76882,76883],{"class":202,"line":283},[200,76884,528],{"emptyLinePlaceholder":20},[200,76886,76887,76889],{"class":202,"line":300},[200,76888,3758],{"class":206},[200,76890,280],{"class":214},[200,76892,76893,76895,76897,76899,76901,76903],{"class":202,"line":310},[200,76894,11812],{"class":235},[200,76896,239],{"class":214},[200,76898,242],{"class":214},[200,76900,11612],{"class":245},[200,76902,249],{"class":214},[200,76904,252],{"class":214},[200,76906,76907,76909,76911,76913,76915,76917],{"class":202,"line":336},[200,76908,12032],{"class":235},[200,76910,239],{"class":214},[200,76912,242],{"class":214},[200,76914,192],{"class":245},[200,76916,249],{"class":214},[200,76918,252],{"class":214},[200,76920,76921,76923,76925],{"class":202,"line":342},[200,76922,5051],{"class":235},[200,76924,239],{"class":214},[200,76926,280],{"class":214},[200,76928,76929,76931,76933],{"class":202,"line":365},[200,76930,12055],{"class":235},[200,76932,239],{"class":214},[200,76934,280],{"class":214},[200,76936,76937,76939,76941,76943,76945,76947,76949,76951],{"class":202,"line":371},[200,76938,12065],{"class":235},[200,76940,239],{"class":214},[200,76942,11960],{"class":214},[200,76944,12072],{"class":210},[200,76946,12075],{"class":245},[200,76948,12078],{"class":206},[200,76950,2898],{"class":214},[200,76952,252],{"class":214},[200,76954,76955,76957,76959,76961,76963,76965,76967,76969,76971,76973,76975,76977,76979,76981,76983,76985,76987,76989,76991,76993,76995],{"class":202,"line":398},[200,76956,11899],{"class":235},[200,76958,239],{"class":214},[200,76960,11960],{"class":214},[200,76962,11963],{"class":245},[200,76964,11966],{"class":214},[200,76966,674],{"class":245},[200,76968,11971],{"class":214},[200,76970,11974],{"class":245},[200,76972,9872],{"class":214},[200,76974,11979],{"class":245},[200,76976,11982],{"class":214},[200,76978,11979],{"class":245},[200,76980,11987],{"class":214},[200,76982,11979],{"class":245},[200,76984,11992],{"class":214},[200,76986,674],{"class":245},[200,76988,11971],{"class":214},[200,76990,674],{"class":245},[200,76992,2898],{"class":214},[200,76994,12003],{"class":2060},[200,76996,252],{"class":214},[200,76998,76999],{"class":202,"line":405},[200,77000,12098],{"class":214},[200,77002,77003,77005,77007,77009,77011,77013,77015,77017],{"class":202,"line":410},[200,77004,12103],{"class":379},[200,77006,12106],{"class":235},[200,77008,227],{"class":214},[200,77010,156],{"class":386},[200,77012,549],{"class":214},[200,77014,3239],{"class":386},[200,77016,2104],{"class":214},[200,77018,280],{"class":214},[200,77020,77021,77023,77025,77027,77029,77031,77033,77035],{"class":202,"line":860},[200,77022,12999],{"class":379},[200,77024,13002],{"class":210},[200,77026,509],{"class":214},[200,77028,13007],{"class":214},[200,77030,13010],{"class":223},[200,77032,227],{"class":235},[200,77034,156],{"class":210},[200,77036,416],{"class":235},[200,77038,77039],{"class":202,"line":889},[200,77040,528],{"emptyLinePlaceholder":20},[200,77042,77043,77045,77047,77049,77051,77053,77055,77057,77059],{"class":202,"line":899},[200,77044,13025],{"class":379},[200,77046,12387],{"class":210},[200,77048,509],{"class":214},[200,77050,11955],{"class":210},[200,77052,215],{"class":214},[200,77054,12349],{"class":223},[200,77056,227],{"class":235},[200,77058,156],{"class":210},[200,77060,416],{"class":235},[200,77062,77063],{"class":202,"line":904},[200,77064,528],{"emptyLinePlaceholder":20},[200,77066,77067,77069],{"class":202,"line":930},[200,77068,13050],{"class":206},[200,77070,280],{"class":214},[200,77072,77073,77075,77077,77079,77081,77083,77085,77087],{"class":202,"line":3655},[200,77074,13057],{"class":379},[200,77076,12382],{"class":210},[200,77078,509],{"class":214},[200,77080,12387],{"class":210},[200,77082,11852],{"class":214},[200,77084,9872],{"class":235},[200,77086,3203],{"class":2060},[200,77088,4511],{"class":235},[200,77090,77091],{"class":202,"line":3660},[200,77092,528],{"emptyLinePlaceholder":20},[200,77094,77095,77097,77099,77101,77103,77105,77107,77109,77111,77113,77115,77117,77119,77121],{"class":202,"line":4035},[200,77096,13057],{"class":379},[200,77098,13082],{"class":210},[200,77100,509],{"class":214},[200,77102,452],{"class":206},[200,77104,12452],{"class":223},[200,77106,227],{"class":235},[200,77108,6202],{"class":223},[200,77110,227],{"class":235},[200,77112,13097],{"class":210},[200,77114,215],{"class":214},[200,77116,12511],{"class":210},[200,77118,549],{"class":214},[200,77120,12382],{"class":210},[200,77122,584],{"class":235},[200,77124,77125,77127,77129,77131,77133,77135,77137,77139],{"class":202,"line":4490},[200,77126,13057],{"class":379},[200,77128,5056],{"class":210},[200,77130,509],{"class":214},[200,77132,452],{"class":206},[200,77134,10976],{"class":223},[200,77136,227],{"class":235},[200,77138,1707],{"class":210},[200,77140,416],{"class":235},[200,77142,77143,77145,77147,77149,77151,77153,77155,77157,77159,77161,77163,77165,77167,77169,77171,77173,77175,77177,77179,77181,77183,77185,77187,77189,77191,77193],{"class":202,"line":4496},[200,77144,13057],{"class":379},[200,77146,12310],{"class":210},[200,77148,509],{"class":214},[200,77150,10995],{"class":223},[200,77152,227],{"class":235},[200,77154,13140],{"class":210},[200,77156,215],{"class":214},[200,77158,1489],{"class":223},[200,77160,227],{"class":235},[200,77162,12736],{"class":210},[200,77164,215],{"class":214},[200,77166,12736],{"class":210},[200,77168,2104],{"class":235},[200,77170,549],{"class":214},[200,77172,5056],{"class":210},[200,77174,215],{"class":214},[200,77176,11019],{"class":210},[200,77178,549],{"class":214},[200,77180,5056],{"class":210},[200,77182,215],{"class":214},[200,77184,11028],{"class":210},[200,77186,549],{"class":214},[200,77188,11033],{"class":2060},[200,77190,549],{"class":214},[200,77192,11033],{"class":2060},[200,77194,416],{"class":235},[200,77196,77197],{"class":202,"line":4502},[200,77198,528],{"emptyLinePlaceholder":20},[200,77200,77201,77203,77205,77207,77209,77211],{"class":202,"line":4508},[200,77202,13057],{"class":379},[200,77204,13191],{"class":210},[200,77206,509],{"class":214},[200,77208,12387],{"class":210},[200,77210,13198],{"class":214},[200,77212,13201],{"class":210},[200,77214,77215,77217,77219,77221,77223,77225,77227,77229,77231,77233,77235,77237],{"class":202,"line":5469},[200,77216,13057],{"class":379},[200,77218,13208],{"class":210},[200,77220,509],{"class":214},[200,77222,13191],{"class":210},[200,77224,13215],{"class":214},[200,77226,12387],{"class":210},[200,77228,11852],{"class":214},[200,77230,9872],{"class":235},[200,77232,3948],{"class":2060},[200,77234,360],{"class":235},[200,77236,215],{"class":214},[200,77238,13230],{"class":210},[200,77240,77241],{"class":202,"line":5474},[200,77242,528],{"emptyLinePlaceholder":20},[200,77244,77245,77247,77249,77251,77253,77255,77257,77259,77261,77263,77265],{"class":202,"line":13237},[200,77246,13057],{"class":379},[200,77248,13242],{"class":210},[200,77250,509],{"class":214},[200,77252,12387],{"class":210},[200,77254,11852],{"class":214},[200,77256,9872],{"class":235},[200,77258,3948],{"class":2060},[200,77260,360],{"class":235},[200,77262,215],{"class":214},[200,77264,6148],{"class":223},[200,77266,3130],{"class":235},[200,77268,77269,77271,77273,77275,77277,77279],{"class":202,"line":13263},[200,77270,13266],{"class":214},[200,77272,12714],{"class":245},[200,77274,227],{"class":214},[200,77276,6163],{"class":245},[200,77278,12721],{"class":214},[200,77280,252],{"class":214},[200,77282,77283,77285,77287,77289,77291,77293,77295,77297,77299,77301,77303,77305,77307,77309,77311,77313,77315,77317,77319,77321],{"class":202,"line":13279},[200,77284,13282],{"class":214},[200,77286,12731],{"class":245},[200,77288,4868],{"class":214},[200,77290,12736],{"class":210},[200,77292,215],{"class":214},[200,77294,11019],{"class":210},[200,77296,413],{"class":214},[200,77298,12745],{"class":245},[200,77300,4868],{"class":214},[200,77302,12736],{"class":210},[200,77304,215],{"class":214},[200,77306,11028],{"class":210},[200,77308,413],{"class":214},[200,77310,12758],{"class":245},[200,77312,4868],{"class":214},[200,77314,11162],{"class":223},[200,77316,11165],{"class":210},[200,77318,413],{"class":214},[200,77320,12769],{"class":245},[200,77322,12772],{"class":214},[200,77324,77325],{"class":202,"line":13323},[200,77326,13326],{"class":235},[200,77328,77329],{"class":202,"line":13329},[200,77330,528],{"emptyLinePlaceholder":20},[200,77332,77333,77335,77337,77339,77341,77343,77345,77347,77349,77351],{"class":202,"line":13334},[200,77334,13337],{"class":210},[200,77336,215],{"class":214},[200,77338,13342],{"class":223},[200,77340,227],{"class":235},[200,77342,13347],{"class":210},[200,77344,549],{"class":214},[200,77346,13208],{"class":210},[200,77348,549],{"class":214},[200,77350,13242],{"class":210},[200,77352,416],{"class":235},[200,77354,77355],{"class":202,"line":13360},[200,77356,528],{"emptyLinePlaceholder":20},[200,77358,77359,77361,77363,77365,77367,77369,77371,77373],{"class":202,"line":13365},[200,77360,13368],{"class":210},[200,77362,509],{"class":214},[200,77364,11955],{"class":210},[200,77366,215],{"class":214},[200,77368,12349],{"class":223},[200,77370,227],{"class":235},[200,77372,156],{"class":210},[200,77374,416],{"class":235},[200,77376,77377,77379,77381,77383,77385,77387,77389],{"class":202,"line":13385},[200,77378,13388],{"class":214},[200,77380,12423],{"class":206},[200,77382,1094],{"class":235},[200,77384,13395],{"class":210},[200,77386,13398],{"class":214},[200,77388,12432],{"class":214},[200,77390,416],{"class":235},[200,77392,77393],{"class":202,"line":13405},[200,77394,528],{"emptyLinePlaceholder":20},[200,77396,77397,77399],{"class":202,"line":13410},[200,77398,13413],{"class":206},[200,77400,280],{"class":214},[200,77402,77403,77405,77407,77409,77411,77413,77415],{"class":202,"line":13418},[200,77404,13421],{"class":235},[200,77406,239],{"class":214},[200,77408,13002],{"class":210},[200,77410,215],{"class":214},[200,77412,11101],{"class":223},[200,77414,1549],{"class":235},[200,77416,252],{"class":214},[200,77418,77419,77421,77423,77425,77427,77429,77431,77433,77435,77437,77439,77441],{"class":202,"line":13436},[200,77420,13439],{"class":235},[200,77422,239],{"class":214},[200,77424,13002],{"class":210},[200,77426,215],{"class":214},[200,77428,13448],{"class":223},[200,77430,227],{"class":235},[200,77432,2767],{"class":214},[200,77434,13455],{"class":235},[200,77436,239],{"class":214},[200,77438,2439],{"class":2438},[200,77440,980],{"class":214},[200,77442,416],{"class":235},[200,77444,77445],{"class":202,"line":13466},[200,77446,13469],{"class":214},[200,77448,77449],{"class":202,"line":13472},[200,77450,4493],{"class":214},[200,77452,77453],{"class":202,"line":13477},[200,77454,339],{"class":214},[200,77456,77457,77459,77461,77463,77465],{"class":202,"line":13482},[200,77458,13485],{"class":235},[200,77460,227],{"class":214},[200,77462,13490],{"class":386},[200,77464,2104],{"class":214},[200,77466,280],{"class":214},[200,77468,77469,77471,77473],{"class":202,"line":13497},[200,77470,13500],{"class":210},[200,77472,509],{"class":214},[200,77474,13505],{"class":210},[200,77476,77477],{"class":202,"line":13508},[200,77478,4977],{"class":214},[200,77480,77481],{"class":202,"line":13513},[200,77482,4505],{"class":214},[200,77484,77485,77487],{"class":202,"line":13518},[200,77486,413],{"class":214},[200,77488,11758],{"class":210},[938,77490,77491],{},[35,77492,77493,13529,77495,13533,77497,13536],{},[200,77494,944],{},[156,77496,13532],{},[156,77498,12511],{},[35,77500,13539,77501],{},[1599,77502,215],{"repo":13542},[35,77504,13545],{},[130,77506,132,77507,132,77511],{},[134,77508,136,77509,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,77510],{"src":13552,"type":141},[143,77512,13555],{},[35,77514,13558,77515,1655,77517,13563,77519,13567],{},[156,77516,11019],{},[156,77518,11028],{},[156,77520,13566],{},[51,77522,13571],{"id":13570},[35,77524,13574,77525,215],{},[39,77526,13579],{"href":13577,"rel":77527},[77],[192,77529,77530],{"className":1354,"code":13582,"language":1357,"meta":13,"style":13},[156,77531,77532],{"__ignoreMap":13},[200,77533,77534,77536,77538,77540],{"class":202,"line":203},[200,77535,1365],{"class":1364},[200,77537,2177],{"class":245},[200,77539,1371],{"class":245},[200,77541,13595],{"class":245},[35,77543,13598,77544,2186],{},[156,77545,1477],{},[192,77547,77548],{"className":1380,"code":13603,"filename":1477,"language":1383,"meta":13,"style":13},[156,77549,77550,77568,77582,77586,77598,77606,77612,77616,77620,77624],{"__ignoreMap":13},[200,77551,77552,77554,77556,77558,77560,77562,77564,77566],{"class":202,"line":203},[200,77553,972],{"class":206},[200,77555,318],{"class":214},[200,77557,1505],{"class":210},[200,77559,980],{"class":214},[200,77561,983],{"class":206},[200,77563,242],{"class":214},[200,77565,1514],{"class":245},[200,77567,991],{"class":214},[200,77569,77570,77572,77574,77576,77578,77580],{"class":202,"line":14},[200,77571,972],{"class":206},[200,77573,13630],{"class":210},[200,77575,1489],{"class":206},[200,77577,242],{"class":214},[200,77579,13579],{"class":245},[200,77581,991],{"class":214},[200,77583,77584],{"class":202,"line":255},[200,77585,528],{"emptyLinePlaceholder":20},[200,77587,77588,77590,77592,77594,77596],{"class":202,"line":272},[200,77589,1390],{"class":206},[200,77591,1393],{"class":206},[200,77593,1505],{"class":223},[200,77595,227],{"class":210},[200,77597,230],{"class":214},[200,77599,77600,77602,77604],{"class":202,"line":283},[200,77601,1537],{"class":235},[200,77603,239],{"class":214},[200,77605,1410],{"class":210},[200,77607,77608,77610],{"class":202,"line":300},[200,77609,13667],{"class":223},[200,77611,466],{"class":210},[200,77613,77614],{"class":202,"line":310},[200,77615,528],{"emptyLinePlaceholder":20},[200,77617,77618],{"class":202,"line":336},[200,77619,13678],{"class":401},[200,77621,77622],{"class":202,"line":342},[200,77623,13683],{"class":210},[200,77625,77626,77628],{"class":202,"line":365},[200,77627,413],{"class":214},[200,77629,416],{"class":210},[35,77631,13692],{},[192,77633,77634],{"className":1354,"code":13695,"language":1357,"meta":13,"style":13},[156,77635,77636],{"__ignoreMap":13},[200,77637,77638,77640],{"class":202,"line":203},[200,77639,1365],{"class":1364},[200,77641,13704],{"class":245},[35,77643,13707,77644,215],{},[156,77645,13710],{},[35,77647,13713,77648,13717],{},[156,77649,13716],{},[35,77651,77652],{},[1707,77653],{"alt":13722,"src":13723},[51,77655,13726],{"id":2460},[35,77657,13729],{},[35,77659,13732],{},[35,77661,13735,77662,13741,77665,1655,77668,106,77671,13755],{},[39,77663,13740],{"href":13738,"rel":77664},[77],[39,77666,13746],{"href":13744,"rel":77667},[77],[39,77669,13751],{"href":13749,"rel":77670},[77],[39,77672,13579],{"href":13577,"rel":77673},[77],[35,77675,13758],{},[35,77677,13761,77678,13764],{},[156,77679,11180],{},[1158,77681],{},[35,77683,13769],{},[1170,77685,13772],{},{"title":13,"searchDepth":14,"depth":14,"links":77687},[77688,77689,77690,77691,77692],{"id":11372,"depth":14,"text":11373},{"id":11577,"depth":14,"text":11578},{"id":12245,"depth":14,"text":12246},{"id":13570,"depth":14,"text":13571},{"id":2460,"depth":14,"text":13726},{},{"title":11311,"description":13781},{"loc":13783,"images":77696},[77697],{"loc":13723},{"id":13792,"title":13793,"body":77699,"contentId":9320,"date":14113,"description":14114,"extension":18,"meta":77912,"navigation":20,"path":14116,"rawbody":14117,"robots":23,"schemaOrg":23,"seo":77913,"sitemap":77914,"stem":14127,"__hash__":14128},{"type":7,"value":77700,"toc":77907},[77701,77706,77708,77712,77714,77716,77718,77723,77728,77730,77734,77739,77744,77752,77756,77758,77760,77764,77769,77773,77775,77777,77779,77833,77835,77839,77841,77843,77845,77847,77865,77869,77871,77875,77879,77881,77883,77885,77887,77892,77894,77899,77901,77903],[35,77702,13798,77703,13804],{},[39,77704,13803],{"href":13801,"rel":77705},[77],[35,77707,13807],{},[35,77709,77710,13813],{},[1216,77711,13812],{},[35,77713,13816],{},[51,77715,13820],{"id":13819},[35,77717,13823],{},[35,77719,13826,77720,13831],{},[39,77721,2934],{"href":13829,"rel":77722},[77],[35,77724,13834,77725,13840],{},[39,77726,13839],{"href":13837,"rel":77727},[77],[35,77729,13843],{},[35,77731,77732],{},[1707,77733],{"alt":13848,"src":13849},[35,77735,13852,77736,13858],{},[39,77737,13857],{"href":13855,"rel":77738},[77],[35,77740,13861,77741,13867],{},[39,77742,13866],{"href":13864,"rel":77743},[77],[35,77745,13870,77746,1655,77749,13881],{},[39,77747,13875],{"href":13873,"rel":77748},[77],[39,77750,13880],{"href":13878,"rel":77751},[77],[35,77753,77754],{},[1707,77755],{"alt":13886,"src":13887},[51,77757,13891],{"id":13890},[35,77759,13894],{},[35,77761,77762],{},[1707,77763],{"alt":13899,"src":13900},[35,77765,13903,77766,13908],{},[39,77767,13907],{"href":6953,"rel":77768},[77],[35,77770,77771],{},[1707,77772],{"alt":13913,"src":13914},[35,77774,13917],{},[35,77776,13920],{},[35,77778,13923],{},[1593,77780,77781],{},[71,77782,77783,77785],{},[6862,77784,13930],{},[1593,77786,77787,77794,77801,77808,77812,77822,77826],{},[71,77788,77789,6865,77791],{},[6862,77790,13937],{},[39,77792,13942],{"href":13940,"rel":77793},[77],[71,77795,77796,6865,77798],{},[6862,77797,13947],{},[39,77799,13952],{"href":13950,"rel":77800},[77],[71,77802,77803,6865,77805],{},[6862,77804,13957],{},[39,77806,13962],{"href":13960,"rel":77807},[77],[71,77809,77810,13968],{},[6862,77811,13967],{},[71,77813,77814,6865,77816,1655,77819],{},[6862,77815,13973],{},[39,77817,13978],{"href":13976,"rel":77818},[77],[39,77820,13983],{"href":13981,"rel":77821},[77],[71,77823,77824,13988],{},[6862,77825,9137],{},[71,77827,77828,6865,77830],{},[6862,77829,13993],{},[39,77831,13998],{"href":13996,"rel":77832},[77],[35,77834,14001],{},[35,77836,77837],{},[1707,77838],{"alt":14006,"src":14007},[51,77840,14011],{"id":14010},[35,77842,14014],{},[35,77844,14017],{},[35,77846,14020],{},[1593,77848,77849,77858],{},[71,77850,77851,14027,77853,215],{},[6862,77852,13967],{},[39,77854,77856],{"href":14030,"rel":77855},[77],[156,77857,14034],{},[71,77859,77860,14039,77862,215],{},[6862,77861,9137],{},[39,77863,14044],{"href":14042,"rel":77864},[77],[35,77866,77867],{},[1707,77868],{"alt":14049,"src":14050},[35,77870,14053],{},[35,77872,14056,77873],{},[1216,77874,14059],{},[35,77876,14062,77877,14065],{},[156,77878,14034],{},[35,77880,14068],{},[35,77882,14071],{},[1158,77884],{},[35,77886,14076],{},[35,77888,14079,77889,14083],{},[39,77890,7894],{"href":7892,"rel":77891},[77],[35,77893,14086],{},[35,77895,14089,77896,14095],{},[39,77897,14094],{"href":14092,"rel":77898},[77],[35,77900,14098],{},[35,77902,14101],{},[35,77904,14104,77905],{},[1216,77906,14107],{},{"title":13,"searchDepth":14,"depth":14,"links":77908},[77909,77910,77911],{"id":13819,"depth":14,"text":13820},{"id":13890,"depth":14,"text":13891},{"id":14010,"depth":14,"text":14011},{},{"title":13793,"description":14114},{"loc":14116,"images":77915},[77916,77917,77918,77919,77920,77921],{"loc":13849},{"loc":13887},{"loc":13900},{"loc":13914},{"loc":14007},{"loc":14050},{"id":14130,"title":14131,"body":77923,"contentId":9340,"date":16366,"description":16367,"extension":18,"meta":79799,"navigation":20,"path":16369,"rawbody":16370,"robots":23,"schemaOrg":23,"seo":79800,"sitemap":79801,"stem":16375,"__hash__":16376},{"type":7,"value":77924,"toc":79793},[77925,77927,77929,77931,77933,77935,77937,77944,77948,77950,77965,77967,77969,77976,77978,77980,77985,77987,77989,77991,78104,78110,78114,78256,78258,78260,78268,78270,78272,78276,78285,78289,78947,78949,78954,78958,78966,78968,78970,78972,78974,79184,79188,79192,79194,79196,79300,79304,79306,79311,79313,79315,79319,79321,79323,79325,79327,79329,79333,79335,79601,79603,79605,79609,79613,79620,79694,79698,79702,79791],[35,77926,14136],{},[35,77928,14139],{},[35,77930,14142],{},[35,77932,14145],{},[35,77934,14148],{},[1158,77936],{},[35,77938,14153,77939,14159,77942],{},[39,77940,14158],{"href":14156,"rel":77941},[77],[1216,77943,14162],{},[35,77945,14165,77946,14169],{},[39,77947,14168],{"href":4715},[35,77949,14172],{},[68,77951,77952,77960],{},[71,77953,14177,77954,14181,77957],{},[39,77955,2934],{"href":13829,"rel":77956},[77],[39,77958,14186],{"href":14184,"rel":77959},[77],[71,77961,14189,77962,14195],{},[39,77963,14194],{"href":14192,"rel":77964},[77],[35,77966,14198],{},[35,77968,14201],{},[35,77970,14204,77971,14209,77974],{},[39,77972,9203],{"href":14207,"rel":77973},[77],[1216,77975,14212],{},[51,77977,14216],{"id":14215},[35,77979,14219],{},[35,77981,14222,77982,215],{},[39,77983,14227],{"href":14225,"rel":77984},[77],[35,77986,14230],{},[35,77988,14233],{},[35,77990,14236],{},[192,77992,77993],{"className":9755,"code":14239,"filename":14240,"language":1383,"meta":13,"style":13},[156,77994,77995,78009,78027,78041,78045,78059,78065,78069,78081,78096,78100],{"__ignoreMap":13},[200,77996,77997,77999,78001,78003,78005,78007],{"class":202,"line":203},[200,77998,972],{"class":206},[200,78000,14249],{"class":210},[200,78002,1489],{"class":206},[200,78004,242],{"class":214},[200,78006,14256],{"class":245},[200,78008,991],{"class":214},[200,78010,78011,78013,78015,78017,78019,78021,78023,78025],{"class":202,"line":14},[200,78012,972],{"class":206},[200,78014,318],{"class":214},[200,78016,14267],{"class":210},[200,78018,980],{"class":214},[200,78020,983],{"class":206},[200,78022,242],{"class":214},[200,78024,14276],{"class":245},[200,78026,991],{"class":214},[200,78028,78029,78031,78033,78035,78037,78039],{"class":202,"line":255},[200,78030,972],{"class":206},[200,78032,14285],{"class":210},[200,78034,1489],{"class":206},[200,78036,242],{"class":214},[200,78038,14292],{"class":245},[200,78040,991],{"class":214},[200,78042,78043],{"class":202,"line":272},[200,78044,528],{"emptyLinePlaceholder":20},[200,78046,78047,78049,78051,78053,78055,78057],{"class":202,"line":283},[200,78048,1390],{"class":206},[200,78050,14305],{"class":379},[200,78052,14308],{"class":210},[200,78054,449],{"class":214},[200,78056,14267],{"class":223},[200,78058,3130],{"class":210},[200,78060,78061,78063],{"class":202,"line":300},[200,78062,14319],{"class":210},[200,78064,252],{"class":214},[200,78066,78067],{"class":202,"line":310},[200,78068,14326],{"class":214},[200,78070,78071,78073,78075,78077,78079],{"class":202,"line":336},[200,78072,14331],{"class":214},[200,78074,14334],{"class":386},[200,78076,390],{"class":214},[200,78078,393],{"class":379},[200,78080,280],{"class":214},[200,78082,78084,78086,78088,78090,78092,78094],{"class":78083,"line":342},[202,9885],[200,78085,14346],{"class":210},[200,78087,215],{"class":214},[200,78089,9767],{"class":223},[200,78091,227],{"class":235},[200,78093,14355],{"class":210},[200,78095,5282],{"class":235},[200,78097,78098],{"class":202,"line":365},[200,78099,368],{"class":214},[200,78101,78102],{"class":202,"line":371},[200,78103,416],{"class":210},[35,78105,14368,78106,14371,78108,14375],{},[156,78107,422],{},[156,78109,14374],{},[35,78111,14378,78112,14382],{},[156,78113,14381],{},[192,78115,78116],{"className":9755,"code":14385,"filename":14240,"language":1383,"meta":13,"style":13},[156,78117,78118,78132,78150,78164,78168,78182,78188,78192,78204,78229,78243,78248,78252],{"__ignoreMap":13},[200,78119,78120,78122,78124,78126,78128,78130],{"class":202,"line":203},[200,78121,972],{"class":206},[200,78123,14249],{"class":210},[200,78125,1489],{"class":206},[200,78127,242],{"class":214},[200,78129,14256],{"class":245},[200,78131,991],{"class":214},[200,78133,78134,78136,78138,78140,78142,78144,78146,78148],{"class":202,"line":14},[200,78135,972],{"class":206},[200,78137,318],{"class":214},[200,78139,14267],{"class":210},[200,78141,980],{"class":214},[200,78143,983],{"class":206},[200,78145,242],{"class":214},[200,78147,14276],{"class":245},[200,78149,991],{"class":214},[200,78151,78152,78154,78156,78158,78160,78162],{"class":202,"line":255},[200,78153,972],{"class":206},[200,78155,14285],{"class":210},[200,78157,1489],{"class":206},[200,78159,242],{"class":214},[200,78161,14292],{"class":245},[200,78163,991],{"class":214},[200,78165,78166],{"class":202,"line":272},[200,78167,528],{"emptyLinePlaceholder":20},[200,78169,78170,78172,78174,78176,78178,78180],{"class":202,"line":283},[200,78171,1390],{"class":206},[200,78173,14305],{"class":379},[200,78175,14308],{"class":210},[200,78177,449],{"class":214},[200,78179,14267],{"class":223},[200,78181,3130],{"class":210},[200,78183,78184,78186],{"class":202,"line":300},[200,78185,14319],{"class":210},[200,78187,252],{"class":214},[200,78189,78190],{"class":202,"line":310},[200,78191,14326],{"class":214},[200,78193,78194,78196,78198,78200,78202],{"class":202,"line":336},[200,78195,14331],{"class":214},[200,78197,14334],{"class":386},[200,78199,390],{"class":214},[200,78201,393],{"class":379},[200,78203,280],{"class":214},[200,78205,78207,78209,78211,78213,78215,78217,78219,78221,78223,78225,78227],{"class":78206,"line":342},[202,9885],[200,78208,5267],{"class":206},[200,78210,1094],{"class":235},[200,78212,972],{"class":206},[200,78214,215],{"class":214},[200,78216,2030],{"class":210},[200,78218,215],{"class":214},[200,78220,14491],{"class":210},[200,78222,215],{"class":214},[200,78224,14496],{"class":210},[200,78226,5282],{"class":235},[200,78228,2767],{"class":214},[200,78230,78231,78233,78235,78237,78239,78241],{"class":202,"line":365},[200,78232,14505],{"class":210},[200,78234,215],{"class":214},[200,78236,9767],{"class":223},[200,78238,227],{"class":235},[200,78240,14355],{"class":210},[200,78242,416],{"class":235},[200,78244,78246],{"class":78245,"line":371},[202,9885],[200,78247,2111],{"class":214},[200,78249,78250],{"class":202,"line":398},[200,78251,368],{"class":214},[200,78253,78254],{"class":202,"line":405},[200,78255,416],{"class":210},[35,78257,14531],{},[35,78259,14534],{},[130,78261,132,78262,132,78266],{},[134,78263,136,78264,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,78265],{"src":14541,"type":141},[143,78267,14544],{},[35,78269,14547],{},[35,78271,14550],{},[35,78273,78274,14556],{},[6862,78275,14555],{},[35,78277,14559,78278,14565,78281,14569,78283,14572],{},[39,78279,14564],{"href":14562,"rel":78280},[77],[156,78282,14568],{},[156,78284,14568],{},[35,78286,14575,78287,14579],{},[156,78288,14578],{},[192,78290,78291],{"className":590,"code":14582,"filename":14583,"language":592,"meta":13,"style":13},[156,78292,78293,78311,78319,78357,78421,78439,78467,78495,78511,78539,78567,78575,78587,78623,78655,78691,78721,78727,78735,78773,78807,78841,78875,78903,78931,78939],{"__ignoreMap":13},[200,78294,78295,78297,78299,78301,78303,78305,78307,78309],{"class":202,"line":203},[200,78296,599],{"class":214},[200,78298,592],{"class":235},[200,78300,14594],{"class":379},[200,78302,449],{"class":214},[200,78304,610],{"class":214},[200,78306,14601],{"class":245},[200,78308,610],{"class":214},[200,78310,647],{"class":214},[200,78312,78313,78315,78317],{"class":202,"line":14},[200,78314,656],{"class":214},[200,78316,14578],{"class":235},[200,78318,647],{"class":214},[200,78320,78321,78323,78325,78327,78329,78331,78333,78335,78337,78339,78341,78343,78345,78347,78349,78351,78353,78355],{"class":202,"line":255},[200,78322,803],{"class":214},[200,78324,14620],{"class":235},[200,78326,14623],{"class":379},[200,78328,449],{"class":214},[200,78330,610],{"class":214},[200,78332,14630],{"class":245},[200,78334,610],{"class":214},[200,78336,14635],{"class":379},[200,78338,449],{"class":214},[200,78340,610],{"class":214},[200,78342,14642],{"class":245},[200,78344,610],{"class":214},[200,78346,14647],{"class":379},[200,78348,449],{"class":214},[200,78350,610],{"class":214},[200,78352,14654],{"class":245},[200,78354,610],{"class":214},[200,78356,647],{"class":214},[200,78358,78359,78361,78363,78365,78367,78369,78371,78373,78375,78377,78379,78381,78383,78385,78387,78389,78391,78393,78395,78397,78399,78401,78403,78405,78407,78409,78411,78413,78415,78417,78419],{"class":202,"line":272},[200,78360,803],{"class":214},[200,78362,14620],{"class":235},[200,78364,14623],{"class":379},[200,78366,449],{"class":214},[200,78368,610],{"class":214},[200,78370,14673],{"class":245},[200,78372,610],{"class":214},[200,78374,14678],{"class":379},[200,78376,449],{"class":214},[200,78378,610],{"class":214},[200,78380,1170],{"class":245},[200,78382,610],{"class":214},[200,78384,14689],{"class":379},[200,78386,449],{"class":214},[200,78388,14694],{"class":214},[200,78390,14697],{"class":210},[200,78392,14700],{"class":214},[200,78394,14703],{"class":245},[200,78396,14706],{"class":214},[200,78398,14635],{"class":379},[200,78400,449],{"class":214},[200,78402,610],{"class":214},[200,78404,14715],{"class":245},[200,78406,10130],{"class":210},[200,78408,14720],{"class":245},[200,78410,10130],{"class":210},[200,78412,14725],{"class":245},[200,78414,10130],{"class":210},[200,78416,14730],{"class":245},[200,78418,610],{"class":214},[200,78420,647],{"class":214},[200,78422,78423,78425,78427,78429,78431,78433,78435,78437],{"class":202,"line":283},[200,78424,803],{"class":214},[200,78426,2030],{"class":235},[200,78428,14743],{"class":379},[200,78430,449],{"class":214},[200,78432,610],{"class":214},[200,78434,14750],{"class":245},[200,78436,610],{"class":214},[200,78438,647],{"class":214},[200,78440,78441,78443,78445,78447,78449,78451,78453,78455,78457,78459,78461,78463,78465],{"class":202,"line":300},[200,78442,803],{"class":214},[200,78444,2030],{"class":235},[200,78446,700],{"class":379},[200,78448,449],{"class":214},[200,78450,610],{"class":214},[200,78452,14769],{"class":245},[200,78454,610],{"class":214},[200,78456,6053],{"class":379},[200,78458,449],{"class":214},[200,78460,610],{"class":214},[200,78462,7045],{"class":245},[200,78464,610],{"class":214},[200,78466,647],{"class":214},[200,78468,78469,78471,78473,78475,78477,78479,78481,78483,78485,78487,78489,78491,78493],{"class":202,"line":310},[200,78470,803],{"class":214},[200,78472,2030],{"class":235},[200,78474,700],{"class":379},[200,78476,449],{"class":214},[200,78478,610],{"class":214},[200,78480,14798],{"class":245},[200,78482,610],{"class":214},[200,78484,6053],{"class":379},[200,78486,449],{"class":214},[200,78488,610],{"class":214},[200,78490,14809],{"class":245},[200,78492,610],{"class":214},[200,78494,647],{"class":214},[200,78496,78497,78499,78501,78503,78505,78507,78509],{"class":202,"line":336},[200,78498,803],{"class":214},[200,78500,3581],{"class":235},[200,78502,674],{"class":214},[200,78504,14824],{"class":210},[200,78506,680],{"class":214},[200,78508,3581],{"class":235},[200,78510,647],{"class":214},[200,78512,78513,78515,78517,78519,78521,78523,78525,78527,78529,78531,78533,78535,78537],{"class":202,"line":342},[200,78514,803],{"class":214},[200,78516,2030],{"class":235},[200,78518,700],{"class":379},[200,78520,449],{"class":214},[200,78522,610],{"class":214},[200,78524,14845],{"class":245},[200,78526,610],{"class":214},[200,78528,6053],{"class":379},[200,78530,449],{"class":214},[200,78532,610],{"class":214},[200,78534,14856],{"class":245},[200,78536,610],{"class":214},[200,78538,647],{"class":214},[200,78540,78541,78543,78545,78547,78549,78551,78553,78555,78557,78559,78561,78563,78565],{"class":202,"line":365},[200,78542,803],{"class":214},[200,78544,2030],{"class":235},[200,78546,700],{"class":379},[200,78548,449],{"class":214},[200,78550,610],{"class":214},[200,78552,14875],{"class":245},[200,78554,610],{"class":214},[200,78556,6053],{"class":379},[200,78558,449],{"class":214},[200,78560,610],{"class":214},[200,78562,14856],{"class":245},[200,78564,610],{"class":214},[200,78566,647],{"class":214},[200,78568,78569,78571,78573],{"class":202,"line":371},[200,78570,803],{"class":214},[200,78572,14896],{"class":235},[200,78574,647],{"class":214},[200,78576,78577,78579,78581,78583,78585],{"class":202,"line":398},[200,78578,14903],{"class":214},[200,78580,227],{"class":210},[200,78582,14908],{"class":379},[200,78584,3729],{"class":214},[200,78586,280],{"class":214},[200,78588,78589,78591,78593,78595,78597,78599,78601,78603,78605,78607,78609,78611,78613,78615,78617,78619,78621],{"class":202,"line":405},[200,78590,12999],{"class":379},[200,78592,14919],{"class":210},[200,78594,509],{"class":214},[200,78596,14924],{"class":210},[200,78598,215],{"class":214},[200,78600,14929],{"class":210},[200,78602,14932],{"class":214},[200,78604,14924],{"class":210},[200,78606,215],{"class":214},[200,78608,14929],{"class":223},[200,78610,227],{"class":235},[200,78612,249],{"class":214},[200,78614,14945],{"class":245},[200,78616,249],{"class":214},[200,78618,2104],{"class":235},[200,78620,215],{"class":214},[200,78622,14954],{"class":210},[200,78624,78625,78627,78629,78631,78633,78635,78637,78639,78641,78643,78645,78647,78649,78651,78653],{"class":202,"line":410},[200,78626,12999],{"class":379},[200,78628,14961],{"class":210},[200,78630,509],{"class":214},[200,78632,14966],{"class":210},[200,78634,215],{"class":214},[200,78636,14971],{"class":223},[200,78638,227],{"class":235},[200,78640,249],{"class":214},[200,78642,14978],{"class":245},[200,78644,249],{"class":214},[200,78646,5282],{"class":235},[200,78648,14985],{"class":214},[200,78650,242],{"class":214},[200,78652,14990],{"class":245},[200,78654,991],{"class":214},[200,78656,78657,78659,78661,78663,78665,78667,78669,78671,78673,78675,78677,78679,78681,78683,78685,78687,78689],{"class":202,"line":860},[200,78658,14997],{"class":206},[200,78660,1094],{"class":235},[200,78662,15002],{"class":210},[200,78664,3298],{"class":214},[200,78666,242],{"class":214},[200,78668,14568],{"class":245},[200,78670,249],{"class":214},[200,78672,15013],{"class":214},[200,78674,1094],{"class":235},[200,78676,15018],{"class":210},[200,78678,14932],{"class":214},[200,78680,14961],{"class":210},[200,78682,13398],{"class":214},[200,78684,242],{"class":214},[200,78686,15029],{"class":245},[200,78688,249],{"class":214},[200,78690,584],{"class":235},[200,78692,78693,78695,78697,78699,78701,78703,78705,78707,78709,78711,78713,78715,78717,78719],{"class":202,"line":889},[200,78694,15038],{"class":210},[200,78696,215],{"class":214},[200,78698,15043],{"class":210},[200,78700,215],{"class":214},[200,78702,15048],{"class":210},[200,78704,215],{"class":214},[200,78706,15053],{"class":223},[200,78708,227],{"class":235},[200,78710,249],{"class":214},[200,78712,14568],{"class":245},[200,78714,249],{"class":214},[200,78716,549],{"class":214},[200,78718,2439],{"class":2438},[200,78720,416],{"class":235},[200,78722,78723,78725],{"class":202,"line":899},[200,78724,2647],{"class":214},[200,78726,11758],{"class":210},[200,78728,78729,78731,78733],{"class":202,"line":904},[200,78730,15078],{"class":214},[200,78732,14896],{"class":235},[200,78734,647],{"class":214},[200,78736,78737,78739,78741,78743,78745,78747,78749,78751,78753,78755,78757,78759,78761,78763,78765,78767,78769,78771],{"class":202,"line":930},[200,78738,803],{"class":214},[200,78740,14896],{"class":235},[200,78742,321],{"class":379},[200,78744,449],{"class":214},[200,78746,610],{"class":214},[200,78748,15097],{"class":245},[200,78750,610],{"class":214},[200,78752,14647],{"class":379},[200,78754,449],{"class":214},[200,78756,15106],{"class":214},[200,78758,10120],{"class":379},[200,78760,449],{"class":214},[200,78762,610],{"class":214},[200,78764,15115],{"class":245},[200,78766,610],{"class":214},[200,78768,10206],{"class":214},[200,78770,14896],{"class":235},[200,78772,647],{"class":214},[200,78774,78775,78777,78779,78781,78783,78785,78787,78789,78791,78793,78795,78797,78799,78801,78803,78805],{"class":202,"line":3655},[200,78776,803],{"class":214},[200,78778,14620],{"class":235},[200,78780,14623],{"class":379},[200,78782,449],{"class":214},[200,78784,610],{"class":214},[200,78786,14703],{"class":245},[200,78788,610],{"class":214},[200,78790,14647],{"class":379},[200,78792,449],{"class":214},[200,78794,15106],{"class":214},[200,78796,14635],{"class":379},[200,78798,449],{"class":214},[200,78800,610],{"class":214},[200,78802,15154],{"class":245},[200,78804,610],{"class":214},[200,78806,647],{"class":214},[200,78808,78809,78811,78813,78815,78817,78819,78821,78823,78825,78827,78829,78831,78833,78835,78837,78839],{"class":202,"line":3660},[200,78810,803],{"class":214},[200,78812,14620],{"class":235},[200,78814,14623],{"class":379},[200,78816,449],{"class":214},[200,78818,610],{"class":214},[200,78820,15173],{"class":245},[200,78822,610],{"class":214},[200,78824,14647],{"class":379},[200,78826,449],{"class":214},[200,78828,15106],{"class":214},[200,78830,14635],{"class":379},[200,78832,449],{"class":214},[200,78834,610],{"class":214},[200,78836,15190],{"class":245},[200,78838,610],{"class":214},[200,78840,647],{"class":214},[200,78842,78843,78845,78847,78849,78851,78853,78855,78857,78859,78861,78863,78865,78867,78869,78871,78873],{"class":202,"line":4035},[200,78844,803],{"class":214},[200,78846,14620],{"class":235},[200,78848,14623],{"class":379},[200,78850,449],{"class":214},[200,78852,610],{"class":214},[200,78854,15173],{"class":245},[200,78856,610],{"class":214},[200,78858,14647],{"class":379},[200,78860,449],{"class":214},[200,78862,15106],{"class":214},[200,78864,14635],{"class":379},[200,78866,449],{"class":214},[200,78868,610],{"class":214},[200,78870,15225],{"class":245},[200,78872,610],{"class":214},[200,78874,647],{"class":214},[200,78876,78877,78879,78881,78883,78885,78887,78889,78891,78893,78895,78897,78899,78901],{"class":202,"line":4490},[200,78878,803],{"class":214},[200,78880,2030],{"class":235},[200,78882,15238],{"class":379},[200,78884,449],{"class":214},[200,78886,610],{"class":214},[200,78888,15245],{"class":245},[200,78890,610],{"class":214},[200,78892,6053],{"class":379},[200,78894,449],{"class":214},[200,78896,610],{"class":214},[200,78898,15256],{"class":245},[200,78900,610],{"class":214},[200,78902,647],{"class":214},[200,78904,78905,78907,78909,78911,78913,78915,78917,78919,78921,78923,78925,78927,78929],{"class":202,"line":4496},[200,78906,803],{"class":214},[200,78908,2030],{"class":235},[200,78910,700],{"class":379},[200,78912,449],{"class":214},[200,78914,610],{"class":214},[200,78916,15275],{"class":245},[200,78918,610],{"class":214},[200,78920,6053],{"class":379},[200,78922,449],{"class":214},[200,78924,610],{"class":214},[200,78926,15256],{"class":245},[200,78928,610],{"class":214},[200,78930,647],{"class":214},[200,78932,78933,78935,78937],{"class":202,"line":4502},[200,78934,892],{"class":214},[200,78936,14578],{"class":235},[200,78938,647],{"class":214},[200,78940,78941,78943,78945],{"class":202,"line":4508},[200,78942,680],{"class":214},[200,78944,592],{"class":235},[200,78946,647],{"class":214},[35,78948,15308],{},[35,78950,15311,78951,15317],{},[39,78952,15316],{"href":15314,"rel":78953},[77],[35,78955,78956],{},[1707,78957],{"alt":15322,"src":15323},[35,78959,15326,78960,15332,78963,15338],{},[39,78961,15331],{"href":15329,"rel":78962},[77],[39,78964,15337],{"href":15335,"rel":78965},[77],[51,78967,15341],{"id":2814},[35,78969,15344],{},[35,78971,15347],{},[35,78973,15350],{},[192,78975,78976],{"className":9755,"code":15353,"filename":15354,"language":1383,"meta":13,"style":13},[156,78977,78978,78982,78996,79000,79004,79044,79048,79052,79059,79090,79094,79110,79140,79146,79162,79166],{"__ignoreMap":13},[200,78979,78980],{"class":202,"line":203},[200,78981,15361],{"class":401},[200,78983,78984,78986,78988,78990,78992,78994],{"class":202,"line":14},[200,78985,443],{"class":379},[200,78987,15368],{"class":210},[200,78989,449],{"class":214},[200,78991,13007],{"class":214},[200,78993,15375],{"class":223},[200,78995,15378],{"class":210},[200,78997,78998],{"class":202,"line":255},[200,78999,528],{"emptyLinePlaceholder":20},[200,79001,79002],{"class":202,"line":272},[200,79003,15387],{"class":401},[200,79005,79006,79008,79010,79012,79014,79016,79018,79020,79022,79024,79026,79028,79030,79032,79034,79036,79038,79040,79042],{"class":202,"line":283},[200,79007,15392],{"class":223},[200,79009,15395],{"class":210},[200,79011,215],{"class":214},[200,79013,15400],{"class":210},[200,79015,215],{"class":214},[200,79017,422],{"class":210},[200,79019,549],{"class":214},[200,79021,15409],{"class":210},[200,79023,215],{"class":214},[200,79025,15414],{"class":210},[200,79027,14985],{"class":214},[200,79029,13007],{"class":214},[200,79031,15421],{"class":223},[200,79033,599],{"class":214},[200,79035,328],{"class":1364},[200,79037,674],{"class":214},[200,79039,1549],{"class":210},[200,79041,549],{"class":214},[200,79043,15434],{"class":210},[200,79045,79046],{"class":202,"line":300},[200,79047,528],{"emptyLinePlaceholder":20},[200,79049,79050],{"class":202,"line":310},[200,79051,15443],{"class":401},[200,79053,79055,79057],{"class":79054,"line":336},[202,9885],[200,79056,15449],{"class":206},[200,79058,15452],{"class":210},[200,79060,79062,79064,79066,79068,79070,79072,79074,79076,79078,79080,79082,79084,79086,79088],{"class":79061,"line":342},[202,9885],[200,79063,6177],{"class":206},[200,79065,15460],{"class":223},[200,79067,15463],{"class":210},[200,79069,549],{"class":214},[200,79071,318],{"class":214},[200,79073,211],{"class":235},[200,79075,239],{"class":214},[200,79077,15474],{"class":210},[200,79079,215],{"class":214},[200,79081,15400],{"class":210},[200,79083,215],{"class":214},[200,79085,15483],{"class":210},[200,79087,413],{"class":214},[200,79089,5282],{"class":210},[200,79091,79092],{"class":202,"line":365},[200,79093,528],{"emptyLinePlaceholder":20},[200,79095,79096,79098,79100,79102,79104,79106,79108],{"class":202,"line":371},[200,79097,443],{"class":379},[200,79099,15498],{"class":210},[200,79101,449],{"class":214},[200,79103,15474],{"class":210},[200,79105,215],{"class":214},[200,79107,15507],{"class":223},[200,79109,466],{"class":210},[200,79111,79112,79114,79116,79118,79120,79122,79124,79126,79128,79130,79132,79134,79136,79138],{"class":202,"line":398},[200,79113,12337],{"class":379},[200,79115,15516],{"class":210},[200,79117,449],{"class":214},[200,79119,1094],{"class":210},[200,79121,207],{"class":206},[200,79123,15525],{"class":223},[200,79125,6536],{"class":214},[200,79127,15530],{"class":210},[200,79129,549],{"class":214},[200,79131,10290],{"class":210},[200,79133,549],{"class":214},[200,79135,15539],{"class":210},[200,79137,14985],{"class":214},[200,79139,15544],{"class":210},[200,79141,79142,79144],{"class":202,"line":405},[200,79143,15449],{"class":206},[200,79145,15551],{"class":210},[200,79147,79148,79150,79152,79154,79156,79158,79160],{"class":202,"line":410},[200,79149,15556],{"class":210},[200,79151,449],{"class":214},[200,79153,452],{"class":206},[200,79155,15563],{"class":210},[200,79157,215],{"class":214},[200,79159,15568],{"class":223},[200,79161,15571],{"class":210},[200,79163,79164],{"class":202,"line":860},[200,79165,528],{"emptyLinePlaceholder":20},[200,79167,79168,79170,79172,79174,79176,79178,79180,79182],{"class":202,"line":889},[200,79169,443],{"class":379},[200,79171,15582],{"class":210},[200,79173,449],{"class":214},[200,79175,452],{"class":206},[200,79177,15589],{"class":223},[200,79179,15592],{"class":210},[200,79181,549],{"class":214},[200,79183,15597],{"class":210},[35,79185,15600,79186,15604],{},[156,79187,15603],{},[35,79189,15607,79190,15611],{},[156,79191,15610],{},[35,79193,15614],{},[35,79195,15617],{},[192,79197,79198],{"className":9755,"code":15620,"filename":15621,"language":1383,"meta":13,"style":13},[156,79199,79200,79219,79223,79237,79241,79256,79266,79296],{"__ignoreMap":13},[200,79201,79203,79205,79207,79209,79211,79213,79215,79217],{"class":79202,"line":203},[202,9885],[200,79204,972],{"class":206},[200,79206,318],{"class":214},[200,79208,15633],{"class":210},[200,79210,980],{"class":214},[200,79212,983],{"class":206},[200,79214,242],{"class":214},[200,79216,15642],{"class":245},[200,79218,249],{"class":214},[200,79220,79221],{"class":202,"line":14},[200,79222,528],{"emptyLinePlaceholder":20},[200,79224,79225,79227,79229,79231,79233,79235],{"class":202,"line":255},[200,79226,443],{"class":379},[200,79228,15655],{"class":210},[200,79230,449],{"class":214},[200,79232,452],{"class":206},[200,79234,10279],{"class":223},[200,79236,15664],{"class":210},[200,79238,79239],{"class":202,"line":272},[200,79240,528],{"emptyLinePlaceholder":20},[200,79242,79244,79246,79248,79250,79252,79254],{"class":79243,"line":283},[202,9885],[200,79245,443],{"class":379},[200,79247,15498],{"class":210},[200,79249,449],{"class":214},[200,79251,452],{"class":206},[200,79253,15633],{"class":223},[200,79255,15684],{"class":210},[200,79257,79258,79260,79262,79264],{"class":202,"line":300},[200,79259,15689],{"class":210},[200,79261,215],{"class":214},[200,79263,14578],{"class":210},[200,79265,252],{"class":214},[200,79267,79268,79270,79272,79274,79276,79278,79280,79282,79284,79286,79288,79290,79292,79294],{"class":202,"line":310},[200,79269,15700],{"class":210},[200,79271,215],{"class":214},[200,79273,6148],{"class":223},[200,79275,227],{"class":210},[200,79277,3333],{"class":214},[200,79279,15711],{"class":245},[200,79281,3333],{"class":214},[200,79283,549],{"class":214},[200,79285,15718],{"class":210},[200,79287,215],{"class":214},[200,79289,15723],{"class":210},[200,79291,3836],{"class":214},[200,79293,3909],{"class":214},[200,79295,416],{"class":210},[200,79297,79298],{"class":202,"line":336},[200,79299,416],{"class":210},[35,79301,15736,79302,15740],{},[156,79303,15739],{},[35,79305,15743],{},[35,79307,79308],{},[39,79309,15750],{"href":15748,"rel":79310},[77],[35,79312,15753],{},[35,79314,15756],{},[35,79316,15759,79317],{},[1216,79318,15762],{},[35,79320,15765],{},[51,79322,15769],{"id":15768},[35,79324,15772],{},[35,79326,15775],{},[35,79328,15778],{},[35,79330,15781,79331,15785],{},[156,79332,15784],{},[35,79334,15788],{},[192,79336,79337],{"className":1380,"code":15791,"filename":15792,"language":1383,"meta":13,"style":13},[156,79338,79339,79353,79363,79381,79393,79405,79411,79415,79419,79459,79483,79487,79503,79525,79543,79547,79561,79581,79587,79591,79595],{"__ignoreMap":13},[200,79340,79341,79343,79345,79347,79349,79351],{"class":202,"line":203},[200,79342,443],{"class":379},[200,79344,15801],{"class":210},[200,79346,239],{"class":214},[200,79348,15806],{"class":1364},[200,79350,509],{"class":214},[200,79352,280],{"class":214},[200,79354,79355,79357,79359,79361],{"class":202,"line":14},[200,79356,15815],{"class":235},[200,79358,239],{"class":214},[200,79360,350],{"class":210},[200,79362,230],{"class":214},[200,79364,79365,79367,79369,79371,79373,79375,79377,79379],{"class":202,"line":255},[200,79366,15826],{"class":223},[200,79368,239],{"class":214},[200,79370,3729],{"class":214},[200,79372,393],{"class":379},[200,79374,15835],{"class":210},[200,79376,215],{"class":214},[200,79378,15840],{"class":210},[200,79380,252],{"class":214},[200,79382,79383,79385,79387,79389,79391],{"class":202,"line":272},[200,79384,15847],{"class":235},[200,79386,239],{"class":214},[200,79388,15852],{"class":214},[200,79390,4797],{"class":2060},[200,79392,252],{"class":214},[200,79394,79395,79397,79399,79401,79403],{"class":202,"line":283},[200,79396,15861],{"class":235},[200,79398,239],{"class":214},[200,79400,242],{"class":214},[200,79402,15868],{"class":245},[200,79404,991],{"class":214},[200,79406,79407,79409],{"class":202,"line":300},[200,79408,5095],{"class":214},[200,79410,4511],{"class":210},[200,79412,79413],{"class":202,"line":310},[200,79414,1129],{"class":214},[200,79416,79417],{"class":202,"line":336},[200,79418,528],{"emptyLinePlaceholder":20},[200,79420,79421,79423,79425,79427,79429,79431,79433,79435,79437,79439,79441,79443,79445,79447,79449,79451,79453,79455,79457],{"class":202,"line":342},[200,79422,15449],{"class":206},[200,79424,1094],{"class":210},[200,79426,972],{"class":206},[200,79428,215],{"class":214},[200,79430,2030],{"class":210},[200,79432,215],{"class":214},[200,79434,15901],{"class":210},[200,79436,15904],{"class":214},[200,79438,15907],{"class":210},[200,79440,215],{"class":214},[200,79442,15912],{"class":210},[200,79444,15904],{"class":214},[200,79446,15917],{"class":214},[200,79448,15920],{"class":210},[200,79450,215],{"class":214},[200,79452,15925],{"class":210},[200,79454,215],{"class":214},[200,79456,15930],{"class":210},[200,79458,230],{"class":214},[200,79460,79461,79463,79465,79467,79469,79471,79473,79475,79477,79479,79481],{"class":202,"line":365},[200,79462,3738],{"class":379},[200,79464,10194],{"class":210},[200,79466,509],{"class":214},[200,79468,211],{"class":210},[200,79470,215],{"class":214},[200,79472,15947],{"class":223},[200,79474,227],{"class":235},[200,79476,249],{"class":214},[200,79478,1170],{"class":245},[200,79480,249],{"class":214},[200,79482,416],{"class":235},[200,79484,79485],{"class":202,"line":371},[200,79486,528],{"emptyLinePlaceholder":20},[200,79488,79489,79491,79493,79495,79497,79499,79501],{"class":202,"line":398},[200,79490,15815],{"class":210},[200,79492,215],{"class":214},[200,79494,15970],{"class":210},[200,79496,509],{"class":214},[200,79498,15835],{"class":210},[200,79500,215],{"class":214},[200,79502,15979],{"class":210},[200,79504,79505,79507,79509,79511,79513,79515,79517,79519,79521,79523],{"class":202,"line":405},[200,79506,15815],{"class":210},[200,79508,215],{"class":214},[200,79510,15988],{"class":223},[200,79512,227],{"class":235},[200,79514,249],{"class":214},[200,79516,15995],{"class":245},[200,79518,249],{"class":214},[200,79520,549],{"class":214},[200,79522,3909],{"class":214},[200,79524,416],{"class":235},[200,79526,79527,79529,79531,79533,79535,79537,79539,79541],{"class":202,"line":410},[200,79528,16008],{"class":210},[200,79530,215],{"class":214},[200,79532,14578],{"class":210},[200,79534,215],{"class":214},[200,79536,16017],{"class":223},[200,79538,227],{"class":235},[200,79540,1170],{"class":210},[200,79542,416],{"class":235},[200,79544,79545],{"class":202,"line":860},[200,79546,528],{"emptyLinePlaceholder":20},[200,79548,79549,79551,79553,79555,79557,79559],{"class":202,"line":889},[200,79550,16032],{"class":210},[200,79552,215],{"class":214},[200,79554,14896],{"class":210},[200,79556,509],{"class":214},[200,79558,350],{"class":235},[200,79560,230],{"class":214},[200,79562,79563,79565,79567,79569,79571,79573,79575,79577,79579],{"class":202,"line":899},[200,79564,15826],{"class":235},[200,79566,239],{"class":214},[200,79568,242],{"class":214},[200,79570,16053],{"class":245},[200,79572,16056],{"class":210},[200,79574,16059],{"class":245},[200,79576,16056],{"class":210},[200,79578,6131],{"class":245},[200,79580,991],{"class":214},[200,79582,79583,79585],{"class":202,"line":904},[200,79584,5095],{"class":214},[200,79586,4511],{"class":235},[200,79588,79589],{"class":202,"line":930},[200,79590,1129],{"class":214},[200,79592,79593],{"class":202,"line":3655},[200,79594,528],{"emptyLinePlaceholder":20},[200,79596,79597,79599],{"class":202,"line":3660},[200,79598,16084],{"class":223},[200,79600,16087],{"class":210},[35,79602,16090],{},[35,79604,16093],{},[35,79606,16096,79607,16099],{},[156,79608,15449],{},[35,79610,16102,79611,16105],{},[156,79612,15920],{},[35,79614,16108,79615,16113,79618,2186],{},[39,79616,16112],{"href":1894,"rel":79617},[77],[156,79619,16116],{},[192,79621,79622],{"className":1380,"code":16119,"filename":16120,"language":1383,"meta":13,"style":13},[156,79623,79624,79636,79642,79652,79668,79674,79686,79690],{"__ignoreMap":13},[200,79625,79626,79628,79630,79632,79634],{"class":202,"line":203},[200,79627,1390],{"class":206},[200,79629,5002],{"class":379},[200,79631,16131],{"class":223},[200,79633,1549],{"class":214},[200,79635,280],{"class":214},[200,79637,79638,79640],{"class":202,"line":14},[200,79639,3758],{"class":206},[200,79641,280],{"class":214},[200,79643,79644,79646,79648,79650],{"class":202,"line":255},[200,79645,16146],{"class":235},[200,79647,239],{"class":214},[200,79649,2439],{"class":2438},[200,79651,252],{"class":214},[200,79653,79654,79656,79658,79660,79662,79664,79666],{"class":202,"line":272},[200,79655,16157],{"class":235},[200,79657,239],{"class":214},[200,79659,318],{"class":214},[200,79661,16164],{"class":235},[200,79663,239],{"class":214},[200,79665,16169],{"class":2438},[200,79667,333],{"class":214},[200,79669,79670,79672],{"class":202,"line":283},[200,79671,16176],{"class":210},[200,79673,252],{"class":214},[200,79675,79676,79678,79680,79682,79684],{"class":202,"line":300},[200,79677,16183],{"class":235},[200,79679,239],{"class":214},[200,79681,16188],{"class":210},[200,79683,215],{"class":214},[200,79685,16193],{"class":210},[200,79687,79688],{"class":202,"line":310},[200,79689,4505],{"class":214},[200,79691,79692],{"class":202,"line":336},[200,79693,1129],{"class":214},[35,79695,16204,79696,16208],{},[156,79697,16207],{},[35,79699,16211,79700,16215],{},[156,79701,16214],{},[2911,79703,16219,79704,16222,79706,79708,79712,79714,79716,79718,79720,79722,79736,79741,79743],{"href":16218},[156,79705,14381],{},[156,79707,14276],{},[35,79709,16227,79710],{},[1216,79711,16230],{},[51,79713,16234],{"id":16233},[35,79715,16237],{},[35,79717,16240],{},[35,79719,16243],{},[35,79721,16246],{},[68,79723,79724,79730],{},[71,79725,16251,79726,16255,79728,16259],{},[156,79727,16254],{},[156,79729,16258],{},[71,79731,16262,79732,1655,79734,215],{},[156,79733,16265],{},[156,79735,15097],{},[35,79737,16270,79738,16276],{},[39,79739,16275],{"href":16273,"rel":79740},[77],[35,79742,16279],{},[2911,79744,79745],{"title":16282,"href":16283},[16285,79746,79747,79749],{"title":16287,"href":16288},[35,79748,16291],{},[2911,79750,79751],{"title":16294,"href":16295},[16285,79752,79753],{"title":16298,"href":16299},[16285,79754,79755,79757,79759,79763,79765,79767,79771,79773,79775,79777,79779,79783,79785,79789],{"title":16302,"href":16303},[35,79756,16306],{},[35,79758,16309],{},[35,79760,79761],{},[1216,79762,16314],{},[51,79764,16318],{"id":16317},[35,79766,16321],{},[35,79768,16324,79769],{},[1216,79770,16327],{},[35,79772,16330],{},[35,79774,16333],{},[35,79776,16336],{},[1158,79778],{},[35,79780,16341,79781,215],{},[156,79782,16344],{},[35,79784,16347],{},[35,79786,79787,16353],{},[6862,79788,16352],{},[35,79790,16356],{},[1170,79792,16359],{},{"title":13,"searchDepth":14,"depth":14,"links":79794},[79795,79796,79797,79798],{"id":14215,"depth":14,"text":14216},{"id":2814,"depth":14,"text":15341},{"id":15768,"depth":14,"text":15769},{"id":16233,"depth":14,"text":16234},{},{"title":14131,"description":16367},{"loc":16369,"images":79802},[79803],{"loc":15323},{"id":16378,"title":16379,"body":79805,"contentId":9315,"date":16849,"description":16850,"extension":18,"meta":80127,"navigation":20,"path":16852,"rawbody":16853,"robots":23,"schemaOrg":23,"seo":80128,"sitemap":80129,"stem":16869,"__hash__":16870},{"type":7,"value":79806,"toc":80122},[79807,79809,79811,79819,79821,79825,79827,79832,79836,79840,79844,79846,79850,79858,79863,79867,79871,79876,79880,79882,79884,79886,80003,80005,80007,80009,80050,80052,80054,80058,80062,80066,80068,80073,80077,80082,80086,80088,80090,80094,80098,80100,80104,80109,80111,80113,80117],[35,79808,16384],{},[35,79810,16387],{},[35,79812,16390,79813,16394,79815,1655,79817,16400],{},[39,79814,16393],{"href":8986},[6862,79816,16397],{},[6862,79818,13967],{},[51,79820,16404],{"id":16403},[35,79822,79823],{},[1216,79824,16409],{},[35,79826,16412],{},[35,79828,16415,79829,16421],{},[39,79830,16420],{"href":16418,"rel":79831},[77],[35,79833,79834],{},[1707,79835],{"alt":16420,"src":16426},[35,79837,79838],{},[1707,79839],{"alt":16431,"src":16432},[35,79841,16435,79842],{},[1216,79843,16438],{},[35,79845,16441],{},[35,79847,79848],{},[1707,79849],{"alt":16446,"src":16447},[35,79851,16450,79852,16456,79855,16462],{},[39,79853,16455],{"href":16453,"rel":79854},[77],[39,79856,16461],{"href":16459,"rel":79857},[77],[35,79859,16465,79860,16471],{},[39,79861,16470],{"href":16468,"rel":79862},[77],[35,79864,79865],{},[1707,79866],{"alt":16476,"src":16477},[35,79868,16480,79869],{},[1216,79870,16483],{},[35,79872,16486,79873,16492],{},[39,79874,16491],{"href":16489,"rel":79875},[77],[35,79877,79878],{},[1707,79879],{"alt":16497,"src":16498},[35,79881,16501],{},[35,79883,16504],{},[35,79885,16507],{},[1593,79887,79888,79939],{},[71,79889,79890,79892],{},[6862,79891,16514],{},[1593,79893,79894,79901,79908,79918,79925,79932],{},[71,79895,79896,6865,79898],{},[6862,79897,16521],{},[39,79899,16526],{"href":16524,"rel":79900},[77],[71,79902,79903,6865,79905],{},[6862,79904,16531],{},[39,79906,16536],{"href":16534,"rel":79907},[77],[71,79909,79910,6865,79912,1655,79915],{},[6862,79911,16541],{},[39,79913,16546],{"href":16544,"rel":79914},[77],[39,79916,16551],{"href":16549,"rel":79917},[77],[71,79919,79920,6865,79922],{},[6862,79921,16556],{},[39,79923,16561],{"href":16559,"rel":79924},[77],[71,79926,79927,6865,79929],{},[6862,79928,16566],{},[39,79930,16571],{"href":16569,"rel":79931},[77],[71,79933,79934,6865,79936],{},[6862,79935,16576],{},[39,79937,16581],{"href":16579,"rel":79938},[77],[71,79940,79941,79943],{},[6862,79942,16586],{},[1593,79944,79945,79952,79962,79972,79979,79989,79996],{},[71,79946,79947,6865,79949,16599],{},[6862,79948,16593],{},[39,79950,16598],{"href":16596,"rel":79951},[77],[71,79953,79954,6865,79956,1655,79959],{},[6862,79955,16604],{},[39,79957,16608],{"href":9596,"rel":79958},[77],[39,79960,16613],{"href":16611,"rel":79961},[77],[71,79963,79964,6865,79966,1655,79969],{},[6862,79965,16618],{},[39,79967,16623],{"href":16621,"rel":79968},[77],[39,79970,16628],{"href":16626,"rel":79971},[77],[71,79973,79974,6865,79976],{},[6862,79975,16633],{},[39,79977,16638],{"href":16636,"rel":79978},[77],[71,79980,79981,6865,79983,1655,79986],{},[6862,79982,16643],{},[39,79984,16648],{"href":16646,"rel":79985},[77],[39,79987,16653],{"href":16651,"rel":79988},[77],[71,79990,79991,6865,79993],{},[6862,79992,16658],{},[39,79994,16663],{"href":16661,"rel":79995},[77],[71,79997,79998,6865,80000,16671],{},[6862,79999,13967],{},[39,80001,7045],{"href":7253,"rel":80002},[77],[35,80004,16674],{},[51,80006,16678],{"id":16677},[35,80008,16681],{},[1593,80010,80011,80032],{},[71,80012,80013,6865,80015,80018,16693,80020,16699,80023,16705,80026,16708,80028,80030],{},[6862,80014,16556],{},[39,80016,16561],{"href":16559,"rel":80017},[77],[7174,80019],{},[39,80021,16698],{"href":16696,"rel":80022},[77],[39,80024,16704],{"href":16702,"rel":80025},[77],[7174,80027],{},[7174,80029],{},[1707,80031],{"alt":16713,"src":16714},[71,80033,80034,6865,80036,1655,80039,80042,16727,80044,16730,80046,80048],{},[6862,80035,16604],{},[39,80037,16608],{"href":9596,"rel":80038},[77],[39,80040,16613],{"href":16611,"rel":80041},[77],[7174,80043],{},[39,80045,16393],{"href":8986},[7174,80047],{},[1707,80049],{"alt":16735,"src":16736},[35,80051,16739],{},[51,80053,7218],{"id":7217},[35,80055,16744,80056,16747],{},[6862,80057,9152],{},[35,80059,80060],{},[1707,80061],{"alt":16752,"src":16753},[35,80063,16756,80064],{},[1216,80065,16759],{},[35,80067,16762],{},[35,80069,16765,80070,16771],{},[39,80071,16770],{"href":16768,"rel":80072},[77],[35,80074,80075],{},[1707,80076],{"alt":16776,"src":16777},[35,80078,16780,80079,16785],{},[39,80080,16784],{"href":14030,"rel":80081},[77],[35,80083,80084],{},[1707,80085],{"alt":16790,"src":16791},[35,80087,16794],{},[1158,80089],{},[35,80091,16799,80092,15917],{},[6862,80093,16802],{},[35,80095,80096],{},[1707,80097],{"alt":16807,"src":16808},[1158,80099],{},[35,80101,80102],{},[1216,80103,16815],{},[35,80105,16818,80106,16822],{},[39,80107,7894],{"href":7892,"rel":80108},[77],[35,80110,16825],{},[35,80112,16828],{},[35,80114,80115],{},[1707,80116],{"alt":16833,"src":16834},[35,80118,16837,80119,16843],{},[39,80120,16842],{"href":16840,"rel":80121},[77],{"title":13,"searchDepth":14,"depth":14,"links":80123},[80124,80125,80126],{"id":16403,"depth":14,"text":16404},{"id":16677,"depth":14,"text":16678},{"id":7217,"depth":14,"text":7218},{},{"title":16379,"description":16850},{"loc":16852,"images":80130},[80131,80132,80133,80134,80135,80136,80137,80138,80139,80140,80141,80142],{"loc":16426},{"loc":16432},{"loc":16447},{"loc":16477},{"loc":16498},{"loc":16714},{"loc":16736},{"loc":16753},{"loc":16777},{"loc":16791},{"loc":16808},{"loc":16834},{"id":16872,"title":16873,"body":80144,"contentId":17221,"date":17222,"description":17223,"extension":18,"meta":80360,"navigation":20,"path":17225,"rawbody":17226,"robots":23,"schemaOrg":23,"seo":80361,"sitemap":80362,"stem":17229,"__hash__":17230},{"type":7,"value":80145,"toc":80352},[80146,80148,80162,80166,80168,80172,80174,80176,80180,80182,80189,80194,80199,80201,80203,80205,80207,80212,80214,80216,80218,80223,80228,80235,80240,80242,80244,80248,80255,80257,80261,80289,80291,80293,80298,80302,80310,80312,80314,80316,80324,80328,80330,80334,80336,80338,80340,80344,80346],[35,80147,16878],{},[35,80149,16881,80150,16887,80153,16893,80156,16899,80159,16905],{},[39,80151,16886],{"href":16884,"rel":80152},[77],[39,80154,16892],{"href":16890,"rel":80155},[77],[39,80157,16898],{"href":16896,"rel":80158},[77],[39,80160,16904],{"href":16902,"rel":80161},[77],[35,80163,16908,80164],{},[1216,80165,16911],{},[35,80167,16914],{},[35,80169,80170],{},[6862,80171,16919],{},[35,80173,16922],{},[35,80175,16925],{},[35,80177,16928,80178],{},[1216,80179,16931],{},[51,80181,16935],{"id":16934},[35,80183,16938,80184,16944,80187],{},[39,80185,16943],{"href":16941,"rel":80186},[77],[1216,80188,16947],{},[35,80190,16950,80191,16956],{},[39,80192,16955],{"href":16953,"rel":80193},[77],[35,80195,80196,16964],{},[39,80197,16963],{"href":16961,"rel":80198},[77],[35,80200,16967],{},[51,80202,16971],{"id":16970},[35,80204,16974],{},[35,80206,16977],{},[35,80208,16980,80209,215],{},[39,80210,16985],{"href":16983,"rel":80211},[77],[35,80213,16988],{},[51,80215,16992],{"id":16991},[35,80217,16995],{},[35,80219,16998,80220,17004],{},[39,80221,17003],{"href":17001,"rel":80222},[77],[35,80224,17007,80225,17013],{},[39,80226,17012],{"href":17010,"rel":80227},[77],[35,80229,17016,80230,17022,80233],{},[39,80231,17021],{"href":17019,"rel":80232},[77],[1216,80234,17025],{},[35,80236,17028,80237,17034],{},[39,80238,17033],{"href":17031,"rel":80239},[77],[35,80241,17037],{},[35,80243,17040],{},[35,80245,17043,80246],{},[6862,80247,17046],{},[35,80249,17049,80250,17055,80253],{},[39,80251,17054],{"href":17052,"rel":80252},[77],[1216,80254,17058],{},[51,80256,17062],{"id":17061},[35,80258,17065,80259],{},[1216,80260,17068],{},[68,80262,80263,80268,80270,80284],{},[71,80264,17073,80265,17079],{},[39,80266,17078],{"href":17076,"rel":80267},[77],[71,80269,17082],{},[71,80271,17085,80272,17091,80275,1655,80278,17102,80281,17108],{},[39,80273,17090],{"href":17088,"rel":80274},[77],[39,80276,17096],{"href":17094,"rel":80277},[77],[39,80279,17101],{"href":17099,"rel":80280},[77],[39,80282,17107],{"href":17105,"rel":80283},[77],[71,80285,17111,80286,17117],{},[39,80287,17116],{"href":17114,"rel":80288},[77],[35,80290,17120],{},[51,80292,17124],{"id":17123},[35,80294,17127,80295,17133],{},[39,80296,17132],{"href":17130,"rel":80297},[77],[35,80299,17136,80300],{},[1216,80301,17139],{},[35,80303,17142,80304,17147,80307,17153],{},[39,80305,16963],{"href":17145,"rel":80306},[77],[39,80308,17152],{"href":17150,"rel":80309},[77],[35,80311,17156],{},[35,80313,17159],{},[35,80315,17162],{},[130,80317,132,80318,132,80322],{},[134,80319,136,80320,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,80321],{"src":17169,"type":141},[143,80323,17172],{},[35,80325,80326],{},[1216,80327,17177],{},[51,80329,17181],{"id":17180},[35,80331,17184,80332],{},[1216,80333,17187],{},[35,80335,17190],{},[35,80337,17193],{},[35,80339,17196],{},[35,80341,80342],{},[6862,80343,54],{},[1158,80345],{},[35,80347,17205,80348],{},[1216,80349,17208,80350,17212],{},[39,80351,17211],{"href":9176},{"title":13,"searchDepth":14,"depth":14,"links":80353},[80354,80355,80356,80357,80358,80359],{"id":16934,"depth":14,"text":16935},{"id":16970,"depth":14,"text":16971},{"id":16991,"depth":14,"text":16992},{"id":17061,"depth":14,"text":17062},{"id":17123,"depth":14,"text":17124},{"id":17180,"depth":14,"text":17181},{},{"title":16873,"description":17223},{"loc":17225},{"id":17232,"title":17233,"body":80364,"contentId":9335,"date":17542,"description":17543,"extension":18,"meta":80565,"navigation":20,"path":17545,"rawbody":17546,"robots":23,"schemaOrg":23,"seo":80566,"sitemap":80567,"stem":17549,"__hash__":17550},{"type":7,"value":80365,"toc":80559},[80366,80371,80375,80377,80379,80381,80385,80387,80389,80394,80398,80400,80402,80404,80406,80408,80413,80415,80420,80425,80427,80434,80436,80438,80446,80448,80450,80452,80454,80456,80458,80460,80462,80464,80483,80485,80490,80492,80494,80496,80498,80500,80502,80507,80509,80514,80516,80518,80520,80522,80524,80536,80538,80549,80551,80555,80557],[35,80367,17238,80368,215],{},[39,80369,17243],{"href":17241,"rel":80370},[77],[35,80372,80373],{},[1216,80374,17248],{},[35,80376,17251],{},[35,80378,17254],{},[1158,80380],{},[35,80382,17259,80383,17263],{},[39,80384,17262],{"href":7947},[35,80386,17266],{},[1158,80388],{},[35,80390,17271,80391,2186],{},[39,80392,17276],{"href":17274,"rel":80393},[77],[938,80395,80396],{},[35,80397,17281],{},[35,80399,17284],{},[35,80401,17287],{},[35,80403,17290],{},[51,80405,17294],{"id":17293},[35,80407,17297],{},[35,80409,17300,80410,17306],{},[39,80411,17305],{"href":17303,"rel":80412},[77],[35,80414,17309],{},[35,80416,17312,80417,17318],{},[39,80418,17317],{"href":17315,"rel":80419},[77],[35,80421,17321,80422,17327],{},[39,80423,17326],{"href":17324,"rel":80424},[77],[35,80426,17330],{},[35,80428,17333,80429,17338,80432],{},[39,80430,13875],{"href":17336,"rel":80431},[77],[1216,80433,17341],{},[35,80435,17344],{},[51,80437,17348],{"id":17347},[35,80439,17351,80440,17357,80443,17362],{},[39,80441,17356],{"href":17354,"rel":80442},[77],[39,80444,17361],{"href":9128,"rel":80445},[77],[35,80447,17365],{},[35,80449,17368],{},[35,80451,17371],{},[35,80453,17374],{},[35,80455,17377],{},[35,80457,17380],{},[35,80459,17383],{},[35,80461,17386],{},[35,80463,17389],{},[35,80465,17392,80466,3092,80469,3092,80472,3092,80475,9502,80478,17418,80481],{},[39,80467,17397],{"href":17395,"rel":80468},[77],[39,80470,17402],{"href":17400,"rel":80471},[77],[39,80473,17407],{"href":17405,"rel":80474},[77],[39,80476,17412],{"href":17410,"rel":80477},[77],[39,80479,17417],{"href":17415,"rel":80480},[77],[1216,80482,17421],{},[35,80484,17424],{},[35,80486,17427,80487,17431],{},[39,80488,1896],{"href":1894,"rel":80489},[77],[51,80491,17435],{"id":17434},[35,80493,17438],{},[35,80495,17441],{},[35,80497,17444],{},[35,80499,17447],{},[35,80501,17450],{},[35,80503,17453,80504,17459],{},[39,80505,17458],{"href":17456,"rel":80506},[77],[51,80508,17463],{"id":17462},[35,80510,17466,80511,17472],{},[39,80512,17471],{"href":17469,"rel":80513},[77],[35,80515,17475],{},[35,80517,17478],{},[35,80519,17481],{},[35,80521,17484],{},[35,80523,17487],{},[1593,80525,80526,80531],{},[71,80527,17492,80528],{},[39,80529,17497],{"href":17495,"rel":80530},[77],[71,80532,17500,80533],{},[39,80534,17505],{"href":17503,"rel":80535},[77],[1158,80537],{},[35,80539,17510,80540,3092,80543,9502,80546,17522],{},[39,80541,13875],{"href":17336,"rel":80542},[77],[39,80544,13880],{"href":17516,"rel":80545},[77],[39,80547,9366],{"href":17520,"rel":80548},[77],[1158,80550],{},[35,80552,17527,80553,215],{},[39,80554,17530],{"href":7947},[1158,80556],{},[35,80558,17535],{},{"title":13,"searchDepth":14,"depth":14,"links":80560},[80561,80562,80563,80564],{"id":17293,"depth":14,"text":17294},{"id":17347,"depth":14,"text":17348},{"id":17434,"depth":14,"text":17435},{"id":17462,"depth":14,"text":17463},{},{"title":17233,"description":17543},{"loc":17545},{"id":17552,"title":17553,"body":80569,"contentId":9505,"date":17740,"description":17741,"extension":18,"meta":80697,"navigation":20,"path":17743,"rawbody":17744,"robots":23,"schemaOrg":23,"seo":80698,"sitemap":80699,"stem":17747,"__hash__":17748},{"type":7,"value":80570,"toc":80695},[80571,80575,80577,80579,80581,80583,80585,80587,80589,80591,80593,80595,80597,80599,80601,80605,80609,80613,80615,80617,80619,80621,80623,80625,80629,80631,80633,80635,80640,80642,80644,80646,80648,80650,80652,80654,80656,80658,80660,80662,80664,80666,80670,80672,80677,80679,80684,80686,80688],[35,80572,80573],{},[1216,80574,17560],{},[35,80576,17563],{},[1158,80578],{},[35,80580,17568],{},[35,80582,17571],{},[1158,80584],{},[35,80586,17576],{},[35,80588,17579],{},[35,80590,17582],{},[35,80592,17585],{},[35,80594,17588],{},[35,80596,17591],{},[35,80598,17594],{},[35,80600,17597],{},[35,80602,17600,80603,17604],{},[1216,80604,17603],{},[35,80606,80607],{},[1216,80608,17609],{},[35,80610,17612,80611,17616],{},[6862,80612,17615],{},[35,80614,17619],{},[35,80616,17622],{},[35,80618,17625],{},[35,80620,17628],{},[1158,80622],{},[35,80624,17633],{},[35,80626,17636,80627,215],{},[1216,80628,17639],{},[35,80630,17642],{},[35,80632,17645],{},[35,80634,17648],{},[35,80636,17651,80637,17657],{},[39,80638,17656],{"href":17654,"rel":80639},[77],[35,80641,17660],{},[35,80643,17663],{},[35,80645,17666],{},[1158,80647],{},[35,80649,17671],{},[35,80651,17674],{},[35,80653,17677],{},[35,80655,17680],{},[35,80657,17683],{},[35,80659,17686],{},[35,80661,17689],{},[35,80663,17692],{},[1158,80665],{},[938,80667,80668],{},[35,80669,17699],{},[35,80671,17702],{},[35,80673,80674],{},[39,80675,17709],{"href":17707,"rel":80676},[77],[35,80678,17712],{},[35,80680,80681,17720],{},[39,80682,17719],{"href":17717,"rel":80683},[77],[35,80685,17723],{},[35,80687,17726],{},[938,80689,80690],{},[35,80691,17731,80692,17737],{},[39,80693,17736],{"href":17734,"rel":80694},[77],{"title":13,"searchDepth":14,"depth":14,"links":80696},[],{},{"title":17553,"description":17741},{"loc":17743},{"id":17750,"title":17751,"body":80701,"contentId":9330,"date":17844,"description":17845,"extension":18,"meta":80767,"navigation":20,"path":17847,"rawbody":17848,"robots":23,"schemaOrg":23,"seo":80768,"sitemap":80769,"stem":17851,"__hash__":17852},{"type":7,"value":80702,"toc":80765},[80703,80707,80709,80711,80715,80717,80719,80721,80723,80725,80727,80729,80731,80733,80735,80737,80739,80741,80745,80747,80749,80751,80753,80755,80757,80759,80761,80763],[35,80704,80705],{},[1216,80706,17758],{},[35,80708,17761],{},[35,80710,17764],{},[35,80712,17767,80713],{},[1216,80714,17770],{},[35,80716,17773],{},[35,80718,17776],{},[35,80720,17779],{},[35,80722,17782],{},[35,80724,17785],{},[35,80726,17788],{},[35,80728,17791],{},[35,80730,17794],{},[35,80732,17797],{},[35,80734,17800],{},[35,80736,17803],{},[35,80738,17806],{},[35,80740,17809],{},[35,80742,80743],{},[1216,80744,17814],{},[1158,80746],{},[35,80748,17819],{},[35,80750,17822],{},[1158,80752],{},[35,80754,17827],{},[35,80756,17830],{},[35,80758,17833],{},[1158,80760],{},[35,80762,17838],{},[35,80764,17841],{},{"title":13,"searchDepth":14,"depth":14,"links":80766},[],{},{"title":17751,"description":17845},{"loc":17847},{"id":17854,"title":17530,"body":80771,"contentId":5669,"date":18185,"description":18186,"extension":18,"meta":80987,"navigation":20,"path":18188,"rawbody":18189,"robots":23,"schemaOrg":23,"seo":80988,"sitemap":80989,"stem":18192,"__hash__":18193},{"type":7,"value":80772,"toc":80982},[80773,80779,80783,80791,80805,80807,80809,80815,80820,80822,80826,80828,80830,80832,80840,80842,80844,80851,80853,80855,80857,80859,80861,80872,80876,80884,80888,80892,80903,80905,80907,80909,80913,80915,80919,80921,80923,80925,80927,80932,80934,80936,80940,80944,80946,80948,80958,80960,80962,80964,80966,80970,80972,80976,80978],[938,80774,80775],{},[35,80776,80777,17863],{},[200,80778,944],{},[938,80780,80781],{},[35,80782,17868],{},[35,80784,17871,80785,17877,80788,215],{},[39,80786,17876],{"href":17874,"rel":80787},[77],[39,80789,17882],{"href":17880,"rel":80790},[77],[35,80792,17885,80793,17891,80796,17897,80799,17891,80802,17906],{},[39,80794,17890],{"href":17888,"rel":80795},[77],[39,80797,17896],{"href":17894,"rel":80798},[77],[39,80800,17902],{"href":17900,"rel":80801},[77],[39,80803,17876],{"href":17874,"rel":80804},[77],[35,80806,17909],{},[1158,80808],{},[938,80810,80811],{},[35,80812,80813,17918],{},[200,80814,944],{},[35,80816,17921,80817,17927],{},[39,80818,17926],{"href":17924,"rel":80819},[77],[51,80821,17931],{"id":17930},[35,80823,17934,80824],{},[1216,80825,17937],{},[35,80827,17940],{},[35,80829,17943],{},[35,80831,17946],{},[1593,80833,80834,80836,80838],{},[71,80835,17951],{},[71,80837,17954],{},[71,80839,17957],{},[35,80841,17960],{},[35,80843,17963],{},[35,80845,17966,80846],{},[1216,80847,80848,17974],{},[39,80849,17973],{"href":17971,"rel":80850},[77],[35,80852,17977],{},[51,80854,17981],{"id":17980},[35,80856,17984],{},[35,80858,17987],{},[35,80860,17990],{},[35,80862,17993,80863,1655,80866,18004,80869,18010],{},[39,80864,17998],{"href":17996,"rel":80865},[77],[39,80867,18003],{"href":18001,"rel":80868},[77],[39,80870,18009],{"href":18007,"rel":80871},[77],[35,80873,18013,80874],{},[1216,80875,18016],{},[35,80877,18019,80878,18025,80881,18031],{},[39,80879,18024],{"href":18022,"rel":80880},[77],[39,80882,18030],{"href":18028,"rel":80883},[77],[35,80885,18034,80886],{},[1216,80887,18037],{},[35,80889,18040,80890],{},[1216,80891,18043],{},[35,80893,18046,80894,18052,80897,18058,80900,18064],{},[39,80895,18051],{"href":18049,"rel":80896},[77],[39,80898,18057],{"href":18055,"rel":80899},[77],[39,80901,18063],{"href":18061,"rel":80902},[77],[35,80904,18067],{},[35,80906,18070],{},[51,80908,18074],{"id":18073},[35,80910,80911,18080],{},[6862,80912,18079],{},[35,80914,18083],{},[35,80916,18086,80917,18090],{},[6862,80918,18089],{},[35,80920,18093],{},[35,80922,18096],{},[35,80924,18099],{},[35,80926,18102],{},[35,80928,18105,80929,18110],{},[39,80930,18109],{"href":17088,"rel":80931},[77],[35,80933,18113],{},[35,80935,18116],{},[35,80937,18119,80938],{},[1216,80939,18122],{},[35,80941,18125,80942],{},[1216,80943,18128],{},[35,80945,18131],{},[35,80947,18134],{},[1593,80949,80950,80952,80954,80956],{},[71,80951,18139],{},[71,80953,18142],{},[71,80955,18145],{},[71,80957,18148],{},[35,80959,18151],{},[35,80961,18154],{},[35,80963,18157],{},[35,80965,18160],{},[35,80967,80968],{},[6862,80969,18165],{},[35,80971,18168],{},[35,80973,80974],{},[1216,80975,18016],{},[1158,80977],{},[35,80979,80980],{},[1216,80981,18179],{},{"title":13,"searchDepth":14,"depth":14,"links":80983},[80984,80985,80986],{"id":17930,"depth":14,"text":17931},{"id":17980,"depth":14,"text":17981},{"id":18073,"depth":14,"text":18074},{},{"title":17530,"description":18186},{"loc":18188},{"id":18195,"title":18196,"body":80991,"contentId":9310,"date":18593,"description":18594,"extension":18,"meta":81277,"navigation":20,"path":18596,"rawbody":18597,"robots":23,"schemaOrg":23,"seo":81278,"sitemap":81279,"stem":18605,"__hash__":18606},{"type":7,"value":80992,"toc":81271},[80993,80998,81004,81006,81010,81014,81016,81018,81020,81171],[35,80994,18201,80995,18207],{},[39,80996,18206],{"href":18204,"rel":80997},[77],[35,80999,18210,81000,13741,81002,18215],{},[39,81001,7154],{"href":8986},[39,81003,7497],{"href":7500},[35,81005,18218],{},[35,81007,81008],{},[1216,81009,18223],{},[35,81011,81012],{},[1707,81013],{"alt":18228,"src":18229},[51,81015,18206],{"id":18232},[35,81017,18235],{},[35,81019,18238],{},[1593,81021,81022,81065,81134],{},[71,81023,81024,2186,81026],{},[6862,81025,18245],{},[1593,81027,81028,81038,81042,81052],{},[71,81029,81030,6865,81032,1655,81035],{},[6862,81031,18252],{},[39,81033,18257],{"href":18255,"rel":81034},[77],[39,81036,18262],{"href":18260,"rel":81037},[77],[71,81039,81040,13968],{},[6862,81041,9137],{},[71,81043,81044,6865,81046,1655,81049],{},[6862,81045,18271],{},[39,81047,18276],{"href":18274,"rel":81048},[77],[39,81050,18281],{"href":18279,"rel":81051},[77],[71,81053,81054,6865,81056,3092,81059,1655,81062],{},[6862,81055,18286],{},[39,81057,18291],{"href":18289,"rel":81058},[77],[39,81060,18296],{"href":18294,"rel":81061},[77],[39,81063,18301],{"href":18299,"rel":81064},[77],[71,81066,81067,2186,81069],{},[6862,81068,18306],{},[1593,81070,81071,81078,81085,81095,81099,81106,81113,81120,81127],{},[71,81072,81073,6865,81075],{},[6862,81074,18313],{},[39,81076,6955],{"href":6953,"rel":81077},[77],[71,81079,81080,6865,81082],{},[6862,81081,18321],{},[39,81083,18326],{"href":18324,"rel":81084},[77],[71,81086,81087,6865,81089,1655,81092],{},[6862,81088,18331],{},[39,81090,18276],{"href":18274,"rel":81091},[77],[39,81093,18339],{"href":18337,"rel":81094},[77],[71,81096,81097,13988],{},[6862,81098,13967],{},[71,81100,81101,6865,81103],{},[6862,81102,18348],{},[39,81104,18353],{"href":18351,"rel":81105},[77],[71,81107,81108,6865,81110],{},[6862,81109,18358],{},[39,81111,18363],{"href":18361,"rel":81112},[77],[71,81114,81115,6865,81117],{},[6862,81116,18368],{},[39,81118,18373],{"href":18371,"rel":81119},[77],[71,81121,81122,6865,81124],{},[6862,81123,18378],{},[39,81125,18383],{"href":18381,"rel":81126},[77],[71,81128,81129,6865,81131],{},[6862,81130,18388],{},[39,81132,18393],{"href":18391,"rel":81133},[77],[71,81135,81136,2186,81138],{},[6862,81137,18398],{},[1593,81139,81140,81147,81154,81161],{},[71,81141,81142,6865,81144],{},[6862,81143,18405],{},[39,81145,18410],{"href":18408,"rel":81146},[77],[71,81148,81149,6865,81151],{},[6862,81150,18415],{},[39,81152,18420],{"href":18418,"rel":81153},[77],[71,81155,81156,6865,81158],{},[6862,81157,18425],{},[39,81159,18430],{"href":18428,"rel":81160},[77],[71,81162,81163,6865,81165,1655,81168],{},[6862,81164,18435],{},[39,81166,18440],{"href":18438,"rel":81167},[77],[39,81169,18445],{"href":18443,"rel":81170},[77],[7151,81172,81173,81175,81177,81179,81181,81218,81220,81222,81224,81234,81240,81244,81248,81250,81252,81256,81258,81260,81262,81267],{"icon":7153,"text":9020,"to":18448,"target":7156},[35,81174,18451],{},[51,81176,18455],{"id":18454},[35,81178,18458],{},[35,81180,18461],{},[1593,81182,81183,81202],{},[71,81184,81185,6865,81187,81190,18473,81192,18479,81195,18485,81198,81200],{},[6862,81186,18313],{},[39,81188,6955],{"href":6953,"rel":81189},[77],[7174,81191],{},[39,81193,18478],{"href":18476,"rel":81194},[77],[39,81196,18484],{"href":18482,"rel":81197},[77],[7174,81199],{},[1707,81201],{"alt":18490,"src":18491},[71,81203,81204,6865,81206,1655,81209,81212,18504,81214,81216],{},[6862,81205,18331],{},[39,81207,18276],{"href":18274,"rel":81208},[77],[39,81210,18339],{"href":18337,"rel":81211},[77],[7174,81213],{},[7174,81215],{},[1707,81217],{"alt":18509,"src":18510},[35,81219,18513],{},[51,81221,18517],{"id":18516},[35,81223,18520],{},[68,81225,81226,81230],{},[71,81227,81228],{},[6862,81229,9137],{},[71,81231,81232],{},[6862,81233,13967],{},[35,81235,18533,81236,1655,81238,18540],{},[39,81237,18536],{"href":8986},[39,81239,18539],{"href":7500},[35,81241,18543,81242],{},[1216,81243,18546],{},[35,81245,81246],{},[1707,81247],{"alt":18551,"src":18552},[51,81249,18556],{"id":18555},[35,81251,18559],{},[938,81253,81254],{},[35,81255,18564],{},[35,81257,18567],{},[35,81259,18570],{},[35,81261,18573],{},[35,81263,18576,81264,215],{},[39,81265,18581],{"href":18579,"rel":81266},[77],[35,81268,81269],{},[1216,81270,18586],{},{"title":13,"searchDepth":14,"depth":14,"links":81272},[81273,81274,81275,81276],{"id":18232,"depth":14,"text":18206},{"id":18454,"depth":14,"text":18455},{"id":18516,"depth":14,"text":18517},{"id":18555,"depth":14,"text":18556},{},{"title":18196,"description":18594},{"loc":18596,"images":81280},[81281,81282,81283,81284],{"loc":18229},{"loc":18491},{"loc":18510},{"loc":18552},{"id":18608,"title":18609,"body":81286,"contentId":9280,"date":19381,"description":19382,"extension":18,"meta":81941,"navigation":20,"path":19384,"rawbody":19385,"robots":23,"schemaOrg":23,"seo":81942,"sitemap":81943,"stem":19388,"__hash__":19389},{"type":7,"value":81287,"toc":81935},[81288,81295,81306,81310,81312,81314,81322,81324,81326,81330,81332,81336,81540,81544,81556,81560,81628,81634,81640,81642,81644,81654,81658,81660,81669,81673,81675,81677,81899,81907,81915,81917,81919,81925,81929,81933],[35,81289,18614,81290,18622],{},[39,81291,81293],{"href":18617,"rel":81292},[77],[6862,81294,18621],{},[35,81296,81297,18627,81299,18633,81304,18637],{},[6862,81298,18621],{},[39,81300,81302],{"href":16702,"rel":81301},[77],[6862,81303,16704],{},[6862,81305,18636],{},[35,81307,18640,81308,18643],{},[6862,81309,18621],{},[51,81311,18647],{"id":18646},[35,81313,18650],{},[130,81315,132,81316,132,81320],{},[134,81317,136,81318,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,81319],{"src":18657,"type":141},[143,81321,18660],{},[35,81323,18663],{},[35,81325,18666],{},[35,81327,18669,81328],{},[1599,81329,215],{"repo":18672},[35,81331,18675],{},[35,81333,18678,81334,18681],{},[6862,81335,18621],{},[192,81337,81338],{"className":1380,"code":18684,"language":1383,"meta":13,"style":13},[156,81339,81340,81356,81386,81410,81424,81436,81444,81456,81486,81490,81502,81518,81530,81534],{"__ignoreMap":13},[200,81341,81342,81344,81346,81348,81350,81352,81354],{"class":202,"line":203},[200,81343,443],{"class":379},[200,81345,18693],{"class":210},[200,81347,449],{"class":214},[200,81349,18698],{"class":223},[200,81351,227],{"class":210},[200,81353,18703],{"class":2438},[200,81355,416],{"class":210},[200,81357,81358,81360,81362,81364,81366,81368,81370,81372,81374,81376,81378,81380,81382,81384],{"class":202,"line":14},[200,81359,443],{"class":379},[200,81361,318],{"class":214},[200,81363,18714],{"class":235},[200,81365,239],{"class":214},[200,81367,18719],{"class":210},[200,81369,549],{"class":214},[200,81371,18724],{"class":235},[200,81373,239],{"class":214},[200,81375,18729],{"class":210},[200,81377,413],{"class":214},[200,81379,509],{"class":214},[200,81381,18736],{"class":223},[200,81383,227],{"class":210},[200,81385,230],{"class":214},[200,81387,81388,81390,81392,81394,81396,81398,81400,81402,81404,81406,81408],{"class":202,"line":255},[200,81389,18745],{"class":223},[200,81391,239],{"class":214},[200,81393,3729],{"class":214},[200,81395,393],{"class":379},[200,81397,18754],{"class":223},[200,81399,227],{"class":210},[200,81401,249],{"class":214},[200,81403,18761],{"class":245},[200,81405,249],{"class":214},[200,81407,549],{"class":214},[200,81409,280],{"class":214},[200,81411,81412,81414,81416,81418,81420,81422],{"class":202,"line":272},[200,81413,18772],{"class":235},[200,81415,239],{"class":214},[200,81417,242],{"class":214},[200,81419,18779],{"class":245},[200,81421,249],{"class":214},[200,81423,252],{"class":214},[200,81425,81426,81428,81430,81432,81434],{"class":202,"line":283},[200,81427,18788],{"class":235},[200,81429,239],{"class":214},[200,81431,18793],{"class":210},[200,81433,215],{"class":214},[200,81435,15979],{"class":210},[200,81437,81438,81440,81442],{"class":202,"line":300},[200,81439,5095],{"class":214},[200,81441,2104],{"class":210},[200,81443,252],{"class":214},[200,81445,81446,81448,81450,81452,81454],{"class":202,"line":310},[200,81447,18810],{"class":223},[200,81449,239],{"class":214},[200,81451,3729],{"class":214},[200,81453,393],{"class":379},[200,81455,280],{"class":214},[200,81457,81458,81460,81462,81464,81466,81468,81470,81472,81474,81476,81478,81480,81482,81484],{"class":202,"line":336},[200,81459,18823],{"class":210},[200,81461,215],{"class":214},[200,81463,18828],{"class":223},[200,81465,227],{"class":235},[200,81467,2767],{"class":214},[200,81469,18835],{"class":235},[200,81471,239],{"class":214},[200,81473,350],{"class":235},[200,81475,249],{"class":214},[200,81477,18844],{"class":245},[200,81479,249],{"class":214},[200,81481,18849],{"class":235},[200,81483,413],{"class":214},[200,81485,416],{"class":235},[200,81487,81488],{"class":202,"line":342},[200,81489,368],{"class":214},[200,81491,81492,81494,81496,81498,81500],{"class":202,"line":365},[200,81493,18862],{"class":223},[200,81495,239],{"class":214},[200,81497,3729],{"class":214},[200,81499,393],{"class":379},[200,81501,280],{"class":214},[200,81503,81504,81506,81508,81510,81512,81514,81516],{"class":202,"line":371},[200,81505,18875],{"class":210},[200,81507,215],{"class":214},[200,81509,15840],{"class":210},[200,81511,215],{"class":214},[200,81513,697],{"class":210},[200,81515,509],{"class":214},[200,81517,18888],{"class":214},[200,81519,81520,81522,81524,81526,81528],{"class":202,"line":398},[200,81521,18893],{"class":210},[200,81523,215],{"class":214},[200,81525,15840],{"class":210},[200,81527,509],{"class":214},[200,81529,18902],{"class":2438},[200,81531,81532],{"class":202,"line":405},[200,81533,4505],{"class":214},[200,81535,81536,81538],{"class":202,"line":410},[200,81537,413],{"class":214},[200,81539,416],{"class":210},[35,81541,10674,81542,18918],{},[156,81543,18917],{},[35,81545,18921,81546,18925,81548,18929,81550,18933,81552,18937,81554,18940],{},[156,81547,18924],{},[156,81549,18928],{},[156,81551,18932],{},[156,81553,18936],{},[156,81555,18844],{},[35,81557,18943,81558,18946],{},[6862,81559,18621],{},[192,81561,81562],{"className":1380,"code":18949,"language":1383,"meta":13,"style":13},[156,81563,81564,81582,81600,81622],{"__ignoreMap":13},[200,81565,81566,81568,81570,81572,81574,81576,81578,81580],{"class":202,"line":203},[200,81567,443],{"class":379},[200,81569,318],{"class":214},[200,81571,18960],{"class":210},[200,81573,413],{"class":214},[200,81575,509],{"class":214},[200,81577,18967],{"class":223},[200,81579,227],{"class":210},[200,81581,230],{"class":214},[200,81583,81584,81586,81588,81590,81592,81594,81596,81598],{"class":202,"line":14},[200,81585,18976],{"class":235},[200,81587,239],{"class":214},[200,81589,350],{"class":210},[200,81591,249],{"class":214},[200,81593,18844],{"class":245},[200,81595,249],{"class":214},[200,81597,360],{"class":210},[200,81599,252],{"class":214},[200,81601,81602,81604,81606,81608,81610,81612,81614,81616,81618,81620],{"class":202,"line":255},[200,81603,6719],{"class":223},[200,81605,239],{"class":214},[200,81607,3729],{"class":214},[200,81609,393],{"class":379},[200,81611,18754],{"class":223},[200,81613,227],{"class":210},[200,81615,249],{"class":214},[200,81617,18761],{"class":245},[200,81619,249],{"class":214},[200,81621,416],{"class":210},[200,81623,81624,81626],{"class":202,"line":272},[200,81625,413],{"class":214},[200,81627,416],{"class":210},[35,81629,19021,81630,19024,81632,19027],{},[156,81631,18936],{},[156,81633,18932],{},[35,81635,19030,81636,19034,81638,19037],{},[156,81637,19033],{},[156,81639,18936],{},[51,81641,19041],{"id":19040},[35,81643,19044],{},[1593,81645,81646,81650],{},[71,81647,81648,19052],{},[6862,81649,19051],{},[71,81651,81652,19058],{},[6862,81653,19057],{},[35,81655,19061,81656,19064],{},[6862,81657,18621],{},[51,81659,19068],{"id":19067},[35,81661,19071,81662,19082],{},[39,81663,19076,81665,19079,81667,610],{"href":19074,"rel":81664},[77],[156,81666,207],{},[156,81668,207],{},[938,81670,81671],{},[35,81672,19087],{},[35,81674,19090],{},[35,81676,19093],{},[192,81678,81679],{"className":1380,"code":19096,"language":1383,"meta":13,"style":13},[156,81680,81681,81697,81727,81751,81765,81777,81785,81807,81839,81843,81857,81873,81885,81889,81893],{"__ignoreMap":13},[200,81682,81683,81685,81687,81689,81691,81693,81695],{"class":202,"line":203},[200,81684,443],{"class":379},[200,81686,18693],{"class":210},[200,81688,449],{"class":214},[200,81690,18698],{"class":223},[200,81692,227],{"class":210},[200,81694,18703],{"class":2438},[200,81696,416],{"class":210},[200,81698,81699,81701,81703,81705,81707,81709,81711,81713,81715,81717,81719,81721,81723,81725],{"class":202,"line":14},[200,81700,443],{"class":379},[200,81702,318],{"class":214},[200,81704,18714],{"class":235},[200,81706,239],{"class":214},[200,81708,18719],{"class":210},[200,81710,549],{"class":214},[200,81712,18724],{"class":235},[200,81714,239],{"class":214},[200,81716,18729],{"class":210},[200,81718,413],{"class":214},[200,81720,509],{"class":214},[200,81722,18736],{"class":223},[200,81724,227],{"class":210},[200,81726,230],{"class":214},[200,81728,81729,81731,81733,81735,81737,81739,81741,81743,81745,81747,81749],{"class":202,"line":255},[200,81730,18745],{"class":223},[200,81732,239],{"class":214},[200,81734,3729],{"class":214},[200,81736,393],{"class":379},[200,81738,18754],{"class":223},[200,81740,227],{"class":210},[200,81742,249],{"class":214},[200,81744,18761],{"class":245},[200,81746,249],{"class":214},[200,81748,549],{"class":214},[200,81750,280],{"class":214},[200,81752,81753,81755,81757,81759,81761,81763],{"class":202,"line":272},[200,81754,18772],{"class":235},[200,81756,239],{"class":214},[200,81758,242],{"class":214},[200,81760,18779],{"class":245},[200,81762,249],{"class":214},[200,81764,252],{"class":214},[200,81766,81767,81769,81771,81773,81775],{"class":202,"line":283},[200,81768,18788],{"class":235},[200,81770,239],{"class":214},[200,81772,18793],{"class":210},[200,81774,215],{"class":214},[200,81776,15979],{"class":210},[200,81778,81779,81781,81783],{"class":202,"line":300},[200,81780,5095],{"class":214},[200,81782,2104],{"class":210},[200,81784,252],{"class":214},[200,81786,81787,81789,81791,81793,81795,81797,81799,81801,81803,81805],{"class":202,"line":310},[200,81788,18810],{"class":223},[200,81790,239],{"class":214},[200,81792,380],{"class":379},[200,81794,1094],{"class":214},[200,81796,19215],{"class":386},[200,81798,549],{"class":214},[200,81800,19220],{"class":386},[200,81802,2104],{"class":214},[200,81804,393],{"class":379},[200,81806,280],{"class":214},[200,81808,81809,81811,81813,81815,81817,81819,81821,81823,81825,81827,81829,81831,81833,81835,81837],{"class":202,"line":336},[200,81810,19231],{"class":206},[200,81812,19234],{"class":210},[200,81814,215],{"class":214},[200,81816,18828],{"class":223},[200,81818,227],{"class":235},[200,81820,2767],{"class":214},[200,81822,18835],{"class":235},[200,81824,239],{"class":214},[200,81826,350],{"class":235},[200,81828,249],{"class":214},[200,81830,18844],{"class":245},[200,81832,249],{"class":214},[200,81834,18849],{"class":235},[200,81836,413],{"class":214},[200,81838,416],{"class":235},[200,81840,81841],{"class":202,"line":342},[200,81842,528],{"emptyLinePlaceholder":20},[200,81844,81845,81847,81849,81851,81853,81855],{"class":202,"line":365},[200,81846,5267],{"class":206},[200,81848,1094],{"class":235},[200,81850,11852],{"class":214},[200,81852,19275],{"class":210},[200,81854,5282],{"class":235},[200,81856,230],{"class":214},[200,81858,81859,81861,81863,81865,81867,81869,81871],{"class":202,"line":371},[200,81860,19284],{"class":210},[200,81862,215],{"class":214},[200,81864,15840],{"class":210},[200,81866,215],{"class":214},[200,81868,697],{"class":210},[200,81870,509],{"class":214},[200,81872,18888],{"class":214},[200,81874,81875,81877,81879,81881,81883],{"class":202,"line":398},[200,81876,19301],{"class":210},[200,81878,215],{"class":214},[200,81880,15840],{"class":210},[200,81882,509],{"class":214},[200,81884,18902],{"class":2438},[200,81886,81887],{"class":202,"line":405},[200,81888,4977],{"class":214},[200,81890,81891],{"class":202,"line":410},[200,81892,368],{"class":214},[200,81894,81895,81897],{"class":202,"line":860},[200,81896,413],{"class":214},[200,81898,416],{"class":210},[35,81900,19326,81901,19329,81903,19332,81905,19335],{},[156,81902,18932],{},[156,81904,18936],{},[156,81906,207],{},[130,81908,132,81909,132,81913],{},[134,81910,136,81911,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,81912],{"src":19342,"type":141},[143,81914,19345],{},[51,81916,19349],{"id":19348},[35,81918,19352],{},[1593,81920,81921,81923],{},[71,81922,19357],{},[71,81924,19360],{},[35,81926,19363,81927,19366],{},[6862,81928,18621],{},[35,81930,19369,81931],{},[1599,81932,215],{"repo":18672},[1170,81934,19374],{},{"title":13,"searchDepth":14,"depth":14,"links":81936},[81937,81938,81939,81940],{"id":18646,"depth":14,"text":18647},{"id":19040,"depth":14,"text":19041},{"id":19067,"depth":14,"text":19068},{"id":19348,"depth":14,"text":19349},{},{"title":18609,"description":19382},{"loc":19384},{"id":19391,"title":19392,"body":81945,"contentId":9275,"date":20430,"description":20431,"extension":18,"meta":82819,"navigation":20,"path":20433,"rawbody":20434,"robots":23,"schemaOrg":23,"seo":82820,"sitemap":82821,"stem":20437,"__hash__":20438},{"type":7,"value":81946,"toc":82813},[81947,81951,81960,81969,81971,81973,81993,81995,82027,82031,82035,82041,82049,82053,82055,82057,82061,82085,82165,82167,82169,82176,82760,82762,82774,82778,82780,82789,82791,82793,82801,82805,82809,82811],[35,81948,81949,19400],{},[6862,81950,19399],{},[35,81952,19403,81953,19407,81955,19415],{},[6862,81954,19406],{},[39,81956,81958],{"href":19410,"rel":81957},[77],[6862,81959,19414],{},[938,81961,81962],{},[35,81963,81964,19422,81966,215],{},[200,81965,944],{},[39,81967,19427],{"href":19425,"rel":81968},[77],[51,81970,19431],{"id":19430},[35,81972,19434],{},[1593,81974,81975,81984],{},[71,81976,19439,81977,19443,81979,19450],{},[6862,81978,19442],{},[39,81980,81982],{"href":19446,"rel":81981},[77],[6862,81983,2929],{},[71,81985,19439,81986,19456,81988,19464],{},[6862,81987,19455],{},[39,81989,81991],{"href":19459,"rel":81990},[77],[6862,81992,19463],{},[35,81994,19467],{},[192,81996,81997],{"className":5903,"code":19470,"filename":19471,"language":18,"meta":13,"style":13},[156,81998,81999,82003,82011,82017,82023],{"__ignoreMap":13},[200,82000,82001],{"class":202,"line":203},[200,82002,19478],{"class":214},[200,82004,82005,82007,82009],{"class":202,"line":14},[200,82006,3295],{"class":235},[200,82008,239],{"class":214},[200,82010,19487],{"class":2060},[200,82012,82013,82015],{"class":202,"line":255},[200,82014,3581],{"class":235},[200,82016,19494],{"class":214},[200,82018,82019,82021],{"class":202,"line":272},[200,82020,3597],{"class":235},[200,82022,19494],{"class":214},[200,82024,82025],{"class":202,"line":283},[200,82026,19478],{"class":214},[35,82028,19507,82029,215],{},[156,82030,19510],{},[35,82032,19513,82033,19516],{},[6862,82034,2929],{},[35,82036,19519,82037,19522,82039,19525],{},[156,82038,19510],{},[156,82040,3295],{},[35,82042,19528,82043,19532,82045,19536,82047],{},[6862,82044,19531],{},[6862,82046,19535],{},[6862,82048,19539],{},[35,82050,82051],{},[1216,82052,19544],{},[51,82054,19548],{"id":19547},[35,82056,19551],{},[35,82058,19554,82059,19557],{},[6862,82060,19463],{},[192,82062,82063],{"className":19560,"code":19561,"filename":19562,"language":19563,"meta":13,"style":13},[156,82064,82065,82069,82073,82077,82081],{"__ignoreMap":13},[200,82066,82067],{"class":202,"line":203},[200,82068,19570],{},[200,82070,82071],{"class":202,"line":14},[200,82072,19575],{},[200,82074,82075],{"class":202,"line":255},[200,82076,528],{"emptyLinePlaceholder":20},[200,82078,82079],{"class":202,"line":272},[200,82080,19584],{},[200,82082,82083],{"class":202,"line":283},[200,82084,19589],{},[192,82086,82087],{"className":19560,"code":19592,"filename":19593,"language":19563,"meta":13,"style":13},[156,82088,82089,82093,82097,82101,82105,82109,82113,82117,82121,82125,82129,82133,82137,82141,82145,82149,82153,82157,82161],{"__ignoreMap":13},[200,82090,82091],{"class":202,"line":203},[200,82092,19600],{},[200,82094,82095],{"class":202,"line":14},[200,82096,528],{"emptyLinePlaceholder":20},[200,82098,82099],{"class":202,"line":255},[200,82100,19609],{},[200,82102,82103],{"class":202,"line":272},[200,82104,528],{"emptyLinePlaceholder":20},[200,82106,82107],{"class":202,"line":283},[200,82108,19618],{},[200,82110,82111],{"class":202,"line":300},[200,82112,19623],{},[200,82114,82115],{"class":202,"line":310},[200,82116,528],{"emptyLinePlaceholder":20},[200,82118,82119],{"class":202,"line":336},[200,82120,19632],{},[200,82122,82123],{"class":202,"line":342},[200,82124,230],{},[200,82126,82127],{"class":202,"line":365},[200,82128,19641],{},[200,82130,82131],{"class":202,"line":371},[200,82132,19646],{},[200,82134,82135],{"class":202,"line":398},[200,82136,19651],{},[200,82138,82139],{"class":202,"line":405},[200,82140,19656],{},[200,82142,82143],{"class":202,"line":410},[200,82144,19661],{},[200,82146,82147],{"class":202,"line":860},[200,82148,19666],{},[200,82150,82151],{"class":202,"line":889},[200,82152,528],{"emptyLinePlaceholder":20},[200,82154,82155],{"class":202,"line":899},[200,82156,19675],{},[200,82158,82159],{"class":202,"line":904},[200,82160,4977],{},[200,82162,82163],{"class":202,"line":930},[200,82164,1129],{},[35,82166,19686],{},[51,82168,19690],{"id":19689},[35,82170,19693,82171,19701],{},[39,82172,82174],{"href":19696,"rel":82173},[77],[6862,82175,19700],{},[192,82177,82178],{"className":1380,"code":19704,"filename":10026,"language":1383,"meta":13,"style":13},[156,82179,82180,82198,82216,82222,82228,82232,82244,82262,82280,82298,82302,82316,82320,82334,82346,82350,82354,82362,82370,82374,82378,82382,82386,82406,82410,82430,82436,82444,82448,82462,82476,82492,82496,82500,82504,82510,82514,82538,82566,82572,82604,82608,82614,82624,82638,82650,82656,82660,82664,82676,82694,82698,82702,82706,82724,82730,82734,82748],{"__ignoreMap":13},[200,82181,82182,82184,82186,82188,82190,82192,82194,82196],{"class":202,"line":203},[200,82183,972],{"class":206},[200,82185,318],{"class":214},[200,82187,19715],{"class":210},[200,82189,980],{"class":214},[200,82191,983],{"class":206},[200,82193,242],{"class":214},[200,82195,19724],{"class":245},[200,82197,991],{"class":214},[200,82199,82200,82202,82204,82206,82208,82210,82212,82214],{"class":202,"line":14},[200,82201,972],{"class":206},[200,82203,318],{"class":214},[200,82205,19735],{"class":210},[200,82207,980],{"class":214},[200,82209,983],{"class":206},[200,82211,242],{"class":214},[200,82213,19744],{"class":245},[200,82215,991],{"class":214},[200,82217,82218,82220],{"class":202,"line":255},[200,82219,972],{"class":206},[200,82221,280],{"class":214},[200,82223,82224,82226],{"class":202,"line":272},[200,82225,19757],{"class":210},[200,82227,252],{"class":214},[200,82229,82230],{"class":202,"line":283},[200,82231,19764],{"class":210},[200,82233,82234,82236,82238,82240,82242],{"class":202,"line":300},[200,82235,413],{"class":214},[200,82237,983],{"class":206},[200,82239,242],{"class":214},[200,82241,19775],{"class":245},[200,82243,991],{"class":214},[200,82245,82246,82248,82250,82252,82254,82256,82258,82260],{"class":202,"line":310},[200,82247,972],{"class":206},[200,82249,318],{"class":214},[200,82251,4858],{"class":210},[200,82253,980],{"class":214},[200,82255,983],{"class":206},[200,82257,242],{"class":214},[200,82259,19794],{"class":245},[200,82261,991],{"class":214},[200,82263,82264,82266,82268,82270,82272,82274,82276,82278],{"class":202,"line":336},[200,82265,972],{"class":206},[200,82267,318],{"class":214},[200,82269,3180],{"class":210},[200,82271,980],{"class":214},[200,82273,983],{"class":206},[200,82275,242],{"class":214},[200,82277,19813],{"class":245},[200,82279,991],{"class":214},[200,82281,82282,82284,82286,82288,82290,82292,82294,82296],{"class":202,"line":342},[200,82283,972],{"class":206},[200,82285,318],{"class":214},[200,82287,19824],{"class":210},[200,82289,980],{"class":214},[200,82291,983],{"class":206},[200,82293,242],{"class":214},[200,82295,19833],{"class":245},[200,82297,991],{"class":214},[200,82299,82300],{"class":202,"line":365},[200,82301,528],{"emptyLinePlaceholder":20},[200,82303,82304,82306,82308,82310,82312,82314],{"class":202,"line":371},[200,82305,443],{"class":379},[200,82307,19846],{"class":210},[200,82309,449],{"class":214},[200,82311,13007],{"class":214},[200,82313,19715],{"class":223},[200,82315,3130],{"class":210},[200,82317,82318],{"class":202,"line":398},[200,82319,3146],{"class":214},[200,82321,82322,82324,82326,82328,82330,82332],{"class":202,"line":405},[200,82323,11812],{"class":235},[200,82325,239],{"class":214},[200,82327,242],{"class":214},[200,82329,19869],{"class":245},[200,82331,249],{"class":214},[200,82333,252],{"class":214},[200,82335,82336,82338,82340,82342,82344],{"class":202,"line":410},[200,82337,19878],{"class":235},[200,82339,239],{"class":214},[200,82341,242],{"class":214},[200,82343,19885],{"class":245},[200,82345,991],{"class":214},[200,82347,82348],{"class":202,"line":860},[200,82349,368],{"class":214},[200,82351,82352],{"class":202,"line":889},[200,82353,3146],{"class":214},[200,82355,82356,82358,82360],{"class":202,"line":899},[200,82357,19900],{"class":235},[200,82359,239],{"class":214},[200,82361,280],{"class":214},[200,82363,82364,82366,82368],{"class":202,"line":904},[200,82365,19909],{"class":235},[200,82367,239],{"class":214},[200,82369,19914],{"class":214},[200,82371,82372],{"class":202,"line":930},[200,82373,4977],{"class":214},[200,82375,82376],{"class":202,"line":3655},[200,82377,4505],{"class":214},[200,82379,82380],{"class":202,"line":3660},[200,82381,416],{"class":210},[200,82383,82384],{"class":202,"line":4035},[200,82385,528],{"emptyLinePlaceholder":20},[200,82387,82388,82390,82392,82394,82396,82398,82400,82402,82404],{"class":202,"line":4490},[200,82389,443],{"class":379},[200,82391,19937],{"class":210},[200,82393,449],{"class":214},[200,82395,3180],{"class":210},[200,82397,215],{"class":214},[200,82399,293],{"class":223},[200,82401,227],{"class":210},[200,82403,19950],{"class":214},[200,82405,416],{"class":210},[200,82407,82408],{"class":202,"line":4496},[200,82409,528],{"emptyLinePlaceholder":20},[200,82411,82412,82414,82416,82418,82420,82422,82424,82426,82428],{"class":202,"line":4502},[200,82413,3123],{"class":210},[200,82415,215],{"class":214},[200,82417,19965],{"class":223},[200,82419,19968],{"class":210},[200,82421,549],{"class":214},[200,82423,380],{"class":379},[200,82425,3729],{"class":214},[200,82427,393],{"class":379},[200,82429,280],{"class":214},[200,82431,82432,82434],{"class":202,"line":4508},[200,82433,3758],{"class":206},[200,82435,280],{"class":214},[200,82437,82438,82440,82442],{"class":202,"line":5469},[200,82439,19989],{"class":235},[200,82441,239],{"class":214},[200,82443,1410],{"class":235},[200,82445,82446],{"class":202,"line":5474},[200,82447,4457],{"class":214},[200,82449,82450,82452,82454,82456,82458,82460],{"class":202,"line":13237},[200,82451,11737],{"class":235},[200,82453,239],{"class":214},[200,82455,242],{"class":214},[200,82457,20008],{"class":245},[200,82459,249],{"class":214},[200,82461,252],{"class":214},[200,82463,82464,82466,82468,82470,82472,82474],{"class":202,"line":13263},[200,82465,20017],{"class":235},[200,82467,239],{"class":214},[200,82469,242],{"class":214},[200,82471,20024],{"class":245},[200,82473,249],{"class":214},[200,82475,252],{"class":214},[200,82477,82478,82480,82482,82484,82486,82488,82490],{"class":202,"line":13279},[200,82479,20033],{"class":235},[200,82481,239],{"class":214},[200,82483,19824],{"class":223},[200,82485,227],{"class":235},[200,82487,20042],{"class":210},[200,82489,2104],{"class":235},[200,82491,252],{"class":214},[200,82493,82494],{"class":202,"line":13323},[200,82495,12098],{"class":214},[200,82497,82498],{"class":202,"line":13329},[200,82499,4499],{"class":235},[200,82501,82502],{"class":202,"line":13334},[200,82503,4505],{"class":214},[200,82505,82506,82508],{"class":202,"line":13360},[200,82507,413],{"class":214},[200,82509,416],{"class":210},[200,82511,82512],{"class":202,"line":13365},[200,82513,528],{"emptyLinePlaceholder":20},[200,82515,82516,82518,82520,82522,82524,82526,82528,82530,82532,82534,82536],{"class":202,"line":13385},[200,82517,3123],{"class":210},[200,82519,215],{"class":214},[200,82521,19965],{"class":223},[200,82523,20079],{"class":210},[200,82525,549],{"class":214},[200,82527,380],{"class":379},[200,82529,1094],{"class":214},[200,82531,6675],{"class":386},[200,82533,2104],{"class":214},[200,82535,393],{"class":379},[200,82537,280],{"class":214},[200,82539,82540,82542,82544,82546,82548,82550,82552,82554,82556,82558,82560,82562,82564],{"class":202,"line":13405},[200,82541,20098],{"class":206},[200,82543,1094],{"class":235},[200,82545,6675],{"class":210},[200,82547,215],{"class":214},[200,82549,20107],{"class":210},[200,82551,215],{"class":214},[200,82553,20112],{"class":210},[200,82555,3298],{"class":214},[200,82557,242],{"class":214},[200,82559,20008],{"class":245},[200,82561,249],{"class":214},[200,82563,5282],{"class":235},[200,82565,230],{"class":214},[200,82567,82568,82570],{"class":202,"line":13410},[200,82569,20129],{"class":206},[200,82571,280],{"class":214},[200,82573,82574,82576,82578,82580,82582,82584,82586,82588,82590,82592,82594,82596,82598,82600,82602],{"class":202,"line":13418},[200,82575,20136],{"class":379},[200,82577,5056],{"class":210},[200,82579,509],{"class":214},[200,82581,452],{"class":206},[200,82583,4858],{"class":223},[200,82585,20147],{"class":214},[200,82587,20150],{"class":235},[200,82589,239],{"class":214},[200,82591,20155],{"class":1364},[200,82593,20158],{"class":214},[200,82595,227],{"class":235},[200,82597,249],{"class":214},[200,82599,20165],{"class":245},[200,82601,249],{"class":214},[200,82603,416],{"class":235},[200,82605,82606],{"class":202,"line":13436},[200,82607,528],{"emptyLinePlaceholder":20},[200,82609,82610,82612],{"class":202,"line":13466},[200,82611,11730],{"class":206},[200,82613,280],{"class":214},[200,82615,82616,82618,82620,82622],{"class":202,"line":13472},[200,82617,20184],{"class":235},[200,82619,239],{"class":214},[200,82621,350],{"class":235},[200,82623,230],{"class":214},[200,82625,82626,82628,82630,82632,82634,82636],{"class":202,"line":13477},[200,82627,4929],{"class":235},[200,82629,239],{"class":214},[200,82631,242],{"class":214},[200,82633,697],{"class":245},[200,82635,249],{"class":214},[200,82637,252],{"class":214},[200,82639,82640,82642,82644,82646,82648],{"class":202,"line":13482},[200,82641,4944],{"class":235},[200,82643,239],{"class":214},[200,82645,5056],{"class":210},[200,82647,215],{"class":214},[200,82649,20217],{"class":210},[200,82651,82652,82654],{"class":202,"line":13497},[200,82653,13388],{"class":214},[200,82655,4511],{"class":235},[200,82657,82658],{"class":202,"line":13508},[200,82659,4493],{"class":214},[200,82661,82662],{"class":202,"line":13513},[200,82663,4977],{"class":214},[200,82665,82666,82668,82670,82672,82674],{"class":202,"line":13518},[200,82667,20236],{"class":206},[200,82669,1094],{"class":235},[200,82671,19275],{"class":210},[200,82673,5282],{"class":235},[200,82675,230],{"class":214},[200,82677,82678,82680,82682,82684,82686,82688,82690,82692],{"class":202,"line":20247},[200,82679,20250],{"class":206},[200,82681,13007],{"class":214},[200,82683,20255],{"class":223},[200,82685,227],{"class":235},[200,82687,249],{"class":214},[200,82689,20262],{"class":245},[200,82691,249],{"class":214},[200,82693,416],{"class":235},[200,82695,82696],{"class":202,"line":20269},[200,82697,4977],{"class":214},[200,82699,82700],{"class":202,"line":20274},[200,82701,4505],{"class":214},[200,82703,82704],{"class":202,"line":20279},[200,82705,528],{"emptyLinePlaceholder":20},[200,82707,82708,82710,82712,82714,82716,82718,82720,82722],{"class":202,"line":20284},[200,82709,20287],{"class":206},[200,82711,13007],{"class":214},[200,82713,20255],{"class":223},[200,82715,227],{"class":235},[200,82717,249],{"class":214},[200,82719,20298],{"class":245},[200,82721,249],{"class":214},[200,82723,416],{"class":235},[200,82725,82726,82728],{"class":202,"line":20305},[200,82727,413],{"class":214},[200,82729,416],{"class":210},[200,82731,82732],{"class":202,"line":20312},[200,82733,528],{"emptyLinePlaceholder":20},[200,82735,82736,82738,82740,82742,82744,82746],{"class":202,"line":20317},[200,82737,443],{"class":379},[200,82739,20322],{"class":210},[200,82741,449],{"class":214},[200,82743,13007],{"class":214},[200,82745,19735],{"class":223},[200,82747,466],{"class":210},[200,82749,82750,82752,82754,82756,82758],{"class":202,"line":20333},[200,82751,207],{"class":206},[200,82753,20338],{"class":210},[200,82755,215],{"class":214},[200,82757,20343],{"class":223},[200,82759,20346],{"class":210},[35,82761,20349],{},[68,82763,82764,82770],{},[71,82765,20354,82766,20358,82768,215],{},[156,82767,20357],{},[156,82769,20008],{},[71,82771,20354,82772,20366],{},[156,82773,20365],{},[35,82775,20369,82776,20372],{},[156,82777,19794],{},[35,82779,20375],{},[938,82781,82782],{},[35,82783,82784,20382,82786,215],{},[200,82785,944],{},[39,82787,20386],{"href":19425,"rel":82788},[77],[51,82790,20390],{"id":20389},[35,82792,20393],{},[130,82794,132,82795,132,82799],{},[134,82796,136,82797,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,82798],{"src":20400,"type":141},[143,82800,20403],{},[35,82802,20406,82803,20410],{},[6862,82804,20409],{},[35,82806,20413,82807,20417],{},[6862,82808,20416],{},[35,82810,20420],{},[1170,82812,20423],{},{"title":13,"searchDepth":14,"depth":14,"links":82814},[82815,82816,82817,82818],{"id":19430,"depth":14,"text":19431},{"id":19547,"depth":14,"text":19548},{"id":19689,"depth":14,"text":19690},{"id":20389,"depth":14,"text":20390},{},{"title":19392,"description":20431},{"loc":20433},{"id":20440,"title":20441,"body":82823,"contentId":9305,"date":20826,"description":20827,"extension":18,"meta":83102,"navigation":20,"path":20829,"rawbody":20830,"robots":23,"schemaOrg":23,"seo":83103,"sitemap":83104,"stem":20844,"__hash__":20845},{"type":7,"value":82824,"toc":83096},[82825,82827,82831,82835,82839,82843,82845,82847,82849,82957],[35,82826,20446],{},[35,82828,20449,82829,20452],{},[6862,82830,7497],{},[35,82832,82833],{},[1707,82834],{"alt":20457,"src":20458},[35,82836,82837],{},[1707,82838],{"alt":20463,"src":20464},[35,82840,82841],{},[1216,82842,20469],{},[51,82844,18539],{"id":20472},[35,82846,20475],{},[35,82848,20478],{},[1593,82850,82851,82916],{},[71,82852,82853,82855],{},[6862,82854,20485],{},[1593,82856,82857,82864,82871,82878,82885,82892,82902,82909],{},[71,82858,82859,6865,82861],{},[6862,82860,20492],{},[39,82862,20497],{"href":20495,"rel":82863},[77],[71,82865,82866,6865,82868],{},[6862,82867,20502],{},[39,82869,16638],{"href":16636,"rel":82870},[77],[71,82872,82873,6865,82875],{},[6862,82874,20510],{},[39,82876,7030],{"href":7028,"rel":82877},[77],[71,82879,82880,6865,82882],{},[6862,82881,20518],{},[39,82883,20523],{"href":20521,"rel":82884},[77],[71,82886,82887,6865,82889],{},[6862,82888,20528],{},[39,82890,20533],{"href":20531,"rel":82891},[77],[71,82893,82894,6865,82896,1655,82899],{},[6862,82895,20538],{},[39,82897,20543],{"href":20541,"rel":82898},[77],[39,82900,20548],{"href":20546,"rel":82901},[77],[71,82903,82904,6865,82906],{},[6862,82905,13967],{},[39,82907,7045],{"href":7043,"rel":82908},[77],[71,82910,82911,6865,82913],{},[6862,82912,20560],{},[39,82914,20565],{"href":20563,"rel":82915},[77],[71,82917,82918,82920],{},[6862,82919,20570],{},[1593,82921,82922,82929,82936,82940,82947],{},[71,82923,82924,6865,82926],{},[6862,82925,20577],{},[39,82927,20582],{"href":20580,"rel":82928},[77],[71,82930,82931,6865,82933],{},[6862,82932,9137],{},[39,82934,7045],{"href":7043,"rel":82935},[77],[71,82937,82938,20595],{},[6862,82939,20594],{},[71,82941,82942,6865,82944],{},[6862,82943,20600],{},[39,82945,20605],{"href":20603,"rel":82946},[77],[71,82948,82949,6865,82951,1655,82954],{},[6862,82950,20610],{},[39,82952,20615],{"href":20613,"rel":82953},[77],[39,82955,20620],{"href":20618,"rel":82956},[77],[7151,82958,82959,82961,82963,82967,82971,82975,82977,82979,83026,83028,83030,83035,83040,83047,83051,83053],{"w-full":13,"icon":7153,"text":7497,"to":20623,"target":7156},[35,82960,20626],{},[35,82962,20629],{},[35,82964,82965],{},[1707,82966],{"alt":20634,"src":20635},[35,82968,82969],{},[1707,82970],{"alt":20640,"src":20641},[35,82972,82973],{},[1707,82974],{"alt":20646,"src":20647},[51,82976,20651],{"id":20650},[35,82978,20654],{},[68,82980,82981,82994,83013],{},[71,82982,82983,6865,82985,82988,20666,82990,82992],{},[6862,82984,20510],{},[39,82986,7030],{"href":7028,"rel":82987},[77],[7174,82989],{},[7174,82991],{},[1707,82993],{"alt":20671,"src":20672},[71,82995,82996,6865,82998,1655,83001,83004,20685,83006,20691,83009,83011],{},[6862,82997,20538],{},[39,82999,20543],{"href":20541,"rel":83000},[77],[39,83002,20548],{"href":20546,"rel":83003},[77],[7174,83005],{},[39,83007,20690],{"href":20688,"rel":83008},[77],[7174,83010],{},[1707,83012],{"alt":20696,"src":20697},[71,83014,83015,6865,83017,83020,20707,83022,83024],{},[6862,83016,20600],{},[39,83018,20605],{"href":20603,"rel":83019},[77],[7174,83021],{},[7174,83023],{},[1707,83025],{"alt":20712,"src":20713},[35,83027,20716],{},[51,83029,20720],{"id":20719},[35,83031,20723,83032,20728],{},[39,83033,8040],{"href":20726,"rel":83034},[77],[35,83036,20731,83037,20736],{},[39,83038,20735],{"href":14042,"rel":83039},[77],[35,83041,83042],{},[1216,83043,20741,83044,20746],{},[39,83045,20745],{"href":14030,"rel":83046},[77],[35,83048,83049],{},[1707,83050],{"alt":20751,"src":20752},[51,83052,20756],{"id":20755},[7151,83054,83055,83057,83078,83082],{"icon":20759,"text":20760,"href":20761},[35,83056,20764],{},[68,83058,83059,83067],{},[71,83060,83061,20771,83063,20774,83065,20777],{},[6862,83062,9137],{},[7174,83064],{},[6862,83066,14044],{},[71,83068,83069,20782,83071,20785,83073,20789,83076,20792],{},[6862,83070,13967],{},[7174,83072],{},[39,83074,20745],{"href":14030,"rel":83075},[77],[156,83077,14034],{},[35,83079,83080],{},[1707,83081],{"alt":20797,"src":20798},[7151,83083,83084,83086,83088,83090,83094],{"icon":20759,"text":20801,"href":1833,"target":7156},[51,83085,20805],{"id":20804},[35,83087,20808],{},[35,83089,20811],{},[35,83091,83092],{},[1216,83093,20816],{},[35,83095,20819],{},{"title":13,"searchDepth":14,"depth":14,"links":83097},[83098,83099,83100,83101],{"id":20472,"depth":14,"text":18539},{"id":20650,"depth":14,"text":20651},{"id":20719,"depth":14,"text":20720},{"id":20755,"depth":14,"text":20756},{},{"title":20441,"description":20827},{"loc":20829,"images":83105},[83106,83107,83108,83109,83110,83111,83112,83113,83114,83115],{"loc":20458},{"loc":20464},{"loc":20635},{"loc":20641},{"loc":20647},{"loc":20672},{"loc":20697},{"loc":20713},{"loc":20752},{"loc":20798},{"id":20847,"title":20848,"body":83117,"contentId":9300,"date":21356,"description":21357,"extension":18,"meta":83462,"navigation":20,"path":21359,"rawbody":21360,"robots":23,"schemaOrg":23,"seo":83463,"sitemap":83464,"stem":21377,"__hash__":21378},{"type":7,"value":83118,"toc":83459},[83119,83123],[35,83120,20853,83121,20856],{},[6862,83122,7154],{},[7918,83124,83125,83127,83131,83135,83137,83139,83141,83149,83151,83153,83243,83247],{"id":20859},[35,83126,20862],{},[35,83128,83129],{},[1216,83130,20867],{},[35,83132,83133],{},[1707,83134],{"alt":20872,"src":20873},[51,83136,20877],{"id":20876},[35,83138,20880],{},[35,83140,20883],{},[35,83142,20886,83143,3092,83145,1655,83147,20896],{},[6862,83144,20889],{},[6862,83146,20892],{},[6862,83148,20895],{},[35,83150,20899],{},[35,83152,20902],{},[68,83154,83155,83179,83213],{},[71,83156,20907,83157],{},[1593,83158,83159,83163,83167,83171,83175],{},[71,83160,83161,20915],{},[6862,83162,20914],{},[71,83164,83165,20915],{},[6862,83166,20920],{},[71,83168,83169,20926],{},[6862,83170,20925],{},[71,83172,83173,20932],{},[6862,83174,20931],{},[71,83176,83177,20938],{},[6862,83178,20937],{},[71,83180,20941,83181],{},[1593,83182,83183,83187,83191,83195,83199,83203,83207],{},[71,83184,83185,20949],{},[6862,83186,20948],{},[71,83188,83189,20955],{},[6862,83190,20954],{},[71,83192,83193,20961],{},[6862,83194,20960],{},[71,83196,83197,20967],{},[6862,83198,20966],{},[71,83200,83201,20973],{},[6862,83202,20972],{},[71,83204,83205,20979],{},[6862,83206,20978],{},[71,83208,83209,20984,83211,2104],{},[6862,83210,9137],{},[1216,83212,20987],{},[71,83214,20990,83215],{},[1593,83216,83217,83221,83225,83229,83235,83239],{},[71,83218,83219,20998],{},[6862,83220,20997],{},[71,83222,83223,21004],{},[6862,83224,21003],{},[71,83226,83227,21009],{},[6862,83228,16604],{},[71,83230,83231,20984,83233,2104],{},[6862,83232,13967],{},[1216,83234,21016],{},[71,83236,83237,21022],{},[6862,83238,21021],{},[71,83240,83241,21028],{},[6862,83242,21027],{},[35,83244,21031,83245,215],{},[6862,83246,21034],{},[7151,83248,83249,83263,83267,83269,83271,83356,83358,83360,83362,83408],{"w-full":13,"icon":7153,"text":7154,"to":7155,"target":7156},[35,83250,21039,83251,21045,83254,21051,83257,21057,83260,21063],{},[39,83252,21044],{"href":21042,"rel":83253},[77],[39,83255,21050],{"href":21048,"rel":83256},[77],[39,83258,21056],{"href":21054,"rel":83259},[77],[39,83261,21062],{"href":21060,"rel":83262},[77],[35,83264,83265],{},[1216,83266,21068],{},[51,83268,21072],{"id":21071},[35,83270,21075],{},[68,83272,83273,83292,83311,83333],{},[71,83274,83275,6865,83277,83280,83282,21094,83285,21100,83288,83290],{},[6862,83276,20925],{},[39,83278,21086],{"href":21084,"rel":83279},[77],[7174,83281],{},[39,83283,21093],{"href":21091,"rel":83284},[77],[39,83286,21099],{"href":21097,"rel":83287},[77],[7174,83289],{},[1707,83291],{"alt":21105,"src":21106},[71,83293,83294,6865,83296,1655,83299,83302,21121,83304,21127,83307,83309],{},[6862,83295,20931],{},[39,83297,6955],{"href":6953,"rel":83298},[77],[39,83300,21118],{"href":21116,"rel":83301},[77],[7174,83303],{},[39,83305,21126],{"href":21124,"rel":83306},[77],[7174,83308],{},[1707,83310],{"alt":21132,"src":21133},[71,83312,83313,6865,83315,83318,21145,83320,21151,83323,21157,83326,215,83329,83331],{},[6862,83314,20966],{},[39,83316,21142],{"href":21140,"rel":83317},[77],[7174,83319],{},[39,83321,21150],{"href":21148,"rel":83322},[77],[39,83324,21156],{"href":21154,"rel":83325},[77],[39,83327,21162],{"href":21160,"rel":83328},[77],[7174,83330],{},[1707,83332],{"alt":21167,"src":21168},[71,83334,83335,6865,83337,1655,83340,83343,21181,83345,21187,83348,83350,83352,83354],{},[6862,83336,16604],{},[39,83338,16608],{"href":9596,"rel":83339},[77],[39,83341,16613],{"href":16611,"rel":83342},[77],[7174,83344],{},[39,83346,21186],{"href":21184,"rel":83347},[77],[7174,83349],{},[1707,83351],{"alt":21192,"src":21193},[7174,83353],{},[1707,83355],{"alt":21198,"src":21199},[35,83357,21202],{},[51,83359,21205],{"id":20755},[35,83361,21208],{},[68,83363,83364,83393],{},[71,83365,83366,83368,21217,83370,21220,83372,83374,83376,21229,83378,21234,83381,21237,83383,83385,83387,21246,83389,21249,83391,21253],{},[6862,83367,9137],{},[7174,83369],{},[6862,83371,14044],{},[7174,83373],{},[1707,83375],{"alt":21225,"src":21226},[7174,83377],{},[39,83379,7894],{"href":21232,"rel":83380},[77],[7174,83382],{},[7174,83384],{},[1707,83386],{"alt":21242,"src":21243},[7174,83388],{},[7174,83390],{},[6862,83392,21252],{},[71,83394,83395,83397,21260,83399,20789,83402,21267,83404,83406],{},[6862,83396,13967],{},[7174,83398],{},[39,83400,21264],{"href":14030,"rel":83401},[77],[156,83403,14034],{},[7174,83405],{},[1707,83407],{"alt":21272,"src":21273},[7151,83409,83410,83412,83414,83416,83421,83427,83431,83435,83439,83443,83447,83449,83457],{"icon":20759,"text":21276,"href":21277,"target":7156},[51,83411,21281],{"id":21280},[35,83413,21284],{},[35,83415,21287],{},[35,83417,21290,83418,21294],{},[39,83419,9589],{"href":9587,"rel":83420},[77],[35,83422,21297,83423,21300,83425,21304],{},[6862,83424,9589],{},[1216,83426,21303],{},[35,83428,83429],{},[1707,83430],{"alt":21309,"src":21310},[35,83432,83433],{},[1707,83434],{"alt":21315,"src":21316},[35,83436,83437],{},[1707,83438],{"alt":21321,"src":21322},[35,83440,83441],{},[1707,83442],{"alt":21327,"src":21328},[35,83444,83445],{},[1216,83446,21333],{},[51,83448,21337],{"id":21336},[35,83450,21340,83451,21345,83454,21349],{},[39,83452,7497],{"href":21343,"rel":83453},[77],[39,83455,9020],{"href":18204,"rel":83456},[77],[35,83458,21352],{},{"title":13,"searchDepth":14,"depth":14,"links":83460},[83461],{"id":20876,"depth":14,"text":20877},{},{"title":20848,"description":21357},{"loc":21359,"images":83465},[83466,83467,83468,83469,83470,83471,83472,83473,83474,83475,83476,83477,83478],{"loc":20873},{"loc":21106},{"loc":21133},{"loc":21168},{"loc":21193},{"loc":21199},{"loc":21226},{"loc":21243},{"loc":21273},{"loc":21310},{"loc":21316},{"loc":21322},{"loc":21328},{"id":21380,"title":21381,"body":83480,"contentId":9270,"date":24204,"description":24205,"extension":18,"meta":86023,"navigation":20,"path":24207,"rawbody":24208,"robots":23,"schemaOrg":23,"seo":86024,"sitemap":86025,"stem":24211,"__hash__":24212},{"type":7,"value":83481,"toc":86015},[83482,83486,86013],[35,83483,21386,83484,21390],{},[6862,83485,21389],{},[7918,83487,83488,83492,83494,83502,83504,83511,83513,83520,83522,83524,83604,83684,83686,83688,83690,83720,83722,83724,83732,83734,83738,83818,83820,83826,84034,84036,84038,84040,84044,84143,84145,84298,84300,84421,84604,84606,84846,84862,84864,84868,84872,85012,85014,85024,85112,85114,85126,85268,85274,85276,85278,85280,85282,85514,85836,85968,85970,85977,85984,85986,85988,85998,86011],{"id":21393},[35,83489,21396,83490,21400],{},[1216,83491,21399],{},[51,83493,21404],{"id":21403},[35,83495,21407,83496,3092,83498,9502,83500,21417],{},[6862,83497,21410],{},[6862,83499,21413],{},[6862,83501,21416],{},[35,83503,21420],{},[11400,83505,132,83507],{"className":83506},[21424,21425],[21427,83508,136,83509],{"title":21429,"description":21430,":ui":21431},[909,83510,21435],{"label":21434},[35,83512,21438],{},[11400,83514,132,83516],{"className":83515},[21424,21425],[21416,83517,136,83518],{"title":21444,"description":21445,":ui":21431},[909,83519,21435],{"label":21448},[35,83521,21451],{},[35,83523,21454],{},[192,83525,83526],{"className":21457,"code":21458,"filename":21459,"language":12075,"meta":13,"style":13},[156,83527,83528,83536,83542,83554,83566,83570,83588,83596],{"__ignoreMap":13},[200,83529,83530,83532,83534],{"class":202,"line":203},[200,83531,599],{"class":214},[200,83533,21468],{"class":235},[200,83535,647],{"class":214},[200,83537,83538,83540],{"class":202,"line":14},[200,83539,656],{"class":214},[200,83541,21477],{"class":235},[200,83543,83544,83546,83548,83550,83552],{"class":202,"line":255},[200,83545,21482],{"class":379},[200,83547,449],{"class":214},[200,83549,610],{"class":214},[200,83551,21429],{"class":245},[200,83553,616],{"class":214},[200,83555,83556,83558,83560,83562,83564],{"class":202,"line":272},[200,83557,3151],{"class":379},[200,83559,449],{"class":214},[200,83561,610],{"class":214},[200,83563,21430],{"class":245},[200,83565,616],{"class":214},[200,83567,83568],{"class":202,"line":283},[200,83569,11550],{"class":214},[200,83571,83572,83574,83576,83578,83580,83582,83584,83586],{"class":202,"line":300},[200,83573,803],{"class":214},[200,83575,21513],{"class":235},[200,83577,21516],{"class":379},[200,83579,449],{"class":214},[200,83581,610],{"class":214},[200,83583,21434],{"class":245},[200,83585,610],{"class":214},[200,83587,21527],{"class":214},[200,83589,83590,83592,83594],{"class":202,"line":310},[200,83591,892],{"class":214},[200,83593,21534],{"class":235},[200,83595,647],{"class":214},[200,83597,83598,83600,83602],{"class":202,"line":336},[200,83599,680],{"class":214},[200,83601,21468],{"class":235},[200,83603,647],{"class":214},[192,83605,83606],{"className":21457,"code":21547,"filename":21548,"language":12075,"meta":13,"style":13},[156,83607,83608,83616,83622,83634,83646,83650,83668,83676],{"__ignoreMap":13},[200,83609,83610,83612,83614],{"class":202,"line":203},[200,83611,599],{"class":214},[200,83613,21468],{"class":235},[200,83615,647],{"class":214},[200,83617,83618,83620],{"class":202,"line":14},[200,83619,656],{"class":214},[200,83621,21565],{"class":235},[200,83623,83624,83626,83628,83630,83632],{"class":202,"line":255},[200,83625,21482],{"class":379},[200,83627,449],{"class":214},[200,83629,610],{"class":214},[200,83631,21444],{"class":245},[200,83633,616],{"class":214},[200,83635,83636,83638,83640,83642,83644],{"class":202,"line":272},[200,83637,3151],{"class":379},[200,83639,449],{"class":214},[200,83641,610],{"class":214},[200,83643,21445],{"class":245},[200,83645,616],{"class":214},[200,83647,83648],{"class":202,"line":283},[200,83649,11550],{"class":214},[200,83651,83652,83654,83656,83658,83660,83662,83664,83666],{"class":202,"line":300},[200,83653,803],{"class":214},[200,83655,21513],{"class":235},[200,83657,21516],{"class":379},[200,83659,449],{"class":214},[200,83661,610],{"class":214},[200,83663,21448],{"class":245},[200,83665,610],{"class":214},[200,83667,21527],{"class":214},[200,83669,83670,83672,83674],{"class":202,"line":310},[200,83671,892],{"class":214},[200,83673,21444],{"class":235},[200,83675,647],{"class":214},[200,83677,83678,83680,83682],{"class":202,"line":336},[200,83679,680],{"class":214},[200,83681,21468],{"class":235},[200,83683,647],{"class":214},[35,83685,21630],{},[35,83687,21633],{},[35,83689,21636],{},[68,83691,83692,83694,83704],{},[71,83693,21641],{},[71,83695,21644,83696],{},[1593,83697,83698,83700,83702],{},[71,83699,21649],{},[71,83701,21652],{},[71,83703,21655],{},[71,83705,21658,83706,83718,21678],{},[1593,83707,83708,83710,83712,83714,83716],{},[71,83709,21663],{},[71,83711,21666],{},[71,83713,21669],{},[71,83715,21672],{},[71,83717,21675],{},[7174,83719],{},[35,83721,21681],{},[35,83723,21684],{},[130,83725,132,83726,132,83730],{},[134,83727,136,83728,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,83729],{"src":21691,"type":141},[143,83731,21694],{},[51,83733,21698],{"id":21697},[35,83735,21701,83736,21705],{},[6862,83737,21704],{},[192,83739,83740],{"className":21457,"code":21708,"filename":21459,"language":12075,"meta":13,"style":13},[156,83741,83742,83750,83756,83768,83780,83784,83802,83810],{"__ignoreMap":13},[200,83743,83744,83746,83748],{"class":202,"line":203},[200,83745,599],{"class":214},[200,83747,21468],{"class":235},[200,83749,647],{"class":214},[200,83751,83752,83754],{"class":202,"line":14},[200,83753,656],{"class":214},[200,83755,21477],{"class":235},[200,83757,83758,83760,83762,83764,83766],{"class":202,"line":255},[200,83759,21482],{"class":379},[200,83761,449],{"class":214},[200,83763,610],{"class":214},[200,83765,21735],{"class":245},[200,83767,616],{"class":214},[200,83769,83770,83772,83774,83776,83778],{"class":202,"line":272},[200,83771,3151],{"class":379},[200,83773,449],{"class":214},[200,83775,610],{"class":214},[200,83777,21748],{"class":245},[200,83779,616],{"class":214},[200,83781,83782],{"class":202,"line":283},[200,83783,11550],{"class":214},[200,83785,83786,83788,83790,83792,83794,83796,83798,83800],{"class":202,"line":300},[200,83787,803],{"class":214},[200,83789,21513],{"class":235},[200,83791,21516],{"class":379},[200,83793,449],{"class":214},[200,83795,610],{"class":214},[200,83797,21769],{"class":245},[200,83799,610],{"class":214},[200,83801,21527],{"class":214},[200,83803,83804,83806,83808],{"class":202,"line":310},[200,83805,892],{"class":214},[200,83807,21534],{"class":235},[200,83809,647],{"class":214},[200,83811,83812,83814,83816],{"class":202,"line":336},[200,83813,680],{"class":214},[200,83815,21468],{"class":235},[200,83817,647],{"class":214},[35,83819,21792],{},[68,83821,83822,83824],{},[71,83823,21797],{},[71,83825,21800],{},[192,83827,83828],{"className":21457,"code":21803,"filename":21804,"language":12075,"meta":13,"style":13},[156,83829,83830,83850,83868,83872,83888,83892,83900,83912,83930,83934,83940,83948,83952,83960,83968,83974,83990,84002,84014,84018,84026],{"__ignoreMap":13},[200,83831,83832,83834,83836,83838,83840,83842,83844,83846,83848],{"class":202,"line":203},[200,83833,599],{"class":214},[200,83835,14896],{"class":235},[200,83837,14594],{"class":379},[200,83839,449],{"class":214},[200,83841,610],{"class":214},[200,83843,1383],{"class":245},[200,83845,610],{"class":214},[200,83847,21825],{"class":379},[200,83849,647],{"class":214},[200,83851,83852,83854,83856,83858,83860,83862,83864,83866],{"class":202,"line":14},[200,83853,972],{"class":206},[200,83855,318],{"class":214},[200,83857,18698],{"class":210},[200,83859,980],{"class":214},[200,83861,983],{"class":206},[200,83863,242],{"class":214},[200,83865,12075],{"class":245},[200,83867,991],{"class":214},[200,83869,83870],{"class":202,"line":255},[200,83871,528],{"emptyLinePlaceholder":20},[200,83873,83874,83876,83878,83880,83882,83884,83886],{"class":202,"line":272},[200,83875,443],{"class":379},[200,83877,21856],{"class":210},[200,83879,449],{"class":214},[200,83881,18698],{"class":223},[200,83883,227],{"class":210},[200,83885,18703],{"class":2438},[200,83887,416],{"class":210},[200,83889,83890],{"class":202,"line":283},[200,83891,528],{"emptyLinePlaceholder":20},[200,83893,83894,83896,83898],{"class":202,"line":300},[200,83895,21875],{"class":223},[200,83897,227],{"class":210},[200,83899,230],{"class":214},[200,83901,83902,83904,83906,83908,83910],{"class":202,"line":310},[200,83903,21884],{"class":223},[200,83905,239],{"class":214},[200,83907,3729],{"class":214},[200,83909,393],{"class":379},[200,83911,280],{"class":214},[200,83913,83914,83916,83918,83920,83922,83924,83926,83928],{"class":202,"line":336},[200,83915,21897],{"class":210},[200,83917,215],{"class":214},[200,83919,15840],{"class":210},[200,83921,509],{"class":214},[200,83923,15917],{"class":214},[200,83925,21908],{"class":210},[200,83927,215],{"class":214},[200,83929,15979],{"class":210},[200,83931,83932],{"class":202,"line":342},[200,83933,4505],{"class":214},[200,83935,83936,83938],{"class":202,"line":365},[200,83937,413],{"class":214},[200,83939,416],{"class":210},[200,83941,83942,83944,83946],{"class":202,"line":371},[200,83943,680],{"class":214},[200,83945,14896],{"class":235},[200,83947,647],{"class":214},[200,83949,83950],{"class":202,"line":398},[200,83951,528],{"emptyLinePlaceholder":20},[200,83953,83954,83956,83958],{"class":202,"line":405},[200,83955,599],{"class":214},[200,83957,21468],{"class":235},[200,83959,647],{"class":214},[200,83961,83962,83964,83966],{"class":202,"line":410},[200,83963,656],{"class":214},[200,83965,11400],{"class":235},[200,83967,647],{"class":214},[200,83969,83970,83972],{"class":202,"line":860},[200,83971,803],{"class":214},[200,83973,21477],{"class":235},[200,83975,83976,83978,83980,83982,83984,83986,83988],{"class":202,"line":889},[200,83977,21961],{"class":379},[200,83979,239],{"class":214},[200,83981,18917],{"class":379},[200,83983,449],{"class":214},[200,83985,610],{"class":214},[200,83987,21908],{"class":210},[200,83989,616],{"class":214},[200,83991,83992,83994,83996,83998,84000],{"class":202,"line":899},[200,83993,3572],{"class":379},[200,83995,449],{"class":214},[200,83997,610],{"class":214},[200,83999,21429],{"class":245},[200,84001,616],{"class":214},[200,84003,84004,84006,84008,84010,84012],{"class":202,"line":904},[200,84005,3588],{"class":379},[200,84007,449],{"class":214},[200,84009,610],{"class":214},[200,84011,21430],{"class":245},[200,84013,616],{"class":214},[200,84015,84016],{"class":202,"line":930},[200,84017,22002],{"class":214},[200,84019,84020,84022,84024],{"class":202,"line":3655},[200,84021,892],{"class":214},[200,84023,11400],{"class":235},[200,84025,647],{"class":214},[200,84027,84028,84030,84032],{"class":202,"line":3660},[200,84029,680],{"class":214},[200,84031,21468],{"class":235},[200,84033,647],{"class":214},[35,84035,22021],{},[51,84037,22025],{"id":22024},[35,84039,22028],{},[35,84041,22031,84042,22034],{},[156,84043,13716],{},[192,84045,84046],{"className":22037,"code":22038,"filename":22039,"language":12075,"meta":13,"style":13},[156,84047,84048,84056,84064,84072,84080,84088,84092,84101,84110,84119,84123,84127,84135],{"__ignoreMap":13},[200,84049,84050,84052,84054],{"class":202,"line":203},[200,84051,599],{"class":214},[200,84053,21468],{"class":235},[200,84055,647],{"class":214},[200,84057,84058,84060,84062],{"class":202,"line":14},[200,84059,656],{"class":214},[200,84061,11400],{"class":235},[200,84063,647],{"class":214},[200,84065,84066,84068,84070],{"class":202,"line":255},[200,84067,803],{"class":214},[200,84069,22064],{"class":235},[200,84071,647],{"class":214},[200,84073,84074,84076,84078],{"class":202,"line":272},[200,84075,22071],{"class":214},[200,84077,22074],{"class":235},[200,84079,21527],{"class":214},[200,84081,84082,84084,84086],{"class":202,"line":283},[200,84083,15078],{"class":214},[200,84085,22064],{"class":235},[200,84087,647],{"class":214},[200,84089,84090],{"class":202,"line":300},[200,84091,528],{"emptyLinePlaceholder":20},[200,84093,84095,84097,84099],{"class":84094,"line":310},[202,9885],[200,84096,803],{"class":214},[200,84098,22096],{"class":235},[200,84100,22099],{"class":214},[200,84102,84104,84106,84108],{"class":84103,"line":336},[202,9885],[200,84105,803],{"class":214},[200,84107,22107],{"class":235},[200,84109,22099],{"class":214},[200,84111,84113,84115,84117],{"class":84112,"line":342},[202,9885],[200,84114,803],{"class":214},[200,84116,22117],{"class":235},[200,84118,22099],{"class":214},[200,84120,84121],{"class":202,"line":365},[200,84122,528],{"emptyLinePlaceholder":20},[200,84124,84125],{"class":202,"line":371},[200,84126,22128],{"class":401},[200,84128,84129,84131,84133],{"class":202,"line":398},[200,84130,892],{"class":214},[200,84132,11400],{"class":235},[200,84134,647],{"class":214},[200,84136,84137,84139,84141],{"class":202,"line":405},[200,84138,680],{"class":214},[200,84140,21468],{"class":235},[200,84142,647],{"class":214},[35,84144,22147],{},[192,84146,84147],{"className":9755,"code":22150,"filename":22151,"language":1383,"meta":13,"style":13},[156,84148,84149,84167,84171,84188,84192,84204,84214,84226,84230,84234,84244,84256,84260,84264,84270,84276,84280,84286,84290,84294],{"__ignoreMap":13},[200,84150,84151,84153,84155,84157,84159,84161,84163,84165],{"class":202,"line":203},[200,84152,972],{"class":206},[200,84154,318],{"class":214},[200,84156,18698],{"class":210},[200,84158,980],{"class":214},[200,84160,983],{"class":206},[200,84162,242],{"class":214},[200,84164,12075],{"class":245},[200,84166,991],{"class":214},[200,84168,84169],{"class":202,"line":14},[200,84170,528],{"emptyLinePlaceholder":20},[200,84172,84174,84176,84178,84180,84182,84184,84186],{"class":84173,"line":255},[202,9885],[200,84175,443],{"class":379},[200,84177,22183],{"class":210},[200,84179,449],{"class":214},[200,84181,18698],{"class":223},[200,84183,227],{"class":210},[200,84185,18703],{"class":2438},[200,84187,5282],{"class":210},[200,84189,84190],{"class":202,"line":272},[200,84191,528],{"emptyLinePlaceholder":20},[200,84193,84194,84196,84198,84200,84202],{"class":202,"line":283},[200,84195,1390],{"class":206},[200,84197,5002],{"class":379},[200,84199,22206],{"class":223},[200,84201,1549],{"class":214},[200,84203,280],{"class":214},[200,84205,84206,84208,84210,84212],{"class":202,"line":300},[200,84207,22215],{"class":379},[200,84209,22218],{"class":223},[200,84211,1549],{"class":214},[200,84213,280],{"class":214},[200,84215,84216,84218,84220,84222,84224],{"class":202,"line":310},[200,84217,22227],{"class":210},[200,84219,215],{"class":214},[200,84221,15840],{"class":210},[200,84223,509],{"class":214},[200,84225,22236],{"class":2438},[200,84227,84228],{"class":202,"line":336},[200,84229,4505],{"class":214},[200,84231,84232],{"class":202,"line":342},[200,84233,528],{"emptyLinePlaceholder":20},[200,84235,84236,84238,84240,84242],{"class":202,"line":365},[200,84237,22215],{"class":379},[200,84239,22251],{"class":223},[200,84241,1549],{"class":214},[200,84243,280],{"class":214},[200,84245,84246,84248,84250,84252,84254],{"class":202,"line":371},[200,84247,22227],{"class":210},[200,84249,215],{"class":214},[200,84251,15840],{"class":210},[200,84253,509],{"class":214},[200,84255,18902],{"class":2438},[200,84257,84258],{"class":202,"line":398},[200,84259,4505],{"class":214},[200,84261,84262],{"class":202,"line":405},[200,84263,528],{"emptyLinePlaceholder":20},[200,84265,84266,84268],{"class":202,"line":410},[200,84267,3758],{"class":206},[200,84269,280],{"class":214},[200,84271,84272,84274],{"class":202,"line":860},[200,84273,22227],{"class":210},[200,84275,252],{"class":214},[200,84277,84278],{"class":202,"line":889},[200,84279,528],{"emptyLinePlaceholder":20},[200,84281,84282,84284],{"class":202,"line":899},[200,84283,18893],{"class":210},[200,84285,252],{"class":214},[200,84287,84288],{"class":202,"line":904},[200,84289,22302],{"class":210},[200,84291,84292],{"class":202,"line":930},[200,84293,4505],{"class":214},[200,84295,84296],{"class":202,"line":3655},[200,84297,1129],{"class":214},[35,84299,22313],{},[192,84301,84302],{"className":22037,"code":22316,"filename":22039,"language":12075,"meta":13,"style":13},[156,84303,84304,84324,84342,84346,84363,84371,84375,84383,84413],{"__ignoreMap":13},[200,84305,84306,84308,84310,84312,84314,84316,84318,84320,84322],{"class":202,"line":203},[200,84307,599],{"class":214},[200,84309,14896],{"class":235},[200,84311,14594],{"class":379},[200,84313,449],{"class":214},[200,84315,610],{"class":214},[200,84317,1383],{"class":245},[200,84319,610],{"class":214},[200,84321,21825],{"class":379},[200,84323,647],{"class":214},[200,84325,84326,84328,84330,84332,84334,84336,84338,84340],{"class":202,"line":14},[200,84327,972],{"class":206},[200,84329,318],{"class":214},[200,84331,22206],{"class":210},[200,84333,980],{"class":214},[200,84335,983],{"class":206},[200,84337,242],{"class":214},[200,84339,22355],{"class":245},[200,84341,991],{"class":214},[200,84343,84344],{"class":202,"line":255},[200,84345,528],{"emptyLinePlaceholder":20},[200,84347,84349,84351,84353,84355,84357,84359,84361],{"class":84348,"line":272},[202,9885],[200,84350,443],{"class":379},[200,84352,318],{"class":214},[200,84354,18693],{"class":210},[200,84356,413],{"class":214},[200,84358,509],{"class":214},[200,84360,22206],{"class":223},[200,84362,6332],{"class":210},[200,84364,84365,84367,84369],{"class":202,"line":283},[200,84366,680],{"class":214},[200,84368,14896],{"class":235},[200,84370,647],{"class":214},[200,84372,84373],{"class":202,"line":300},[200,84374,528],{"emptyLinePlaceholder":20},[200,84376,84377,84379,84381],{"class":202,"line":310},[200,84378,599],{"class":214},[200,84380,21468],{"class":235},[200,84382,647],{"class":214},[200,84384,84385,84387,84389,84391,84393,84395,84397,84399,84401,84403,84405,84407,84409,84411],{"class":202,"line":336},[200,84386,656],{"class":214},[200,84388,21513],{"class":235},[200,84390,21516],{"class":379},[200,84392,449],{"class":214},[200,84394,610],{"class":214},[200,84396,21434],{"class":245},[200,84398,610],{"class":214},[200,84400,22417],{"class":214},[200,84402,22420],{"class":379},[200,84404,449],{"class":214},[200,84406,610],{"class":214},[200,84408,18917],{"class":210},[200,84410,610],{"class":214},[200,84412,21527],{"class":214},[200,84414,84415,84417,84419],{"class":202,"line":342},[200,84416,680],{"class":214},[200,84418,21468],{"class":235},[200,84420,647],{"class":214},[192,84422,84423],{"className":22037,"code":22441,"filename":22442,"language":12075,"meta":13,"style":13},[156,84424,84425,84445,84463,84467,84488,84496,84500,84508,84514,84530,84542,84554,84558,84588,84596],{"__ignoreMap":13},[200,84426,84427,84429,84431,84433,84435,84437,84439,84441,84443],{"class":202,"line":203},[200,84428,599],{"class":214},[200,84430,14896],{"class":235},[200,84432,14594],{"class":379},[200,84434,449],{"class":214},[200,84436,610],{"class":214},[200,84438,1383],{"class":245},[200,84440,610],{"class":214},[200,84442,21825],{"class":379},[200,84444,647],{"class":214},[200,84446,84447,84449,84451,84453,84455,84457,84459,84461],{"class":202,"line":14},[200,84448,972],{"class":206},[200,84450,318],{"class":214},[200,84452,22206],{"class":210},[200,84454,980],{"class":214},[200,84456,983],{"class":206},[200,84458,242],{"class":214},[200,84460,22355],{"class":245},[200,84462,991],{"class":214},[200,84464,84465],{"class":202,"line":255},[200,84466,528],{"emptyLinePlaceholder":20},[200,84468,84470,84472,84474,84476,84478,84480,84482,84484,84486],{"class":84469,"line":272},[202,9885],[200,84471,443],{"class":379},[200,84473,318],{"class":214},[200,84475,22496],{"class":210},[200,84477,549],{"class":214},[200,84479,22501],{"class":210},[200,84481,413],{"class":214},[200,84483,509],{"class":214},[200,84485,22206],{"class":223},[200,84487,6332],{"class":210},[200,84489,84490,84492,84494],{"class":202,"line":283},[200,84491,680],{"class":214},[200,84493,14896],{"class":235},[200,84495,647],{"class":214},[200,84497,84498],{"class":202,"line":300},[200,84499,528],{"emptyLinePlaceholder":20},[200,84501,84502,84504,84506],{"class":202,"line":310},[200,84503,599],{"class":214},[200,84505,21468],{"class":235},[200,84507,647],{"class":214},[200,84509,84510,84512],{"class":202,"line":336},[200,84511,656],{"class":214},[200,84513,21477],{"class":235},[200,84515,84516,84518,84520,84522,84524,84526,84528],{"class":202,"line":342},[200,84517,22540],{"class":379},[200,84519,239],{"class":214},[200,84521,18917],{"class":379},[200,84523,449],{"class":214},[200,84525,610],{"class":214},[200,84527,22551],{"class":210},[200,84529,616],{"class":214},[200,84531,84532,84534,84536,84538,84540],{"class":202,"line":365},[200,84533,21482],{"class":379},[200,84535,449],{"class":214},[200,84537,610],{"class":214},[200,84539,21429],{"class":245},[200,84541,616],{"class":214},[200,84543,84544,84546,84548,84550,84552],{"class":202,"line":371},[200,84545,3151],{"class":379},[200,84547,449],{"class":214},[200,84549,610],{"class":214},[200,84551,21430],{"class":245},[200,84553,616],{"class":214},[200,84555,84556],{"class":202,"line":398},[200,84557,11550],{"class":214},[200,84559,84560,84562,84564,84566,84568,84570,84572,84574,84576,84578,84580,84582,84584,84586],{"class":202,"line":405},[200,84561,803],{"class":214},[200,84563,21513],{"class":235},[200,84565,21516],{"class":379},[200,84567,449],{"class":214},[200,84569,610],{"class":214},[200,84571,22596],{"class":245},[200,84573,610],{"class":214},[200,84575,22417],{"class":214},[200,84577,22420],{"class":379},[200,84579,449],{"class":214},[200,84581,610],{"class":214},[200,84583,22609],{"class":210},[200,84585,610],{"class":214},[200,84587,21527],{"class":214},[200,84589,84590,84592,84594],{"class":202,"line":410},[200,84591,892],{"class":214},[200,84593,21534],{"class":235},[200,84595,647],{"class":214},[200,84597,84598,84600,84602],{"class":202,"line":860},[200,84599,680],{"class":214},[200,84601,21468],{"class":235},[200,84603,647],{"class":214},[35,84605,22632],{},[68,84607,84608,84610],{},[71,84609,22637],{},[71,84611,22640,84612],{},[192,84613,84614],{"className":9755,"code":22643,"language":1383,"meta":13,"style":13},[156,84615,84616,84634,84638,84654,84671,84688,84692,84704,84730,84742,84754,84766,84770,84774,84784,84796,84800,84804,84810,84816,84822,84828,84834,84838,84842],{"__ignoreMap":13},[200,84617,84618,84620,84622,84624,84626,84628,84630,84632],{"class":202,"line":203},[200,84619,972],{"class":206},[200,84621,318],{"class":214},[200,84623,18698],{"class":210},[200,84625,980],{"class":214},[200,84627,983],{"class":206},[200,84629,242],{"class":214},[200,84631,12075],{"class":245},[200,84633,991],{"class":214},[200,84635,84636],{"class":202,"line":14},[200,84637,528],{"emptyLinePlaceholder":20},[200,84639,84640,84642,84644,84646,84648,84650,84652],{"class":202,"line":255},[200,84641,443],{"class":379},[200,84643,22183],{"class":210},[200,84645,449],{"class":214},[200,84647,18698],{"class":223},[200,84649,227],{"class":210},[200,84651,18703],{"class":2438},[200,84653,416],{"class":210},[200,84655,84657,84659,84661,84663,84665,84667,84669],{"class":84656,"line":272},[202,9885],[200,84658,443],{"class":379},[200,84660,22691],{"class":210},[200,84662,449],{"class":214},[200,84664,18698],{"class":223},[200,84666,227],{"class":210},[200,84668,22700],{"class":214},[200,84670,5282],{"class":210},[200,84672,84674,84676,84678,84680,84682,84684,84686],{"class":84673,"line":283},[202,9885],[200,84675,443],{"class":379},[200,84677,22710],{"class":210},[200,84679,449],{"class":214},[200,84681,18698],{"class":223},[200,84683,227],{"class":210},[200,84685,22700],{"class":214},[200,84687,5282],{"class":210},[200,84689,84690],{"class":202,"line":300},[200,84691,528],{"emptyLinePlaceholder":20},[200,84693,84694,84696,84698,84700,84702],{"class":202,"line":310},[200,84695,1390],{"class":206},[200,84697,5002],{"class":379},[200,84699,22206],{"class":223},[200,84701,1549],{"class":214},[200,84703,280],{"class":214},[200,84705,84706,84708,84710,84712,84714,84716,84718,84720,84722,84724,84726,84728],{"class":202,"line":336},[200,84707,22215],{"class":379},[200,84709,22218],{"class":223},[200,84711,227],{"class":214},[200,84713,22747],{"class":386},[200,84715,239],{"class":214},[200,84717,22752],{"class":1364},[200,84719,549],{"class":214},[200,84721,22757],{"class":386},[200,84723,239],{"class":214},[200,84725,22752],{"class":1364},[200,84727,2104],{"class":214},[200,84729,280],{"class":214},[200,84731,84732,84734,84736,84738,84740],{"class":202,"line":342},[200,84733,21482],{"class":210},[200,84735,215],{"class":214},[200,84737,15840],{"class":210},[200,84739,509],{"class":214},[200,84741,22778],{"class":210},[200,84743,84744,84746,84748,84750,84752],{"class":202,"line":365},[200,84745,3151],{"class":210},[200,84747,215],{"class":214},[200,84749,15840],{"class":210},[200,84751,509],{"class":214},[200,84753,22791],{"class":210},[200,84755,84756,84758,84760,84762,84764],{"class":202,"line":371},[200,84757,22227],{"class":210},[200,84759,215],{"class":214},[200,84761,15840],{"class":210},[200,84763,509],{"class":214},[200,84765,22236],{"class":2438},[200,84767,84768],{"class":202,"line":398},[200,84769,4505],{"class":214},[200,84771,84772],{"class":202,"line":405},[200,84773,528],{"emptyLinePlaceholder":20},[200,84775,84776,84778,84780,84782],{"class":202,"line":410},[200,84777,22215],{"class":379},[200,84779,22251],{"class":223},[200,84781,1549],{"class":214},[200,84783,280],{"class":214},[200,84785,84786,84788,84790,84792,84794],{"class":202,"line":860},[200,84787,22227],{"class":210},[200,84789,215],{"class":214},[200,84791,15840],{"class":210},[200,84793,509],{"class":214},[200,84795,18902],{"class":2438},[200,84797,84798],{"class":202,"line":889},[200,84799,4505],{"class":214},[200,84801,84802],{"class":202,"line":899},[200,84803,528],{"emptyLinePlaceholder":20},[200,84805,84806,84808],{"class":202,"line":904},[200,84807,3758],{"class":206},[200,84809,280],{"class":214},[200,84811,84812,84814],{"class":202,"line":930},[200,84813,22227],{"class":210},[200,84815,252],{"class":214},[200,84817,84818,84820],{"class":202,"line":3655},[200,84819,21482],{"class":210},[200,84821,252],{"class":214},[200,84823,84824,84826],{"class":202,"line":3660},[200,84825,3151],{"class":210},[200,84827,252],{"class":214},[200,84829,84830,84832],{"class":202,"line":4035},[200,84831,18893],{"class":210},[200,84833,252],{"class":214},[200,84835,84836],{"class":202,"line":4490},[200,84837,22302],{"class":210},[200,84839,84840],{"class":202,"line":4496},[200,84841,4505],{"class":214},[200,84843,84844],{"class":202,"line":4502},[200,84845,1129],{"class":214},[938,84847,84848],{},[35,84849,84850,22890,84852,17891,84857,22905],{},[200,84851,944],{},[39,84853,84855],{"href":22893,"rel":84854},[77],[156,84856,22897],{},[39,84858,84860],{"href":22900,"rel":84859},[77],[156,84861,22904],{},[51,84863,22909],{"id":22908},[35,84865,22912,84866,22916],{},[6862,84867,22915],{},[35,84869,22919,84870,22923],{},[6862,84871,22922],{},[192,84873,84874],{"className":1380,"code":22926,"filename":22927,"language":1383,"meta":13,"style":13},[156,84875,84876,84896,84910,84924,84930,84934,84960,84984,84990,84994],{"__ignoreMap":13},[200,84877,84878,84880,84882,84884,84886,84888,84890,84892,84894],{"class":202,"line":203},[200,84879,443],{"class":379},[200,84881,22936],{"class":210},[200,84883,449],{"class":214},[200,84885,22941],{"class":210},[200,84887,215],{"class":214},[200,84889,18917],{"class":223},[200,84891,22948],{"class":210},[200,84893,549],{"class":214},[200,84895,280],{"class":214},[200,84897,84898,84900,84902,84904,84906,84908],{"class":202,"line":14},[200,84899,22957],{"class":235},[200,84901,239],{"class":214},[200,84903,242],{"class":214},[200,84905,22964],{"class":245},[200,84907,249],{"class":214},[200,84909,252],{"class":214},[200,84911,84912,84914,84916,84918,84920,84922],{"class":202,"line":255},[200,84913,22973],{"class":235},[200,84915,239],{"class":214},[200,84917,242],{"class":214},[200,84919,22980],{"class":245},[200,84921,249],{"class":214},[200,84923,252],{"class":214},[200,84925,84926,84928],{"class":202,"line":272},[200,84927,413],{"class":214},[200,84929,416],{"class":210},[200,84931,84932],{"class":202,"line":283},[200,84933,528],{"emptyLinePlaceholder":20},[200,84935,84936,84938,84940,84942,84944,84946,84948,84950,84952,84954,84956,84958],{"class":202,"line":300},[200,84937,22999],{"class":210},[200,84939,215],{"class":214},[200,84941,23004],{"class":223},[200,84943,1549],{"class":210},[200,84945,215],{"class":214},[200,84947,23011],{"class":223},[200,84949,227],{"class":210},[200,84951,227],{"class":214},[200,84953,3546],{"class":386},[200,84955,2104],{"class":214},[200,84957,393],{"class":379},[200,84959,280],{"class":214},[200,84961,84962,84964,84966,84968,84970,84972,84974,84976,84978,84980,84982],{"class":202,"line":310},[200,84963,1112],{"class":210},[200,84965,215],{"class":214},[200,84967,1117],{"class":223},[200,84969,227],{"class":235},[200,84971,3333],{"class":214},[200,84973,23038],{"class":245},[200,84975,4868],{"class":214},[200,84977,3546],{"class":210},[200,84979,23045],{"class":214},[200,84981,5282],{"class":235},[200,84983,23050],{"class":401},[200,84985,84986,84988],{"class":202,"line":336},[200,84987,413],{"class":214},[200,84989,416],{"class":210},[200,84991,84992],{"class":202,"line":342},[200,84993,528],{"emptyLinePlaceholder":20},[200,84995,84996,84998,85000,85002,85004,85006,85008,85010],{"class":202,"line":365},[200,84997,22999],{"class":210},[200,84999,215],{"class":214},[200,85001,22609],{"class":223},[200,85003,227],{"class":210},[200,85005,249],{"class":214},[200,85007,23075],{"class":245},[200,85009,249],{"class":214},[200,85011,416],{"class":210},[35,85013,23082],{},[35,85015,23085,85016,23090,85019,23095,85022,23099],{},[39,85017,2934],{"href":23088,"rel":85018},[77],[39,85020,9203],{"href":23093,"rel":85021},[77],[156,85023,23098],{},[192,85025,85026],{"className":21457,"code":23102,"filename":22039,"language":12075,"meta":13,"style":13},[156,85027,85028,85048,85060,85064,85076,85080,85092,85100,85104],{"__ignoreMap":13},[200,85029,85030,85032,85034,85036,85038,85040,85042,85044,85046],{"class":202,"line":203},[200,85031,599],{"class":214},[200,85033,14896],{"class":235},[200,85035,21825],{"class":379},[200,85037,14594],{"class":379},[200,85039,449],{"class":214},[200,85041,610],{"class":214},[200,85043,1383],{"class":245},[200,85045,610],{"class":214},[200,85047,647],{"class":214},[200,85049,85050,85052,85054,85056,85058],{"class":202,"line":14},[200,85051,443],{"class":379},[200,85053,23131],{"class":210},[200,85055,449],{"class":214},[200,85057,23136],{"class":223},[200,85059,466],{"class":210},[200,85061,85062],{"class":202,"line":255},[200,85063,528],{"emptyLinePlaceholder":20},[200,85065,85066,85068,85070,85072,85074],{"class":202,"line":272},[200,85067,3726],{"class":379},[200,85069,5002],{"class":379},[200,85071,23151],{"class":223},[200,85073,1549],{"class":214},[200,85075,280],{"class":214},[200,85077,85078],{"class":202,"line":283},[200,85079,23160],{"class":210},[200,85081,85082,85084,85086,85088,85090],{"class":202,"line":300},[200,85083,3770],{"class":214},[200,85085,23167],{"class":223},[200,85087,227],{"class":235},[200,85089,23172],{"class":210},[200,85091,416],{"class":235},[200,85093,85094,85096,85098],{"class":202,"line":310},[200,85095,3770],{"class":214},[200,85097,18917],{"class":223},[200,85099,466],{"class":235},[200,85101,85102],{"class":202,"line":336},[200,85103,1129],{"class":214},[200,85105,85106,85108,85110],{"class":202,"line":342},[200,85107,680],{"class":214},[200,85109,14896],{"class":235},[200,85111,647],{"class":214},[35,85113,23197],{},[35,85115,23200,85116,23208,85121,23216],{},[39,85117,85119],{"href":23203,"rel":85118},[77],[156,85120,23207],{},[39,85122,85124],{"href":23211,"rel":85123},[77],[156,85125,23215],{},[192,85127,85128],{"className":21457,"code":23219,"filename":23220,"language":12075,"meta":13,"style":13},[156,85129,85130,85150,85170,85178,85182,85190,85196,85208,85220,85232,85244,85256,85260],{"__ignoreMap":13},[200,85131,85132,85134,85136,85138,85140,85142,85144,85146,85148],{"class":202,"line":203},[200,85133,599],{"class":214},[200,85135,14896],{"class":235},[200,85137,14594],{"class":379},[200,85139,449],{"class":214},[200,85141,610],{"class":214},[200,85143,1383],{"class":245},[200,85145,610],{"class":214},[200,85147,21825],{"class":379},[200,85149,647],{"class":214},[200,85151,85152,85154,85156,85158,85160,85162,85164,85166,85168],{"class":202,"line":14},[200,85153,443],{"class":379},[200,85155,318],{"class":214},[200,85157,23251],{"class":210},[200,85159,549],{"class":214},[200,85161,22501],{"class":210},[200,85163,413],{"class":214},[200,85165,509],{"class":214},[200,85167,23136],{"class":223},[200,85169,466],{"class":210},[200,85171,85172,85174,85176],{"class":202,"line":255},[200,85173,680],{"class":214},[200,85175,14896],{"class":235},[200,85177,647],{"class":214},[200,85179,85180],{"class":202,"line":272},[200,85181,528],{"emptyLinePlaceholder":20},[200,85183,85184,85186,85188],{"class":202,"line":283},[200,85185,599],{"class":214},[200,85187,21468],{"class":235},[200,85189,647],{"class":214},[200,85191,85192,85194],{"class":202,"line":300},[200,85193,656],{"class":214},[200,85195,23290],{"class":235},[200,85197,85198,85200,85202,85204,85206],{"class":202,"line":310},[200,85199,23295],{"class":379},[200,85201,449],{"class":214},[200,85203,610],{"class":214},[200,85205,23302],{"class":245},[200,85207,616],{"class":214},[200,85209,85210,85212,85214,85216,85218],{"class":202,"line":336},[200,85211,23309],{"class":379},[200,85213,449],{"class":214},[200,85215,610],{"class":214},[200,85217,23316],{"class":245},[200,85219,616],{"class":214},[200,85221,85222,85224,85226,85228,85230],{"class":202,"line":342},[200,85223,23323],{"class":379},[200,85225,449],{"class":214},[200,85227,610],{"class":214},[200,85229,23330],{"class":245},[200,85231,616],{"class":214},[200,85233,85234,85236,85238,85240,85242],{"class":202,"line":365},[200,85235,23337],{"class":379},[200,85237,449],{"class":214},[200,85239,610],{"class":214},[200,85241,23344],{"class":245},[200,85243,616],{"class":214},[200,85245,85246,85248,85250,85252,85254],{"class":202,"line":371},[200,85247,23351],{"class":379},[200,85249,449],{"class":214},[200,85251,610],{"class":214},[200,85253,23358],{"class":245},[200,85255,616],{"class":214},[200,85257,85258],{"class":202,"line":398},[200,85259,23365],{"class":214},[200,85261,85262,85264,85266],{"class":202,"line":405},[200,85263,680],{"class":214},[200,85265,21468],{"class":235},[200,85267,647],{"class":214},[938,85269,85270],{},[35,85271,85272,23380],{},[200,85273,944],{},[35,85275,23383],{},[51,85277,23387],{"id":23386},[35,85279,23390],{},[35,85281,21454],{},[192,85283,85284],{"className":21457,"code":23395,"filename":22039,"language":12075,"meta":13,"style":13},[156,85285,85286,85306,85320,85324,85336,85346,85350,85364,85372,85386,85398,85402,85410,85416,85424,85428,85436,85440,85448,85456,85486,85490,85498,85506],{"__ignoreMap":13},[200,85287,85288,85290,85292,85294,85296,85298,85300,85302,85304],{"class":202,"line":203},[200,85289,599],{"class":214},[200,85291,14896],{"class":235},[200,85293,14594],{"class":379},[200,85295,449],{"class":214},[200,85297,610],{"class":214},[200,85299,1383],{"class":245},[200,85301,610],{"class":214},[200,85303,21825],{"class":379},[200,85305,647],{"class":214},[200,85307,85308,85310,85312,85314,85316,85318],{"class":202,"line":14},[200,85309,972],{"class":206},[200,85311,23424],{"class":210},[200,85313,1489],{"class":206},[200,85315,242],{"class":214},[200,85317,23431],{"class":245},[200,85319,991],{"class":214},[200,85321,85322],{"class":202,"line":255},[200,85323,528],{"emptyLinePlaceholder":20},[200,85325,85326,85328,85330,85332,85334],{"class":202,"line":272},[200,85327,443],{"class":379},[200,85329,23131],{"class":210},[200,85331,449],{"class":214},[200,85333,23136],{"class":223},[200,85335,466],{"class":210},[200,85337,85338,85340,85342,85344],{"class":202,"line":283},[200,85339,14908],{"class":379},[200,85341,23456],{"class":223},[200,85343,1549],{"class":214},[200,85345,280],{"class":214},[200,85347,85348],{"class":202,"line":300},[200,85349,23160],{"class":210},[200,85351,85352,85354,85356,85358,85360,85362],{"class":202,"line":310},[200,85353,3770],{"class":214},[200,85355,23167],{"class":223},[200,85357,227],{"class":235},[200,85359,23475],{"class":210},[200,85361,549],{"class":214},[200,85363,280],{"class":214},[200,85365,85366,85368,85370],{"class":202,"line":336},[200,85367,23484],{"class":235},[200,85369,239],{"class":214},[200,85371,280],{"class":214},[200,85373,85374,85376,85378,85380,85382,85384],{"class":202,"line":342},[200,85375,23493],{"class":235},[200,85377,239],{"class":214},[200,85379,242],{"class":214},[200,85381,23500],{"class":245},[200,85383,249],{"class":214},[200,85385,252],{"class":214},[200,85387,85388,85390,85392,85394,85396],{"class":202,"line":365},[200,85389,20017],{"class":235},[200,85391,239],{"class":214},[200,85393,242],{"class":214},[200,85395,23515],{"class":245},[200,85397,991],{"class":214},[200,85399,85400],{"class":202,"line":371},[200,85401,12098],{"class":214},[200,85403,85404,85406,85408],{"class":202,"line":398},[200,85405,23526],{"class":235},[200,85407,239],{"class":214},[200,85409,22236],{"class":2438},[200,85411,85412,85414],{"class":202,"line":405},[200,85413,2111],{"class":214},[200,85415,416],{"class":235},[200,85417,85418,85420,85422],{"class":202,"line":410},[200,85419,3770],{"class":214},[200,85421,18917],{"class":223},[200,85423,466],{"class":235},[200,85425,85426],{"class":202,"line":860},[200,85427,1129],{"class":214},[200,85429,85430,85432,85434],{"class":202,"line":889},[200,85431,680],{"class":214},[200,85433,14896],{"class":235},[200,85435,647],{"class":214},[200,85437,85438],{"class":202,"line":899},[200,85439,528],{"emptyLinePlaceholder":20},[200,85441,85442,85444,85446],{"class":202,"line":904},[200,85443,599],{"class":214},[200,85445,21468],{"class":235},[200,85447,647],{"class":214},[200,85449,85450,85452,85454],{"class":202,"line":930},[200,85451,656],{"class":214},[200,85453,11400],{"class":235},[200,85455,647],{"class":214},[200,85457,85458,85460,85462,85464,85466,85468,85470,85472,85474,85476,85478,85480,85482,85484],{"class":202,"line":3655},[200,85459,803],{"class":214},[200,85461,21513],{"class":235},[200,85463,21516],{"class":379},[200,85465,449],{"class":214},[200,85467,610],{"class":214},[200,85469,23591],{"class":245},[200,85471,610],{"class":214},[200,85473,22417],{"class":214},[200,85475,22420],{"class":379},[200,85477,449],{"class":214},[200,85479,610],{"class":214},[200,85481,23604],{"class":210},[200,85483,610],{"class":214},[200,85485,21527],{"class":214},[200,85487,85488],{"class":202,"line":3660},[200,85489,528],{"emptyLinePlaceholder":20},[200,85491,85492,85494,85496],{"class":202,"line":4035},[200,85493,803],{"class":214},[200,85495,23619],{"class":235},[200,85497,21527],{"class":214},[200,85499,85500,85502,85504],{"class":202,"line":4490},[200,85501,892],{"class":214},[200,85503,11400],{"class":235},[200,85505,647],{"class":214},[200,85507,85508,85510,85512],{"class":202,"line":4496},[200,85509,680],{"class":214},[200,85511,21468],{"class":235},[200,85513,647],{"class":214},[192,85515,85516],{"className":21457,"code":23640,"filename":23641,"language":12075,"meta":13,"style":13},[156,85517,85518,85538,85552,85556,85568,85576,85584,85590,85594,85606,85616,85620,85634,85642,85656,85668,85672,85680,85686,85694,85698,85706,85710,85718,85724,85742,85760,85764,85776,85782,85794,85808,85812,85820,85828],{"__ignoreMap":13},[200,85519,85520,85522,85524,85526,85528,85530,85532,85534,85536],{"class":202,"line":203},[200,85521,599],{"class":214},[200,85523,14896],{"class":235},[200,85525,14594],{"class":379},[200,85527,449],{"class":214},[200,85529,610],{"class":214},[200,85531,1383],{"class":245},[200,85533,610],{"class":214},[200,85535,21825],{"class":379},[200,85537,647],{"class":214},[200,85539,85540,85542,85544,85546,85548,85550],{"class":202,"line":14},[200,85541,972],{"class":206},[200,85543,23670],{"class":210},[200,85545,1489],{"class":206},[200,85547,242],{"class":214},[200,85549,23677],{"class":245},[200,85551,991],{"class":214},[200,85553,85554],{"class":202,"line":255},[200,85555,528],{"emptyLinePlaceholder":20},[200,85557,85558,85560,85562,85564,85566],{"class":202,"line":272},[200,85559,443],{"class":379},[200,85561,23690],{"class":210},[200,85563,449],{"class":214},[200,85565,23695],{"class":223},[200,85567,23698],{"class":214},[200,85569,85570,85572,85574],{"class":202,"line":283},[200,85571,23703],{"class":235},[200,85573,239],{"class":214},[200,85575,23708],{"class":1364},[200,85577,85578,85580,85582],{"class":202,"line":300},[200,85579,258],{"class":235},[200,85581,239],{"class":214},[200,85583,23708],{"class":1364},[200,85585,85586,85588],{"class":202,"line":310},[200,85587,23721],{"class":214},[200,85589,466],{"class":210},[200,85591,85592],{"class":202,"line":336},[200,85593,528],{"emptyLinePlaceholder":20},[200,85595,85596,85598,85600,85602,85604],{"class":202,"line":342},[200,85597,443],{"class":379},[200,85599,23131],{"class":210},[200,85601,449],{"class":214},[200,85603,23136],{"class":223},[200,85605,466],{"class":210},[200,85607,85608,85610,85612,85614],{"class":202,"line":365},[200,85609,14908],{"class":379},[200,85611,23456],{"class":223},[200,85613,1549],{"class":214},[200,85615,280],{"class":214},[200,85617,85618],{"class":202,"line":371},[200,85619,23160],{"class":210},[200,85621,85622,85624,85626,85628,85630,85632],{"class":202,"line":398},[200,85623,3770],{"class":214},[200,85625,23167],{"class":223},[200,85627,227],{"class":235},[200,85629,23764],{"class":210},[200,85631,549],{"class":214},[200,85633,280],{"class":214},[200,85635,85636,85638,85640],{"class":202,"line":405},[200,85637,23484],{"class":235},[200,85639,239],{"class":214},[200,85641,280],{"class":214},[200,85643,85644,85646,85648,85650,85652,85654],{"class":202,"line":410},[200,85645,23493],{"class":235},[200,85647,239],{"class":214},[200,85649,242],{"class":214},[200,85651,23787],{"class":245},[200,85653,249],{"class":214},[200,85655,252],{"class":214},[200,85657,85658,85660,85662,85664,85666],{"class":202,"line":860},[200,85659,20017],{"class":235},[200,85661,239],{"class":214},[200,85663,242],{"class":214},[200,85665,23802],{"class":245},[200,85667,991],{"class":214},[200,85669,85670],{"class":202,"line":889},[200,85671,12098],{"class":214},[200,85673,85674,85676,85678],{"class":202,"line":899},[200,85675,23526],{"class":235},[200,85677,239],{"class":214},[200,85679,22236],{"class":2438},[200,85681,85682,85684],{"class":202,"line":904},[200,85683,2111],{"class":214},[200,85685,416],{"class":235},[200,85687,85688,85690,85692],{"class":202,"line":930},[200,85689,3770],{"class":214},[200,85691,18917],{"class":223},[200,85693,466],{"class":235},[200,85695,85696],{"class":202,"line":3655},[200,85697,1129],{"class":214},[200,85699,85700,85702,85704],{"class":202,"line":3660},[200,85701,680],{"class":214},[200,85703,14896],{"class":235},[200,85705,647],{"class":214},[200,85707,85708],{"class":202,"line":4035},[200,85709,528],{"emptyLinePlaceholder":20},[200,85711,85712,85714,85716],{"class":202,"line":4490},[200,85713,599],{"class":214},[200,85715,21468],{"class":235},[200,85717,647],{"class":214},[200,85719,85720,85722],{"class":202,"line":4496},[200,85721,656],{"class":214},[200,85723,21477],{"class":235},[200,85725,85726,85728,85730,85732,85734,85736,85738,85740],{"class":202,"line":4502},[200,85727,23865],{"class":214},[200,85729,3581],{"class":379},[200,85731,449],{"class":214},[200,85733,610],{"class":214},[200,85735,23874],{"class":210},[200,85737,215],{"class":214},[200,85739,3581],{"class":210},[200,85741,616],{"class":214},[200,85743,85744,85746,85748,85750,85752,85754,85756,85758],{"class":202,"line":4508},[200,85745,23865],{"class":214},[200,85747,3597],{"class":379},[200,85749,449],{"class":214},[200,85751,610],{"class":214},[200,85753,23874],{"class":210},[200,85755,215],{"class":214},[200,85757,3597],{"class":210},[200,85759,616],{"class":214},[200,85761,85762],{"class":202,"line":5469},[200,85763,11550],{"class":214},[200,85765,85766,85768,85770,85772,85774],{"class":202,"line":5474},[200,85767,803],{"class":214},[200,85769,21468],{"class":235},[200,85771,23911],{"class":214},[200,85773,23914],{"class":379},[200,85775,647],{"class":214},[200,85777,85778,85780],{"class":202,"line":13237},[200,85779,22071],{"class":214},[200,85781,23923],{"class":235},[200,85783,85784,85786,85788,85790,85792],{"class":202,"line":13263},[200,85785,23928],{"class":379},[200,85787,449],{"class":214},[200,85789,610],{"class":214},[200,85791,23935],{"class":245},[200,85793,616],{"class":214},[200,85795,85796,85798,85800,85802,85804,85806],{"class":202,"line":13279},[200,85797,23942],{"class":214},[200,85799,22420],{"class":379},[200,85801,449],{"class":214},[200,85803,610],{"class":214},[200,85805,23604],{"class":210},[200,85807,616],{"class":214},[200,85809,85810],{"class":202,"line":13323},[200,85811,23957],{"class":214},[200,85813,85814,85816,85818],{"class":202,"line":13329},[200,85815,15078],{"class":214},[200,85817,21468],{"class":235},[200,85819,647],{"class":214},[200,85821,85822,85824,85826],{"class":202,"line":13334},[200,85823,892],{"class":214},[200,85825,21534],{"class":235},[200,85827,647],{"class":214},[200,85829,85830,85832,85834],{"class":202,"line":13360},[200,85831,680],{"class":214},[200,85833,21468],{"class":235},[200,85835,647],{"class":214},[192,85837,85838],{"className":21457,"code":23984,"filename":23985,"language":12075,"meta":13,"style":13},[156,85839,85840,85860,85872,85880,85888,85894,85902,85906,85914,85920,85938,85956,85960],{"__ignoreMap":13},[200,85841,85842,85844,85846,85848,85850,85852,85854,85856,85858],{"class":202,"line":203},[200,85843,599],{"class":214},[200,85845,14896],{"class":235},[200,85847,14594],{"class":379},[200,85849,449],{"class":214},[200,85851,610],{"class":214},[200,85853,1383],{"class":245},[200,85855,610],{"class":214},[200,85857,21825],{"class":379},[200,85859,647],{"class":214},[200,85861,85862,85864,85866,85868,85870],{"class":202,"line":14},[200,85863,443],{"class":379},[200,85865,23690],{"class":210},[200,85867,449],{"class":214},[200,85869,23695],{"class":223},[200,85871,23698],{"class":214},[200,85873,85874,85876,85878],{"class":202,"line":255},[200,85875,23703],{"class":235},[200,85877,239],{"class":214},[200,85879,23708],{"class":1364},[200,85881,85882,85884,85886],{"class":202,"line":272},[200,85883,258],{"class":235},[200,85885,239],{"class":214},[200,85887,23708],{"class":1364},[200,85889,85890,85892],{"class":202,"line":283},[200,85891,23721],{"class":214},[200,85893,466],{"class":210},[200,85895,85896,85898,85900],{"class":202,"line":300},[200,85897,680],{"class":214},[200,85899,14896],{"class":235},[200,85901,647],{"class":214},[200,85903,85904],{"class":202,"line":310},[200,85905,528],{"emptyLinePlaceholder":20},[200,85907,85908,85910,85912],{"class":202,"line":336},[200,85909,599],{"class":214},[200,85911,21468],{"class":235},[200,85913,647],{"class":214},[200,85915,85916,85918],{"class":202,"line":342},[200,85917,656],{"class":214},[200,85919,21477],{"class":235},[200,85921,85922,85924,85926,85928,85930,85932,85934,85936],{"class":202,"line":365},[200,85923,23865],{"class":214},[200,85925,3581],{"class":379},[200,85927,449],{"class":214},[200,85929,610],{"class":214},[200,85931,23874],{"class":210},[200,85933,215],{"class":214},[200,85935,3581],{"class":210},[200,85937,616],{"class":214},[200,85939,85940,85942,85944,85946,85948,85950,85952,85954],{"class":202,"line":371},[200,85941,23865],{"class":214},[200,85943,3597],{"class":379},[200,85945,449],{"class":214},[200,85947,610],{"class":214},[200,85949,23874],{"class":210},[200,85951,215],{"class":214},[200,85953,3597],{"class":210},[200,85955,616],{"class":214},[200,85957,85958],{"class":202,"line":398},[200,85959,23365],{"class":214},[200,85961,85962,85964,85966],{"class":202,"line":405},[200,85963,680],{"class":214},[200,85965,21468],{"class":235},[200,85967,647],{"class":214},[35,85969,21630],{},[35,85971,24120,85972],{},[1599,85973,24124,85974,215],{"repo":24123},[39,85975,24129],{"href":24127,"rel":85976},[77],[130,85978,132,85979,132,85982],{},[24133,85980],{"src":24127,"width":24135,"height":24136,"frameBorder":3948,"scrolling":24137,"allowFullScreen":20,"className":85981},[24139],[143,85983,24142],{},[51,85985,24146],{"id":24145},[35,85987,24149],{},[35,85989,24152,85990,24158,85993,24166],{},[39,85991,24157],{"href":24155,"rel":85992},[77],[39,85994,85996],{"href":24161,"rel":85995},[77],[156,85997,24165],{},[35,85999,24169,86000,24175,86003,1655,86006,24186,86009,24189],{},[39,86001,24174],{"href":24172,"rel":86002},[77],[39,86004,24180],{"href":24178,"rel":86005},[77],[39,86007,24185],{"href":24183,"rel":86008},[77],[156,86010,23098],{},[35,86012,24192],{},[1170,86014,24195],{},{"title":13,"searchDepth":14,"depth":14,"links":86016},[86017,86018,86019,86020,86021,86022],{"id":21403,"depth":14,"text":21404},{"id":21697,"depth":14,"text":21698},{"id":22024,"depth":14,"text":22025},{"id":22908,"depth":14,"text":22909},{"id":23386,"depth":14,"text":23387},{"id":24145,"depth":14,"text":24146},{},{"title":21381,"description":24205},{"loc":24207},{"id":24214,"title":24215,"body":86027,"contentId":9265,"date":28920,"description":28921,"extension":18,"meta":89870,"navigation":20,"path":28923,"rawbody":28924,"robots":23,"schemaOrg":23,"seo":89871,"sitemap":89872,"stem":28934,"__hash__":28935},{"type":7,"value":86028,"toc":89857},[86029,86033,86042,86056,86060,86069,86075,86089,86091,86147,86149,86151,86153,86171,86179,86181,86187,86189,86197,86199,86201,86215,86219,86223,86429,86431,86433,86435,86441,86445,86447,86449,86456,86460,86462,86546,86556,86558,86562,86576,86692,86702,86704,86724,86816,86820,86839,86863,86868,86872,86881,86891,86893,86917,86921,86923,86927,86929,86931,86933,86954,87022,87024,87038,87218,87222,87226,87230,87232,87234,87238,87240,87246,87248,87256,87258,87260,87262,87268,87270,87272,87274,87282,87289,87293,87297,87304,87309,87314,87320,87325,87336,87380,87384,87440,87446,87448,87508,87510,87550,87552,87636,87645,87651,87655,87664,87674,87688,87698,87700,89855],[35,86030,24220,86031,215],{},[6862,86032,24223],{},[35,86034,24226,86035,24232,86038,106,86040],{},[39,86036,24231],{"href":24229,"rel":86037},[77],[6862,86039,24235],{},[1216,86041,24238],{},[35,86043,24241,86044,6865,86047,24250,86050,1655,86052,24257,86054,24260],{},[39,86045,24246],{"href":24244,"rel":86046},[77],[39,86048,17876],{"href":17874,"rel":86049},[77],[6862,86051,24253],{},[6862,86053,24256],{},[6862,86055,19463],{},[35,86057,86058],{},[1707,86059],{"alt":24265,"src":24266},[35,86061,24269,86062,24277,86067,24280],{},[39,86063,86065],{"href":24272,"rel":86064},[77],[6862,86066,24276],{},[6862,86068,24253],{},[938,86070,86071],{},[35,86072,86073,24287],{},[200,86074,944],{},[35,86076,24290,86077,24296,86080,13741,86082,24302,86084,24308,86087,24312],{},[39,86078,24295],{"href":24293,"rel":86079},[77],[6862,86081,24299],{},[6862,86083,2934],{},[39,86085,24307],{"href":24305,"rel":86086},[77],[156,86088,24311],{},[35,86090,24315],{},[192,86092,86093],{"className":21457,"code":24318,"language":12075,"meta":13,"style":13},[156,86094,86095,86103,86107,86111,86119,86123,86131,86135,86139],{"__ignoreMap":13},[200,86096,86097,86099,86101],{"class":202,"line":203},[200,86098,599],{"class":214},[200,86100,19563],{"class":235},[200,86102,647],{"class":214},[200,86104,86105],{"class":202,"line":14},[200,86106,24333],{"class":210},[200,86108,86109],{"class":202,"line":255},[200,86110,24338],{"class":210},[200,86112,86113,86115,86117],{"class":202,"line":272},[200,86114,680],{"class":214},[200,86116,19563],{"class":235},[200,86118,647],{"class":214},[200,86120,86121],{"class":202,"line":283},[200,86122,528],{"emptyLinePlaceholder":20},[200,86124,86125,86127,86129],{"class":202,"line":300},[200,86126,599],{"class":214},[200,86128,21468],{"class":235},[200,86130,647],{"class":214},[200,86132,86133],{"class":202,"line":310},[200,86134,24363],{"class":401},[200,86136,86137],{"class":202,"line":336},[200,86138,24368],{"class":210},[200,86140,86141,86143,86145],{"class":202,"line":342},[200,86142,680],{"class":214},[200,86144,21468],{"class":235},[200,86146,647],{"class":214},[1158,86148],{},[35,86150,24381],{},[35,86152,24384],{},[68,86154,86155,86157],{},[71,86156,24389],{},[71,86158,24392,86159,3092,86161,9502,86163,24400,86165,17891,86168,24409],{},[6862,86160,19463],{},[6862,86162,17998],{},[6862,86164,24399],{},[39,86166,14044],{"href":14042,"rel":86167},[77],[39,86169,24408],{"href":24406,"rel":86170},[77],[938,86172,86173],{},[35,86174,86175,24416,86177,24419],{},[200,86176,944],{},[6862,86178,24399],{},[35,86180,24422],{},[938,86182,86183],{},[35,86184,86185,24429],{},[200,86186,944],{},[51,86188,24433],{"id":24432},[35,86190,24436,86191,24440,86194,24445],{},[39,86192,24231],{"href":24229,"rel":86193},[77],[39,86195,24444],{"href":13738,"rel":86196},[77],[35,86198,24448],{},[35,86200,24451],{},[130,86202,132,86203,132,86207],{},[134,86204,136,86205,132],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,86206],{"src":24458,"type":141},[143,86208,24461,86209,1655,86211,3092,86213,24471],{},[39,86210,17876],{"href":17874,"target":7156},[39,86212,24466],{"href":24305,"target":7156},[39,86214,24470],{"href":24469,"target":7156},[35,86216,24474,86217,24478],{},[156,86218,24477],{},[35,86220,24481,86221,24485],{},[156,86222,24484],{},[192,86224,86225],{"className":21457,"code":24488,"language":12075,"meta":13,"style":13},[156,86226,86227,86235,86239,86243,86247,86251,86255,86259,86267,86271,86291,86305,86313,86317,86325,86331,86347,86365,86377,86417,86421],{"__ignoreMap":13},[200,86228,86229,86231,86233],{"class":202,"line":203},[200,86230,599],{"class":214},[200,86232,19563],{"class":235},[200,86234,647],{"class":214},[200,86236,86237],{"class":202,"line":14},[200,86238,24503],{"class":210},[200,86240,86241],{"class":202,"line":255},[200,86242,24508],{"class":210},[200,86244,86245],{"class":202,"line":272},[200,86246,528],{"emptyLinePlaceholder":20},[200,86248,86249],{"class":202,"line":283},[200,86250,24517],{"class":210},[200,86252,86253],{"class":202,"line":300},[200,86254,528],{"emptyLinePlaceholder":20},[200,86256,86257],{"class":202,"line":310},[200,86258,24526],{"class":210},[200,86260,86261,86263,86265],{"class":202,"line":336},[200,86262,680],{"class":214},[200,86264,19563],{"class":235},[200,86266,647],{"class":214},[200,86268,86269],{"class":202,"line":342},[200,86270,528],{"emptyLinePlaceholder":20},[200,86272,86273,86275,86277,86279,86281,86283,86285,86287,86289],{"class":202,"line":365},[200,86274,599],{"class":214},[200,86276,14896],{"class":235},[200,86278,14594],{"class":379},[200,86280,449],{"class":214},[200,86282,610],{"class":214},[200,86284,1383],{"class":245},[200,86286,610],{"class":214},[200,86288,21825],{"class":379},[200,86290,647],{"class":214},[200,86292,86293,86295,86297,86299,86301,86303],{"class":202,"line":371},[200,86294,972],{"class":206},[200,86296,24565],{"class":210},[200,86298,1489],{"class":206},[200,86300,242],{"class":214},[200,86302,24572],{"class":245},[200,86304,991],{"class":214},[200,86306,86307,86309,86311],{"class":202,"line":398},[200,86308,680],{"class":214},[200,86310,14896],{"class":235},[200,86312,647],{"class":214},[200,86314,86315],{"class":202,"line":405},[200,86316,528],{"emptyLinePlaceholder":20},[200,86318,86319,86321,86323],{"class":202,"line":410},[200,86320,599],{"class":214},[200,86322,21468],{"class":235},[200,86324,647],{"class":214},[200,86326,86327,86329],{"class":202,"line":860},[200,86328,656],{"class":214},[200,86330,24601],{"class":235},[200,86332,86333,86335,86337,86339,86341,86343,86345],{"class":202,"line":889},[200,86334,23309],{"class":206},[200,86336,449],{"class":214},[200,86338,610],{"class":214},[200,86340,24612],{"class":210},[200,86342,24615],{"class":214},[200,86344,24618],{"class":210},[200,86346,616],{"class":214},[200,86348,86349,86351,86353,86355,86357,86359,86361,86363],{"class":202,"line":899},[200,86350,23865],{"class":214},[200,86352,24627],{"class":379},[200,86354,449],{"class":214},[200,86356,610],{"class":214},[200,86358,24612],{"class":210},[200,86360,215],{"class":214},[200,86362,3295],{"class":210},[200,86364,616],{"class":214},[200,86366,86367,86369,86371,86373,86375],{"class":202,"line":904},[200,86368,23337],{"class":379},[200,86370,449],{"class":214},[200,86372,610],{"class":214},[200,86374,24612],{"class":210},[200,86376,616],{"class":214},[200,86378,86379,86381,86383,86385,86387,86389,86391,86393,86395,86397,86399,86401,86403,86405,86407,86409,86411,86413,86415],{"class":202,"line":930},[200,86380,24656],{"class":214},[200,86382,24484],{"class":379},[200,86384,449],{"class":214},[200,86386,610],{"class":214},[200,86388,24484],{"class":223},[200,86390,24667],{"class":214},[200,86392,24612],{"class":210},[200,86394,24672],{"class":214},[200,86396,10944],{"class":223},[200,86398,227],{"class":214},[200,86400,24679],{"class":386},[200,86402,393],{"class":379},[200,86404,24684],{"class":210},[200,86406,215],{"class":214},[200,86408,24689],{"class":210},[200,86410,24692],{"class":214},[200,86412,24679],{"class":210},[200,86414,2104],{"class":214},[200,86416,616],{"class":214},[200,86418,86419],{"class":202,"line":3655},[200,86420,23365],{"class":214},[200,86422,86423,86425,86427],{"class":202,"line":3660},[200,86424,680],{"class":214},[200,86426,21468],{"class":235},[200,86428,647],{"class":214},[35,86430,24713],{},[35,86432,24716],{},[35,86434,24719],{},[68,86436,86437,86439],{},[71,86438,24724],{},[71,86440,24727],{},[68,86442,86443],{"start":272},[71,86444,24732],{},[35,86446,24735],{},[51,86448,24724],{"id":24738},[35,86450,24741,86451,106,86454],{},[39,86452,24399],{"href":24744,"rel":86453},[77],[1216,86455,24748],{},[35,86457,24751,86458,24754],{},[6862,86459,24399],{},[35,86461,24757],{},[192,86463,86464],{"className":21457,"code":24760,"language":12075,"meta":13,"style":13},[156,86465,86466,86476,86480,86488,86492,86500,86508,86512,86520,86524,86534,86538],{"__ignoreMap":13},[200,86467,86468,86470,86472,86474],{"class":202,"line":203},[200,86469,599],{"class":214},[200,86471,14896],{"class":235},[200,86473,21825],{"class":379},[200,86475,647],{"class":214},[200,86477,86478],{"class":202,"line":14},[200,86479,24777],{"class":401},[200,86481,86482,86484,86486],{"class":202,"line":255},[200,86483,680],{"class":214},[200,86485,14896],{"class":235},[200,86487,647],{"class":214},[200,86489,86490],{"class":202,"line":272},[200,86491,528],{"emptyLinePlaceholder":20},[200,86493,86494,86496,86498],{"class":202,"line":283},[200,86495,599],{"class":214},[200,86497,21468],{"class":235},[200,86499,647],{"class":214},[200,86501,86502,86504,86506],{"class":202,"line":300},[200,86503,656],{"class":214},[200,86505,11400],{"class":235},[200,86507,21527],{"class":214},[200,86509,86510],{"class":202,"line":310},[200,86511,24810],{"class":401},[200,86513,86514,86516,86518],{"class":202,"line":336},[200,86515,680],{"class":214},[200,86517,21468],{"class":235},[200,86519,647],{"class":214},[200,86521,86522],{"class":202,"line":342},[200,86523,528],{"emptyLinePlaceholder":20},[200,86525,86526,86528,86530,86532],{"class":202,"line":365},[200,86527,599],{"class":214},[200,86529,1170],{"class":235},[200,86531,24831],{"class":379},[200,86533,647],{"class":214},[200,86535,86536],{"class":202,"line":371},[200,86537,24838],{"class":401},[200,86539,86540,86542,86544],{"class":202,"line":398},[200,86541,680],{"class":214},[200,86543,1170],{"class":235},[200,86545,647],{"class":214},[35,86547,24849,86548,24852,86550,24856,86553,215],{},[156,86549,11866],{},[39,86551,13740],{"href":13738,"rel":86552},[77],[39,86554,13579],{"href":13577,"rel":86555},[77],[35,86557,24862],{},[35,86559,86560],{},[1707,86561],{"alt":24867,"src":24868},[35,86563,24871,86564,24874,86566,24882,86571,24889],{},[156,86565,11866],{},[39,86567,86569],{"href":24877,"rel":86568},[77],[156,86570,24881],{},[39,86572,86574],{"href":24885,"rel":86573},[77],[156,86575,11785],{},[192,86577,86578],{"className":1380,"code":24892,"language":1383,"meta":13,"style":13},[156,86579,86580,86598,86624,86630,86634,86638,86650,86660,86666,86672,86676,86688],{"__ignoreMap":13},[200,86581,86582,86584,86586,86588,86590,86592,86594,86596],{"class":202,"line":203},[200,86583,14908],{"class":379},[200,86585,11830],{"class":223},[200,86587,227],{"class":214},[200,86589,156],{"class":386},[200,86591,549],{"class":214},[200,86593,24909],{"class":386},[200,86595,2104],{"class":214},[200,86597,280],{"class":214},[200,86599,86600,86602,86604,86606,86608,86610,86612,86614,86616,86618,86620,86622],{"class":202,"line":14},[200,86601,20098],{"class":206},[200,86603,1094],{"class":235},[200,86605,11852],{"class":214},[200,86607,24924],{"class":210},[200,86609,215],{"class":214},[200,86611,11859],{"class":223},[200,86613,227],{"class":235},[200,86615,249],{"class":214},[200,86617,11866],{"class":245},[200,86619,249],{"class":214},[200,86621,6478],{"class":235},[200,86623,230],{"class":214},[200,86625,86626,86628],{"class":202,"line":255},[200,86627,3311],{"class":206},[200,86629,24947],{"class":210},[200,86631,86632],{"class":202,"line":272},[200,86633,4505],{"class":214},[200,86635,86636],{"class":202,"line":283},[200,86637,528],{"emptyLinePlaceholder":20},[200,86639,86640,86642,86644,86646,86648],{"class":202,"line":300},[200,86641,3758],{"class":206},[200,86643,13007],{"class":214},[200,86645,24964],{"class":223},[200,86647,227],{"class":235},[200,86649,230],{"class":214},[200,86651,86652,86654,86656,86658],{"class":202,"line":310},[200,86653,24973],{"class":235},[200,86655,239],{"class":214},[200,86657,24978],{"class":210},[200,86659,252],{"class":214},[200,86661,86662,86664],{"class":202,"line":336},[200,86663,24985],{"class":210},[200,86665,252],{"class":214},[200,86667,86668,86670],{"class":202,"line":342},[200,86669,5119],{"class":210},[200,86671,252],{"class":214},[200,86673,86674],{"class":202,"line":365},[200,86675,24998],{"class":210},[200,86677,86678,86680,86682,86684,86686],{"class":202,"line":371},[200,86679,5095],{"class":214},[200,86681,2104],{"class":235},[200,86683,215],{"class":214},[200,86685,11785],{"class":223},[200,86687,466],{"class":235},[200,86689,86690],{"class":202,"line":398},[200,86691,1129],{"class":214},[35,86693,15736,86694,25019,86696,25023,86698,25027,86700,25030],{},[156,86695,11785],{},[156,86697,25022],{},[156,86699,25026],{},[156,86701,11866],{},[35,86703,25033],{},[68,86705,86706],{},[71,86707,86708,25043,86711,25049,86714,25052,86716,25058,86719,25067],{},[39,86709,25042],{"href":25040,"rel":86710},[77],[39,86712,25048],{"href":25046,"rel":86713},[77],[156,86715,13740],{},[39,86717,25057],{"href":25055,"rel":86718},[77],[39,86720,25063,86722],{"href":25061,"rel":86721},[77],[156,86723,25066],{},[192,86725,86726],{"className":25070,"code":25071,"language":25072,"meta":13,"style":13},[156,86727,86728,86746,86750,86776],{"__ignoreMap":13},[200,86729,86730,86732,86734,86736,86738,86740,86742,86744],{"class":202,"line":203},[200,86731,972],{"class":206},[200,86733,318],{"class":214},[200,86735,25083],{"class":210},[200,86737,980],{"class":214},[200,86739,983],{"class":206},[200,86741,242],{"class":214},[200,86743,25048],{"class":245},[200,86745,991],{"class":214},[200,86747,86748],{"class":202,"line":14},[200,86749,528],{"emptyLinePlaceholder":20},[200,86751,86752,86754,86756,86758,86760,86762,86764,86766,86768,86770,86772,86774],{"class":202,"line":255},[200,86753,443],{"class":379},[200,86755,25104],{"class":210},[200,86757,449],{"class":214},[200,86759,25083],{"class":223},[200,86761,25111],{"class":210},[200,86763,549],{"class":214},[200,86765,318],{"class":214},[200,86767,25118],{"class":210},[200,86769,413],{"class":214},[200,86771,2104],{"class":210},[200,86773,215],{"class":214},[200,86775,25127],{"class":210},[200,86777,86778,86780,86782,86784,86786,86788,86790,86792,86794,86796,86798,86800,86802,86804,86806,86808,86810,86812,86814],{"class":202,"line":272},[200,86779,443],{"class":379},[200,86781,25134],{"class":210},[200,86783,449],{"class":214},[200,86785,25139],{"class":210},[200,86787,215],{"class":214},[200,86789,25144],{"class":210},[200,86791,215],{"class":214},[200,86793,3281],{"class":223},[200,86795,227],{"class":210},[200,86797,25153],{"class":386},[200,86799,393],{"class":379},[200,86801,25158],{"class":210},[200,86803,215],{"class":214},[200,86805,25163],{"class":210},[200,86807,6558],{"class":214},[200,86809,242],{"class":214},[200,86811,19563],{"class":245},[200,86813,249],{"class":214},[200,86815,416],{"class":210},[35,86817,86818],{},[1707,86819],{"alt":25178,"src":25179},[68,86821,86822,86827,86834],{"start":14},[71,86823,86824,25189],{},[39,86825,25188],{"href":25186,"rel":86826},[77],[71,86828,86829,25197,86832,215],{},[39,86830,25196],{"href":25194,"rel":86831},[77],[39,86833,25201],{"href":25200},[71,86835,86836,25209],{},[39,86837,25208],{"href":25206,"rel":86838},[77],[192,86840,86841],{"className":1380,"code":25212,"language":1383,"meta":13,"style":13},[156,86842,86843],{"__ignoreMap":13},[200,86844,86845,86847,86849,86851,86853,86855,86857,86859,86861],{"class":202,"line":203},[200,86846,207],{"class":206},[200,86848,25221],{"class":223},[200,86850,25224],{"class":210},[200,86852,249],{"class":214},[200,86854,25229],{"class":245},[200,86856,249],{"class":214},[200,86858,549],{"class":214},[200,86860,25236],{"class":214},[200,86862,25239],{"class":210},[35,86864,25242,86865,25248],{},[39,86866,25247],{"href":25245,"rel":86867},[77],[35,86869,86870],{},[1707,86871],{"alt":25253,"src":25254},[35,86873,25257,86874,25263,86877,25267,86879,215],{},[39,86875,25262],{"href":25260,"rel":86876},[77],[156,86878,25266],{},[39,86880,25201],{"href":25270},[68,86882,86883],{"start":283},[71,86884,25275,86885,25281,86888,25287],{},[39,86886,25280],{"href":25278,"rel":86887},[77],[39,86889,25286],{"href":25284,"rel":86890},[77],[35,86892,25290],{},[192,86894,86895],{"className":1380,"code":25293,"language":1383,"meta":13,"style":13},[156,86896,86897],{"__ignoreMap":13},[200,86898,86899,86901,86903,86905,86907,86909,86911,86913,86915],{"class":202,"line":203},[200,86900,207],{"class":206},[200,86902,25221],{"class":223},[200,86904,25224],{"class":210},[200,86906,249],{"class":214},[200,86908,25308],{"class":245},[200,86910,249],{"class":214},[200,86912,549],{"class":214},[200,86914,25236],{"class":214},[200,86916,25239],{"class":210},[68,86918,86919],{"start":300},[71,86920,25321],{},[35,86922,25324],{},[35,86924,86925],{},[1707,86926],{"alt":25329,"src":25330},[35,86928,25333],{},[35,86930,25336],{},[35,86932,25339],{},[68,86934,86935,86946],{},[71,86936,5778,86937,25349,86940,25355,86943,25361],{},[39,86938,25348],{"href":25346,"rel":86939},[77],[39,86941,25354],{"href":25352,"rel":86942},[77],[39,86944,25360],{"href":25358,"rel":86945},[77],[71,86947,25364,86948,25370,86951,215],{},[39,86949,25369],{"href":25367,"rel":86950},[77],[39,86952,25375],{"href":25373,"rel":86953},[77],[192,86955,86956],{"className":1380,"code":25378,"language":1383,"meta":13,"style":13},[156,86957,86958,86972,87008,87012,87018],{"__ignoreMap":13},[200,86959,86960,86962,86964,86966,86968,86970],{"class":202,"line":203},[200,86961,14908],{"class":379},[200,86963,25387],{"class":223},[200,86965,227],{"class":214},[200,86967,3123],{"class":386},[200,86969,2104],{"class":214},[200,86971,280],{"class":214},[200,86973,86974,86976,86978,86980,86982,86984,86986,86988,86990,86992,86994,86996,86998,87000,87002,87004,87006],{"class":202,"line":14},[200,86975,25400],{"class":210},[200,86977,215],{"class":214},[200,86979,25405],{"class":210},[200,86981,215],{"class":214},[200,86983,25410],{"class":223},[200,86985,227],{"class":235},[200,86987,249],{"class":214},[200,86989,25417],{"class":245},[200,86991,249],{"class":214},[200,86993,549],{"class":214},[200,86995,1094],{"class":214},[200,86997,25426],{"class":386},[200,86999,549],{"class":214},[200,87001,24909],{"class":386},[200,87003,2104],{"class":214},[200,87005,393],{"class":379},[200,87007,280],{"class":214},[200,87009,87010],{"class":202,"line":255},[200,87011,25441],{"class":401},[200,87013,87014,87016],{"class":202,"line":272},[200,87015,5095],{"class":214},[200,87017,416],{"class":235},[200,87019,87020],{"class":202,"line":283},[200,87021,1129],{"class":214},[35,87023,25454],{},[68,87025,87026,87031],{},[71,87027,87028,25464],{},[39,87029,25463],{"href":25461,"rel":87030},[77],[71,87032,87033,25472,87036,25475],{},[39,87034,25471],{"href":25469,"rel":87035},[77],[156,87037,11785],{},[192,87039,87040],{"className":1380,"code":25478,"language":1383,"meta":13,"style":13},[156,87041,87042,87056,87094,87118,87146,87150,87162,87172,87182,87188,87192,87204,87208,87214],{"__ignoreMap":13},[200,87043,87044,87046,87048,87050,87052,87054],{"class":202,"line":203},[200,87045,14908],{"class":379},[200,87047,25387],{"class":223},[200,87049,227],{"class":214},[200,87051,3123],{"class":386},[200,87053,2104],{"class":214},[200,87055,280],{"class":214},[200,87057,87058,87060,87062,87064,87066,87068,87070,87072,87074,87076,87078,87080,87082,87084,87086,87088,87090,87092],{"class":202,"line":14},[200,87059,25400],{"class":210},[200,87061,215],{"class":214},[200,87063,25405],{"class":210},[200,87065,215],{"class":214},[200,87067,25410],{"class":223},[200,87069,227],{"class":235},[200,87071,249],{"class":214},[200,87073,25417],{"class":245},[200,87075,249],{"class":214},[200,87077,549],{"class":214},[200,87079,380],{"class":379},[200,87081,1094],{"class":214},[200,87083,25426],{"class":386},[200,87085,549],{"class":214},[200,87087,24909],{"class":386},[200,87089,2104],{"class":214},[200,87091,393],{"class":379},[200,87093,280],{"class":214},[200,87095,87096,87098,87100,87102,87104,87106,87108,87110,87112,87114,87116],{"class":202,"line":255},[200,87097,5267],{"class":206},[200,87099,1094],{"class":235},[200,87101,24924],{"class":210},[200,87103,215],{"class":214},[200,87105,11859],{"class":223},[200,87107,227],{"class":235},[200,87109,249],{"class":214},[200,87111,11866],{"class":245},[200,87113,249],{"class":214},[200,87115,6478],{"class":235},[200,87117,230],{"class":214},[200,87119,87120,87122,87124,87126,87128,87130,87132,87134,87136,87138,87140,87142,87144],{"class":202,"line":272},[200,87121,20136],{"class":379},[200,87123,25563],{"class":210},[200,87125,509],{"class":214},[200,87127,25568],{"class":210},[200,87129,215],{"class":214},[200,87131,25573],{"class":223},[200,87133,227],{"class":235},[200,87135,24924],{"class":210},[200,87137,549],{"class":214},[200,87139,242],{"class":214},[200,87141,14750],{"class":245},[200,87143,249],{"class":214},[200,87145,416],{"class":235},[200,87147,87148],{"class":202,"line":283},[200,87149,528],{"emptyLinePlaceholder":20},[200,87151,87152,87154,87156,87158,87160],{"class":202,"line":300},[200,87153,25596],{"class":206},[200,87155,13007],{"class":214},[200,87157,24964],{"class":223},[200,87159,227],{"class":235},[200,87161,230],{"class":214},[200,87163,87164,87166,87168,87170],{"class":202,"line":310},[200,87165,25609],{"class":235},[200,87167,239],{"class":214},[200,87169,24978],{"class":210},[200,87171,252],{"class":214},[200,87173,87174,87176,87178,87180],{"class":202,"line":336},[200,87175,11899],{"class":235},[200,87177,239],{"class":214},[200,87179,25563],{"class":210},[200,87181,252],{"class":214},[200,87183,87184,87186],{"class":202,"line":342},[200,87185,25630],{"class":210},[200,87187,252],{"class":214},[200,87189,87190],{"class":202,"line":365},[200,87191,25637],{"class":210},[200,87193,87194,87196,87198,87200,87202],{"class":202,"line":371},[200,87195,2647],{"class":214},[200,87197,2104],{"class":235},[200,87199,215],{"class":214},[200,87201,11785],{"class":223},[200,87203,466],{"class":235},[200,87205,87206],{"class":202,"line":398},[200,87207,4977],{"class":214},[200,87209,87210,87212],{"class":202,"line":405},[200,87211,5095],{"class":214},[200,87213,416],{"class":235},[200,87215,87216],{"class":202,"line":410},[200,87217,1129],{"class":214},[35,87219,25666,87220,25669],{},[156,87221,11785],{},[35,87223,25672,87224,25676],{},[156,87225,25675],{},[35,87227,25679,87228,25682],{},[156,87229,11785],{},[35,87231,25685],{},[35,87233,25688],{},[35,87235,87236],{},[1707,87237],{"alt":25693,"src":25694},[35,87239,25697],{},[938,87241,87242],{},[35,87243,87244,25704],{},[200,87245,944],{},[35,87247,25707],{},[938,87249,87250],{},[35,87251,87252,25714,87254,25719],{},[200,87253,944],{},[39,87255,25718],{"href":25717},[51,87257,25723],{"id":25722},[35,87259,25726],{},[35,87261,25729],{},[68,87263,87264,87266],{},[71,87265,25734],{},[71,87267,25737],{},[35,87269,25740],{},[1929,87271,25744],{"id":25743},[35,87273,25747],{},[35,87275,25750,87276,25756,87279,25762],{},[39,87277,25755],{"href":25753,"rel":87278},[77],[39,87280,25761],{"href":25759,"rel":87281},[77],[35,87283,25765,87284,215],{},[39,87285,87287],{"href":25206,"rel":87286},[77],[156,87288,25771],{},[1929,87290,25775,87291],{"id":25774},[156,87292,25229],{},[35,87294,25780,87295,25783],{},[156,87296,25229],{},[35,87298,25786,87299,25789,87301,215],{},[156,87300,11180],{},[39,87302,25794],{"href":25792,"rel":87303},[77],[35,87305,25797,87306,25803],{},[39,87307,25802],{"href":25800,"rel":87308},[77],[35,87310,25806,87311,25812],{},[39,87312,25811],{"href":25809,"rel":87313},[77],[68,87315,87316,87318],{},[71,87317,25817],{},[71,87319,25820],{},[35,87321,25823,87322,215],{},[39,87323,25828],{"href":25826,"rel":87324},[77],[35,87326,25831,87327,25834,87329,25838,87331,25842,87333,215],{},[156,87328,25022],{},[156,87330,25837],{},[156,87332,25841],{},[39,87334,25847],{"href":25845,"rel":87335},[77],[192,87337,87338],{"className":19560,"code":25850,"filename":25851,"language":19563,"meta":13,"style":13},[156,87339,87340,87344,87348,87352,87356,87360,87364,87368,87372,87376],{"__ignoreMap":13},[200,87341,87342],{"class":202,"line":203},[200,87343,19600],{},[200,87345,87346],{"class":202,"line":14},[200,87347,528],{"emptyLinePlaceholder":20},[200,87349,87350],{"class":202,"line":255},[200,87351,25866],{},[200,87353,87354],{"class":202,"line":272},[200,87355,528],{"emptyLinePlaceholder":20},[200,87357,87358],{"class":202,"line":283},[200,87359,25875],{},[200,87361,87362],{"class":202,"line":300},[200,87363,230],{},[200,87365,87366],{"class":202,"line":310},[200,87367,25884],{},[200,87369,87370],{"class":202,"line":336},[200,87371,25889],{},[200,87373,87374],{"class":202,"line":342},[200,87375,25894],{},[200,87377,87378],{"class":202,"line":365},[200,87379,1129],{},[35,87381,25901,87382,25905],{},[156,87383,25904],{},[192,87385,87386],{"className":19560,"code":25908,"filename":25909,"language":19563,"meta":13,"style":13},[156,87387,87388,87392,87396,87400,87404,87408,87412,87416,87420,87424,87428,87432,87436],{"__ignoreMap":13},[200,87389,87390],{"class":202,"line":203},[200,87391,19600],{},[200,87393,87394],{"class":202,"line":14},[200,87395,528],{"emptyLinePlaceholder":20},[200,87397,87398],{"class":202,"line":255},[200,87399,25866],{},[200,87401,87402],{"class":202,"line":272},[200,87403,528],{"emptyLinePlaceholder":20},[200,87405,87406],{"class":202,"line":283},[200,87407,25932],{},[200,87409,87410],{"class":202,"line":300},[200,87411,230],{},[200,87413,87414],{"class":202,"line":310},[200,87415,25941],{},[200,87417,87418],{"class":202,"line":336},[200,87419,19661],{},[200,87421,87422],{"class":202,"line":342},[200,87423,25950],{},[200,87425,87426],{"class":202,"line":365},[200,87427,25955],{},[200,87429,87430],{"class":202,"line":371},[200,87431,13469],{},[200,87433,87434],{"class":202,"line":398},[200,87435,4977],{},[200,87437,87438],{"class":202,"line":405},[200,87439,1129],{},[938,87441,87442],{},[35,87443,87444,25974],{},[200,87445,944],{},[35,87447,25977],{},[192,87449,87450],{"className":4292,"code":25980,"language":4294,"meta":13,"style":13},[156,87451,87452,87456,87460,87464,87468,87472,87476,87480,87484,87488,87492,87496,87500,87504],{"__ignoreMap":13},[200,87453,87454],{"class":202,"line":203},[200,87455,25987],{},[200,87457,87458],{"class":202,"line":14},[200,87459,25992],{},[200,87461,87462],{"class":202,"line":255},[200,87463,25997],{},[200,87465,87466],{"class":202,"line":272},[200,87467,26002],{},[200,87469,87470],{"class":202,"line":283},[200,87471,26007],{},[200,87473,87474],{"class":202,"line":300},[200,87475,26012],{},[200,87477,87478],{"class":202,"line":310},[200,87479,26017],{},[200,87481,87482],{"class":202,"line":336},[200,87483,26022],{},[200,87485,87486],{"class":202,"line":342},[200,87487,26027],{},[200,87489,87490],{"class":202,"line":365},[200,87491,26032],{},[200,87493,87494],{"class":202,"line":371},[200,87495,26037],{},[200,87497,87498],{"class":202,"line":398},[200,87499,26042],{},[200,87501,87502],{"class":202,"line":405},[200,87503,25992],{},[200,87505,87506],{"class":202,"line":410},[200,87507,26051],{},[35,87509,26054],{},[192,87511,87512],{"className":21457,"code":26057,"language":12075,"meta":13,"style":13},[156,87513,87514,87522,87526,87530,87534,87538,87542],{"__ignoreMap":13},[200,87515,87516,87518,87520],{"class":202,"line":203},[200,87517,599],{"class":214},[200,87519,19563],{"class":235},[200,87521,647],{"class":214},[200,87523,87524],{"class":202,"line":14},[200,87525,26072],{"class":210},[200,87527,87528],{"class":202,"line":255},[200,87529,528],{"emptyLinePlaceholder":20},[200,87531,87532],{"class":202,"line":272},[200,87533,26081],{"class":210},[200,87535,87536],{"class":202,"line":283},[200,87537,528],{"emptyLinePlaceholder":20},[200,87539,87540],{"class":202,"line":300},[200,87541,26090],{"class":210},[200,87543,87544,87546,87548],{"class":202,"line":310},[200,87545,680],{"class":214},[200,87547,19563],{"class":235},[200,87549,647],{"class":214},[35,87551,26101],{},[192,87553,87554],{"className":19560,"code":26104,"language":19563,"meta":13,"style":13},[156,87555,87556,87560,87564,87568,87572,87576,87580,87584,87588,87592,87596,87600,87604,87608,87612,87616,87620,87624,87628,87632],{"__ignoreMap":13},[200,87557,87558],{"class":202,"line":203},[200,87559,19600],{},[200,87561,87562],{"class":202,"line":14},[200,87563,528],{"emptyLinePlaceholder":20},[200,87565,87566],{"class":202,"line":255},[200,87567,26119],{},[200,87569,87570],{"class":202,"line":272},[200,87571,26124],{},[200,87573,87574],{"class":202,"line":283},[200,87575,26129],{},[200,87577,87578],{"class":202,"line":300},[200,87579,26134],{},[200,87581,87582],{"class":202,"line":310},[200,87583,26139],{},[200,87585,87586],{"class":202,"line":336},[200,87587,528],{"emptyLinePlaceholder":20},[200,87589,87590],{"class":202,"line":342},[200,87591,26148],{},[200,87593,87594],{"class":202,"line":365},[200,87595,230],{},[200,87597,87598],{"class":202,"line":371},[200,87599,26157],{},[200,87601,87602],{"class":202,"line":398},[200,87603,26162],{},[200,87605,87606],{"class":202,"line":405},[200,87607,26167],{},[200,87609,87610],{"class":202,"line":410},[200,87611,26172],{},[200,87613,87614],{"class":202,"line":860},[200,87615,19661],{},[200,87617,87618],{"class":202,"line":889},[200,87619,26181],{},[200,87621,87622],{"class":202,"line":899},[200,87623,26186],{},[200,87625,87626],{"class":202,"line":904},[200,87627,26191],{},[200,87629,87630],{"class":202,"line":930},[200,87631,4977],{},[200,87633,87634],{"class":202,"line":3655},[200,87635,1129],{},[35,87637,26202,87638,26206,87640,215],{},[156,87639,26205],{},[39,87641,87643],{"href":26209,"rel":87642},[77],[156,87644,26213],{},[35,87646,26216,87647,26220,87649,26224],{},[156,87648,26219],{},[6862,87650,26223],{},[35,87652,26227,87653,26231],{},[156,87654,26230],{},[35,87656,26227,87657,26241,87662,26244],{},[39,87658,87660],{"href":26236,"rel":87659},[77],[156,87661,26240],{},[156,87663,23874],{},[938,87665,87666],{},[35,87667,87668,26251,87670,26255,87672,215],{},[200,87669,944],{},[156,87671,26254],{},[156,87673,26258],{},[938,87675,87676],{},[35,87677,87678,87680,26268,87682,26274,87685,215],{},[200,87679,944],{},[156,87681,26267],{},[39,87683,26273],{"href":26271,"rel":87684},[77],[39,87686,26279],{"href":26277,"rel":87687},[77],[35,87689,26282,87690,26286,87692,26292,87695,26297],{},[156,87691,26285],{},[39,87693,26291],{"href":26289,"rel":87694},[77],[39,87696,26296],{"href":25245,"rel":87697},[77],[35,87699,26300],{},[26302,87701,87702,87706,87708,87710,87712,87778,87785,87790,87796,87798,87800,87802,87809,87853,87859,87864,87908,87914,87916,87918,87920,87937,87961,87976,87992,87994,88006,88014,88016,88028,88032,88252,88267,88271,88427,88429,88431,88491,88493,88681,88700,88708,88714,88720,89070,89084,89086,89116,89120,89134,89136,89218,89224,89245,89247,89387,89396,89402,89404,89412,89416,89424,89426,89434,89436,89438,89440,89444,89446,89448,89452,89454,89456,89462,89464,89466,89472,89480,89818,89824,89828,89833,89838,89843,89851,89853],{"name":26304,":items":26305,":default-expanded":26306},[35,87703,26309,87704,215],{},[39,87705,25201],{"href":26312},[1929,87707,26316],{"id":26315},[35,87709,26319],{},[35,87711,26322],{},[26324,87713,87714,87730],{},[26327,87715,87716],{},[26330,87717,87718,87720,87722,87724,87726,87728],{},[26333,87719,3295],{},[26333,87721,11180],{},[26333,87723,26339],{},[26333,87725,26342],{},[26333,87727,26345],{},[26333,87729,26348],{},[26350,87731,87732,87746,87760],{},[26330,87733,87734,87736,87738,87740,87742,87744],{},[26355,87735,3203],{},[26355,87737,26359],{},[26355,87739,26362],{},[26355,87741,26365],{},[26355,87743,26368],{},[26355,87745,26371],{},[26330,87747,87748,87750,87752,87754,87756,87758],{},[26355,87749,4797],{},[26355,87751,26378],{},[26355,87753,26381],{},[26355,87755,26384],{},[26355,87757,26387],{},[26355,87759,26390],{},[26330,87761,87762,87764,87768,87770,87772,87776],{},[26355,87763,26395],{},[26355,87765,26398,87766,11866],{},[200,87767,26401],{},[26355,87769,26404],{},[26355,87771,26407],{},[26355,87773,26410,87774,26413],{},[200,87775,26401],{},[26355,87777,26416],{},[35,87779,26419,87780,26425,87783,26428],{},[39,87781,26424],{"href":26422,"rel":87782},[77],[156,87784,26348],{},[35,87786,26431,87787,26436],{},[39,87788,26435],{"href":25194,"rel":87789},[77],[938,87791,87792],{},[35,87793,87794,26443],{},[200,87795,944],{},[35,87797,26446],{},[35,87799,26449],{},[35,87801,26452],{},[35,87803,26455,87804,26464],{},[39,87805,26460,87807],{"href":26458,"rel":87806},[77],[156,87808,26463],{},[192,87810,87811],{"className":19560,"code":26467,"language":19563,"meta":13,"style":13},[156,87812,87813,87817,87821,87825,87829,87833,87837,87841,87845,87849],{"__ignoreMap":13},[200,87814,87815],{"class":202,"line":203},[200,87816,26474],{},[200,87818,87819],{"class":202,"line":14},[200,87820,230],{},[200,87822,87823],{"class":202,"line":255},[200,87824,26483],{},[200,87826,87827],{"class":202,"line":272},[200,87828,26488],{},[200,87830,87831],{"class":202,"line":283},[200,87832,26493],{},[200,87834,87835],{"class":202,"line":300},[200,87836,26498],{},[200,87838,87839],{"class":202,"line":310},[200,87840,26503],{},[200,87842,87843],{"class":202,"line":336},[200,87844,26508],{},[200,87846,87847],{"class":202,"line":342},[200,87848,26513],{},[200,87850,87851],{"class":202,"line":365},[200,87852,1129],{},[938,87854,87855],{},[35,87856,87857,26524],{},[200,87858,944],{},[35,87860,19439,87861,26532],{},[39,87862,26531],{"href":26529,"rel":87863},[77],[192,87865,87866],{"className":19560,"code":26535,"language":19563,"meta":13,"style":13},[156,87867,87868,87872,87876,87880,87884,87888,87892,87896,87900,87904],{"__ignoreMap":13},[200,87869,87870],{"class":202,"line":203},[200,87871,19600],{},[200,87873,87874],{"class":202,"line":14},[200,87875,528],{"emptyLinePlaceholder":20},[200,87877,87878],{"class":202,"line":255},[200,87879,26550],{},[200,87881,87882],{"class":202,"line":272},[200,87883,528],{"emptyLinePlaceholder":20},[200,87885,87886],{"class":202,"line":283},[200,87887,26559],{},[200,87889,87890],{"class":202,"line":300},[200,87891,528],{"emptyLinePlaceholder":20},[200,87893,87894],{"class":202,"line":310},[200,87895,26568],{},[200,87897,87898],{"class":202,"line":336},[200,87899,230],{},[200,87901,87902],{"class":202,"line":342},[200,87903,26577],{},[200,87905,87906],{"class":202,"line":365},[200,87907,1129],{},[35,87909,26584,87910,26588,87912,26591],{},[156,87911,26587],{},[156,87913,26463],{},[1929,87915,26595],{"id":26594},[35,87917,26598],{},[35,87919,26601],{},[35,87921,26604,87922,26612,87927,26618,87930,26627,87935,215],{},[39,87923,87925,26611],{"href":26607,"rel":87924},[77],[156,87926,24276],{},[39,87928,26617],{"href":26615,"rel":87929},[77],[39,87931,26623,87933],{"href":26621,"rel":87932},[77],[156,87934,26626],{},[156,87936,26630],{},[192,87938,87939],{"className":19560,"code":26633,"language":19563,"meta":13,"style":13},[156,87940,87941,87945,87949,87953,87957],{"__ignoreMap":13},[200,87942,87943],{"class":202,"line":203},[200,87944,19600],{},[200,87946,87947],{"class":202,"line":14},[200,87948,528],{"emptyLinePlaceholder":20},[200,87950,87951],{"class":202,"line":255},[200,87952,26648],{},[200,87954,87955],{"class":202,"line":272},[200,87956,528],{"emptyLinePlaceholder":20},[200,87958,87959],{"class":202,"line":283},[200,87960,26657],{},[35,87962,26660,87963,3092,87965,26668,87968,26672,87970,26678,87973,26684],{},[156,87964,5366],{},[39,87966,26667],{"href":26665,"rel":87967},[77],[156,87969,26671],{},[39,87971,26677],{"href":26675,"rel":87972},[77],[39,87974,26683],{"href":26681,"rel":87975},[77],[192,87977,87978],{"className":19560,"code":26687,"language":19563,"meta":13,"style":13},[156,87979,87980,87984,87988],{"__ignoreMap":13},[200,87981,87982],{"class":202,"line":203},[200,87983,26694],{},[200,87985,87986],{"class":202,"line":14},[200,87987,26699],{},[200,87989,87990],{"class":202,"line":255},[200,87991,26704],{},[35,87993,26707],{},[68,87995,87996,88000],{},[71,87997,87998,26715],{},[156,87999,26714],{},[71,88001,88002,26721,88004,215],{},[156,88003,26720],{},[156,88005,25229],{},[35,88007,26726,88008,26729,88010,26733,88012,26737],{},[156,88009,2898],{},[156,88011,26732],{},[156,88013,26736],{},[35,88015,26740],{},[68,88017,88018,88023],{},[71,88019,26745,88020,215],{},[39,88021,26750],{"href":26748,"rel":88022},[77],[71,88024,26753,88025,215],{},[39,88026,26758],{"href":26756,"rel":88027},[77],[35,88029,26761,88030,26765],{},[156,88031,26764],{},[192,88033,88034],{"className":590,"code":26768,"language":592,"meta":13,"style":13},[156,88035,88036],{"__ignoreMap":13},[200,88037,88038,88040,88042,88044,88046,88048,88050,88052,88054,88056,88058,88060,88062,88064,88066,88068,88070,88072,88074,88076,88078,88080,88082,88084,88086,88088,88090,88092,88094,88096,88098,88100,88102,88104,88106,88108,88110,88112,88114,88116,88118,88120,88122,88124,88126,88128,88130,88132,88134,88136,88138,88140,88142,88144,88146,88148,88150,88152,88154,88156,88158,88160,88162,88164,88166,88168,88170,88172,88174,88176,88178,88180,88182,88184,88186,88188,88190,88192,88194,88196,88198,88200,88202,88204,88206,88208,88210,88212,88214,88216,88218,88220,88222,88224,88226,88228,88230,88232,88234,88236,88238,88240,88242,88244,88246,88248,88250],{"class":202,"line":203},[200,88039,599],{"class":214},[200,88041,11400],{"class":235},[200,88043,3239],{"class":379},[200,88045,449],{"class":214},[200,88047,610],{"class":214},[200,88049,26785],{"class":245},[200,88051,610],{"class":214},[200,88053,26790],{"class":379},[200,88055,449],{"class":214},[200,88057,610],{"class":214},[200,88059,2767],{"class":245},[200,88061,26799],{"class":210},[200,88063,23207],{"class":245},[200,88065,26799],{"class":210},[200,88067,239],{"class":245},[200,88069,26799],{"class":210},[200,88071,26810],{"class":245},[200,88073,26799],{"class":210},[200,88075,549],{"class":245},[200,88077,26799],{"class":210},[200,88079,23874],{"class":245},[200,88081,26799],{"class":210},[200,88083,26823],{"class":245},[200,88085,26799],{"class":210},[200,88087,26828],{"class":245},[200,88089,26799],{"class":210},[200,88091,26833],{"class":245},[200,88093,26799],{"class":210},[200,88095,26838],{"class":245},[200,88097,26799],{"class":210},[200,88099,26823],{"class":245},[200,88101,26799],{"class":210},[200,88103,26847],{"class":245},[200,88105,26799],{"class":210},[200,88107,26852],{"class":245},[200,88109,26799],{"class":210},[200,88111,26857],{"class":245},[200,88113,26799],{"class":210},[200,88115,26823],{"class":245},[200,88117,26799],{"class":210},[200,88119,2030],{"class":245},[200,88121,26799],{"class":210},[200,88123,26870],{"class":245},[200,88125,26799],{"class":210},[200,88127,26875],{"class":245},[200,88129,26799],{"class":210},[200,88131,26823],{"class":245},[200,88133,26799],{"class":210},[200,88135,20112],{"class":245},[200,88137,26799],{"class":210},[200,88139,239],{"class":245},[200,88141,26799],{"class":210},[200,88143,26892],{"class":245},[200,88145,26799],{"class":210},[200,88147,26897],{"class":245},[200,88149,26799],{"class":210},[200,88151,26902],{"class":245},[200,88153,26799],{"class":210},[200,88155,26907],{"class":245},[200,88157,26799],{"class":210},[200,88159,26912],{"class":245},[200,88161,26799],{"class":210},[200,88163,239],{"class":245},[200,88165,26799],{"class":210},[200,88167,26921],{"class":245},[200,88169,26799],{"class":210},[200,88171,549],{"class":245},[200,88173,26799],{"class":210},[200,88175,26930],{"class":245},[200,88177,26799],{"class":210},[200,88179,26935],{"class":245},[200,88181,26799],{"class":210},[200,88183,26940],{"class":245},[200,88185,26799],{"class":210},[200,88187,239],{"class":245},[200,88189,26799],{"class":210},[200,88191,26949],{"class":245},[200,88193,26799],{"class":210},[200,88195,26954],{"class":245},[200,88197,26799],{"class":210},[200,88199,2035],{"class":245},[200,88201,26799],{"class":210},[200,88203,239],{"class":245},[200,88205,26799],{"class":210},[200,88207,26967],{"class":245},[200,88209,26799],{"class":210},[200,88211,549],{"class":245},[200,88213,26799],{"class":210},[200,88215,26976],{"class":245},[200,88217,26799],{"class":210},[200,88219,239],{"class":245},[200,88221,26799],{"class":210},[200,88223,26985],{"class":245},[200,88225,26799],{"class":210},[200,88227,549],{"class":245},[200,88229,26799],{"class":210},[200,88231,26994],{"class":245},[200,88233,26799],{"class":210},[200,88235,26999],{"class":245},[200,88237,26799],{"class":210},[200,88239,27004],{"class":245},[200,88241,26799],{"class":210},[200,88243,27009],{"class":245},[200,88245,610],{"class":214},[200,88247,10206],{"class":214},[200,88249,11400],{"class":235},[200,88251,647],{"class":214},[35,88253,419,88254,27022,88256,27026,88258,1655,88260,27031,88262,27035,88264,215],{},[156,88255,26857],{},[6862,88257,27025],{},[6862,88259,26875],{},[6862,88261,26902],{},[156,88263,27034],{},[39,88265,27040],{"href":27038,"rel":88266},[77],[35,88268,27043,88269,27046],{},[156,88270,26921],{},[192,88272,88273],{"className":25070,"code":27049,"language":25072,"meta":13,"style":13},[156,88274,88275,88297,88301,88327,88361,88393,88399,88403,88423],{"__ignoreMap":13},[200,88276,88277,88279,88281,88283,88285,88287,88289,88291,88293,88295],{"class":202,"line":203},[200,88278,972],{"class":206},[200,88280,318],{"class":214},[200,88282,18698],{"class":210},[200,88284,549],{"class":214},[200,88286,27064],{"class":210},[200,88288,980],{"class":214},[200,88290,983],{"class":206},[200,88292,242],{"class":214},[200,88294,12075],{"class":245},[200,88296,991],{"class":214},[200,88298,88299],{"class":202,"line":14},[200,88300,528],{"emptyLinePlaceholder":20},[200,88302,88303,88305,88307,88309,88311,88313,88315,88317,88319,88321,88323,88325],{"class":202,"line":255},[200,88304,1390],{"class":206},[200,88306,1393],{"class":206},[200,88308,5002],{"class":379},[200,88310,27089],{"class":223},[200,88312,3966],{"class":214},[200,88314,27094],{"class":386},[200,88316,549],{"class":214},[200,88318,27099],{"class":386},[200,88320,27102],{"class":214},[200,88322,27105],{"class":386},[200,88324,2104],{"class":214},[200,88326,280],{"class":214},[200,88328,88329,88331,88333,88335,88337,88339,88341,88343,88345,88347,88349,88351,88353,88355,88357,88359],{"class":202,"line":272},[200,88330,27114],{"class":210},[200,88332,215],{"class":214},[200,88334,27119],{"class":223},[200,88336,227],{"class":235},[200,88338,26857],{"class":210},[200,88340,215],{"class":214},[200,88342,26875],{"class":210},[200,88344,2104],{"class":235},[200,88346,215],{"class":214},[200,88348,27134],{"class":223},[200,88350,227],{"class":235},[200,88352,227],{"class":214},[200,88354,24627],{"class":386},[200,88356,2104],{"class":214},[200,88358,393],{"class":379},[200,88360,280],{"class":214},[200,88362,88363,88365,88367,88369,88371,88373,88375,88377,88379,88381,88383,88385,88387,88389,88391],{"class":202,"line":283},[200,88364,27151],{"class":210},[200,88366,9872],{"class":235},[200,88368,24627],{"class":210},[200,88370,18849],{"class":235},[200,88372,449],{"class":214},[200,88374,18698],{"class":223},[200,88376,227],{"class":235},[200,88378,27166],{"class":223},[200,88380,227],{"class":235},[200,88382,26857],{"class":210},[200,88384,215],{"class":214},[200,88386,26875],{"class":210},[200,88388,9872],{"class":235},[200,88390,24627],{"class":210},[200,88392,27181],{"class":235},[200,88394,88395,88397],{"class":202,"line":300},[200,88396,5095],{"class":214},[200,88398,416],{"class":235},[200,88400,88401],{"class":202,"line":310},[200,88402,528],{"emptyLinePlaceholder":20},[200,88404,88405,88407,88409,88411,88413,88415,88417,88419,88421],{"class":202,"line":336},[200,88406,3758],{"class":206},[200,88408,27105],{"class":223},[200,88410,227],{"class":235},[200,88412,2767],{"class":214},[200,88414,27094],{"class":210},[200,88416,549],{"class":214},[200,88418,27099],{"class":210},[200,88420,980],{"class":214},[200,88422,416],{"class":235},[200,88424,88425],{"class":202,"line":342},[200,88426,1129],{"class":214},[35,88428,27218],{},[35,88430,27221],{},[192,88432,88433],{"className":21457,"code":27224,"language":12075,"meta":13,"style":13},[156,88434,88435,88443,88447,88451,88455,88459,88467,88471,88479,88483],{"__ignoreMap":13},[200,88436,88437,88439,88441],{"class":202,"line":203},[200,88438,599],{"class":214},[200,88440,19563],{"class":235},[200,88442,647],{"class":214},[200,88444,88445],{"class":202,"line":14},[200,88446,26072],{"class":210},[200,88448,88449],{"class":202,"line":255},[200,88450,528],{"emptyLinePlaceholder":20},[200,88452,88453],{"class":202,"line":272},[200,88454,27247],{"class":210},[200,88456,88457],{"class":202,"line":283},[200,88458,26090],{"class":210},[200,88460,88461,88463,88465],{"class":202,"line":300},[200,88462,680],{"class":214},[200,88464,19563],{"class":235},[200,88466,647],{"class":214},[200,88468,88469],{"class":202,"line":310},[200,88470,528],{"emptyLinePlaceholder":20},[200,88472,88473,88475,88477],{"class":202,"line":336},[200,88474,599],{"class":214},[200,88476,21468],{"class":235},[200,88478,647],{"class":214},[200,88480,88481],{"class":202,"line":342},[200,88482,24368],{"class":210},[200,88484,88485,88487,88489],{"class":202,"line":365},[200,88486,680],{"class":214},[200,88488,21468],{"class":235},[200,88490,647],{"class":214},[35,88492,27286],{},[192,88494,88495],{"className":21457,"code":27289,"language":12075,"meta":13,"style":13},[156,88496,88497,88507,88525,88539,88543,88547,88583,88587,88605,88609,88649,88657,88661,88669,88673],{"__ignoreMap":13},[200,88498,88499,88501,88503,88505],{"class":202,"line":203},[200,88500,599],{"class":214},[200,88502,14896],{"class":235},[200,88504,21825],{"class":379},[200,88506,647],{"class":214},[200,88508,88509,88511,88513,88515,88517,88519,88521,88523],{"class":202,"line":14},[200,88510,972],{"class":206},[200,88512,318],{"class":214},[200,88514,27310],{"class":210},[200,88516,980],{"class":214},[200,88518,983],{"class":206},[200,88520,242],{"class":214},[200,88522,27319],{"class":245},[200,88524,991],{"class":214},[200,88526,88527,88529,88531,88533,88535,88537],{"class":202,"line":255},[200,88528,972],{"class":206},[200,88530,27328],{"class":210},[200,88532,1489],{"class":206},[200,88534,242],{"class":214},[200,88536,27335],{"class":245},[200,88538,991],{"class":214},[200,88540,88541],{"class":202,"line":272},[200,88542,528],{"emptyLinePlaceholder":20},[200,88544,88545],{"class":202,"line":283},[200,88546,27346],{"class":401},[200,88548,88549,88551,88553,88555,88557,88559,88561,88563,88565,88567,88569,88571,88573,88575,88577,88579,88581],{"class":202,"line":300},[200,88550,443],{"class":379},[200,88552,27353],{"class":210},[200,88554,449],{"class":214},[200,88556,27310],{"class":223},[200,88558,25224],{"class":210},[200,88560,249],{"class":214},[200,88562,20112],{"class":245},[200,88564,249],{"class":214},[200,88566,549],{"class":214},[200,88568,242],{"class":214},[200,88570,26857],{"class":245},[200,88572,249],{"class":214},[200,88574,360],{"class":210},[200,88576,549],{"class":214},[200,88578,27380],{"class":210},[200,88580,215],{"class":214},[200,88582,27385],{"class":210},[200,88584,88585],{"class":202,"line":310},[200,88586,528],{"emptyLinePlaceholder":20},[200,88588,88589,88591,88593,88595,88597,88599,88601,88603],{"class":202,"line":336},[200,88590,443],{"class":379},[200,88592,318],{"class":214},[200,88594,700],{"class":210},[200,88596,549],{"class":214},[200,88598,27402],{"class":210},[200,88600,413],{"class":214},[200,88602,509],{"class":214},[200,88604,27409],{"class":210},[200,88606,88607],{"class":202,"line":342},[200,88608,528],{"emptyLinePlaceholder":20},[200,88610,88611,88613,88615,88617,88619,88621,88623,88625,88627,88629,88631,88633,88635,88637,88639,88641,88643,88645,88647],{"class":202,"line":365},[200,88612,27418],{"class":223},[200,88614,27421],{"class":210},[200,88616,549],{"class":214},[200,88618,318],{"class":214},[200,88620,3239],{"class":235},[200,88622,239],{"class":214},[200,88624,242],{"class":214},[200,88626,27434],{"class":245},[200,88628,249],{"class":214},[200,88630,549],{"class":214},[200,88632,27441],{"class":235},[200,88634,239],{"class":214},[200,88636,27446],{"class":206},[200,88638,215],{"class":214},[200,88640,2030],{"class":210},[200,88642,6536],{"class":214},[200,88644,27455],{"class":210},[200,88646,413],{"class":214},[200,88648,416],{"class":210},[200,88650,88651,88653,88655],{"class":202,"line":371},[200,88652,680],{"class":214},[200,88654,14896],{"class":235},[200,88656,647],{"class":214},[200,88658,88659],{"class":202,"line":398},[200,88660,528],{"emptyLinePlaceholder":20},[200,88662,88663,88665,88667],{"class":202,"line":405},[200,88664,599],{"class":214},[200,88666,21468],{"class":235},[200,88668,647],{"class":214},[200,88670,88671],{"class":202,"line":410},[200,88672,24368],{"class":210},[200,88674,88675,88677,88679],{"class":202,"line":860},[200,88676,680],{"class":214},[200,88678,21468],{"class":235},[200,88680,647],{"class":214},[35,88682,27494,88683,27502,88688,27506,88690,27509,88692,27512,88694,27516,88696,27520,88698,27523],{},[39,88684,88686],{"href":27497,"rel":88685},[77],[156,88687,27501],{},[156,88689,27505],{},[6862,88691,27025],{},[156,88693,23874],{},[156,88695,27515],{},[156,88697,27519],{},[156,88699,27501],{},[35,88701,27526,88702,1655,88704,27531,88706,27534],{},[156,88703,20112],{},[156,88705,26857],{},[156,88707,27501],{},[35,88709,27537,88710,27540,88712,27544],{},[156,88711,27505],{},[156,88713,27543],{},[35,88715,27547,88716,27550,88718,27553],{},[156,88717,27543],{},[156,88719,25308],{},[192,88721,88722],{"className":25070,"code":27556,"language":25072,"meta":13,"style":13},[156,88723,88724,88738,88752,88756,88776,88788,88808,88812,88818,88822,88856,88896,88900,88914,88922,88926,88930,88940,88948,88976,88980,88984,88994,89012,89028,89048,89052,89056,89060,89066],{"__ignoreMap":13},[200,88725,88726,88728,88730,88732,88734,88736],{"class":202,"line":203},[200,88727,972],{"class":206},[200,88729,27565],{"class":210},[200,88731,1489],{"class":206},[200,88733,242],{"class":214},[200,88735,27572],{"class":245},[200,88737,991],{"class":214},[200,88739,88740,88742,88744,88746,88748,88750],{"class":202,"line":14},[200,88741,972],{"class":206},[200,88743,27581],{"class":210},[200,88745,1489],{"class":206},[200,88747,242],{"class":214},[200,88749,27588],{"class":245},[200,88751,991],{"class":214},[200,88753,88754],{"class":202,"line":255},[200,88755,528],{"emptyLinePlaceholder":20},[200,88757,88758,88760,88762,88764,88766,88768,88770,88772,88774],{"class":202,"line":272},[200,88759,1390],{"class":206},[200,88761,14305],{"class":379},[200,88763,18960],{"class":210},[200,88765,449],{"class":214},[200,88767,350],{"class":210},[200,88769,249],{"class":214},[200,88771,20112],{"class":245},[200,88773,249],{"class":214},[200,88775,4511],{"class":210},[200,88777,88778,88780,88782,88784,88786],{"class":202,"line":283},[200,88779,1390],{"class":206},[200,88781,14305],{"class":379},[200,88783,27623],{"class":210},[200,88785,449],{"class":214},[200,88787,6433],{"class":210},[200,88789,88790,88792,88794,88796,88798,88800,88802,88804,88806],{"class":202,"line":300},[200,88791,1390],{"class":206},[200,88793,14305],{"class":379},[200,88795,27636],{"class":210},[200,88797,449],{"class":214},[200,88799,350],{"class":210},[200,88801,249],{"class":214},[200,88803,27645],{"class":245},[200,88805,249],{"class":214},[200,88807,4511],{"class":210},[200,88809,88810],{"class":202,"line":310},[200,88811,528],{"emptyLinePlaceholder":20},[200,88813,88814,88816],{"class":202,"line":336},[200,88815,12337],{"class":379},[200,88817,27660],{"class":210},[200,88819,88820],{"class":202,"line":342},[200,88821,528],{"emptyLinePlaceholder":20},[200,88823,88824,88826,88828,88830,88832,88834,88836,88838,88840,88842,88844,88846,88848,88850,88852,88854],{"class":202,"line":365},[200,88825,1390],{"class":206},[200,88827,5002],{"class":379},[200,88829,27310],{"class":223},[200,88831,227],{"class":214},[200,88833,27119],{"class":386},[200,88835,509],{"class":214},[200,88837,27681],{"class":210},[200,88839,549],{"class":214},[200,88841,27686],{"class":386},[200,88843,509],{"class":214},[200,88845,27691],{"class":214},[200,88847,27694],{"class":386},[200,88849,509],{"class":214},[200,88851,16169],{"class":2438},[200,88853,2104],{"class":214},[200,88855,280],{"class":214},[200,88857,88858,88860,88862,88864,88866,88868,88870,88872,88874,88876,88878,88880,88882,88884,88886,88888,88890,88892,88894],{"class":202,"line":371},[200,88859,3738],{"class":379},[200,88861,27099],{"class":210},[200,88863,509],{"class":214},[200,88865,1094],{"class":235},[200,88867,27715],{"class":210},[200,88869,14932],{"class":214},[200,88871,27720],{"class":210},[200,88873,5282],{"class":235},[200,88875,27725],{"class":214},[200,88877,27720],{"class":210},[200,88879,2186],{"class":214},[200,88881,13007],{"class":214},[200,88883,27734],{"class":223},[200,88885,227],{"class":235},[200,88887,23874],{"class":210},[200,88889,2104],{"class":235},[200,88891,215],{"class":214},[200,88893,27745],{"class":223},[200,88895,466],{"class":235},[200,88897,88898],{"class":202,"line":398},[200,88899,528],{"emptyLinePlaceholder":20},[200,88901,88902,88904,88906,88908,88910,88912],{"class":202,"line":405},[200,88903,20098],{"class":206},[200,88905,1094],{"class":235},[200,88907,11852],{"class":214},[200,88909,27715],{"class":210},[200,88911,5282],{"class":235},[200,88913,230],{"class":214},[200,88915,88916,88918,88920],{"class":202,"line":410},[200,88917,27770],{"class":210},[200,88919,509],{"class":214},[200,88921,27775],{"class":210},[200,88923,88924],{"class":202,"line":860},[200,88925,4505],{"class":214},[200,88927,88928],{"class":202,"line":889},[200,88929,528],{"emptyLinePlaceholder":20},[200,88931,88932,88934,88936,88938],{"class":202,"line":899},[200,88933,3738],{"class":379},[200,88935,27790],{"class":210},[200,88937,509],{"class":214},[200,88939,280],{"class":214},[200,88941,88942,88944,88946],{"class":202,"line":904},[200,88943,27799],{"class":214},[200,88945,26875],{"class":210},[200,88947,252],{"class":214},[200,88949,88950,88952,88954,88956,88958,88960,88962,88964,88966,88968,88970,88972,88974],{"class":202,"line":930},[200,88951,27808],{"class":214},[200,88953,27811],{"class":223},[200,88955,25224],{"class":235},[200,88957,573],{"class":214},[200,88959,26902],{"class":210},[200,88961,549],{"class":214},[200,88963,27822],{"class":214},[200,88965,27825],{"class":210},[200,88967,360],{"class":235},[200,88969,549],{"class":214},[200,88971,27099],{"class":210},[200,88973,2104],{"class":235},[200,88975,252],{"class":214},[200,88977,88978],{"class":202,"line":3655},[200,88979,4505],{"class":214},[200,88981,88982],{"class":202,"line":3660},[200,88983,528],{"emptyLinePlaceholder":20},[200,88985,88986,88988,88990,88992],{"class":202,"line":4035},[200,88987,3738],{"class":379},[200,88989,27850],{"class":210},[200,88991,509],{"class":214},[200,88993,19914],{"class":214},[200,88995,88996,88998,89000,89002,89004,89006,89008,89010],{"class":202,"line":4490},[200,88997,6438],{"class":206},[200,88999,1094],{"class":235},[200,89001,443],{"class":379},[200,89003,18835],{"class":210},[200,89005,6457],{"class":214},[200,89007,27869],{"class":210},[200,89009,5282],{"class":235},[200,89011,230],{"class":214},[200,89013,89014,89016,89018,89020,89022,89024,89026],{"class":202,"line":4496},[200,89015,5267],{"class":206},[200,89017,1094],{"class":235},[200,89019,24627],{"class":210},[200,89021,27884],{"class":214},[200,89023,27790],{"class":210},[200,89025,5282],{"class":235},[200,89027,230],{"class":214},[200,89029,89030,89032,89034,89036,89038,89040,89042,89044,89046],{"class":202,"line":4502},[200,89031,27895],{"class":210},[200,89033,9872],{"class":235},[200,89035,24627],{"class":210},[200,89037,18849],{"class":235},[200,89039,449],{"class":214},[200,89041,27790],{"class":210},[200,89043,9872],{"class":235},[200,89045,24627],{"class":210},[200,89047,4511],{"class":235},[200,89049,89050],{"class":202,"line":4508},[200,89051,4977],{"class":214},[200,89053,89054],{"class":202,"line":5469},[200,89055,4505],{"class":214},[200,89057,89058],{"class":202,"line":5474},[200,89059,528],{"emptyLinePlaceholder":20},[200,89061,89062,89064],{"class":202,"line":13237},[200,89063,3758],{"class":206},[200,89065,27930],{"class":210},[200,89067,89068],{"class":202,"line":13263},[200,89069,1129],{"class":214},[35,89071,27937,89072,1655,89074,27942,89076,1655,89078,27948,89080,27952,89082,27955],{},[156,89073,26875],{},[156,89075,26857],{},[6862,89077,27025],{},[6862,89079,27947],{},[156,89081,27951],{},[156,89083,26921],{},[35,89085,27958],{},[1593,89087,89088,89095,89102,89109],{},[71,89089,89090,27969],{},[39,89091,89093],{"href":27965,"rel":89092},[77],[156,89094,26921],{},[71,89096,89097,27979],{},[39,89098,89100],{"href":27974,"rel":89099},[77],[156,89101,27978],{},[71,89103,89104,27988],{},[39,89105,89107],{"href":27984,"rel":89106},[77],[156,89108,1117],{},[71,89110,89111,27998],{},[39,89112,89114],{"href":27993,"rel":89113},[77],[156,89115,27997],{},[35,89117,28001,89118,28004],{},[156,89119,27947],{},[938,89121,89122],{},[35,89123,89124,28011,89126,28014,89128,1655,89131,215],{},[200,89125,944],{},[156,89127,27947],{},[39,89129,28019],{"href":28017,"rel":89130},[77],[39,89132,28024],{"href":28022,"rel":89133},[77],[35,89135,28027],{},[192,89137,89138],{"className":21457,"code":28030,"language":12075,"meta":13,"style":13},[156,89139,89140,89148,89152,89156,89160,89168,89172,89180,89198,89202,89210],{"__ignoreMap":13},[200,89141,89142,89144,89146],{"class":202,"line":203},[200,89143,599],{"class":214},[200,89145,19563],{"class":235},[200,89147,647],{"class":214},[200,89149,89150],{"class":202,"line":14},[200,89151,28045],{"class":210},[200,89153,89154],{"class":202,"line":255},[200,89155,28050],{"class":210},[200,89157,89158],{"class":202,"line":272},[200,89159,28055],{"class":210},[200,89161,89162,89164,89166],{"class":202,"line":283},[200,89163,680],{"class":214},[200,89165,19563],{"class":235},[200,89167,647],{"class":214},[200,89169,89170],{"class":202,"line":300},[200,89171,528],{"emptyLinePlaceholder":20},[200,89173,89174,89176,89178],{"class":202,"line":310},[200,89175,599],{"class":214},[200,89177,21468],{"class":235},[200,89179,647],{"class":214},[200,89181,89182,89184,89186,89188,89190,89192,89194,89196],{"class":202,"line":336},[200,89183,656],{"class":214},[200,89185,909],{"class":235},[200,89187,28084],{"class":379},[200,89189,449],{"class":214},[200,89191,610],{"class":214},[200,89193,28091],{"class":245},[200,89195,610],{"class":214},[200,89197,647],{"class":214},[200,89199,89200],{"class":202,"line":342},[200,89201,28100],{"class":210},[200,89203,89204,89206,89208],{"class":202,"line":365},[200,89205,892],{"class":214},[200,89207,909],{"class":235},[200,89209,647],{"class":214},[200,89211,89212,89214,89216],{"class":202,"line":371},[200,89213,680],{"class":214},[200,89215,21468],{"class":235},[200,89217,647],{"class":214},[35,89219,28119,89220,28122,89222,28125],{},[156,89221,24484],{},[156,89223,27505],{},[68,89225,89226,89235,89240],{},[71,89227,28130,89228,28136,89231,1655,89233,215],{},[39,89229,28135],{"href":28133,"rel":89230},[77],[156,89232,28139],{},[156,89234,28142],{},[71,89236,28145,89237,215],{},[39,89238,28150],{"href":28148,"rel":89239},[77],[71,89241,28153,89242,215],{},[39,89243,28158],{"href":28156,"rel":89244},[77],[35,89246,28161],{},[192,89248,89249],{"className":28164,"code":28165,"language":28166,"meta":13,"style":13},[156,89250,89251,89255,89259,89263,89267,89271,89275,89279,89283,89287,89291,89295,89299,89303,89307,89311,89315,89319,89323,89327,89331,89335,89339,89343,89347,89351,89355,89359,89363,89367,89371,89375,89379,89383],{"__ignoreMap":13},[200,89252,89253],{"class":202,"line":203},[200,89254,28173],{},[200,89256,89257],{"class":202,"line":14},[200,89258,28178],{},[200,89260,89261],{"class":202,"line":255},[200,89262,28183],{},[200,89264,89265],{"class":202,"line":272},[200,89266,28188],{},[200,89268,89269],{"class":202,"line":283},[200,89270,528],{"emptyLinePlaceholder":20},[200,89272,89273],{"class":202,"line":300},[200,89274,230],{},[200,89276,89277],{"class":202,"line":310},[200,89278,28201],{},[200,89280,89281],{"class":202,"line":336},[200,89282,28206],{},[200,89284,89285],{"class":202,"line":342},[200,89286,28211],{},[200,89288,89289],{"class":202,"line":365},[200,89290,28216],{},[200,89292,89293],{"class":202,"line":371},[200,89294,28221],{},[200,89296,89297],{"class":202,"line":398},[200,89298,28226],{},[200,89300,89301],{"class":202,"line":405},[200,89302,28231],{},[200,89304,89305],{"class":202,"line":410},[200,89306,28236],{},[200,89308,89309],{"class":202,"line":860},[200,89310,28241],{},[200,89312,89313],{"class":202,"line":889},[200,89314,28246],{},[200,89316,89317],{"class":202,"line":899},[200,89318,4493],{},[200,89320,89321],{"class":202,"line":904},[200,89322,339],{},[200,89324,89325],{"class":202,"line":930},[200,89326,28259],{},[200,89328,89329],{"class":202,"line":3655},[200,89330,28264],{},[200,89332,89333],{"class":202,"line":3660},[200,89334,28269],{},[200,89336,89337],{"class":202,"line":4035},[200,89338,4924],{},[200,89340,89341],{"class":202,"line":4490},[200,89342,28278],{},[200,89344,89345],{"class":202,"line":4496},[200,89346,28283],{},[200,89348,89349],{"class":202,"line":4502},[200,89350,28288],{},[200,89352,89353],{"class":202,"line":4508},[200,89354,28293],{},[200,89356,89357],{"class":202,"line":5469},[200,89358,28298],{},[200,89360,89361],{"class":202,"line":5474},[200,89362,28303],{},[200,89364,89365],{"class":202,"line":13237},[200,89366,28308],{},[200,89368,89369],{"class":202,"line":13263},[200,89370,13469],{},[200,89372,89373],{"class":202,"line":13279},[200,89374,28317],{},[200,89376,89377],{"class":202,"line":13323},[200,89378,4977],{},[200,89380,89381],{"class":202,"line":13329},[200,89382,4505],{},[200,89384,89385],{"class":202,"line":13334},[200,89386,1129],{},[35,89388,28332,89389,1655,89391,28337,89393,215],{},[156,89390,28139],{},[156,89392,28142],{},[39,89394,28342],{"href":28340,"rel":89395},[77],[35,89397,28345,89398,28349,89400,28352],{},[156,89399,28348],{},[156,89401,26875],{},[35,89403,28355],{},[192,89405,89406],{"className":28358,"code":28359,"language":28360,"meta":13,"style":13},[156,89407,89408],{"__ignoreMap":13},[200,89409,89410],{"class":202,"line":203},[200,89411,28359],{"class":214},[35,89413,28369,89414,28372],{},[156,89415,24484],{},[35,89417,28375,89418,28378,89420,28381,89422,15917],{},[156,89419,24477],{},[156,89421,24477],{},[156,89423,27645],{},[51,89425,28387],{"id":28386},[35,89427,28390,89428,28395,89431,28401],{},[39,89429,28394],{"href":26756,"rel":89430},[77],[39,89432,28400],{"href":28398,"rel":89433},[77],[35,89435,28404],{},[51,89437,28408],{"id":28407},[35,89439,28411],{},[35,89441,28414,89442,28418],{},[156,89443,28417],{},[35,89445,28421],{},[35,89447,28424],{},[35,89449,28427,89450,215],{},[39,89451,25718],{"href":25717},[35,89453,28432],{},[51,89455,28436],{"id":28435},[35,89457,28439,89458,28443,89460,28447],{},[6862,89459,28442],{},[6862,89461,28446],{},[35,89463,28450],{},[35,89465,28453],{},[938,89467,89468],{},[35,89469,89470,28460],{},[200,89471,944],{},[35,89473,28463,89474,28469,89477,28475],{},[39,89475,28468],{"href":28466,"rel":89476},[77],[39,89478,28474],{"href":28472,"rel":89479},[77],[192,89481,89482],{"className":28358,"code":28478,"language":28360,"meta":13,"style":13},[156,89483,89484,89488,89510,89522,89534,89550,89554,89558,89584,89596,89608,89624,89628,89640,89656,89660,89664,89676,89680,89696,89700,89704,89722,89748,89766,89778,89782,89798,89802,89806,89810,89814],{"__ignoreMap":13},[200,89485,89486],{"class":202,"line":203},[200,89487,230],{"class":214},[200,89489,89490,89492,89494,89496,89498,89500,89502,89504,89506,89508],{"class":202,"line":14},[200,89491,28489],{"class":214},[200,89493,28492],{"class":379},[200,89495,610],{"class":214},[200,89497,239],{"class":214},[200,89499,28499],{"class":214},[200,89501,28502],{"class":245},[200,89503,28505],{"class":210},[200,89505,3899],{"class":245},[200,89507,610],{"class":214},[200,89509,252],{"class":214},[200,89511,89512,89514,89516,89518,89520],{"class":202,"line":255},[200,89513,28489],{"class":214},[200,89515,28518],{"class":379},[200,89517,610],{"class":214},[200,89519,239],{"class":214},[200,89521,280],{"class":214},[200,89523,89524,89526,89528,89530,89532],{"class":202,"line":272},[200,89525,28529],{"class":214},[200,89527,3203],{"class":1364},[200,89529,610],{"class":214},[200,89531,239],{"class":214},[200,89533,280],{"class":214},[200,89535,89536,89538,89540,89542,89544,89546,89548],{"class":202,"line":283},[200,89537,28542],{"class":214},[200,89539,20112],{"class":2060},[200,89541,610],{"class":214},[200,89543,239],{"class":214},[200,89545,28499],{"class":214},[200,89547,28553],{"class":245},[200,89549,616],{"class":214},[200,89551,89552],{"class":202,"line":300},[200,89553,4977],{"class":214},[200,89555,89556],{"class":202,"line":310},[200,89557,368],{"class":214},[200,89559,89560,89562,89564,89566,89568,89570,89572,89574,89576,89578,89580,89582],{"class":202,"line":336},[200,89561,28489],{"class":214},[200,89563,28570],{"class":379},[200,89565,610],{"class":214},[200,89567,239],{"class":214},[200,89569,28499],{"class":214},[200,89571,28579],{"class":245},[200,89573,28505],{"class":210},[200,89575,28584],{"class":245},[200,89577,28505],{"class":210},[200,89579,28589],{"class":245},[200,89581,610],{"class":214},[200,89583,252],{"class":214},[200,89585,89586,89588,89590,89592,89594],{"class":202,"line":342},[200,89587,28489],{"class":214},[200,89589,28600],{"class":379},[200,89591,610],{"class":214},[200,89593,239],{"class":214},[200,89595,280],{"class":214},[200,89597,89598,89600,89602,89604,89606],{"class":202,"line":365},[200,89599,28529],{"class":214},[200,89601,3203],{"class":1364},[200,89603,610],{"class":214},[200,89605,239],{"class":214},[200,89607,280],{"class":214},[200,89609,89610,89612,89614,89616,89618,89620,89622],{"class":202,"line":371},[200,89611,28542],{"class":214},[200,89613,20112],{"class":2060},[200,89615,610],{"class":214},[200,89617,239],{"class":214},[200,89619,28499],{"class":214},[200,89621,28633],{"class":245},[200,89623,616],{"class":214},[200,89625,89626],{"class":202,"line":398},[200,89627,339],{"class":214},[200,89629,89630,89632,89634,89636,89638],{"class":202,"line":405},[200,89631,28529],{"class":214},[200,89633,4797],{"class":1364},[200,89635,610],{"class":214},[200,89637,239],{"class":214},[200,89639,280],{"class":214},[200,89641,89642,89644,89646,89648,89650,89652,89654],{"class":202,"line":410},[200,89643,28542],{"class":214},[200,89645,20112],{"class":2060},[200,89647,610],{"class":214},[200,89649,239],{"class":214},[200,89651,28499],{"class":214},[200,89653,28666],{"class":245},[200,89655,616],{"class":214},[200,89657,89658],{"class":202,"line":860},[200,89659,4977],{"class":214},[200,89661,89662],{"class":202,"line":889},[200,89663,368],{"class":214},[200,89665,89666,89668,89670,89672,89674],{"class":202,"line":899},[200,89667,28489],{"class":214},[200,89669,28683],{"class":379},[200,89671,610],{"class":214},[200,89673,239],{"class":214},[200,89675,1410],{"class":214},[200,89677,89678],{"class":202,"line":904},[200,89679,19661],{"class":214},[200,89681,89682,89684,89686,89688,89690,89692,89694],{"class":202,"line":930},[200,89683,28542],{"class":214},[200,89685,28700],{"class":1364},[200,89687,610],{"class":214},[200,89689,239],{"class":214},[200,89691,28499],{"class":214},[200,89693,28709],{"class":245},[200,89695,616],{"class":214},[200,89697,89698],{"class":202,"line":3655},[200,89699,339],{"class":214},[200,89701,89702],{"class":202,"line":3660},[200,89703,19661],{"class":214},[200,89705,89706,89708,89710,89712,89714,89716,89718,89720],{"class":202,"line":4035},[200,89707,28542],{"class":214},[200,89709,28492],{"class":1364},[200,89711,610],{"class":214},[200,89713,239],{"class":214},[200,89715,28499],{"class":214},[200,89717,28734],{"class":245},[200,89719,610],{"class":214},[200,89721,252],{"class":214},[200,89723,89724,89726,89728,89730,89732,89734,89736,89738,89740,89742,89744,89746],{"class":202,"line":4490},[200,89725,28542],{"class":214},[200,89727,28570],{"class":1364},[200,89729,610],{"class":214},[200,89731,239],{"class":214},[200,89733,28499],{"class":214},[200,89735,28753],{"class":245},[200,89737,28505],{"class":210},[200,89739,28758],{"class":245},[200,89741,28505],{"class":210},[200,89743,28763],{"class":245},[200,89745,610],{"class":214},[200,89747,252],{"class":214},[200,89749,89750,89752,89754,89756,89758,89760,89762,89764],{"class":202,"line":4496},[200,89751,28542],{"class":214},[200,89753,20112],{"class":1364},[200,89755,610],{"class":214},[200,89757,239],{"class":214},[200,89759,28499],{"class":214},[200,89761,28782],{"class":245},[200,89763,610],{"class":214},[200,89765,252],{"class":214},[200,89767,89768,89770,89772,89774,89776],{"class":202,"line":4502},[200,89769,28542],{"class":214},[200,89771,28683],{"class":1364},[200,89773,610],{"class":214},[200,89775,239],{"class":214},[200,89777,1410],{"class":214},[200,89779,89780],{"class":202,"line":4508},[200,89781,4924],{"class":214},[200,89783,89784,89786,89788,89790,89792,89794,89796],{"class":202,"line":5469},[200,89785,28807],{"class":214},[200,89787,28700],{"class":2060},[200,89789,610],{"class":214},[200,89791,239],{"class":214},[200,89793,28499],{"class":214},[200,89795,28782],{"class":245},[200,89797,616],{"class":214},[200,89799,89800],{"class":202,"line":5474},[200,89801,13469],{"class":214},[200,89803,89804],{"class":202,"line":13237},[200,89805,28317],{"class":214},[200,89807,89808],{"class":202,"line":13263},[200,89809,4977],{"class":214},[200,89811,89812],{"class":202,"line":13279},[200,89813,13683],{"class":214},[200,89815,89816],{"class":202,"line":13323},[200,89817,1129],{"class":214},[35,89819,28842,89820,28846,89822,28849],{},[156,89821,28845],{},[156,89823,28700],{},[35,89825,28852,89826,28856],{},[156,89827,28855],{},[35,89829,28859,89830,28865],{},[39,89831,28864],{"href":28862,"rel":89832},[77],[35,89834,28868,89835,28874],{},[39,89836,28873],{"href":28871,"rel":89837},[77],[35,89839,28877,89840,215],{},[39,89841,28882],{"href":28880,"rel":89842},[77],[35,89844,28885,89845,28891,89848,215],{},[39,89846,28890],{"href":28888,"rel":89847},[77],[39,89849,28896],{"href":28894,"rel":89850},[77],[35,89852,28899],{},[35,89854,28902],{},[1170,89856,28905],{},{"title":13,"searchDepth":14,"depth":14,"links":89858},[89859,89860,89861,89867,89868,89869],{"id":24432,"depth":14,"text":24433},{"id":24738,"depth":14,"text":24724},{"id":25722,"depth":14,"text":25723,"children":89862},[89863,89864,89865,89866],{"id":25743,"depth":255,"text":25744},{"id":25774,"depth":255,"text":28914},{"id":26315,"depth":255,"text":26316},{"id":26594,"depth":255,"text":26595},{"id":28386,"depth":14,"text":28387},{"id":28407,"depth":14,"text":28408},{"id":28435,"depth":14,"text":28436},{},{"title":24215,"description":28921},{"loc":28923,"images":89873},[89874,89875,89876,89877,89878,89879],{"loc":24266},{"loc":24868},{"loc":25179},{"loc":25254},{"loc":25330},{"loc":25694},{"id":28937,"title":28938,"body":89881,"contentId":9260,"date":30447,"description":30448,"extension":18,"meta":91150,"navigation":20,"path":30450,"rawbody":30451,"robots":23,"schemaOrg":23,"seo":91151,"sitemap":91152,"stem":30459,"__hash__":30460},{"type":7,"value":89882,"toc":91143},[89883,89887,89891,89900,89902,89908,89912,89918,89924,89926,89930,89932,89938,89974,89980,89984,89986,89988,89995,89999,90001,90003,90005,90015,90017,90129,90131,90253,90261,90263,90272,90274,90276,90278,90284,90290,90296,90298,90340,90342,90384,90386,90396,90398,90464,90466,90468,90470,90622,90624,90633,90641,90643,90645,90647,90655,90659,90743,90751,90753,90761,90765,90767,90775,90777,90779,90783,91005,91015,91017,91019,91021,91023,91031,91047,91051,91113,91119,91121,91125,91127,91129,91133,91135,91137,91141],[35,89884,89885],{},[1216,89886,28945],{},[35,89888,28948,89889,28951],{},[6862,89890,19463],{},[35,89892,28954,89893,1655,89895,28959,89897,215],{},[6862,89894,19463],{},[6862,89896,24399],{},[39,89898,28964],{"href":28962,"rel":89899},[77],[51,89901,28968],{"id":28967},[35,89903,28971,89904,28974,89906,215],{},[6862,89905,24399],{},[6862,89907,19463],{},[35,89909,28979,89910,28982],{},[6862,89911,24399],{},[35,89913,28985,89914,28988,89916],{},[6862,89915,19463],{},[1216,89917,28991],{},[35,89919,28994,89920,1655,89922,28999],{},[6862,89921,19463],{},[6862,89923,24399],{},[35,89925,29002],{},[35,89927,89928],{},[1707,89929],{"alt":29007,"src":29008},[35,89931,29011],{},[68,89933,89934,89936],{},[71,89935,29016],{},[71,89937,29019],{},[192,89939,89940],{"className":590,"code":29022,"language":592,"meta":13,"style":13},[156,89941,89942],{"__ignoreMap":13},[200,89943,89944,89946,89948,89950,89952,89954,89956,89958,89960,89962,89964,89966,89968,89970,89972],{"class":202,"line":203},[200,89945,599],{"class":214},[200,89947,14896],{"class":235},[200,89949,321],{"class":379},[200,89951,449],{"class":214},[200,89953,610],{"class":214},[200,89955,15097],{"class":245},[200,89957,610],{"class":214},[200,89959,10120],{"class":379},[200,89961,449],{"class":214},[200,89963,610],{"class":214},[200,89965,29049],{"class":245},[200,89967,610],{"class":214},[200,89969,10206],{"class":214},[200,89971,14896],{"class":235},[200,89973,647],{"class":214},[68,89975,89976,89978],{"start":255},[71,89977,29062],{},[71,89979,29065],{},[35,89981,29068,89982],{},[1216,89983,29071],{},[35,89985,29074],{},[51,89987,29078],{"id":29077},[35,89989,29081,89990,29087,89993],{},[39,89991,29086],{"href":29084,"rel":89992},[77],[1216,89994,29090],{},[35,89996,89997],{},[1707,89998],{"alt":29095,"src":29096},[35,90000,29099],{},[35,90002,29102],{},[35,90004,29105],{},[1593,90006,90007,90011],{},[71,90008,90009,29113],{},[6862,90010,29112],{},[71,90012,90013,29119],{},[6862,90014,29118],{},[35,90016,29122],{},[192,90018,90019],{"className":1380,"code":29125,"filename":29126,"language":1383,"meta":13,"style":13},[156,90020,90021,90041,90059,90097,90115,90125],{"__ignoreMap":13},[200,90022,90023,90025,90027,90029,90031,90033,90035,90037,90039],{"class":202,"line":203},[200,90024,29133],{"class":210},[200,90026,215],{"class":214},[200,90028,29138],{"class":223},[200,90030,509],{"class":214},[200,90032,5002],{"class":379},[200,90034,1094],{"class":214},[200,90036,29147],{"class":386},[200,90038,2104],{"class":214},[200,90040,280],{"class":214},[200,90042,90043,90045,90047,90049,90051,90053,90055,90057],{"class":202,"line":14},[200,90044,1112],{"class":210},[200,90046,215],{"class":214},[200,90048,1117],{"class":223},[200,90050,227],{"class":235},[200,90052,249],{"class":214},[200,90054,29166],{"class":245},[200,90056,249],{"class":214},[200,90058,416],{"class":235},[200,90060,90061,90063,90065,90067,90069,90071,90073,90075,90077,90079,90081,90083,90085,90087,90089,90091,90093,90095],{"class":202,"line":255},[200,90062,3738],{"class":379},[200,90064,29177],{"class":210},[200,90066,509],{"class":214},[200,90068,11154],{"class":214},[200,90070,29184],{"class":245},[200,90072,4868],{"class":214},[200,90074,29147],{"class":210},[200,90076,215],{"class":214},[200,90078,29193],{"class":210},[200,90080,3948],{"class":2060},[200,90082,18849],{"class":210},[200,90084,29200],{"class":214},[200,90086,29203],{"class":210},[200,90088,215],{"class":214},[200,90090,29193],{"class":210},[200,90092,3203],{"class":2060},[200,90094,360],{"class":210},[200,90096,29214],{"class":214},[200,90098,90099,90101,90103,90105,90107,90109,90111,90113],{"class":202,"line":272},[200,90100,1112],{"class":210},[200,90102,215],{"class":214},[200,90104,1117],{"class":223},[200,90106,227],{"class":235},[200,90108,249],{"class":214},[200,90110,29229],{"class":245},[200,90112,249],{"class":214},[200,90114,416],{"class":235},[200,90116,90117,90119,90121,90123],{"class":202,"line":283},[200,90118,29238],{"class":223},[200,90120,227],{"class":235},[200,90122,29243],{"class":210},[200,90124,416],{"class":235},[200,90126,90127],{"class":202,"line":300},[200,90128,1129],{"class":214},[35,90130,29252],{},[192,90132,90133],{"className":1380,"code":29255,"filename":29256,"language":1383,"meta":13,"style":13},[156,90134,90135,90157,90161,90189,90207,90225,90231,90235],{"__ignoreMap":13},[200,90136,90137,90139,90141,90143,90145,90147,90149,90151,90153,90155],{"class":202,"line":203},[200,90138,443],{"class":379},[200,90140,29265],{"class":210},[200,90142,449],{"class":214},[200,90144,13007],{"class":214},[200,90146,29272],{"class":223},[200,90148,227],{"class":210},[200,90150,249],{"class":214},[200,90152,29126],{"class":245},[200,90154,249],{"class":214},[200,90156,416],{"class":210},[200,90158,90159],{"class":202,"line":14},[200,90160,528],{"emptyLinePlaceholder":20},[200,90162,90163,90165,90167,90169,90171,90173,90175,90177,90179,90181,90183,90185,90187],{"class":202,"line":255},[200,90164,29291],{"class":210},[200,90166,215],{"class":214},[200,90168,29296],{"class":223},[200,90170,227],{"class":210},[200,90172,249],{"class":214},[200,90174,29303],{"class":245},[200,90176,249],{"class":214},[200,90178,549],{"class":214},[200,90180,1094],{"class":214},[200,90182,29147],{"class":386},[200,90184,2104],{"class":214},[200,90186,393],{"class":379},[200,90188,280],{"class":214},[200,90190,90191,90193,90195,90197,90199,90201,90203,90205],{"class":202,"line":272},[200,90192,1112],{"class":210},[200,90194,215],{"class":214},[200,90196,1117],{"class":223},[200,90198,227],{"class":235},[200,90200,249],{"class":214},[200,90202,29332],{"class":245},[200,90204,249],{"class":214},[200,90206,416],{"class":235},[200,90208,90209,90211,90213,90215,90217,90219,90221,90223],{"class":202,"line":283},[200,90210,1112],{"class":210},[200,90212,215],{"class":214},[200,90214,1117],{"class":223},[200,90216,227],{"class":235},[200,90218,29147],{"class":210},[200,90220,215],{"class":214},[200,90222,12736],{"class":210},[200,90224,416],{"class":235},[200,90226,90227,90229],{"class":202,"line":300},[200,90228,413],{"class":214},[200,90230,416],{"class":210},[200,90232,90233],{"class":202,"line":310},[200,90234,528],{"emptyLinePlaceholder":20},[200,90236,90237,90239,90241,90243,90245,90247,90249,90251],{"class":202,"line":336},[200,90238,29291],{"class":210},[200,90240,215],{"class":214},[200,90242,29373],{"class":223},[200,90244,25224],{"class":210},[200,90246,4797],{"class":2060},[200,90248,549],{"class":214},[200,90250,29382],{"class":2060},[200,90252,29385],{"class":210},[35,90254,29388,90255,29391,90257,29394,90259,215],{},[156,90256,29373],{},[156,90258,29373],{},[156,90260,29296],{},[35,90262,29399],{},[938,90264,90265],{},[35,90266,90267,29406,90269,29412],{},[200,90268,944],{},[39,90270,29411],{"href":29409,"rel":90271},[77],[51,90273,18647],{"id":18646},[35,90275,29417],{},[35,90277,29420],{},[938,90279,90280],{},[35,90281,90282,29428],{},[200,90283,29427],{},[35,90285,29431,90286,1655,90288,29438],{},[156,90287,29434],{},[156,90289,29437],{},[35,90291,29441,90292,29444,90294,215],{},[156,90293,29434],{},[156,90295,29434],{},[35,90297,29449],{},[192,90299,90300],{"className":590,"code":29452,"language":592,"meta":13,"style":13},[156,90301,90302,90310,90332],{"__ignoreMap":13},[200,90303,90304,90306,90308],{"class":202,"line":203},[200,90305,599],{"class":214},[200,90307,14896],{"class":235},[200,90309,647],{"class":214},[200,90311,90312,90314,90316,90318,90320,90322,90324,90326,90328,90330],{"class":202,"line":14},[200,90313,3738],{"class":379},[200,90315,29265],{"class":210},[200,90317,449],{"class":214},[200,90319,13007],{"class":214},[200,90321,29272],{"class":223},[200,90323,227],{"class":210},[200,90325,249],{"class":214},[200,90327,29126],{"class":245},[200,90329,249],{"class":214},[200,90331,416],{"class":210},[200,90333,90334,90336,90338],{"class":202,"line":255},[200,90335,680],{"class":214},[200,90337,14896],{"class":235},[200,90339,647],{"class":214},[35,90341,29495],{},[192,90343,90344],{"className":590,"code":29498,"language":592,"meta":13,"style":13},[156,90345,90346,90354,90376],{"__ignoreMap":13},[200,90347,90348,90350,90352],{"class":202,"line":203},[200,90349,599],{"class":214},[200,90351,14896],{"class":235},[200,90353,647],{"class":214},[200,90355,90356,90358,90360,90362,90364,90366,90368,90370,90372,90374],{"class":202,"line":14},[200,90357,3738],{"class":379},[200,90359,29265],{"class":210},[200,90361,449],{"class":214},[200,90363,13007],{"class":214},[200,90365,29272],{"class":223},[200,90367,227],{"class":210},[200,90369,249],{"class":214},[200,90371,29527],{"class":245},[200,90373,249],{"class":214},[200,90375,416],{"class":210},[200,90377,90378,90380,90382],{"class":202,"line":255},[200,90379,680],{"class":214},[200,90381,14896],{"class":235},[200,90383,647],{"class":214},[35,90385,29542],{},[35,90387,29545,90388,29549,90390,106,90392,1655,90394,29557],{},[156,90389,29548],{},[156,90391,29552],{},[156,90393,29548],{},[156,90395,29552],{},[35,90397,29560],{},[192,90399,90400],{"className":1354,"code":29563,"language":1357,"meta":13,"style":13},[156,90401,90402],{"__ignoreMap":13},[200,90403,90404,90406,90408,90410,90412,90414,90416,90418,90420,90422,90424,90426,90428,90430,90432,90434,90436,90438,90440,90442,90444,90446,90448,90450,90452,90454,90456,90458,90460,90462],{"class":202,"line":203},[200,90405,29570],{"class":1364},[200,90407,29573],{"class":245},[200,90409,29576],{"class":245},[200,90411,29579],{"class":245},[200,90413,29582],{"class":245},[200,90415,29576],{"class":245},[200,90417,242],{"class":214},[200,90419,29589],{"class":245},[200,90421,249],{"class":214},[200,90423,2186],{"class":245},[200,90425,29596],{"class":245},[200,90427,29599],{"class":245},[200,90429,29602],{"class":245},[200,90431,242],{"class":214},[200,90433,29607],{"class":245},[200,90435,249],{"class":214},[200,90437,29612],{"class":245},[200,90439,29615],{"class":245},[200,90441,29618],{"class":245},[200,90443,29621],{"class":245},[200,90445,29624],{"class":245},[200,90447,29627],{"class":245},[200,90449,29630],{"class":245},[200,90451,249],{"class":214},[200,90453,29635],{"class":245},[200,90455,249],{"class":214},[200,90457,29640],{"class":245},[200,90459,2186],{"class":245},[200,90461,249],{"class":214},[200,90463,29647],{"class":245},[35,90465,29650],{},[35,90467,29653],{},[35,90469,29656],{},[192,90471,90472],{"className":1380,"code":29659,"language":1383,"meta":13,"style":13},[156,90473,90474,90492,90506,90520,90524,90536,90552,90564,90576,90586,90592,90602,90606,90610,90614,90618],{"__ignoreMap":13},[200,90475,90476,90478,90480,90482,90484,90486,90488,90490],{"class":202,"line":203},[200,90477,972],{"class":206},[200,90479,29668],{"class":214},[200,90481,14678],{"class":206},[200,90483,29673],{"class":210},[200,90485,1489],{"class":206},[200,90487,242],{"class":214},[200,90489,29680],{"class":245},[200,90491,991],{"class":214},[200,90493,90494,90496,90498,90500,90502,90504],{"class":202,"line":14},[200,90495,972],{"class":206},[200,90497,29689],{"class":210},[200,90499,1489],{"class":206},[200,90501,242],{"class":214},[200,90503,29696],{"class":245},[200,90505,991],{"class":214},[200,90507,90508,90510,90512,90514,90516,90518],{"class":202,"line":255},[200,90509,972],{"class":206},[200,90511,29705],{"class":210},[200,90513,1489],{"class":206},[200,90515,242],{"class":214},[200,90517,29712],{"class":245},[200,90519,991],{"class":214},[200,90521,90522],{"class":202,"line":272},[200,90523,528],{"emptyLinePlaceholder":20},[200,90525,90526,90528,90530,90532,90534],{"class":202,"line":283},[200,90527,29723],{"class":210},[200,90529,215],{"class":214},[200,90531,29728],{"class":210},[200,90533,449],{"class":214},[200,90535,280],{"class":214},[200,90537,90538,90540,90542,90544,90546,90548,90550],{"class":202,"line":300},[200,90539,29737],{"class":235},[200,90541,227],{"class":214},[200,90543,29742],{"class":386},[200,90545,549],{"class":214},[200,90547,21516],{"class":386},[200,90549,2104],{"class":214},[200,90551,280],{"class":214},[200,90553,90554,90556,90558,90560,90562],{"class":202,"line":310},[200,90555,29755],{"class":206},[200,90557,1094],{"class":235},[200,90559,659],{"class":210},[200,90561,5282],{"class":235},[200,90563,230],{"class":214},[200,90565,90566,90568,90570,90572,90574],{"class":202,"line":336},[200,90567,29768],{"class":206},[200,90569,242],{"class":214},[200,90571,28360],{"class":245},[200,90573,249],{"class":214},[200,90575,19494],{"class":214},[200,90577,90578,90580,90582,90584],{"class":202,"line":342},[200,90579,13413],{"class":206},[200,90581,13007],{"class":214},[200,90583,29785],{"class":223},[200,90585,466],{"class":235},[200,90587,90588,90590],{"class":202,"line":365},[200,90589,29792],{"class":206},[200,90591,19494],{"class":214},[200,90593,90594,90596,90598,90600],{"class":202,"line":371},[200,90595,13413],{"class":206},[200,90597,13007],{"class":214},[200,90599,29803],{"class":223},[200,90601,466],{"class":235},[200,90603,90604],{"class":202,"line":398},[200,90605,4977],{"class":214},[200,90607,90608],{"class":202,"line":405},[200,90609,368],{"class":214},[200,90611,90612],{"class":202,"line":410},[200,90613,1129],{"class":214},[200,90615,90616],{"class":202,"line":860},[200,90617,528],{"emptyLinePlaceholder":20},[200,90619,90620],{"class":202,"line":889},[200,90621,29826],{"class":401},[35,90623,29829],{},[938,90625,90626],{},[35,90627,90628,29836,90630,29842],{},[200,90629,944],{},[39,90631,29841],{"href":29839,"rel":90632},[77],[938,90634,90635],{},[35,90636,90637,29849,90639,29853],{},[200,90638,944],{},[156,90640,29852],{},[51,90642,29857],{"id":29856},[35,90644,29860],{},[35,90646,29863],{},[192,90648,90649],{"className":4292,"code":29866,"language":4294,"meta":13,"style":13},[156,90650,90651],{"__ignoreMap":13},[200,90652,90653],{"class":202,"line":203},[200,90654,29866],{},[35,90656,29875,90657,29879],{},[156,90658,29878],{},[192,90660,90661],{"className":25070,"code":29882,"language":25072,"meta":13,"style":13},[156,90662,90663,90681,90691,90701,90705,90719,90731,90735,90739],{"__ignoreMap":13},[200,90664,90665,90667,90669,90671,90673,90675,90677,90679],{"class":202,"line":203},[200,90666,1390],{"class":206},[200,90668,1393],{"class":206},[200,90670,5002],{"class":379},[200,90672,29895],{"class":223},[200,90674,227],{"class":214},[200,90676,29900],{"class":386},[200,90678,2104],{"class":214},[200,90680,280],{"class":214},[200,90682,90683,90685,90687,90689],{"class":202,"line":14},[200,90684,3758],{"class":206},[200,90686,13007],{"class":214},[200,90688,29272],{"class":223},[200,90690,3130],{"class":235},[200,90692,90693,90695,90697,90699],{"class":202,"line":255},[200,90694,1415],{"class":214},[200,90696,29921],{"class":245},[200,90698,249],{"class":214},[200,90700,252],{"class":214},[200,90702,90703],{"class":202,"line":272},[200,90704,19661],{"class":214},[200,90706,90707,90709,90711,90713,90715,90717],{"class":202,"line":283},[200,90708,29934],{"class":235},[200,90710,239],{"class":214},[200,90712,242],{"class":214},[200,90714,15097],{"class":245},[200,90716,249],{"class":214},[200,90718,252],{"class":214},[200,90720,90721,90723,90725,90727,90729],{"class":202,"line":300},[200,90722,2068],{"class":235},[200,90724,239],{"class":214},[200,90726,9842],{"class":210},[200,90728,6536],{"class":214},[200,90730,29957],{"class":210},[200,90732,90733],{"class":202,"line":310},[200,90734,4977],{"class":214},[200,90736,90737],{"class":202,"line":336},[200,90738,29966],{"class":235},[200,90740,90741],{"class":202,"line":342},[200,90742,1129],{"class":214},[35,90744,29973,90745,29976,90747,29979,90749,29982],{},[156,90746,29878],{},[156,90748,29589],{},[156,90750,29589],{},[35,90752,29985],{},[192,90754,90755],{"className":4292,"code":29988,"language":4294,"meta":13,"style":13},[156,90756,90757],{"__ignoreMap":13},[200,90758,90759],{"class":202,"line":203},[200,90760,29988],{},[35,90762,29997,90763,30000],{},[156,90764,29552],{},[35,90766,30003],{},[192,90768,90769],{"className":4292,"code":30006,"language":4294,"meta":13,"style":13},[156,90770,90771],{"__ignoreMap":13},[200,90772,90773],{"class":202,"line":203},[200,90774,30006],{},[35,90776,30015],{},[35,90778,30018],{},[35,90780,30021,90781,30024],{},[156,90782,1477],{},[192,90784,90785],{"className":1380,"code":30027,"language":1383,"meta":13,"style":13},[156,90786,90787,90805,90809,90821,90829,90839,90845,90859,90875,90901,90905,90909,90913,90937,90949,90959,90969,90973,90977,90981,90985,90993,90999],{"__ignoreMap":13},[200,90788,90789,90791,90793,90795,90797,90799,90801,90803],{"class":202,"line":203},[200,90790,972],{"class":206},[200,90792,318],{"class":214},[200,90794,1505],{"class":210},[200,90796,980],{"class":214},[200,90798,983],{"class":206},[200,90800,242],{"class":214},[200,90802,1514],{"class":245},[200,90804,991],{"class":214},[200,90806,90807],{"class":202,"line":14},[200,90808,528],{"emptyLinePlaceholder":20},[200,90810,90811,90813,90815,90817,90819],{"class":202,"line":255},[200,90812,1390],{"class":206},[200,90814,1393],{"class":206},[200,90816,1505],{"class":223},[200,90818,227],{"class":210},[200,90820,230],{"class":214},[200,90822,90823,90825,90827],{"class":202,"line":272},[200,90824,1537],{"class":235},[200,90826,239],{"class":214},[200,90828,1410],{"class":210},[200,90830,90831,90833,90835,90837],{"class":202,"line":283},[200,90832,11719],{"class":210},[200,90834,1549],{"class":214},[200,90836,393],{"class":379},[200,90838,280],{"class":214},[200,90840,90841,90843],{"class":202,"line":300},[200,90842,11730],{"class":206},[200,90844,280],{"class":214},[200,90846,90847,90849,90851,90853,90855,90857],{"class":202,"line":310},[200,90848,11737],{"class":235},[200,90850,239],{"class":214},[200,90852,242],{"class":214},[200,90854,30098],{"class":245},[200,90856,249],{"class":214},[200,90858,252],{"class":214},[200,90860,90861,90863,90865,90867,90869,90871,90873],{"class":202,"line":336},[200,90862,30107],{"class":235},[200,90864,227],{"class":214},[200,90866,156],{"class":386},[200,90868,549],{"class":214},[200,90870,3239],{"class":386},[200,90872,2104],{"class":214},[200,90874,280],{"class":214},[200,90876,90877,90879,90881,90883,90885,90887,90889,90891,90893,90895,90897,90899],{"class":202,"line":342},[200,90878,30124],{"class":206},[200,90880,1094],{"class":235},[200,90882,4741],{"class":214},[200,90884,30131],{"class":210},[200,90886,215],{"class":214},[200,90888,30136],{"class":210},[200,90890,13398],{"class":214},[200,90892,242],{"class":214},[200,90894,30143],{"class":245},[200,90896,249],{"class":214},[200,90898,5282],{"class":235},[200,90900,230],{"class":214},[200,90902,90903],{"class":202,"line":365},[200,90904,30154],{"class":206},[200,90906,90907],{"class":202,"line":371},[200,90908,30159],{"class":214},[200,90910,90911],{"class":202,"line":398},[200,90912,528],{"emptyLinePlaceholder":20},[200,90914,90915,90917,90919,90921,90923,90925,90927,90929,90931,90933,90935],{"class":202,"line":405},[200,90916,30124],{"class":206},[200,90918,1094],{"class":235},[200,90920,3295],{"class":210},[200,90922,215],{"class":214},[200,90924,3856],{"class":223},[200,90926,227],{"class":235},[200,90928,249],{"class":214},[200,90930,29291],{"class":245},[200,90932,249],{"class":214},[200,90934,6478],{"class":235},[200,90936,230],{"class":214},[200,90938,90939,90941,90943,90945,90947],{"class":202,"line":410},[200,90940,30192],{"class":206},[200,90942,4179],{"class":210},[200,90944,215],{"class":214},[200,90946,6148],{"class":223},[200,90948,3130],{"class":235},[200,90950,90951,90953,90955,90957],{"class":202,"line":860},[200,90952,30205],{"class":214},[200,90954,30208],{"class":245},[200,90956,249],{"class":214},[200,90958,252],{"class":214},[200,90960,90961,90963,90965,90967],{"class":202,"line":889},[200,90962,30205],{"class":214},[200,90964,30219],{"class":245},[200,90966,249],{"class":214},[200,90968,252],{"class":214},[200,90970,90971],{"class":202,"line":899},[200,90972,30228],{"class":235},[200,90974,90975],{"class":202,"line":904},[200,90976,30159],{"class":214},[200,90978,90979],{"class":202,"line":930},[200,90980,4965],{"class":214},[200,90982,90983],{"class":202,"line":3655},[200,90984,4493],{"class":214},[200,90986,90987,90989,90991],{"class":202,"line":3660},[200,90988,2111],{"class":214},[200,90990,30247],{"class":210},[200,90992,252],{"class":214},[200,90994,90995,90997],{"class":202,"line":4035},[200,90996,1427],{"class":210},[200,90998,252],{"class":214},[200,91000,91001,91003],{"class":202,"line":4490},[200,91002,413],{"class":214},[200,91004,416],{"class":210},[938,91006,91007],{},[35,91008,91009,30268,91011,30271,91013,30274],{},[200,91010,944],{},[156,91012,30208],{},[156,91014,29552],{},[35,91016,30277],{},[35,91018,30280],{},[35,91020,30283],{},[35,91022,30286],{},[35,91024,30289,91025,2873,91027,30296,91029,30299],{},[156,91026,30292],{},[156,91028,30295],{},[156,91030,26630],{},[192,91032,91033],{"className":19560,"code":30302,"language":19563,"meta":13,"style":13},[156,91034,91035,91039,91043],{"__ignoreMap":13},[200,91036,91037],{"class":202,"line":203},[200,91038,30309],{},[200,91040,91041],{"class":202,"line":14},[200,91042,30314],{},[200,91044,91045],{"class":202,"line":255},[200,91046,1129],{},[35,91048,30321,91049,2186],{},[156,91050,30292],{},[192,91052,91053],{"className":19560,"code":30326,"language":19563,"meta":13,"style":13},[156,91054,91055,91059,91063,91067,91071,91075,91079,91083,91087,91091,91095,91099,91105,91109],{"__ignoreMap":13},[200,91056,91057],{"class":202,"line":203},[200,91058,19600],{},[200,91060,91061],{"class":202,"line":14},[200,91062,528],{"emptyLinePlaceholder":20},[200,91064,91065],{"class":202,"line":255},[200,91066,19575],{},[200,91068,91069],{"class":202,"line":272},[200,91070,528],{"emptyLinePlaceholder":20},[200,91072,91073],{"class":202,"line":283},[200,91074,30349],{},[200,91076,91077],{"class":202,"line":300},[200,91078,30354],{},[200,91080,91081],{"class":202,"line":310},[200,91082,30359],{},[200,91084,91085],{"class":202,"line":336},[200,91086,30364],{},[200,91088,91089],{"class":202,"line":342},[200,91090,30369],{},[200,91092,91093],{"class":202,"line":365},[200,91094,30374],{},[200,91096,91097],{"class":202,"line":371},[200,91098,30379],{},[200,91100,91101,91103],{"class":202,"line":398},[200,91102,30384],{},[200,91104,30387],{},[200,91106,91107],{"class":202,"line":405},[200,91108,30392],{},[200,91110,91111],{"class":202,"line":410},[200,91112,30397],{},[938,91114,91115],{},[35,91116,91117,30404],{},[200,91118,944],{},[35,91120,30407],{},[35,91122,91123],{},[1707,91124],{"alt":30412,"src":30413},[35,91126,30416],{},[35,91128,30419],{},[35,91130,91131],{},[1707,91132],{"alt":30424,"src":30425},[51,91134,24146],{"id":24145},[35,91136,30430],{},[35,91138,30433,91139,30436],{},[6862,91140,24399],{},[1170,91142,30439],{},{"title":13,"searchDepth":14,"depth":14,"links":91144},[91145,91146,91147,91148,91149],{"id":28967,"depth":14,"text":28968},{"id":29077,"depth":14,"text":29078},{"id":18646,"depth":14,"text":18647},{"id":29856,"depth":14,"text":29857},{"id":24145,"depth":14,"text":24146},{},{"title":28938,"description":30448},{"loc":30450,"images":91153},[91154,91155,91156,91157],{"loc":29008},{"loc":29096},{"loc":30413},{"loc":30425},{"id":30462,"title":30463,"body":91159,"contentId":8795,"date":30938,"description":30939,"extension":18,"meta":91465,"navigation":20,"path":30941,"rawbody":30942,"robots":23,"schemaOrg":23,"seo":91466,"sitemap":91467,"stem":30953,"__hash__":30954},{"type":7,"value":91160,"toc":91454},[91161,91163,91170,91172,91176,91178,91180,91190,91198,91202,91204,91206,91217,91223,91227,91229,91233,91239,91241,91251,91256,91260,91262,91266,91274,91278,91286,91290,91300,91304,91308,91314,91318,91322,91326,91328,91334,91340,91342,91352,91356,91358,91360,91362,91372,91378,91384,91394,91399,91403,91405,91407,91409,91421,91430,91434,91442,91444,91446,91448,91450],[35,91162,30468],{},[35,91164,30471,91165,30475,91167,30480],{},[6862,91166,30474],{},[39,91168,30479],{"href":8798,"rel":91169},[77],[35,91171,30483],{},[938,91173,91174],{},[35,91175,30488],{},[35,91177,30491],{},[51,91179,30495],{"id":30494},[35,91181,30498,91182,30501,91184,30504,91186,30507,91188,30510],{},[6862,91183,21156],{},[6862,91185,30495],{},[6862,91187,22922],{},[6862,91189,22922],{},[35,91191,30513,91192,14181,91194,13741,91196,30521],{},[1216,91193,30516],{},[6862,91195,18003],{},[6862,91197,17998],{},[35,91199,91200],{},[1707,91201],{"alt":30526,"src":30527},[35,91203,30530],{},[1929,91205,30534],{"id":30533},[35,91207,30537,91208,30541,91210,30547,91213,30551,91215,30555],{},[6862,91209,30540],{},[39,91211,30546],{"href":30544,"rel":91212},[77],[6862,91214,30550],{},[6862,91216,30554],{},[35,91218,30558,91219,30561,91221],{},[6862,91220,30554],{},[3045,91222,215],{"id":30564},[35,91224,30567,91225],{},[3045,91226,30571],{"id":30570},[1929,91228,18581],{"id":30574},[35,91230,30577,91231,30580],{},[6862,91232,18581],{},[35,91234,30583,91235,30586,91237],{},[6862,91236,2934],{},[3045,91238,215],{"id":30589},[35,91240,30592],{},[1593,91242,91243,91247],{},[71,91244,91245],{},[3045,91246],{"id":30599},[71,91248,91249],{},[3045,91250],{"id":30604},[35,91252,30607,91253,215],{},[39,91254,9234],{"href":30610,"rel":91255},[77],[35,91257,30614,91258,30618],{},[6862,91259,30617],{},[51,91261,8958],{"id":8957},[35,91263,30623,91264,30626],{},[6862,91265,7154],{},[35,91267,30629,91268,30633,91270,30636,91272],{},[6862,91269,30632],{},[6862,91271,30540],{},[3045,91273,215],{"id":30639},[35,91275,91276],{},[1707,91277],{"alt":30644,"src":30645},[35,91279,30648,91280,30651,91282,30654,91284],{},[6862,91281,7154],{},[6862,91283,30540],{},[3045,91285,215],{"id":20859},[35,91287,91288],{},[1707,91289],{"alt":30661,"src":30662},[35,91291,30665,91292,30669,91294,30672,91296,30675,91298],{},[6862,91293,30668],{},[6862,91295,2934],{},[6862,91297,17998],{},[3045,91299,215],{"id":30678},[35,91301,91302],{},[1707,91303],{"alt":30683,"src":30684},[35,91305,30687,91306,30691],{},[6862,91307,30690],{},[35,91309,30694,91310,30698,91312,30702],{},[6862,91311,30697],{},[6862,91313,30701],{},[35,91315,91316],{},[1707,91317],{"alt":30707,"src":30708},[35,91319,30711,91320,30715],{},[6862,91321,30714],{},[35,91323,91324],{},[1707,91325],{"alt":30720,"src":30721},[51,91327,30725],{"id":30724},[35,91329,30728,91330,30732,91332],{},[6862,91331,30731],{},[3045,91333,215],{"id":30735},[35,91335,30738,91336,30742,91338,30746],{},[6862,91337,30741],{},[6862,91339,30745],{},[51,91341,30750],{"id":30749},[35,91343,30753,91344,30756,91346,3092,91348,1655,91350,30766],{},[6862,91345,21389],{},[6862,91347,30759],{},[6862,91349,30762],{},[6862,91351,30765],{},[35,91353,30769,91354,30772],{},[6862,91355,19463],{},[35,91357,30775],{},[51,91359,30779],{"id":30778},[35,91361,30782],{},[35,91363,30785,91364,3092,91366,3092,91368,9502,91370,30794],{},[6862,91365,2934],{},[6862,91367,17998],{},[6862,91369,30540],{},[6862,91371,19463],{},[35,91373,30797,91374,30800,91376],{},[6862,91375,2929],{},[3045,91377,30804],{"id":30803},[35,91379,30807,91380],{},[3045,91381,30811,91382,30814],{"id":30810},[6862,91383,17998],{},[35,91385,30817,91386,30821,91388],{},[6862,91387,30820],{},[3045,91389,3092,91390],{"id":30824},[3045,91391,9502,91392],{"id":30827},[3045,91393,215],{"id":30830},[35,91395,30833,91396,30839],{},[39,91397,30838],{"href":30836,"rel":91398},[77],[35,91400,30842,91401,30845],{},[6862,91402,11636],{},[35,91404,30848],{},[51,91406,30852],{"id":30851},[35,91408,30855],{},[35,91410,30858,91411,30861,91413,30865,91415,14181,91417,1655,91419,30875],{},[6862,91412,19463],{},[6862,91414,30864],{},[6862,91416,30868],{},[6862,91418,30871],{},[6862,91420,30874],{},[35,91422,30878,91423,30881,91425,30885,91427,30889],{},[6862,91424,17998],{},[6862,91426,30884],{},[39,91428,8877],{"href":8875,"rel":91429},[77],[35,91431,30892,91432,30895],{},[6862,91433,7894],{},[35,91435,30898,91436,30902,91438,1655,91440,30908],{},[6862,91437,30901],{},[6862,91439,7154],{},[6862,91441,30907],{},[35,91443,30911],{},[35,91445,30914],{},[35,91447,30917],{},[35,91449,30920],{},[35,91451,91452],{},[1707,91453],{"alt":30925,"src":30926},{"title":13,"searchDepth":14,"depth":14,"links":91455},[91456,91460,91461,91462,91463,91464],{"id":30494,"depth":14,"text":30495,"children":91457},[91458,91459],{"id":30533,"depth":255,"text":30534},{"id":30574,"depth":255,"text":18581},{"id":8957,"depth":14,"text":8958},{"id":30724,"depth":14,"text":30725},{"id":30749,"depth":14,"text":30750},{"id":30778,"depth":14,"text":30779},{"id":30851,"depth":14,"text":30852},{},{"title":30463,"description":30939},{"loc":30941,"images":91468},[91469,91470,91471,91472,91473,91474,91475],{"loc":30527},{"loc":30645},{"loc":30662},{"loc":30684},{"loc":30708},{"loc":30721},{"loc":30926},{"id":30956,"title":30957,"body":91477,"contentId":33224,"date":33225,"description":33226,"extension":18,"meta":93374,"navigation":20,"path":33228,"rawbody":33229,"robots":23,"schemaOrg":23,"seo":93375,"sitemap":93376,"stem":33232,"__hash__":33233},{"type":7,"value":91478,"toc":93365},[91479,91485,91489,91491,91495,91515,91525,91535,91541,91543,91545,91557,91568,91570,91574,91618,91622,91628,91646,91654,93363],[35,91480,30962,91481,30965,91483,30968],{},[6862,91482,2934],{},[6862,91484,2934],{},[35,91486,30971,91487,30975],{},[6862,91488,30974],{},[51,91490,30979],{"id":30978},[35,91492,30982,91493,30985],{},[6862,91494,2934],{},[192,91496,91497],{"className":2166,"code":30988,"language":2168,"meta":13,"style":13},[156,91498,91499],{"__ignoreMap":13},[200,91500,91501,91503,91505,91507,91509,91511,91513],{"class":202,"line":203},[200,91502,30995],{"class":1364},[200,91504,30998],{"class":245},[200,91506,31001],{"class":245},[200,91508,31004],{"class":214},[200,91510,31007],{"class":245},[200,91512,31010],{"class":210},[200,91514,647],{"class":214},[938,91516,91517],{},[35,91518,91519,31020,91521,31024,91523,215],{},[200,91520,31019],{},[156,91522,31023],{},[39,91524,31028],{"href":31027},[938,91526,91527],{},[35,91528,91529,31035,91531,31038,91533,215],{},[200,91530,944],{},[6862,91532,1365],{},[6862,91534,1365],{},[35,91536,31043,91537,31047,91539,215],{},[156,91538,31046],{},[156,91540,31050],{},[51,91542,31054],{"id":31053},[35,91544,31057],{},[192,91546,91547],{"className":2166,"code":31060,"language":2168,"meta":13,"style":13},[156,91548,91549],{"__ignoreMap":13},[200,91550,91551,91553,91555],{"class":202,"line":203},[200,91552,30995],{"class":1364},[200,91554,31069],{"class":245},[200,91556,31072],{"class":245},[35,91558,31075,91559,31079,91561,31084,91564,31088,91566,31092],{},[6862,91560,31078],{},[39,91562,24157],{"href":31082,"rel":91563},[77],[156,91565,31087],{},[6862,91567,31091],{},[51,91569,31028],{"id":31095},[35,91571,31098,91572,2186],{},[156,91573,31101],{},[192,91575,91576],{"className":1380,"code":31104,"language":1383,"meta":13,"style":13},[156,91577,91578,91590,91598,91608,91612],{"__ignoreMap":13},[200,91579,91580,91582,91584,91586,91588],{"class":202,"line":203},[200,91581,1390],{"class":206},[200,91583,1393],{"class":206},[200,91585,1396],{"class":223},[200,91587,227],{"class":210},[200,91589,230],{"class":214},[200,91591,91592,91594,91596],{"class":202,"line":14},[200,91593,31123],{"class":235},[200,91595,239],{"class":214},[200,91597,280],{"class":214},[200,91599,91600,91602,91604,91606],{"class":202,"line":255},[200,91601,31132],{"class":235},[200,91603,239],{"class":214},[200,91605,11033],{"class":2060},[200,91607,252],{"class":214},[200,91609,91610],{"class":202,"line":272},[200,91611,4505],{"class":214},[200,91613,91614,91616],{"class":202,"line":283},[200,91615,413],{"class":214},[200,91617,416],{"class":210},[35,91619,31151,91620,31155],{},[6862,91621,31154],{},[35,91623,31158,91624,31161,91626,31164],{},[156,91625,26785],{},[156,91627,31050],{},[192,91629,91630],{"className":2166,"code":31167,"language":2168,"meta":13,"style":13},[156,91631,91632],{"__ignoreMap":13},[200,91633,91634,91636,91638,91640,91642,91644],{"class":202,"line":203},[200,91635,31174],{"class":1364},[200,91637,14334],{"class":245},[200,91639,14932],{"class":214},[200,91641,31181],{"class":1364},[200,91643,31184],{"class":245},[200,91645,31187],{"class":245},[938,91647,91648],{},[35,91649,91650,31194,91652,215],{},[200,91651,31019],{},[156,91653,31023],{},[7918,91655,91656,91658,91660,91686,91695,91750,91754,91778,91782,91844,91850,91858,91860,91864,91866,91874],{"text":31199,"href":31200},[51,91657,31204],{"id":31203},[35,91659,31207],{},[192,91661,91662],{"className":2166,"code":31210,"language":2168,"meta":13,"style":13},[156,91663,91664],{"__ignoreMap":13},[200,91665,91666,91668,91670,91672,91674,91676,91678,91680,91682,91684],{"class":202,"line":203},[200,91667,30995],{"class":1364},[200,91669,31219],{"class":245},[200,91671,31222],{"class":245},[200,91673,2177],{"class":245},[200,91675,31227],{"class":245},[200,91677,14932],{"class":214},[200,91679,31232],{"class":1364},[200,91681,31235],{"class":245},[200,91683,1371],{"class":245},[200,91685,31240],{"class":245},[35,91687,31243,91688,31247,91690,31253,91693,2186],{},[156,91689,31246],{},[39,91691,31252],{"href":31250,"rel":91692},[77],[156,91694,1382],{},[192,91696,91697],{"className":9755,"code":31258,"language":1383,"meta":13,"style":13},[156,91698,91699,91711,91719,91727,91736,91740,91744],{"__ignoreMap":13},[200,91700,91701,91703,91705,91707,91709],{"class":202,"line":203},[200,91702,1390],{"class":206},[200,91704,1393],{"class":206},[200,91706,1396],{"class":223},[200,91708,227],{"class":210},[200,91710,230],{"class":214},[200,91712,91713,91715,91717],{"class":202,"line":14},[200,91714,31277],{"class":235},[200,91716,239],{"class":214},[200,91718,280],{"class":214},[200,91720,91721,91723,91725],{"class":202,"line":255},[200,91722,24973],{"class":235},[200,91724,239],{"class":214},[200,91726,280],{"class":214},[200,91728,91730,91732,91734],{"class":91729,"line":272},[202,9885],[200,91731,31295],{"class":235},[200,91733,239],{"class":214},[200,91735,2439],{"class":2438},[200,91737,91738],{"class":202,"line":283},[200,91739,4977],{"class":214},[200,91741,91742],{"class":202,"line":300},[200,91743,4505],{"class":214},[200,91745,91746,91748],{"class":202,"line":310},[200,91747,413],{"class":214},[200,91749,416],{"class":210},[35,91751,31316,91752,31320],{},[156,91753,31319],{},[192,91755,91756],{"className":28358,"code":31323,"language":28360,"meta":13,"style":13},[156,91757,91758,91762,91774],{"__ignoreMap":13},[200,91759,91760],{"class":202,"line":203},[200,91761,230],{"class":214},[200,91763,91764,91766,91768,91770,91772],{"class":202,"line":14},[200,91765,28489],{"class":214},[200,91767,31336],{"class":379},[200,91769,610],{"class":214},[200,91771,239],{"class":214},[200,91773,18902],{"class":214},[200,91775,91776],{"class":202,"line":255},[200,91777,1129],{"class":214},[35,91779,31349,91780,2186],{},[156,91781,31352],{},[192,91783,91784],{"className":28358,"code":31355,"language":28360,"meta":13,"style":13},[156,91785,91786,91790,91802,91820,91836,91840],{"__ignoreMap":13},[200,91787,91788],{"class":202,"line":203},[200,91789,230],{"class":214},[200,91791,91792,91794,91796,91798,91800],{"class":202,"line":14},[200,91793,28489],{"class":214},[200,91795,31368],{"class":379},[200,91797,610],{"class":214},[200,91799,239],{"class":214},[200,91801,280],{"class":214},[200,91803,91804,91806,91808,91810,91812,91814,91816,91818],{"class":202,"line":255},[200,91805,28529],{"class":214},[200,91807,31381],{"class":1364},[200,91809,610],{"class":214},[200,91811,239],{"class":214},[200,91813,28499],{"class":214},[200,91815,31390],{"class":245},[200,91817,610],{"class":214},[200,91819,252],{"class":214},[200,91821,91822,91824,91826,91828,91830,91832,91834],{"class":202,"line":272},[200,91823,28529],{"class":214},[200,91825,31401],{"class":1364},[200,91827,610],{"class":214},[200,91829,239],{"class":214},[200,91831,28499],{"class":214},[200,91833,31410],{"class":245},[200,91835,616],{"class":214},[200,91837,91838],{"class":202,"line":283},[200,91839,4505],{"class":214},[200,91841,91842],{"class":202,"line":300},[200,91843,1129],{"class":214},[35,91845,31423,91846,31427,91848,31431],{},[156,91847,31426],{},[156,91849,31430],{},[35,91851,31434,91852,31438,91854,1655,91856,31444],{},[6862,91853,31437],{},[6862,91855,31441],{},[6862,91857,17998],{},[51,91859,31448],{"id":31447},[35,91861,31451,91862,31454],{},[6862,91863,31154],{},[1929,91865,31458],{"id":31457},[35,91867,31461,91868,31465,91871,31470],{},[39,91869,9203],{"href":14207,"rel":91870},[77],[39,91872,31469],{"href":9201,"rel":91873},[77],[7918,91875,91876,91882,91884,91898,91902,91940,91944,91972,91976,92014,92021,92097,92103,92147,92153,92155,92162,92164,92178,92182,92198,92202,92290,92297,92373,92375,92377,92382,92384,92386,92393,92409,92411,92418,92432,92436,92540,92544,92568,92573,92575,92582,92598,92600,92602,92606,92612,92812,92817,93027,93029,93034,93038,93074,93076,93078,93080,93082,93088,93146,93148,93156,93192,93200,93206,93216,93258,93270,93274,93279,93285,93303,93307,93312],{"id":30810},[938,91877,91878],{},[35,91879,91880,31479],{},[200,91881,944],{},[35,91883,31482],{},[192,91885,91886],{"className":2166,"code":31485,"language":2168,"meta":13,"style":13},[156,91887,91888],{"__ignoreMap":13},[200,91889,91890,91892,91894,91896],{"class":202,"line":203},[200,91891,1365],{"class":1364},[200,91893,2177],{"class":245},[200,91895,31496],{"class":245},[200,91897,31499],{"class":245},[35,91899,31502,91900,2186],{},[156,91901,1382],{},[192,91903,91904],{"className":1380,"code":31507,"language":1383,"meta":13,"style":13},[156,91905,91906,91918,91934],{"__ignoreMap":13},[200,91907,91908,91910,91912,91914,91916],{"class":202,"line":203},[200,91909,1390],{"class":206},[200,91911,1393],{"class":206},[200,91913,1396],{"class":223},[200,91915,227],{"class":210},[200,91917,230],{"class":214},[200,91919,91920,91922,91924,91926,91928,91930,91932],{"class":202,"line":14},[200,91921,1405],{"class":235},[200,91923,239],{"class":214},[200,91925,350],{"class":210},[200,91927,249],{"class":214},[200,91929,31534],{"class":245},[200,91931,249],{"class":214},[200,91933,4511],{"class":210},[200,91935,91936,91938],{"class":202,"line":255},[200,91937,413],{"class":214},[200,91939,416],{"class":210},[35,91941,31547,91942,2186],{},[156,91943,31550],{},[192,91945,91946],{"className":31553,"code":31554,"language":31555,"meta":13,"style":13},[156,91947,91948,91960],{"__ignoreMap":13},[200,91949,91950,91952,91954,91956,91958],{"class":202,"line":203},[200,91951,31562],{"class":206},[200,91953,28499],{"class":214},[200,91955,31567],{"class":245},[200,91957,610],{"class":214},[200,91959,5944],{"class":214},[200,91961,91962,91964,91966,91968,91970],{"class":202,"line":14},[200,91963,31562],{"class":206},[200,91965,28499],{"class":214},[200,91967,31534],{"class":245},[200,91969,610],{"class":214},[200,91971,5944],{"class":214},[35,91973,31586,91974,2186],{},[156,91975,1382],{},[192,91977,91978],{"className":1380,"code":31591,"language":1383,"meta":13,"style":13},[156,91979,91980,91992,92008],{"__ignoreMap":13},[200,91981,91982,91984,91986,91988,91990],{"class":202,"line":203},[200,91983,1390],{"class":206},[200,91985,1393],{"class":206},[200,91987,1396],{"class":223},[200,91989,227],{"class":210},[200,91991,230],{"class":214},[200,91993,91994,91996,91998,92000,92002,92004,92006],{"class":202,"line":14},[200,91995,31610],{"class":235},[200,91997,239],{"class":214},[200,91999,350],{"class":210},[200,92001,249],{"class":214},[200,92003,31619],{"class":245},[200,92005,249],{"class":214},[200,92007,4511],{"class":210},[200,92009,92010,92012],{"class":202,"line":255},[200,92011,413],{"class":214},[200,92013,416],{"class":210},[35,92015,31632,92016,31635,92018,2186],{},[156,92017,31319],{},[39,92019,31640],{"href":31638,"rel":92020},[77],[192,92022,92023],{"className":28358,"code":31643,"language":28360,"meta":13,"style":13},[156,92024,92025,92029,92041,92057,92061,92073,92089,92093],{"__ignoreMap":13},[200,92026,92027],{"class":202,"line":203},[200,92028,230],{"class":214},[200,92030,92031,92033,92035,92037,92039],{"class":202,"line":14},[200,92032,28489],{"class":214},[200,92034,31656],{"class":379},[200,92036,610],{"class":214},[200,92038,239],{"class":214},[200,92040,280],{"class":214},[200,92042,92043,92045,92047,92049,92051,92053,92055],{"class":202,"line":255},[200,92044,28529],{"class":214},[200,92046,31669],{"class":1364},[200,92048,610],{"class":214},[200,92050,239],{"class":214},[200,92052,28499],{"class":214},[200,92054,31567],{"class":245},[200,92056,616],{"class":214},[200,92058,92059],{"class":202,"line":272},[200,92060,368],{"class":214},[200,92062,92063,92065,92067,92069,92071],{"class":202,"line":283},[200,92064,28489],{"class":214},[200,92066,31690],{"class":379},[200,92068,610],{"class":214},[200,92070,239],{"class":214},[200,92072,280],{"class":214},[200,92074,92075,92077,92079,92081,92083,92085,92087],{"class":202,"line":300},[200,92076,28529],{"class":214},[200,92078,31703],{"class":1364},[200,92080,610],{"class":214},[200,92082,239],{"class":214},[200,92084,28499],{"class":214},[200,92086,25410],{"class":245},[200,92088,616],{"class":214},[200,92090,92091],{"class":202,"line":310},[200,92092,4505],{"class":214},[200,92094,92095],{"class":202,"line":336},[200,92096,1129],{"class":214},[35,92098,31724,92099,31728,92101,31732],{},[156,92100,31727],{},[156,92102,31731],{},[192,92104,92105],{"className":21457,"code":31735,"language":12075,"meta":13,"style":13},[156,92106,92107,92115,92123,92131,92139],{"__ignoreMap":13},[200,92108,92109,92111,92113],{"class":202,"line":203},[200,92110,599],{"class":214},[200,92112,21468],{"class":235},[200,92114,647],{"class":214},[200,92116,92117,92119,92121],{"class":202,"line":14},[200,92118,656],{"class":214},[200,92120,31752],{"class":235},[200,92122,647],{"class":214},[200,92124,92125,92127,92129],{"class":202,"line":255},[200,92126,803],{"class":214},[200,92128,31761],{"class":235},[200,92130,21527],{"class":214},[200,92132,92133,92135,92137],{"class":202,"line":272},[200,92134,892],{"class":214},[200,92136,31752],{"class":235},[200,92138,647],{"class":214},[200,92140,92141,92143,92145],{"class":202,"line":283},[200,92142,680],{"class":214},[200,92144,21468],{"class":235},[200,92146,647],{"class":214},[938,92148,92149],{},[35,92150,92151,31786],{},[200,92152,944],{},[1929,92154,31790],{"id":31789},[35,92156,31793,92157,31798,92160,31801],{},[39,92158,31790],{"href":31796,"rel":92159},[77],[6862,92161,24399],{},[35,92163,31804],{},[192,92165,92166],{"className":2166,"code":31807,"language":2168,"meta":13,"style":13},[156,92167,92168],{"__ignoreMap":13},[200,92169,92170,92172,92174,92176],{"class":202,"line":203},[200,92171,1365],{"class":1364},[200,92173,2177],{"class":245},[200,92175,31818],{"class":245},[200,92177,31821],{"class":245},[35,92179,31824,92180,31827],{},[156,92181,31550],{},[192,92183,92184],{"className":31553,"code":31830,"language":31555,"meta":13,"style":13},[156,92185,92186],{"__ignoreMap":13},[200,92187,92188,92190,92192,92194,92196],{"class":202,"line":203},[200,92189,31562],{"class":206},[200,92191,28499],{"class":214},[200,92193,31567],{"class":245},[200,92195,610],{"class":214},[200,92197,5944],{"class":214},[35,92199,31632,92200,31849],{},[156,92201,1382],{},[192,92203,92204],{"className":1380,"code":31852,"language":1383,"meta":13,"style":13},[156,92205,92206,92220,92224,92236,92254,92262,92270,92276,92280,92284],{"__ignoreMap":13},[200,92207,92208,92210,92212,92214,92216,92218],{"class":202,"line":203},[200,92209,972],{"class":206},[200,92211,31861],{"class":210},[200,92213,1489],{"class":206},[200,92215,242],{"class":214},[200,92217,31868],{"class":245},[200,92219,991],{"class":214},[200,92221,92222],{"class":202,"line":14},[200,92223,528],{"emptyLinePlaceholder":20},[200,92225,92226,92228,92230,92232,92234],{"class":202,"line":255},[200,92227,1390],{"class":206},[200,92229,1393],{"class":206},[200,92231,1396],{"class":223},[200,92233,227],{"class":210},[200,92235,230],{"class":214},[200,92237,92238,92240,92242,92244,92246,92248,92250,92252],{"class":202,"line":272},[200,92239,31610],{"class":235},[200,92241,239],{"class":214},[200,92243,350],{"class":210},[200,92245,249],{"class":214},[200,92247,31619],{"class":245},[200,92249,249],{"class":214},[200,92251,360],{"class":210},[200,92253,252],{"class":214},[200,92255,92256,92258,92260],{"class":202,"line":283},[200,92257,2605],{"class":235},[200,92259,239],{"class":214},[200,92261,280],{"class":214},[200,92263,92264,92266,92268],{"class":202,"line":300},[200,92265,2614],{"class":235},[200,92267,239],{"class":214},[200,92269,1410],{"class":210},[200,92271,92272,92274],{"class":202,"line":310},[200,92273,31925],{"class":223},[200,92275,466],{"class":210},[200,92277,92278],{"class":202,"line":336},[200,92279,4499],{"class":210},[200,92281,92282],{"class":202,"line":342},[200,92283,4505],{"class":214},[200,92285,92286,92288],{"class":202,"line":365},[200,92287,413],{"class":214},[200,92289,416],{"class":210},[35,92291,31944,92292,31947,92294,2186],{},[156,92293,31319],{},[39,92295,31640],{"href":31638,"rel":92296},[77],[192,92298,92299],{"className":28358,"code":31643,"language":28360,"meta":13,"style":13},[156,92300,92301,92305,92317,92333,92337,92349,92365,92369],{"__ignoreMap":13},[200,92302,92303],{"class":202,"line":203},[200,92304,230],{"class":214},[200,92306,92307,92309,92311,92313,92315],{"class":202,"line":14},[200,92308,28489],{"class":214},[200,92310,31656],{"class":379},[200,92312,610],{"class":214},[200,92314,239],{"class":214},[200,92316,280],{"class":214},[200,92318,92319,92321,92323,92325,92327,92329,92331],{"class":202,"line":255},[200,92320,28529],{"class":214},[200,92322,31669],{"class":1364},[200,92324,610],{"class":214},[200,92326,239],{"class":214},[200,92328,28499],{"class":214},[200,92330,31567],{"class":245},[200,92332,616],{"class":214},[200,92334,92335],{"class":202,"line":272},[200,92336,368],{"class":214},[200,92338,92339,92341,92343,92345,92347],{"class":202,"line":283},[200,92340,28489],{"class":214},[200,92342,31690],{"class":379},[200,92344,610],{"class":214},[200,92346,239],{"class":214},[200,92348,280],{"class":214},[200,92350,92351,92353,92355,92357,92359,92361,92363],{"class":202,"line":300},[200,92352,28529],{"class":214},[200,92354,31703],{"class":1364},[200,92356,610],{"class":214},[200,92358,239],{"class":214},[200,92360,28499],{"class":214},[200,92362,25410],{"class":245},[200,92364,616],{"class":214},[200,92366,92367],{"class":202,"line":310},[200,92368,4505],{"class":214},[200,92370,92371],{"class":202,"line":336},[200,92372,1129],{"class":214},[35,92374,32029],{},[51,92376,32033],{"id":32032},[35,92378,32036,92379,32041],{},[39,92380,24157],{"href":32039,"rel":92381},[77],[35,92383,32044],{},[35,92385,32047],{},[68,92387,92388],{},[71,92389,32052,92390,32058],{},[39,92391,32057],{"href":32055,"rel":92392},[77],[192,92394,92395],{"className":2166,"code":32061,"language":2168,"meta":13,"style":13},[156,92396,92397],{"__ignoreMap":13},[200,92398,92399,92401,92403,92405,92407],{"class":202,"line":203},[200,92400,30995],{"class":1364},[200,92402,31219],{"class":245},[200,92404,31222],{"class":245},[200,92406,2177],{"class":245},[200,92408,32076],{"class":245},[35,92410,32079],{},[68,92412,92413],{"start":14},[71,92414,32084,92415,32090],{},[39,92416,32089],{"href":32087,"rel":92417},[77],[192,92419,92420],{"className":2166,"code":32093,"language":2168,"meta":13,"style":13},[156,92421,92422],{"__ignoreMap":13},[200,92423,92424,92426,92428,92430],{"class":202,"line":203},[200,92425,30995],{"class":1364},[200,92427,31235],{"class":245},[200,92429,1371],{"class":245},[200,92431,32106],{"class":245},[35,92433,31824,92434,32112],{},[156,92435,32111],{},[192,92437,92438],{"className":25070,"code":32115,"language":25072,"meta":13,"style":13},[156,92439,92440,92462,92466,92482,92490,92498,92506,92522,92530,92536],{"__ignoreMap":13},[200,92441,92442,92444,92446,92448,92450,92452,92454,92456,92458,92460],{"class":202,"line":203},[200,92443,972],{"class":206},[200,92445,318],{"class":214},[200,92447,32126],{"class":210},[200,92449,549],{"class":214},[200,92451,32131],{"class":210},[200,92453,980],{"class":214},[200,92455,983],{"class":206},[200,92457,242],{"class":214},[200,92459,32140],{"class":245},[200,92461,991],{"class":214},[200,92463,92464],{"class":202,"line":14},[200,92465,528],{"emptyLinePlaceholder":20},[200,92467,92468,92470,92472,92474,92476,92478,92480],{"class":202,"line":255},[200,92469,32151],{"class":401},[200,92471,32154],{"class":206},[200,92473,3786],{"class":32157},[200,92475,318],{"class":206},[200,92477,32163],{"class":32162},[200,92479,413],{"class":206},[200,92481,26134],{"class":401},[200,92483,92484,92486,92488],{"class":202,"line":272},[200,92485,32172],{"class":214},[200,92487,509],{"class":214},[200,92489,280],{"class":214},[200,92491,92492,92494,92496],{"class":202,"line":283},[200,92493,1537],{"class":235},[200,92495,239],{"class":214},[200,92497,1410],{"class":210},[200,92499,92500,92502,92504],{"class":202,"line":300},[200,92501,32189],{"class":223},[200,92503,227],{"class":210},[200,92505,230],{"class":214},[200,92507,92508,92510,92512,92514,92516,92518,92520],{"class":202,"line":310},[200,92509,32198],{"class":235},[200,92511,239],{"class":214},[200,92513,32126],{"class":223},[200,92515,25224],{"class":210},[200,92517,32207],{"class":401},[200,92519,32210],{"class":210},[200,92521,252],{"class":214},[200,92523,92524,92526,92528],{"class":202,"line":336},[200,92525,2111],{"class":214},[200,92527,2104],{"class":210},[200,92529,252],{"class":214},[200,92531,92532,92534],{"class":202,"line":342},[200,92533,1427],{"class":210},[200,92535,252],{"class":214},[200,92537,92538],{"class":202,"line":365},[200,92539,1129],{"class":214},[35,92541,32233,92542,2186],{},[156,92543,31550],{},[192,92545,92546],{"className":31553,"code":32238,"language":31555,"meta":13,"style":13},[156,92547,92548,92560],{"__ignoreMap":13},[200,92549,92550,92552,92554,92556,92558],{"class":202,"line":203},[200,92551,31562],{"class":206},[200,92553,28499],{"class":214},[200,92555,31567],{"class":245},[200,92557,610],{"class":214},[200,92559,5944],{"class":214},[200,92561,92562,92564,92566],{"class":202,"line":14},[200,92563,32257],{"class":206},[200,92565,32260],{"class":210},[200,92567,5944],{"class":214},[35,92569,32265,92570,215],{},[39,92571,32057],{"href":32055,"rel":92572},[77],[51,92574,32272],{"id":32271},[35,92576,32275,92577,32278,92579,32284],{},[6862,92578,2934],{},[39,92580,32283],{"href":32281,"rel":92581},[77],[192,92583,92584],{"className":2166,"code":32287,"language":2168,"meta":13,"style":13},[156,92585,92586],{"__ignoreMap":13},[200,92587,92588,92590,92592,92594,92596],{"class":202,"line":203},[200,92589,30995],{"class":1364},[200,92591,31219],{"class":245},[200,92593,31222],{"class":245},[200,92595,2177],{"class":245},[200,92597,32302],{"class":245},[35,92599,32305],{},[51,92601,32309],{"id":32308},[35,92603,32312,92604,32315],{},[6862,92605,11636],{},[35,92607,32318,92608,32322,92610,2186],{},[6862,92609,32321],{},[156,92611,32325],{},[192,92613,92614],{"className":32328,"code":32329,"language":32330,"meta":13,"style":13},[156,92615,92616,92624,92628,92634,92640,92646,92652,92658,92664,92670,92674,92680,92686,92694,92702,92706,92712,92724,92734,92746,92752,92760,92768,92772,92782,92790,92794,92804],{"__ignoreMap":13},[200,92617,92618,92620,92622],{"class":202,"line":203},[200,92619,20112],{"class":235},[200,92621,239],{"class":214},[200,92623,32341],{"class":245},[200,92625,92626],{"class":202,"line":14},[200,92627,528],{"emptyLinePlaceholder":20},[200,92629,92630,92632],{"class":202,"line":255},[200,92631,25410],{"class":2438},[200,92633,19494],{"class":214},[200,92635,92636,92638],{"class":202,"line":272},[200,92637,32356],{"class":235},[200,92639,19494],{"class":214},[200,92641,92642,92644],{"class":202,"line":283},[200,92643,32363],{"class":235},[200,92645,19494],{"class":214},[200,92647,92648,92650],{"class":202,"line":300},[200,92649,32370],{"class":214},[200,92651,32373],{"class":245},[200,92653,92654,92656],{"class":202,"line":310},[200,92655,32378],{"class":235},[200,92657,19494],{"class":214},[200,92659,92660,92662],{"class":202,"line":336},[200,92661,32363],{"class":235},[200,92663,19494],{"class":214},[200,92665,92666,92668],{"class":202,"line":342},[200,92667,32370],{"class":214},[200,92669,32373],{"class":245},[200,92671,92672],{"class":202,"line":365},[200,92673,528],{"emptyLinePlaceholder":20},[200,92675,92676,92678],{"class":202,"line":371},[200,92677,32401],{"class":235},[200,92679,19494],{"class":214},[200,92681,92682,92684],{"class":202,"line":398},[200,92683,32408],{"class":235},[200,92685,19494],{"class":214},[200,92687,92688,92690,92692],{"class":202,"line":405},[200,92689,11812],{"class":235},[200,92691,239],{"class":214},[200,92693,32419],{"class":245},[200,92695,92696,92698,92700],{"class":202,"line":410},[200,92697,32424],{"class":235},[200,92699,239],{"class":214},[200,92701,32429],{"class":245},[200,92703,92704],{"class":202,"line":860},[200,92705,528],{"emptyLinePlaceholder":20},[200,92707,92708,92710],{"class":202,"line":889},[200,92709,32438],{"class":235},[200,92711,19494],{"class":214},[200,92713,92714,92716,92718,92720,92722],{"class":202,"line":899},[200,92715,32370],{"class":214},[200,92717,32447],{"class":235},[200,92719,239],{"class":214},[200,92721,32452],{"class":245},[200,92723,32455],{"class":401},[200,92725,92726,92728,92730,92732],{"class":202,"line":904},[200,92727,32370],{"class":214},[200,92729,32462],{"class":235},[200,92731,239],{"class":214},[200,92733,32467],{"class":245},[200,92735,92736,92738,92740,92742,92744],{"class":202,"line":930},[200,92737,32370],{"class":214},[200,92739,32447],{"class":235},[200,92741,239],{"class":214},[200,92743,32478],{"class":245},[200,92745,32455],{"class":401},[200,92747,92748,92750],{"class":202,"line":3655},[200,92749,32485],{"class":235},[200,92751,19494],{"class":214},[200,92753,92754,92756,92758],{"class":202,"line":3660},[200,92755,32492],{"class":235},[200,92757,239],{"class":214},[200,92759,32497],{"class":2060},[200,92761,92762,92764,92766],{"class":202,"line":4035},[200,92763,32502],{"class":235},[200,92765,239],{"class":214},[200,92767,32507],{"class":245},[200,92769,92770],{"class":202,"line":4490},[200,92771,528],{"emptyLinePlaceholder":20},[200,92773,92774,92776,92778,92780],{"class":202,"line":4496},[200,92775,32370],{"class":214},[200,92777,700],{"class":235},[200,92779,239],{"class":214},[200,92781,32522],{"class":245},[200,92783,92784,92786,92788],{"class":202,"line":4502},[200,92785,32527],{"class":235},[200,92787,239],{"class":214},[200,92789,32532],{"class":245},[200,92791,92792],{"class":202,"line":4508},[200,92793,528],{"emptyLinePlaceholder":20},[200,92795,92796,92798,92800,92802],{"class":202,"line":5469},[200,92797,32370],{"class":214},[200,92799,700],{"class":235},[200,92801,239],{"class":214},[200,92803,32547],{"class":245},[200,92805,92806,92808,92810],{"class":202,"line":5474},[200,92807,32527],{"class":235},[200,92809,239],{"class":214},[200,92811,32556],{"class":245},[35,92813,32559,92814,2186],{},[39,92815,32564],{"href":32562,"rel":92816},[77],[192,92818,92819],{"className":32328,"code":32567,"language":32330,"meta":13,"style":13},[156,92820,92821,92831,92835,92841,92847,92853,92865,92869,92875,92883,92887,92893,92899,92907,92913,92925,92935,92947,92953,92961,92969,92973,92983,92991,92995,93005,93013,93017],{"__ignoreMap":13},[200,92822,92823,92825,92827,92829],{"class":202,"line":203},[200,92824,20112],{"class":235},[200,92826,239],{"class":214},[200,92828,32578],{"class":245},[200,92830,32581],{"class":401},[200,92832,92833],{"class":202,"line":14},[200,92834,528],{"emptyLinePlaceholder":20},[200,92836,92837,92839],{"class":202,"line":255},[200,92838,25410],{"class":2438},[200,92840,19494],{"class":214},[200,92842,92843,92845],{"class":202,"line":272},[200,92844,32378],{"class":235},[200,92846,19494],{"class":214},[200,92848,92849,92851],{"class":202,"line":283},[200,92850,32356],{"class":235},[200,92852,19494],{"class":214},[200,92854,92855,92857,92859,92861,92863],{"class":202,"line":300},[200,92856,32363],{"class":235},[200,92858,239],{"class":214},[200,92860,350],{"class":214},[200,92862,32614],{"class":245},[200,92864,4511],{"class":214},[200,92866,92867],{"class":202,"line":310},[200,92868,528],{"emptyLinePlaceholder":20},[200,92870,92871,92873],{"class":202,"line":336},[200,92872,32625],{"class":235},[200,92874,19494],{"class":214},[200,92876,92877,92879,92881],{"class":202,"line":342},[200,92878,32632],{"class":235},[200,92880,239],{"class":214},[200,92882,32637],{"class":245},[200,92884,92885],{"class":202,"line":365},[200,92886,528],{"emptyLinePlaceholder":20},[200,92888,92889,92891],{"class":202,"line":371},[200,92890,32401],{"class":235},[200,92892,19494],{"class":214},[200,92894,92895,92897],{"class":202,"line":398},[200,92896,32652],{"class":235},[200,92898,19494],{"class":214},[200,92900,92901,92903,92905],{"class":202,"line":405},[200,92902,32424],{"class":235},[200,92904,239],{"class":214},[200,92906,32429],{"class":245},[200,92908,92909,92911],{"class":202,"line":410},[200,92910,32438],{"class":235},[200,92912,19494],{"class":214},[200,92914,92915,92917,92919,92921,92923],{"class":202,"line":860},[200,92916,32370],{"class":214},[200,92918,32447],{"class":235},[200,92920,239],{"class":214},[200,92922,32679],{"class":245},[200,92924,32455],{"class":401},[200,92926,92927,92929,92931,92933],{"class":202,"line":889},[200,92928,32370],{"class":214},[200,92930,32462],{"class":235},[200,92932,239],{"class":214},[200,92934,32467],{"class":245},[200,92936,92937,92939,92941,92943,92945],{"class":202,"line":899},[200,92938,32370],{"class":214},[200,92940,32447],{"class":235},[200,92942,239],{"class":214},[200,92944,32702],{"class":245},[200,92946,32455],{"class":401},[200,92948,92949,92951],{"class":202,"line":904},[200,92950,32485],{"class":235},[200,92952,19494],{"class":214},[200,92954,92955,92957,92959],{"class":202,"line":930},[200,92956,32492],{"class":235},[200,92958,239],{"class":214},[200,92960,32719],{"class":2060},[200,92962,92963,92965,92967],{"class":202,"line":3655},[200,92964,32502],{"class":235},[200,92966,239],{"class":214},[200,92968,32507],{"class":245},[200,92970,92971],{"class":202,"line":3660},[200,92972,528],{"emptyLinePlaceholder":20},[200,92974,92975,92977,92979,92981],{"class":202,"line":4035},[200,92976,32370],{"class":214},[200,92978,700],{"class":235},[200,92980,239],{"class":214},[200,92982,32522],{"class":245},[200,92984,92985,92987,92989],{"class":202,"line":4490},[200,92986,32527],{"class":235},[200,92988,239],{"class":214},[200,92990,32532],{"class":245},[200,92992,92993],{"class":202,"line":4496},[200,92994,528],{"emptyLinePlaceholder":20},[200,92996,92997,92999,93001,93003],{"class":202,"line":4502},[200,92998,32370],{"class":214},[200,93000,700],{"class":235},[200,93002,239],{"class":214},[200,93004,32764],{"class":245},[200,93006,93007,93009,93011],{"class":202,"line":4508},[200,93008,32527],{"class":235},[200,93010,239],{"class":214},[200,93012,32773],{"class":245},[200,93014,93015],{"class":202,"line":5469},[200,93016,528],{"emptyLinePlaceholder":20},[200,93018,93019,93021,93023,93025],{"class":202,"line":5474},[200,93020,32370],{"class":214},[200,93022,32447],{"class":235},[200,93024,239],{"class":214},[200,93026,32788],{"class":245},[51,93028,32792],{"id":32791},[35,93030,32795,93031,32801],{},[39,93032,32800],{"href":32798,"rel":93033},[77],[35,93035,31824,93036,32807],{},[156,93037,32806],{},[192,93039,93040],{"className":28358,"code":32810,"language":28360,"meta":13,"style":13},[156,93041,93042,93046,93058,93066,93070],{"__ignoreMap":13},[200,93043,93044],{"class":202,"line":203},[200,93045,230],{"class":214},[200,93047,93048,93050,93052,93054,93056],{"class":202,"line":14},[200,93049,28489],{"class":214},[200,93051,32823],{"class":379},[200,93053,610],{"class":214},[200,93055,239],{"class":214},[200,93057,1410],{"class":214},[200,93059,93060,93062,93064],{"class":202,"line":255},[200,93061,28529],{"class":214},[200,93063,32836],{"class":245},[200,93065,616],{"class":214},[200,93067,93068],{"class":202,"line":272},[200,93069,13683],{"class":214},[200,93071,93072],{"class":202,"line":283},[200,93073,1129],{"class":214},[35,93075,32849],{},[51,93077,32853],{"id":32852},[35,93079,32856],{},[1929,93081,32860],{"id":32859},[35,93083,32863,93084,32866,93086,32869],{},[156,93085,12075],{},[156,93087,14896],{},[192,93089,93090],{"className":21457,"code":32872,"language":12075,"meta":13,"style":13},[156,93091,93092,93110,93114,93122,93126,93134,93138],{"__ignoreMap":13},[200,93093,93094,93096,93098,93100,93102,93104,93106,93108],{"class":202,"line":203},[200,93095,599],{"class":214},[200,93097,14896],{"class":235},[200,93099,14594],{"class":379},[200,93101,449],{"class":214},[200,93103,610],{"class":214},[200,93105,1383],{"class":245},[200,93107,610],{"class":214},[200,93109,647],{"class":214},[200,93111,93112],{"class":202,"line":14},[200,93113,32897],{"class":401},[200,93115,93116,93118,93120],{"class":202,"line":255},[200,93117,680],{"class":214},[200,93119,14896],{"class":235},[200,93121,647],{"class":214},[200,93123,93124],{"class":202,"line":272},[200,93125,528],{"emptyLinePlaceholder":20},[200,93127,93128,93130,93132],{"class":202,"line":283},[200,93129,599],{"class":214},[200,93131,21468],{"class":235},[200,93133,647],{"class":214},[200,93135,93136],{"class":202,"line":300},[200,93137,10648],{"class":210},[200,93139,93140,93142,93144],{"class":202,"line":310},[200,93141,680],{"class":214},[200,93143,21468],{"class":235},[200,93145,647],{"class":214},[1929,93147,32933],{"id":32932},[35,93149,32936,93150,32939,93152,32942,93154,32946],{},[6862,93151,31441],{},[156,93153,12075],{},[156,93155,32945],{},[192,93157,93158],{"className":21457,"code":32949,"language":12075,"meta":13,"style":13},[156,93159,93160,93180,93184],{"__ignoreMap":13},[200,93161,93162,93164,93166,93168,93170,93172,93174,93176,93178],{"class":202,"line":203},[200,93163,599],{"class":214},[200,93165,14896],{"class":235},[200,93167,14594],{"class":379},[200,93169,449],{"class":214},[200,93171,610],{"class":214},[200,93173,1383],{"class":245},[200,93175,610],{"class":214},[200,93177,21825],{"class":379},[200,93179,647],{"class":214},[200,93181,93182],{"class":202,"line":14},[200,93183,32897],{"class":401},[200,93185,93186,93188,93190],{"class":202,"line":255},[200,93187,680],{"class":214},[200,93189,14896],{"class":235},[200,93191,647],{"class":214},[938,93193,93194],{},[35,93195,93196,32990,93198,32994],{},[200,93197,944],{},[156,93199,32993],{},[1929,93201,32998,93202,33002,93204],{"id":32997},[156,93203,33001],{},[156,93205,1382],{},[35,93207,33007,93208,33002,93210,33012,93212,33016,93214,2186],{},[156,93209,33001],{},[156,93211,1382],{},[156,93213,33015],{},[156,93215,33001],{},[192,93217,93218],{"className":1380,"code":33021,"language":1383,"meta":13,"style":13},[156,93219,93220,93232,93240,93248,93252],{"__ignoreMap":13},[200,93221,93222,93224,93226,93228,93230],{"class":202,"line":203},[200,93223,1390],{"class":206},[200,93225,1393],{"class":206},[200,93227,1396],{"class":223},[200,93229,227],{"class":210},[200,93231,230],{"class":214},[200,93233,93234,93236,93238],{"class":202,"line":14},[200,93235,33040],{"class":235},[200,93237,239],{"class":214},[200,93239,280],{"class":214},[200,93241,93242,93244,93246],{"class":202,"line":255},[200,93243,33049],{"class":235},[200,93245,239],{"class":214},[200,93247,18888],{"class":214},[200,93249,93250],{"class":202,"line":272},[200,93251,4505],{"class":214},[200,93253,93254,93256],{"class":202,"line":283},[200,93255,413],{"class":214},[200,93257,416],{"class":210},[938,93259,93260],{},[35,93261,93262,33070,93264,33074,93266,33077,93268,215],{},[200,93263,31019],{},[156,93265,33073],{},[6862,93267,2934],{},[156,93269,33080],{},[35,93271,31824,93272,33086],{},[156,93273,33085],{},[192,93275,93277],{"className":93276,"code":33091,"language":697},[33090],[156,93278,33091],{"__ignoreMap":13},[35,93280,33096,93281,33099,93283,2186],{},[156,93282,33085],{},[156,93284,33102],{},[192,93286,93287],{"className":2166,"code":33105,"language":2168,"meta":13,"style":13},[156,93288,93289],{"__ignoreMap":13},[200,93290,93291,93293,93295,93297,93299,93301],{"class":202,"line":203},[200,93292,33112],{"class":223},[200,93294,28499],{"class":214},[200,93296,33085],{"class":245},[200,93298,610],{"class":214},[200,93300,33121],{"class":214},[200,93302,33124],{"class":245},[35,93304,33127,93305,33131],{},[156,93306,33130],{},[192,93308,93310],{"className":93309,"code":33091,"language":697},[33090],[156,93311,33091],{"__ignoreMap":13},[7918,93313,93314,93316,93320,93326,93337,93339,93349,93351,93355,93361],{"text":33139,"href":33140},[1929,93315,33144],{"id":33143},[35,93317,33147,93318,33150],{},[6862,93319,2934],{},[1929,93321,33154,93322,1655,93324],{"id":33153},[156,93323,33157],{},[156,93325,33160],{},[35,93327,32998,93328,33166,93330,33169,93332,215],{},[156,93329,33165],{},[156,93331,33160],{},[39,93333,93335],{"href":33172,"rel":93334},[77],[6862,93336,19794],{},[35,93338,33178],{},[68,93340,93341,93345],{},[71,93342,33183,93343,33186],{},[156,93344,33160],{},[71,93346,33183,93347,33191],{},[156,93348,33157],{},[51,93350,33195],{"id":33194},[35,93352,33198,93353,33201],{},[6862,93354,2934],{},[938,93356,93357],{},[35,93358,93359,33208],{},[200,93360,29427],{},[35,93362,33211],{},[1170,93364,33214],{},{"title":13,"searchDepth":14,"depth":14,"links":93366},[93367,93368,93369,93370,93371],{"id":30978,"depth":14,"text":30979},{"id":31053,"depth":14,"text":31054},{"id":31095,"depth":14,"text":31028},{"id":31203,"depth":14,"text":31204},{"id":31447,"depth":14,"text":31448,"children":93372},[93373],{"id":31457,"depth":255,"text":31458},{},{"title":30957,"description":33226},{"loc":33228},{"id":33235,"title":33236,"body":93378,"contentId":33630,"date":33631,"description":33632,"extension":18,"meta":93640,"navigation":20,"path":33634,"rawbody":33635,"robots":23,"schemaOrg":23,"seo":93641,"sitemap":93642,"stem":33638,"__hash__":33639},{"type":7,"value":93379,"toc":93632},[93380,93392,93398,93404,93406,93416,93418,93420,93422,93424,93432,93464,93468,93470,93482,93488,93494,93496,93498,93516],[35,93381,33241,93382,33245,93384,3092,93386,9502,93388,33254,93390,33257],{},[6862,93383,33244],{},[6862,93385,33248],{},[6862,93387,30874],{},[6862,93389,33253],{},[6862,93391,21389],{},[35,93393,33260,93394,33263,93396,33267],{},[6862,93395,30765],{},[6862,93397,33266],{},[938,93399,93400],{},[35,93401,93402,33275],{},[200,93403,33274],{},[35,93405,33278],{},[68,93407,93408,93412],{},[71,93409,93410,33286],{},[6862,93411,33285],{},[71,93413,93414,33292],{},[6862,93415,33291],{},[35,93417,33295],{},[35,93419,33298],{},[35,93421,33301],{},[51,93423,33305],{"id":33304},[35,93425,33308,93426,3092,93428,9502,93430,33315],{},[6862,93427,30759],{},[6862,93429,30762],{},[6862,93431,30765],{},[1593,93433,93434,93439,93444,93449,93454,93459],{},[71,93435,93436,33325],{},[39,93437,33324],{"href":33322,"rel":93438},[77],[71,93440,93441,33325],{},[39,93442,33332],{"href":33330,"rel":93443},[77],[71,93445,93446,33325],{},[39,93447,33339],{"href":33337,"rel":93448},[77],[71,93450,93451,33347],{},[39,93452,33346],{"href":33344,"rel":93453},[77],[71,93455,93456,33347],{},[39,93457,33354],{"href":33352,"rel":93458},[77],[71,93460,93461,33347],{},[39,93462,33361],{"href":33359,"rel":93463},[77],[35,93465,33364,93466,33368],{},[6862,93467,33367],{},[51,93469,33372],{"id":33371},[35,93471,33375,93472,3092,93474,4727,93476,33382,93478,14181,93480,33389],{},[6862,93473,18003],{},[6862,93475,17998],{},[6862,93477,22922],{},[6862,93479,33385],{},[6862,93481,33388],{},[35,93483,33392,93484,1655,93486,215],{},[6862,93485,33395],{},[6862,93487,19463],{},[938,93489,93490],{},[35,93491,93492,33404],{},[200,93493,944],{},[35,93495,33407],{},[35,93497,33410],{},[1593,93499,93500,93507,93511],{},[71,93501,5778,93502,33418,93504,33424],{},[6862,93503,33417],{},[39,93505,33423],{"href":33421,"rel":93506},[77],[71,93508,33427,93509,33431],{},[6862,93510,33430],{},[71,93512,33434,93513,33440],{},[39,93514,33439],{"href":33437,"rel":93515},[77],[7151,93517,93518,93520,93525,93542,93544,93546,93552,93564,93566,93568,93570,93576,93583,93585,93590,93595],{"icon":33443,"text":33444,"to":33445},[51,93519,33449],{"id":33448},[35,93521,33452,93522,33458],{},[39,93523,33457],{"href":33455,"rel":93524},[77],[1593,93526,93527,93532,93537],{},[71,93528,93529,215],{},[39,93530,33467],{"href":33465,"rel":93531},[77],[71,93533,93534,215],{},[39,93535,33474],{"href":33472,"rel":93536},[77],[71,93538,93539,33482],{},[39,93540,33481],{"href":33479,"rel":93541},[77],[51,93543,33486],{"id":33485},[35,93545,33489],{},[35,93547,33492,93548,1655,93550,33497],{},[6862,93549,31790],{},[6862,93551,18636],{},[1593,93553,93554,93559],{},[71,93555,93556,33506],{},[39,93557,31790],{"href":33504,"rel":93558},[77],[71,93560,93561,33513],{},[39,93562,18636],{"href":33511,"rel":93563},[77],[35,93565,33516],{},[51,93567,33520],{"id":33519},[35,93569,33523],{},[35,93571,33526,93572,33530,93574,33534],{},[6862,93573,33529],{},[6862,93575,33533],{},[35,93577,33537,93578,33540,93580,33546],{},[6862,93579,24253],{},[39,93581,33545],{"href":33543,"rel":93582},[77],[51,93584,33550],{"id":33549},[35,93586,33553,93587,33558],{},[39,93588,1365],{"href":33556,"rel":93589},[77],[35,93591,33561,93592,33566],{},[39,93593,2929],{"href":33564,"rel":93594},[77],[7918,93596,93597,93602,93604,93606,93618,93620,93628,93630],{"id":33569},[35,93598,33572,93599,33576],{},[39,93600,2934],{"href":13829,"rel":93601},[77],[35,93603,33579],{},[35,93605,33582],{},[1593,93607,93608,93613],{},[71,93609,93610,33591],{},[39,93611,30540],{"href":33589,"rel":93612},[77],[71,93614,93615,33599],{},[39,93616,33598],{"href":33596,"rel":93617},[77],[51,93619,33603],{"id":33602},[35,93621,33606,93622,14181,93624,1655,93626,33615],{},[6862,93623,33609],{},[6862,93625,33612],{},[6862,93627,22922],{},[35,93629,33618],{},[35,93631,33621],{},{"title":13,"searchDepth":14,"depth":14,"links":93633},[93634,93635,93636,93637,93638,93639],{"id":33304,"depth":14,"text":33305},{"id":33371,"depth":14,"text":33372},{"id":33448,"depth":14,"text":33449},{"id":33485,"depth":14,"text":33486},{"id":33519,"depth":14,"text":33520},{"id":33549,"depth":14,"text":33550},{},{"title":33236,"description":33632},{"loc":33634},{"id":33641,"title":33642,"body":93644,"contentId":30824,"date":33833,"description":33834,"extension":18,"meta":93758,"navigation":20,"path":33836,"rawbody":33837,"robots":23,"schemaOrg":23,"seo":93759,"sitemap":93760,"stem":33840,"__hash__":33841},{"type":7,"value":93645,"toc":93756},[93646,93652,93658],[35,93647,33647,93648,33651,93650,33655],{},[6862,93649,33650],{},[6862,93651,33654],{},[35,93653,33658,93654,33662,93656,33666],{},[6862,93655,33661],{},[6862,93657,33665],{},[7918,93659,93660],{"id":33669},[7918,93661,93662,93674,93676,93680,93684],{"id":33672},[35,93663,33675,93664,1655,93666,33683,93668,1655,93670,33690,93672,33694],{},[39,93665,33678],{"href":21},[39,93667,33682],{"href":33681},[6862,93669,33686],{},[6862,93671,33689],{},[6862,93673,33693],{},[51,93675,33698],{"id":33697},[35,93677,33701,93678,215],{},[6862,93679,33704],{},[134,93681,132,93682],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,93683],{"src":33709,"type":141},[33711,93685,93686,93692,93696,93698,93700,93702,93710,93716,93724,93728,93732,93734,93740,93742,93744],{},[35,93687,33715,93688,33719,93690,33723],{},[6862,93689,33718],{},[6862,93691,33722],{},[35,93693,33726,93694,33730],{},[6862,93695,33729],{},[35,93697,33733],{},[51,93699,33737],{"id":33736},[35,93701,33740],{},[35,93703,33743,93704,33747,93706,33751,93708,215],{},[6862,93705,33746],{},[6862,93707,33750],{},[6862,93709,33754],{},[35,93711,33757,93712,33761,93714,33765],{},[6862,93713,33760],{},[6862,93715,33764],{},[35,93717,33768,93718,33772,93720,33777,93722,33781],{},[6862,93719,33771],{},[33774,93721,33776],{},[33774,93723,33780],{},[35,93725,33768,93726,33787],{},[6862,93727,33786],{},[134,93729,132,93730],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,93731],{"src":33792,"type":141},[51,93733,33796],{"id":33795},[35,93735,33799,93736,33803,93738,33806],{},[6862,93737,33802],{},[6862,93739,33718],{},[35,93741,33809],{},[1158,93743],{},[35,93745,33814,93746,33817,93748,33821,93751,33827,93754,33830],{},[6862,93747,30820],{},[39,93749,19463],{"href":19459,"rel":93750},[77],[39,93752,33826],{"href":33824,"rel":93753},[77],[6862,93755,33729],{},{"title":13,"searchDepth":14,"depth":14,"links":93757},[],{},{"title":33642,"description":33834},{"loc":33836},{"id":33843,"title":33844,"body":93762,"contentId":33672,"date":34052,"description":34053,"extension":18,"meta":93898,"navigation":20,"path":34055,"rawbody":34056,"robots":23,"schemaOrg":23,"seo":93899,"sitemap":93900,"stem":34059,"__hash__":34060},{"type":7,"value":93763,"toc":93894},[93764,93770],[35,93765,33849,93766,33854,93768,215],{},[39,93767,33853],{"href":33852},[6862,93769,33857],{},[7918,93771,93772,93774,93776,93783,93785,93792,93794],{"id":33669},[51,93773,33863],{"id":33862},[35,93775,33866],{},[35,93777,33869,93778,33873,93781],{},[39,93779,19463],{"href":19459,"rel":93780},[77],[1216,93782,33876],{},[51,93784,33880],{"id":33879},[35,93786,33883,93787,33889,93790,33893],{},[39,93788,33888],{"href":33886,"rel":93789},[77],[1216,93791,33892],{},[35,93793,33896],{},[7918,93795,93796,93798,93800,93802,93806,93810,93844,93848,93850,93876,93878,93884,93886,93890],{"id":21393},[35,93797,33901],{},[51,93799,33905],{"id":33904},[35,93801,33908],{},[134,93803,132,93804],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,93805],{"src":33913,"type":141},[35,93807,33916,93808],{},[1216,93809,33919],{},[1593,93811,93812,93816,93820,93826,93830,93834,93838],{},[71,93813,93814,33927],{},[6862,93815,33926],{},[71,93817,93818,33933],{},[6862,93819,33932],{},[71,93821,93822,33939,93824],{},[6862,93823,33938],{},[1216,93825,33942],{},[71,93827,93828,33948],{},[6862,93829,33947],{},[71,93831,93832,33954],{},[6862,93833,33953],{},[71,93835,93836,33960],{},[6862,93837,33959],{},[71,93839,93840,33966,93842],{},[6862,93841,33965],{},[1216,93843,33969],{},[35,93845,33972,93846],{},[1216,93847,33975],{},[35,93849,33978],{},[1593,93851,93852,93862,93872],{},[71,93853,93854,33986,93856,1655,93859,33997],{},[6862,93855,33985],{},[39,93857,33991],{"href":33989,"rel":93858},[77],[39,93860,33996],{"href":33994,"rel":93861},[77],[71,93863,93864,34003,93866,1655,93869,34014],{},[6862,93865,34002],{},[39,93867,34008],{"href":34006,"rel":93868},[77],[39,93870,34013],{"href":34011,"rel":93871},[77],[71,93873,93874,34020],{},[6862,93875,34019],{},[35,93877,34023],{},[35,93879,34026,93880,34030,93882,34034],{},[6862,93881,34029],{},[6862,93883,34033],{},[1158,93885],{},[35,93887,93888,34042],{},[6862,93889,34041],{},[35,93891,93892],{},[1216,93893,34047],{},{"title":13,"searchDepth":14,"depth":14,"links":93895},[93896,93897],{"id":33862,"depth":14,"text":33863},{"id":33879,"depth":14,"text":33880},{},{"title":33844,"description":34053},{"loc":34055},{"id":34062,"title":34063,"body":93902,"contentId":33669,"date":34285,"description":34286,"extension":18,"meta":94048,"navigation":20,"path":34288,"rawbody":34289,"robots":23,"schemaOrg":23,"seo":94049,"sitemap":94050,"stem":34292,"__hash__":34293},{"type":7,"value":93903,"toc":94042},[93904,93906,93908,93915],[35,93905,34068],{},[51,93907,34072],{"id":34071},[35,93909,34075,93910,34079,93913],{},[39,93911,2929],{"href":33564,"rel":93912},[77],[1216,93914,34082],{},[7918,93916,93917,93919,93924,93928,93933,93943,93945,93952,93954,93958,93960,93972,93976,93978,94028,94032,94036,94040],{"id":30564},[51,93918,34088],{"id":34087},[35,93920,34091,93921,34095],{},[39,93922,19463],{"href":19459,"rel":93923},[77],[35,93925,34098,93926],{},[1216,93927,34101],{},[35,93929,34104,93930,34109],{},[39,93931,22922],{"href":34107,"rel":93932},[77],[35,93934,93935],{},[1216,93936,34114,93937,34120,93940,34124],{},[39,93938,34119],{"href":34117,"rel":93939},[77],[39,93941,33439],{"href":33445,"rel":93942},[77],[51,93944,34128],{"id":34127},[35,93946,34131,93947],{},[1216,93948,34134,93949,215],{},[39,93950,34139],{"href":34137,"rel":93951},[77],[35,93953,34142],{},[35,93955,34145,93956],{},[1216,93957,34148],{},[51,93959,34152],{"id":34151},[35,93961,34155,93962,34159,93964,106,93967],{},[6862,93963,34158],{},[39,93965,19463],{"href":19459,"rel":93966},[77],[1216,93968,34165,93969,215],{},[39,93970,2929],{"href":19446,"rel":93971},[77],[134,93973,132,93974],{"autoPlay":20,"loop":20,"muted":20,"playsInline":20},[138,93975],{"src":34173,"type":141},[35,93977,34176],{},[1593,93979,93980,93985,93990,94001,94006,94011,94016,94021],{},[71,93981,93982,34186],{},[39,93983,34185],{"href":34183,"rel":93984},[77],[71,93986,93987,34194],{},[39,93988,34193],{"href":34191,"rel":93989},[77],[71,93991,93992,3092,93995,3092,93998,34212],{},[39,93993,34201],{"href":34199,"rel":93994},[77],[39,93996,34206],{"href":34204,"rel":93997},[77],[39,93999,34211],{"href":34209,"rel":94000},[77],[71,94002,94003,34220],{},[39,94004,34219],{"href":34217,"rel":94005},[77],[71,94007,94008,34228],{},[39,94009,34227],{"href":34225,"rel":94010},[77],[71,94012,34231,94013,34237],{},[39,94014,34236],{"href":34234,"rel":94015},[77],[71,94017,94018,34245],{},[39,94019,34244],{"href":34242,"rel":94020},[77],[71,94022,34248,94023],{},[1216,94024,34251,94025,33806],{},[39,94026,34256],{"href":34254,"rel":94027},[77],[35,94029,34259,94030],{},[1216,94031,34262],{},[35,94033,34265,94034],{},[1599,94035,34270],{"repo":34268,"name":34269},[35,94037,94038],{},[1216,94039,34275],{},[35,94041,34278],{},{"title":13,"searchDepth":14,"depth":14,"links":94043},[94044,94045,94046,94047],{"id":34071,"depth":14,"text":34072},{"id":34087,"depth":14,"text":34088},{"id":34127,"depth":14,"text":34128},{"id":34151,"depth":14,"text":34152},{},{"title":34063,"description":34286},{"loc":34288},{"id":34295,"title":34296,"body":94052,"contentId":21393,"date":36298,"description":36299,"extension":18,"meta":95703,"navigation":20,"path":36301,"rawbody":36302,"robots":23,"schemaOrg":23,"seo":95704,"sitemap":95705,"stem":36305,"__hash__":36306},{"type":7,"value":94053,"toc":95692},[94054,94060,94064,94078,95690],[35,94055,34301,94056],{},[1599,94057,8048,94058],{"repo":34304},[1216,94059,34307],{},[35,94061,34310,94062],{},[1216,94063,34313],{},[35,94065,34316,94066,34321,94069,34327,94072,13741,94075,215],{},[39,94067,18636],{"href":34319,"rel":94068},[77],[39,94070,34326],{"href":34324,"rel":94071},[77],[39,94073,34332],{"href":34330,"rel":94074},[77],[39,94076,24399],{"href":34335,"rel":94077},[77],[7918,94079,94080,94090,94092,94094,94214,94216,94304,94308,94310,94315,94319,94373,94379,94509,94511,94517,94521,94526,94542,94546,94553,94562,94564,94574,94576,94578,94588,94590,94598,94602,94606,94628,94632,94742,94748,94750,94760,94774,94780,94942,94946,94948,94952,95002,95006,95011,95013,95019,95021,95023,95039,95041,95049,95065,95067,95069,95075,95147,95165,95173,95177,95311,95315,95320,95328,95330,95334,95336,95338,95343,95347,95369,95373,95389,95391,95401,95409,95413,95419,95497,95499,95515,95521,95523,95525,95529,95536,95543,95557,95561,95623,95631,95639,95641,95653,95655,95657,95659,95673,95679,95684,95688],{"id":30810},[938,94081,94082],{},[35,94083,94084,34345,94086,34349,94088],{},[200,94085,944],{},[6862,94087,34348],{},[1216,94089,34352],{},[35,94091,34355],{},[35,94093,34358],{},[192,94095,94096],{"className":21457,"code":34361,"language":12075,"meta":13,"style":13},[156,94097,94098,94118,94136,94140,94156,94164,94168,94176,94194,94198,94206],{"__ignoreMap":13},[200,94099,94100,94102,94104,94106,94108,94110,94112,94114,94116],{"class":202,"line":203},[200,94101,599],{"class":214},[200,94103,14896],{"class":235},[200,94105,14594],{"class":379},[200,94107,449],{"class":214},[200,94109,610],{"class":214},[200,94111,1383],{"class":245},[200,94113,610],{"class":214},[200,94115,21825],{"class":379},[200,94117,647],{"class":214},[200,94119,94120,94122,94124,94126,94128,94130,94132,94134],{"class":202,"line":14},[200,94121,972],{"class":206},[200,94123,318],{"class":214},[200,94125,34392],{"class":210},[200,94127,980],{"class":214},[200,94129,983],{"class":206},[200,94131,242],{"class":214},[200,94133,34401],{"class":245},[200,94135,991],{"class":214},[200,94137,94138],{"class":202,"line":255},[200,94139,528],{"emptyLinePlaceholder":20},[200,94141,94142,94144,94146,94148,94150,94152,94154],{"class":202,"line":272},[200,94143,443],{"class":379},[200,94145,318],{"class":214},[200,94147,34416],{"class":210},[200,94149,413],{"class":214},[200,94151,509],{"class":214},[200,94153,34392],{"class":223},[200,94155,466],{"class":210},[200,94157,94158,94160,94162],{"class":202,"line":283},[200,94159,680],{"class":214},[200,94161,14896],{"class":235},[200,94163,647],{"class":214},[200,94165,94166],{"class":202,"line":300},[200,94167,528],{"emptyLinePlaceholder":20},[200,94169,94170,94172,94174],{"class":202,"line":310},[200,94171,599],{"class":214},[200,94173,21468],{"class":235},[200,94175,647],{"class":214},[200,94177,94178,94180,94182,94184,94186,94188,94190,94192],{"class":202,"line":336},[200,94179,656],{"class":214},[200,94181,11400],{"class":235},[200,94183,34453],{"class":379},[200,94185,449],{"class":214},[200,94187,610],{"class":214},[200,94189,26847],{"class":245},[200,94191,610],{"class":214},[200,94193,647],{"class":214},[200,94195,94196],{"class":202,"line":342},[200,94197,34468],{"class":210},[200,94199,94200,94202,94204],{"class":202,"line":365},[200,94201,892],{"class":214},[200,94203,11400],{"class":235},[200,94205,647],{"class":214},[200,94207,94208,94210,94212],{"class":202,"line":371},[200,94209,680],{"class":214},[200,94211,21468],{"class":235},[200,94213,647],{"class":214},[35,94215,34487],{},[192,94217,94218],{"className":1380,"code":34490,"language":1383,"meta":13,"style":13},[156,94219,94220,94238,94242,94254,94282,94286,94292,94296,94300],{"__ignoreMap":13},[200,94221,94222,94224,94226,94228,94230,94232,94234,94236],{"class":202,"line":203},[200,94223,972],{"class":206},[200,94225,318],{"class":214},[200,94227,18698],{"class":210},[200,94229,980],{"class":214},[200,94231,983],{"class":206},[200,94233,242],{"class":214},[200,94235,12075],{"class":245},[200,94237,991],{"class":214},[200,94239,94240],{"class":202,"line":14},[200,94241,528],{"emptyLinePlaceholder":20},[200,94243,94244,94246,94248,94250,94252],{"class":202,"line":255},[200,94245,1390],{"class":206},[200,94247,5002],{"class":379},[200,94249,34392],{"class":223},[200,94251,1549],{"class":214},[200,94253,280],{"class":214},[200,94255,94256,94258,94260,94262,94264,94266,94268,94270,94272,94274,94276,94278,94280],{"class":202,"line":272},[200,94257,3738],{"class":379},[200,94259,34533],{"class":210},[200,94261,509],{"class":214},[200,94263,18698],{"class":223},[200,94265,227],{"class":235},[200,94267,2767],{"class":214},[200,94269,700],{"class":235},[200,94271,239],{"class":214},[200,94273,242],{"class":214},[200,94275,34550],{"class":245},[200,94277,249],{"class":214},[200,94279,980],{"class":214},[200,94281,416],{"class":235},[200,94283,94284],{"class":202,"line":283},[200,94285,528],{"emptyLinePlaceholder":20},[200,94287,94288,94290],{"class":202,"line":300},[200,94289,3758],{"class":206},[200,94291,280],{"class":214},[200,94293,94294],{"class":202,"line":310},[200,94295,34571],{"class":210},[200,94297,94298],{"class":202,"line":336},[200,94299,4505],{"class":214},[200,94301,94302],{"class":202,"line":342},[200,94303,1129],{"class":214},[35,94305,34582,94306],{},[1216,94307,34585],{},[35,94309,34588],{},[192,94311,94313],{"className":94312,"code":34592,"language":697},[33090],[156,94314,34592],{"__ignoreMap":13},[35,94316,34597,94317,34601],{},[156,94318,34600],{},[192,94320,94321],{"className":1380,"code":34604,"language":1383,"meta":13,"style":13},[156,94322,94323,94337,94341,94351,94355],{"__ignoreMap":13},[200,94324,94325,94327,94329,94331,94333,94335],{"class":202,"line":203},[200,94326,972],{"class":206},[200,94328,34613],{"class":210},[200,94330,1489],{"class":206},[200,94332,242],{"class":214},[200,94334,34620],{"class":245},[200,94336,991],{"class":214},[200,94338,94339],{"class":202,"line":14},[200,94340,528],{"emptyLinePlaceholder":20},[200,94342,94343,94345,94347,94349],{"class":202,"line":255},[200,94344,1390],{"class":206},[200,94346,318],{"class":214},[200,94348,34635],{"class":210},[200,94350,34638],{"class":214},[200,94352,94353],{"class":202,"line":272},[200,94354,528],{"emptyLinePlaceholder":20},[200,94356,94357,94359,94361,94363,94365,94367,94369,94371],{"class":202,"line":283},[200,94358,1390],{"class":206},[200,94360,318],{"class":214},[200,94362,34392],{"class":210},[200,94364,980],{"class":214},[200,94366,983],{"class":206},[200,94368,242],{"class":214},[200,94370,34659],{"class":245},[200,94372,991],{"class":214},[35,94374,34664,94375,34667,94377,2186],{},[156,94376,31352],{},[156,94378,34670],{},[192,94380,94381],{"className":28358,"code":34673,"language":28360,"meta":13,"style":13},[156,94382,94383,94387,94405,94417,94429,94447,94463,94467,94471,94489,94505],{"__ignoreMap":13},[200,94384,94385],{"class":202,"line":203},[200,94386,230],{"class":214},[200,94388,94389,94391,94393,94395,94397,94399,94401,94403],{"class":202,"line":14},[200,94390,28489],{"class":214},[200,94392,3786],{"class":379},[200,94394,610],{"class":214},[200,94396,239],{"class":214},[200,94398,28499],{"class":214},[200,94400,15097],{"class":245},[200,94402,610],{"class":214},[200,94404,252],{"class":214},[200,94406,94407,94409,94411,94413,94415],{"class":202,"line":255},[200,94408,28489],{"class":214},[200,94410,34670],{"class":379},[200,94412,610],{"class":214},[200,94414,239],{"class":214},[200,94416,280],{"class":214},[200,94418,94419,94421,94423,94425,94427],{"class":202,"line":272},[200,94420,28529],{"class":214},[200,94422,215],{"class":1364},[200,94424,610],{"class":214},[200,94426,239],{"class":214},[200,94428,280],{"class":214},[200,94430,94431,94433,94435,94437,94439,94441,94443,94445],{"class":202,"line":283},[200,94432,28542],{"class":214},[200,94434,34728],{"class":2060},[200,94436,610],{"class":214},[200,94438,239],{"class":214},[200,94440,28499],{"class":214},[200,94442,34737],{"class":245},[200,94444,610],{"class":214},[200,94446,252],{"class":214},[200,94448,94449,94451,94453,94455,94457,94459,94461],{"class":202,"line":300},[200,94450,28542],{"class":214},[200,94452,972],{"class":2060},[200,94454,610],{"class":214},[200,94456,239],{"class":214},[200,94458,28499],{"class":214},[200,94460,34756],{"class":245},[200,94462,616],{"class":214},[200,94464,94465],{"class":202,"line":310},[200,94466,4977],{"class":214},[200,94468,94469],{"class":202,"line":336},[200,94470,368],{"class":214},[200,94472,94473,94475,94477,94479,94481,94483,94485,94487],{"class":202,"line":342},[200,94474,28489],{"class":214},[200,94476,32614],{"class":379},[200,94478,610],{"class":214},[200,94480,239],{"class":214},[200,94482,28499],{"class":214},[200,94484,34781],{"class":245},[200,94486,610],{"class":214},[200,94488,252],{"class":214},[200,94490,94491,94493,94495,94497,94499,94501,94503],{"class":202,"line":365},[200,94492,28489],{"class":214},[200,94494,34728],{"class":379},[200,94496,610],{"class":214},[200,94498,239],{"class":214},[200,94500,28499],{"class":214},[200,94502,34800],{"class":245},[200,94504,616],{"class":214},[200,94506,94507],{"class":202,"line":371},[200,94508,1129],{"class":214},[51,94510,34810],{"id":34809},[938,94512,94513],{},[35,94514,94515,34817],{},[200,94516,944],{},[35,94518,34820,94519,34823],{},[156,94520,5154],{},[192,94522,94524],{"className":94523,"code":34827,"language":697},[33090],[156,94525,34827],{"__ignoreMap":13},[35,94527,34832,94528,34836,94530,34840,94532],{},[156,94529,34835],{},[156,94531,34839],{},[1216,94533,34843,94534,34847,94536,13741,94539,34858],{},[156,94535,34846],{},[39,94537,34852],{"href":34850,"rel":94538},[77],[39,94540,34857],{"href":34855,"rel":94541},[77],[35,94543,34861,94544,34865],{},[156,94545,34864],{},[35,94547,34868,94548,34874,94551],{},[39,94549,34873],{"href":34871,"rel":94550},[77],[1216,94552,34877],{},[35,94554,34880,94555,8048,94557,34891],{},[6862,94556,34348],{},[1216,94558,34885,94559],{},[39,94560,34890],{"href":34888,"rel":94561},[77],[51,94563,34895],{"id":34894},[35,94565,34898,94566,34901,94568,34907,94571,215],{},[156,94567,11866],{},[39,94569,34906],{"href":34904,"rel":94570},[77],[39,94572,30540],{"href":34910,"rel":94573},[77],[35,94575,34914],{},[35,94577,34917],{},[192,94579,94580],{"className":1354,"code":34920,"language":1357,"meta":13,"style":13},[156,94581,94582],{"__ignoreMap":13},[200,94583,94584,94586],{"class":202,"line":203},[200,94585,30995],{"class":1364},[200,94587,34929],{"class":245},[35,94589,34932],{},[192,94591,94592],{"className":4292,"code":34935,"language":4294,"meta":13,"style":13},[156,94593,94594],{"__ignoreMap":13},[200,94595,94596],{"class":202,"line":203},[200,94597,34935],{},[35,94599,94600],{},[1216,94601,34946],{},[35,94603,34949,94604,34952],{},[156,94605,34600],{},[192,94607,94608],{"className":1380,"code":34955,"language":1383,"meta":13,"style":13},[156,94609,94610],{"__ignoreMap":13},[200,94611,94612,94614,94616,94618,94620,94622,94624,94626],{"class":202,"line":203},[200,94613,1390],{"class":206},[200,94615,318],{"class":214},[200,94617,34392],{"class":210},[200,94619,980],{"class":214},[200,94621,983],{"class":206},[200,94623,242],{"class":214},[200,94625,34659],{"class":245},[200,94627,991],{"class":214},[35,94629,34978,94630,34982],{},[156,94631,34981],{},[192,94633,94634],{"className":1354,"code":34985,"language":1357,"meta":13,"style":13},[156,94635,94636,94644,94664,94672,94684,94696,94722,94726],{"__ignoreMap":13},[200,94637,94638,94640,94642],{"class":202,"line":203},[200,94639,34992],{"class":1364},[200,94641,31232],{"class":245},[200,94643,34929],{"class":245},[200,94645,94646,94648,94650,94652,94654,94656,94658,94660,94662],{"class":202,"line":14},[200,94647,35001],{"class":1364},[200,94649,35004],{"class":245},[200,94651,35007],{"class":245},[200,94653,35010],{"class":245},[200,94655,35013],{"class":245},[200,94657,35016],{"class":210},[200,94659,9872],{"class":214},[200,94661,35021],{"class":210},[200,94663,4511],{"class":214},[200,94665,94666,94668,94670],{"class":202,"line":255},[200,94667,35001],{"class":1364},[200,94669,35030],{"class":245},[200,94671,35033],{"class":245},[200,94673,94674,94676,94678,94680,94682],{"class":202,"line":272},[200,94675,35001],{"class":1364},[200,94677,35040],{"class":245},[200,94679,35043],{"class":245},[200,94681,35046],{"class":245},[200,94683,35049],{"class":245},[200,94685,94686,94688,94690,94692,94694],{"class":202,"line":283},[200,94687,35054],{"class":1364},[200,94689,35057],{"class":245},[200,94691,35060],{"class":245},[200,94693,662],{"class":245},[200,94695,35033],{"class":245},[200,94697,94698,94700,94702,94704,94706,94708,94710,94712,94714,94716,94718,94720],{"class":202,"line":300},[200,94699,35069],{"class":1364},[200,94701,35072],{"class":210},[200,94703,35075],{"class":245},[200,94705,35078],{"class":2060},[200,94707,35081],{"class":245},[200,94709,35084],{"class":245},[200,94711,35087],{"class":245},[200,94713,35078],{"class":2060},[200,94715,35081],{"class":245},[200,94717,35094],{"class":245},[200,94719,34392],{"class":245},[200,94721,416],{"class":210},[200,94723,94724],{"class":202,"line":310},[200,94725,528],{"emptyLinePlaceholder":20},[200,94727,94728,94730,94732,94734,94736,94738,94740],{"class":202,"line":336},[200,94729,35107],{"class":1364},[200,94731,35110],{"class":245},[200,94733,35043],{"class":245},[200,94735,35115],{"class":245},[200,94737,35118],{"class":210},[200,94739,35121],{"class":245},[200,94741,35124],{"class":210},[35,94743,35127,94744,35130,94746,215],{},[156,94745,11180],{},[156,94747,5154],{},[51,94749,35136],{"id":35135},[35,94751,35139,94752,35143,94754,35146,94756,35149,94758,215],{},[156,94753,35142],{},[156,94755,11866],{},[156,94757,5154],{},[156,94759,35152],{},[192,94761,94762],{"className":1354,"code":35155,"language":1357,"meta":13,"style":13},[156,94763,94764],{"__ignoreMap":13},[200,94765,94766,94768,94770,94772],{"class":202,"line":203},[200,94767,35152],{"class":1364},[200,94769,35164],{"class":245},[200,94771,35167],{"class":245},[200,94773,35170],{"class":245},[35,94775,35173,94776,35176,94778,35179],{},[156,94777,34670],{},[156,94779,31352],{},[192,94781,94782],{"className":28358,"code":35182,"language":28360,"meta":13,"style":13},[156,94783,94784,94788,94806,94818,94830,94848,94864,94868,94880,94896,94900,94904,94922,94938],{"__ignoreMap":13},[200,94785,94786],{"class":202,"line":203},[200,94787,230],{"class":214},[200,94789,94790,94792,94794,94796,94798,94800,94802,94804],{"class":202,"line":14},[200,94791,28489],{"class":214},[200,94793,3786],{"class":379},[200,94795,610],{"class":214},[200,94797,239],{"class":214},[200,94799,28499],{"class":214},[200,94801,15097],{"class":245},[200,94803,610],{"class":214},[200,94805,252],{"class":214},[200,94807,94808,94810,94812,94814,94816],{"class":202,"line":255},[200,94809,28489],{"class":214},[200,94811,34670],{"class":379},[200,94813,610],{"class":214},[200,94815,239],{"class":214},[200,94817,280],{"class":214},[200,94819,94820,94822,94824,94826,94828],{"class":202,"line":272},[200,94821,28529],{"class":214},[200,94823,215],{"class":1364},[200,94825,610],{"class":214},[200,94827,239],{"class":214},[200,94829,280],{"class":214},[200,94831,94832,94834,94836,94838,94840,94842,94844,94846],{"class":202,"line":283},[200,94833,28542],{"class":214},[200,94835,34728],{"class":2060},[200,94837,610],{"class":214},[200,94839,239],{"class":214},[200,94841,28499],{"class":214},[200,94843,34737],{"class":245},[200,94845,610],{"class":214},[200,94847,252],{"class":214},[200,94849,94850,94852,94854,94856,94858,94860,94862],{"class":202,"line":300},[200,94851,28542],{"class":214},[200,94853,972],{"class":2060},[200,94855,610],{"class":214},[200,94857,239],{"class":214},[200,94859,28499],{"class":214},[200,94861,34756],{"class":245},[200,94863,616],{"class":214},[200,94865,94866],{"class":202,"line":310},[200,94867,339],{"class":214},[200,94869,94870,94872,94874,94876,94878],{"class":202,"line":336},[200,94871,28529],{"class":214},[200,94873,35275],{"class":1364},[200,94875,610],{"class":214},[200,94877,239],{"class":214},[200,94879,280],{"class":214},[200,94881,94882,94884,94886,94888,94890,94892,94894],{"class":202,"line":342},[200,94883,28542],{"class":214},[200,94885,972],{"class":2060},[200,94887,610],{"class":214},[200,94889,239],{"class":214},[200,94891,28499],{"class":214},[200,94893,35296],{"class":245},[200,94895,616],{"class":214},[200,94897,94898],{"class":202,"line":365},[200,94899,4977],{"class":214},[200,94901,94902],{"class":202,"line":371},[200,94903,368],{"class":214},[200,94905,94906,94908,94910,94912,94914,94916,94918,94920],{"class":202,"line":398},[200,94907,28489],{"class":214},[200,94909,32614],{"class":379},[200,94911,610],{"class":214},[200,94913,239],{"class":214},[200,94915,28499],{"class":214},[200,94917,34781],{"class":245},[200,94919,610],{"class":214},[200,94921,252],{"class":214},[200,94923,94924,94926,94928,94930,94932,94934,94936],{"class":202,"line":405},[200,94925,28489],{"class":214},[200,94927,34728],{"class":379},[200,94929,610],{"class":214},[200,94931,239],{"class":214},[200,94933,28499],{"class":214},[200,94935,34800],{"class":245},[200,94937,616],{"class":214},[200,94939,94940],{"class":202,"line":410},[200,94941,1129],{"class":214},[35,94943,35347,94944,35351],{},[1216,94945,35350],{},[51,94947,35355],{"id":35354},[35,94949,35358,94950,35361],{},[156,94951,34839],{},[192,94953,94954],{"className":21457,"code":35364,"language":12075,"meta":13,"style":13},[156,94955,94956,94976,94994],{"__ignoreMap":13},[200,94957,94958,94960,94962,94964,94966,94968,94970,94972,94974],{"class":202,"line":203},[200,94959,599],{"class":214},[200,94961,14896],{"class":235},[200,94963,14594],{"class":379},[200,94965,449],{"class":214},[200,94967,610],{"class":214},[200,94969,1383],{"class":245},[200,94971,610],{"class":214},[200,94973,21825],{"class":379},[200,94975,647],{"class":214},[200,94977,94978,94980,94982,94984,94986,94988,94990,94992],{"class":202,"line":14},[200,94979,972],{"class":206},[200,94981,318],{"class":214},[200,94983,34392],{"class":210},[200,94985,980],{"class":214},[200,94987,983],{"class":206},[200,94989,242],{"class":214},[200,94991,34401],{"class":245},[200,94993,991],{"class":214},[200,94995,94996,94998,95000],{"class":202,"line":255},[200,94997,680],{"class":214},[200,94999,14896],{"class":235},[200,95001,647],{"class":214},[35,95003,35415,95004,35418],{},[156,95005,5154],{},[192,95007,95009],{"className":95008,"code":35422,"language":697},[33090],[156,95010,35422],{"__ignoreMap":13},[35,95012,35427],{},[35,95014,35430,95015,35434,95017,35438],{},[156,95016,35433],{},[156,95018,35437],{},[51,95020,35442],{"id":35441},[35,95022,35445],{},[68,95024,95025,95032],{},[71,95026,95027,35453,95029,35458],{},[6862,95028,35452],{},[39,95030,24399],{"href":35456,"rel":95031},[77],[71,95033,95034,35464,95036,35470],{},[6862,95035,35463],{},[39,95037,35469],{"href":35467,"rel":95038},[77],[35,95040,35473],{},[35,95042,35476,95043,35146,95045,35481,95047,35485],{},[156,95044,11180],{},[156,95046,5154],{},[1216,95048,35484],{},[938,95050,95051],{},[35,95052,95053,35492,95055,35496,95057,35500,95059,35505,95062,215],{},[200,95054,944],{},[156,95056,35495],{},[156,95058,35499],{},[39,95060,11661],{"href":35503,"rel":95061},[77],[39,95063,35510],{"href":35508,"rel":95064},[77],[51,95066,35514],{"id":35513},[35,95068,35517],{},[35,95070,35520,95071,35524,95073,35527],{},[156,95072,35523],{},[156,95074,31352],{},[192,95076,95077],{"className":1380,"code":35530,"language":1383,"meta":13,"style":13},[156,95078,95079,95097,95101,95113,95131,95141],{"__ignoreMap":13},[200,95080,95081,95083,95085,95087,95089,95091,95093,95095],{"class":202,"line":203},[200,95082,972],{"class":206},[200,95084,318],{"class":214},[200,95086,35541],{"class":210},[200,95088,980],{"class":214},[200,95090,983],{"class":206},[200,95092,242],{"class":214},[200,95094,34906],{"class":245},[200,95096,991],{"class":214},[200,95098,95099],{"class":202,"line":14},[200,95100,528],{"emptyLinePlaceholder":20},[200,95102,95103,95105,95107,95109,95111],{"class":202,"line":255},[200,95104,1390],{"class":206},[200,95106,1393],{"class":206},[200,95108,35541],{"class":223},[200,95110,227],{"class":210},[200,95112,230],{"class":214},[200,95114,95115,95117,95119,95121,95123,95125,95127,95129],{"class":202,"line":272},[200,95116,35572],{"class":235},[200,95118,239],{"class":214},[200,95120,350],{"class":210},[200,95122,249],{"class":214},[200,95124,35581],{"class":245},[200,95126,249],{"class":214},[200,95128,360],{"class":210},[200,95130,252],{"class":214},[200,95132,95133,95135,95137,95139],{"class":202,"line":283},[200,95134,35592],{"class":235},[200,95136,239],{"class":214},[200,95138,2439],{"class":2438},[200,95140,252],{"class":214},[200,95142,95143,95145],{"class":202,"line":300},[200,95144,413],{"class":214},[200,95146,416],{"class":210},[35,95148,35607,95149,35611,95151,35614,95153,35617,95155,35620,95157,35624,95159,35627,95161,35631,95163,1569],{},[1216,95150,35610],{},[156,95152,6465],{},[156,95154,35581],{},[156,95156,2898],{},[156,95158,35623],{},[156,95160,35469],{},[156,95162,35630],{},[156,95164,35634],{},[35,95166,35637,95167,35176,95169,35642,95171,215],{},[156,95168,34670],{},[156,95170,31352],{},[156,95172,34600],{},[35,95174,35647,95175,35650],{},[156,95176,34981],{},[192,95178,95179],{"className":1354,"code":35653,"language":1357,"meta":13,"style":13},[156,95180,95181,95189,95197,95209,95221,95235,95247,95259,95271,95283,95295],{"__ignoreMap":13},[200,95182,95183,95185,95187],{"class":202,"line":203},[200,95184,34992],{"class":1364},[200,95186,31232],{"class":245},[200,95188,34929],{"class":245},[200,95190,95191,95193,95195],{"class":202,"line":14},[200,95192,35001],{"class":1364},[200,95194,35030],{"class":245},[200,95196,35033],{"class":245},[200,95198,95199,95201,95203,95205,95207],{"class":202,"line":255},[200,95200,35001],{"class":1364},[200,95202,35040],{"class":245},[200,95204,35043],{"class":245},[200,95206,35046],{"class":245},[200,95208,35049],{"class":245},[200,95210,95211,95213,95215,95217,95219],{"class":202,"line":272},[200,95212,35054],{"class":1364},[200,95214,35057],{"class":245},[200,95216,35060],{"class":245},[200,95218,662],{"class":245},[200,95220,35033],{"class":245},[200,95222,95223,95225,95227,95229,95231,95233],{"class":202,"line":283},[200,95224,35700],{"class":1364},[200,95226,35072],{"class":210},[200,95228,35075],{"class":245},[200,95230,35707],{"class":2060},[200,95232,35710],{"class":245},[200,95234,416],{"class":210},[200,95236,95237,95239,95241,95243,95245],{"class":202,"line":300},[200,95238,35717],{"class":1364},[200,95240,35720],{"class":245},[200,95242,35723],{"class":210},[200,95244,35726],{"class":245},[200,95246,416],{"class":210},[200,95248,95249,95251,95253,95255,95257],{"class":202,"line":310},[200,95250,35717],{"class":1364},[200,95252,35735],{"class":245},[200,95254,35738],{"class":210},[200,95256,35726],{"class":245},[200,95258,416],{"class":210},[200,95260,95261,95263,95265,95267,95269],{"class":202,"line":336},[200,95262,35717],{"class":1364},[200,95264,35749],{"class":245},[200,95266,35752],{"class":210},[200,95268,35726],{"class":245},[200,95270,416],{"class":210},[200,95272,95273,95275,95277,95279,95281],{"class":202,"line":342},[200,95274,35717],{"class":1364},[200,95276,35763],{"class":245},[200,95278,35766],{"class":210},[200,95280,35726],{"class":245},[200,95282,416],{"class":210},[200,95284,95285,95287,95289,95291,95293],{"class":202,"line":365},[200,95286,35717],{"class":1364},[200,95288,35777],{"class":245},[200,95290,35780],{"class":210},[200,95292,35726],{"class":245},[200,95294,416],{"class":210},[200,95296,95297,95299,95301,95303,95305,95307,95309],{"class":202,"line":371},[200,95298,35107],{"class":1364},[200,95300,35110],{"class":245},[200,95302,35043],{"class":245},[200,95304,35115],{"class":245},[200,95306,35118],{"class":210},[200,95308,35121],{"class":245},[200,95310,35801],{"class":210},[35,95312,35804,95313,35807],{},[156,95314,5154],{},[192,95316,95318],{"className":95317,"code":35811,"language":697},[33090],[156,95319,35811],{"__ignoreMap":13},[35,95321,35816,95322,35819,95324,35823,95326,35826],{},[156,95323,11180],{},[156,95325,35822],{},[156,95327,5154],{},[51,95329,35830],{"id":35829},[35,95331,35833,95332,35837],{},[6862,95333,35836],{},[35,95335,35840],{},[35,95337,35843],{},[35,95339,35846,95340,215],{},[39,95341,35850],{"href":33556,"rel":95342},[77],[35,95344,35520,95345,35856],{},[156,95346,35855],{},[192,95348,95349],{"className":32328,"code":35859,"language":32330,"meta":13,"style":13},[156,95350,95351,95357,95363],{"__ignoreMap":13},[200,95352,95353,95355],{"class":202,"line":203},[200,95354,35866],{"class":235},[200,95356,19494],{"class":214},[200,95358,95359,95361],{"class":202,"line":14},[200,95360,35873],{"class":214},[200,95362,35876],{"class":2060},[200,95364,95365,95367],{"class":202,"line":255},[200,95366,35873],{"class":214},[200,95368,35883],{"class":245},[35,95370,35886,95371,2186],{},[156,95372,35889],{},[192,95374,95375],{"className":1354,"code":35892,"language":1357,"meta":13,"style":13},[156,95376,95377],{"__ignoreMap":13},[200,95378,95379,95381,95383,95385,95387],{"class":202,"line":203},[200,95380,30995],{"class":1364},[200,95382,35901],{"class":245},[200,95384,35904],{"class":245},[200,95386,35907],{"class":245},[200,95388,35910],{"class":245},[35,95390,35913],{},[192,95392,95393],{"className":1354,"code":35916,"language":1357,"meta":13,"style":13},[156,95394,95395],{"__ignoreMap":13},[200,95396,95397,95399],{"class":202,"line":203},[200,95398,1365],{"class":1364},[200,95400,35925],{"class":245},[938,95402,95403],{},[35,95404,95405,35932,95407,35936],{},[200,95406,944],{},[156,95408,35935],{},[35,95410,35939,95411,35942],{},[156,95412,35889],{},[35,95414,35945,95415,35948,95417,2186],{},[156,95416,22039],{},[156,95418,35951],{},[192,95420,95421],{"className":21457,"code":35954,"language":12075,"meta":13,"style":13},[156,95422,95423,95443,95461,95469,95473,95481,95489],{"__ignoreMap":13},[200,95424,95425,95427,95429,95431,95433,95435,95437,95439,95441],{"class":202,"line":203},[200,95426,599],{"class":214},[200,95428,14896],{"class":235},[200,95430,21825],{"class":379},[200,95432,14594],{"class":379},[200,95434,449],{"class":214},[200,95436,610],{"class":214},[200,95438,1383],{"class":245},[200,95440,610],{"class":214},[200,95442,647],{"class":214},[200,95444,95445,95447,95449,95451,95453,95455,95457,95459],{"class":202,"line":14},[200,95446,972],{"class":206},[200,95448,318],{"class":214},[200,95450,34635],{"class":210},[200,95452,980],{"class":214},[200,95454,983],{"class":206},[200,95456,242],{"class":214},[200,95458,35993],{"class":245},[200,95460,991],{"class":214},[200,95462,95463,95465,95467],{"class":202,"line":255},[200,95464,680],{"class":214},[200,95466,14896],{"class":235},[200,95468,647],{"class":214},[200,95470,95471],{"class":202,"line":272},[200,95472,528],{"emptyLinePlaceholder":20},[200,95474,95475,95477,95479],{"class":202,"line":283},[200,95476,599],{"class":214},[200,95478,21468],{"class":235},[200,95480,647],{"class":214},[200,95482,95483,95485,95487],{"class":202,"line":300},[200,95484,656],{"class":214},[200,95486,35951],{"class":235},[200,95488,21527],{"class":214},[200,95490,95491,95493,95495],{"class":202,"line":310},[200,95492,680],{"class":214},[200,95494,21468],{"class":235},[200,95496,647],{"class":214},[35,95498,36034],{},[192,95500,95501],{"className":1354,"code":36037,"language":1357,"meta":13,"style":13},[156,95502,95503],{"__ignoreMap":13},[200,95504,95505,95507,95509,95511,95513],{"class":202,"line":203},[200,95506,36044],{"class":223},[200,95508,35904],{"class":245},[200,95510,14932],{"class":214},[200,95512,36051],{"class":1364},[200,95514,13704],{"class":245},[35,95516,36056,95517,36059,95519],{},[156,95518,35951],{},[1216,95520,36062],{},[51,95522,36066],{"id":36065},[35,95524,36069],{},[35,95526,36072,95527,36076],{},[156,95528,36075],{},[35,95530,36079,95531,36085,95534,215],{},[39,95532,36084],{"href":36082,"rel":95533},[77],[156,95535,36088],{},[35,95537,36091,95538,36097,95541,36101],{},[39,95539,36096],{"href":36094,"rel":95540},[77],[1216,95542,36100],{},[192,95544,95545],{"className":1354,"code":36104,"language":1357,"meta":13,"style":13},[156,95546,95547],{"__ignoreMap":13},[200,95548,95549,95551,95553,95555],{"class":202,"line":203},[200,95550,1365],{"class":1364},[200,95552,36113],{"class":245},[200,95554,1371],{"class":245},[200,95556,36118],{"class":245},[35,95558,36121,95559,2186],{},[156,95560,31352],{},[192,95562,95563],{"className":28358,"code":36126,"language":28360,"meta":13,"style":13},[156,95564,95565,95569,95581,95599,95615,95619],{"__ignoreMap":13},[200,95566,95567],{"class":202,"line":203},[200,95568,230],{"class":214},[200,95570,95571,95573,95575,95577,95579],{"class":202,"line":14},[200,95572,28489],{"class":214},[200,95574,31368],{"class":379},[200,95576,610],{"class":214},[200,95578,239],{"class":214},[200,95580,280],{"class":214},[200,95582,95583,95585,95587,95589,95591,95593,95595,95597],{"class":202,"line":255},[200,95584,28529],{"class":214},[200,95586,36151],{"class":1364},[200,95588,610],{"class":214},[200,95590,239],{"class":214},[200,95592,28499],{"class":214},[200,95594,34906],{"class":245},[200,95596,610],{"class":214},[200,95598,252],{"class":214},[200,95600,95601,95603,95605,95607,95609,95611,95613],{"class":202,"line":272},[200,95602,28529],{"class":214},[200,95604,36170],{"class":1364},[200,95606,610],{"class":214},[200,95608,239],{"class":214},[200,95610,28499],{"class":214},[200,95612,36179],{"class":245},[200,95614,616],{"class":214},[200,95616,95617],{"class":202,"line":283},[200,95618,4505],{"class":214},[200,95620,95621],{"class":202,"line":300},[200,95622,1129],{"class":214},[35,95624,36192,95625,36195,95627,36198,95629,36201],{},[156,95626,36151],{},[156,95628,34906],{},[156,95630,36170],{},[938,95632,95633],{},[35,95634,95635,36208,95637,36212],{},[200,95636,944],{},[156,95638,36211],{},[35,95640,36215],{},[192,95642,95643],{"className":1354,"code":36218,"language":1357,"meta":13,"style":13},[156,95644,95645],{"__ignoreMap":13},[200,95646,95647,95649,95651],{"class":202,"line":203},[200,95648,36084],{"class":1364},[200,95650,32462],{"class":245},[200,95652,36229],{"class":245},[35,95654,36232],{},[51,95656,36236],{"id":36235},[35,95658,36239],{},[1593,95660,95661,95663,95665,95667,95669,95671],{},[71,95662,36244],{},[71,95664,36247],{},[71,95666,36250],{},[71,95668,36253],{},[71,95670,36256],{},[71,95672,36259],{},[35,95674,36262,95675],{},[1599,95676,36265,95677,215],{"repo":34304},[6862,95678,36268],{},[35,95680,36271,95681,215],{},[39,95682,36276],{"href":36274,"rel":95683},[77],[35,95685,95686],{},[1216,95687,36281],{},[7918,95689],{"id":30810},[1170,95691,36286],{},{"title":13,"searchDepth":14,"depth":14,"links":95693},[95694,95695,95696,95697,95698,95699,95700,95701,95702],{"id":34809,"depth":14,"text":34810},{"id":34894,"depth":14,"text":34895},{"id":35135,"depth":14,"text":35136},{"id":35354,"depth":14,"text":35355},{"id":35441,"depth":14,"text":35442},{"id":35513,"depth":14,"text":35514},{"id":35829,"depth":14,"text":35830},{"id":36065,"depth":14,"text":36066},{"id":36235,"depth":14,"text":36236},{},{"title":34296,"description":36299},{"loc":36301},{"id":36308,"title":36309,"body":95707,"contentId":30678,"date":36670,"description":36671,"extension":18,"meta":95938,"navigation":20,"path":36673,"rawbody":36674,"robots":23,"schemaOrg":23,"seo":95939,"sitemap":95940,"stem":36696,"__hash__":36697},{"type":7,"value":95708,"toc":95931},[95709,95717,95721,95723,95725,95729,95733,95735,95737,95748,95752,95756,95761,95765,95769,95773,95775,95779,95781,95789,95793,95795,95802,95806,95808,95810,95819,95823,95827,95829,95833,95835,95837,95841,95845,95847,95867,95869,95879,95883,95887,95889,95893,95895,95905,95909,95913,95917,95919,95923,95925,95927],[35,95710,36314,95711,36320,95714,36324],{},[39,95712,36319],{"href":36317,"rel":95713},[77],[39,95715,17998],{"href":34319,"rel":95716},[77],[35,95718,36327,95719],{},[1216,95720,36330],{},[51,95722,36334],{"id":36333},[35,95724,36337],{},[35,95726,95727],{},[1707,95728],{"alt":36342,"src":36343},[35,95730,36346,95731],{},[1216,95732,36349],{},[51,95734,36353],{"id":36352},[35,95736,36356],{},[35,95738,36359,95739,36365,95742,36371,95745,36377],{},[39,95740,36364],{"href":36362,"rel":95741},[77],[39,95743,36370],{"href":36368,"rel":95744},[77],[39,95746,36376],{"href":36374,"rel":95747},[77],[35,95749,95750],{},[1707,95751],{"alt":36382,"src":36383},[35,95753,95754],{},[1707,95755],{"alt":36388,"src":36389},[35,95757,36392,95758,36398],{},[39,95759,36397],{"href":36395,"rel":95760},[77],[35,95762,95763],{},[1707,95764],{"alt":36403,"src":36404},[35,95766,95767],{},[1707,95768],{"alt":36409,"src":36410},[35,95770,95771],{},[1216,95772,36415],{},[35,95774,36418],{},[35,95776,95777],{},[1707,95778],{"alt":36423,"src":36424},[35,95780,36427],{},[35,95782,36430,95783,36436,95786,36442],{},[39,95784,36435],{"href":36433,"rel":95785},[77],[39,95787,36441],{"href":36439,"rel":95788},[77],[35,95790,95791],{},[1707,95792],{"alt":36447,"src":36448},[35,95794,36451],{},[35,95796,36454,95797,36460,95800],{},[39,95798,36459],{"href":36457,"rel":95799},[77],[1216,95801,36463],{},[35,95803,95804],{},[1707,95805],{"alt":36468,"src":36469},[35,95807,36472],{},[35,95809,36475],{},[35,95811,36478,95812,36484,95815,36488,95817],{},[39,95813,36483],{"href":36481,"rel":95814},[77],[1216,95816,36487],{},[1216,95818,36491],{},[35,95820,95821],{},[1707,95822],{"alt":36496,"src":36497},[35,95824,95825],{},[1707,95826],{"alt":36502,"src":36503},[35,95828,36506],{},[35,95830,95831],{},[1707,95832],{"alt":36511,"src":36512},[35,95834,36515],{},[51,95836,36519],{"id":36518},[938,95838,95839],{},[35,95840,36524],{},[35,95842,36527,95843],{},[1216,95844,36530],{},[35,95846,36533],{},[1593,95848,95849,95851,95853,95855,95857,95859,95861,95863,95865],{},[71,95850,36538],{},[71,95852,36541],{},[71,95854,36544],{},[71,95856,36547],{},[71,95858,36550],{},[71,95860,36553],{},[71,95862,36556],{},[71,95864,36559],{},[71,95866,36562],{},[35,95868,36565],{},[35,95870,36568,95871,36574,95874,36580,95877],{},[39,95872,36573],{"href":36571,"rel":95873},[77],[39,95875,36579],{"href":36577,"rel":95876},[77],[1216,95878,36583],{},[35,95880,95881],{},[1707,95882],{"alt":36588,"src":36589},[35,95884,95885],{},[1707,95886],{"alt":36594,"src":36595},[35,95888,36598],{},[35,95890,95891],{},[1707,95892],{"alt":36603,"src":36604},[51,95894,36608],{"id":36607},[35,95896,36611,95897,1655,95900,36619,95903],{},[39,95898,36319],{"href":36317,"rel":95899},[77],[39,95901,1896],{"href":36617,"rel":95902},[77],[1216,95904,36622],{},[35,95906,95907],{},[1707,95908],{"alt":36627,"src":36628},[35,95910,95911],{},[1707,95912],{"alt":36633,"src":36634},[35,95914,95915],{},[1707,95916],{"alt":36639,"src":36640},[51,95918,36644],{"id":36643},[35,95920,36647,95921],{},[1216,95922,36650],{},[35,95924,36653],{},[35,95926,36656],{},[35,95928,95929],{},[1707,95930],{"alt":36661,"src":36662},{"title":13,"searchDepth":14,"depth":14,"links":95932},[95933,95934,95935,95936,95937],{"id":36333,"depth":14,"text":36334},{"id":36352,"depth":14,"text":36353},{"id":36518,"depth":14,"text":36519},{"id":36607,"depth":14,"text":36608},{"id":36643,"depth":14,"text":36644},{},{"title":36309,"description":36671},{"loc":36673,"images":95941},[95942,95943,95944,95945,95946,95947,95948,95949,95950,95951,95952,95953,95954,95955,95956,95957,95958,95959],{"loc":36343},{"loc":36383},{"loc":36389},{"loc":36404},{"loc":36410},{"loc":36424},{"loc":36448},{"loc":36469},{"loc":36497},{"loc":36503},{"loc":36512},{"loc":36589},{"loc":36595},{"loc":36604},{"loc":36628},{"loc":36634},{"loc":36640},{"loc":36662},{"id":36699,"title":36700,"body":95961,"contentId":37843,"date":37844,"description":37845,"extension":18,"meta":96936,"navigation":20,"path":37847,"rawbody":37848,"robots":23,"schemaOrg":23,"seo":96937,"sitemap":96938,"stem":37855,"__hash__":37856},{"type":7,"value":95962,"toc":96933},[95963,95969,95971,95973,96027,96031,96033,96035,96037,96041,96053,96509,96513,96753,96757,96761,96766,96768,96775,96779,96781,96791,96795,96801,96805,96809,96811,96815,96821,96823,96825,96827,96931],[938,95964,95965],{},[35,95966,95967,36709],{},[200,95968,944],{},[35,95970,36712],{},[35,95972,36715],{},[192,95974,95975],{"className":25070,"code":36718,"language":25072,"meta":13,"style":13},[156,95976,95977,95997,96021],{"__ignoreMap":13},[200,95978,95979,95981,95983,95985,95987,95989,95991,95993,95995],{"class":202,"line":203},[200,95980,36725],{"class":223},[200,95982,227],{"class":210},[200,95984,249],{"class":214},[200,95986,36732],{"class":245},[200,95988,249],{"class":214},[200,95990,549],{"class":214},[200,95992,3729],{"class":214},[200,95994,393],{"class":379},[200,95996,280],{"class":214},[200,95998,95999,96001,96003,96005,96007,96009,96011,96013,96015,96017,96019],{"class":202,"line":14},[200,96000,36747],{"class":223},[200,96002,227],{"class":235},[200,96004,3203],{"class":2060},[200,96006,13215],{"class":214},[200,96008,3953],{"class":2060},[200,96010,2104],{"class":235},[200,96012,215],{"class":214},[200,96014,36762],{"class":223},[200,96016,227],{"class":235},[200,96018,4797],{"class":2060},[200,96020,416],{"class":235},[200,96022,96023,96025],{"class":202,"line":255},[200,96024,413],{"class":214},[200,96026,416],{"class":210},[35,96028,36777,96029,36781],{},[6862,96030,36780],{},[35,96032,36784],{},[51,96034,36788],{"id":36787},[35,96036,36791],{},[35,96038,36794,96039],{},[1216,96040,36797],{},[35,96042,36800,96043,36806,96046,36812,96049,36816,96051],{},[39,96044,36805],{"href":36803,"rel":96045},[77],[39,96047,36811],{"href":36809,"rel":96048},[77],[1216,96050,36815],{},[1216,96052,36819],{},[192,96054,96055],{"className":1380,"code":36822,"language":1383,"meta":13,"style":13},[156,96056,96057,96079,96093,96097,96113,96125,96145,96149,96165,96189,96205,96215,96229,96233,96257,96281,96287,96291,96313,96327,96331,96347,96359,96375,96379,96395,96419,96455,96479,96503],{"__ignoreMap":13},[200,96058,96059,96061,96063,96065,96067,96069,96071,96073,96075,96077],{"class":202,"line":203},[200,96060,36725],{"class":223},[200,96062,227],{"class":210},[200,96064,249],{"class":214},[200,96066,36835],{"class":245},[200,96068,249],{"class":214},[200,96070,549],{"class":214},[200,96072,380],{"class":379},[200,96074,3729],{"class":214},[200,96076,393],{"class":379},[200,96078,280],{"class":214},[200,96080,96081,96083,96085,96087,96089,96091],{"class":202,"line":14},[200,96082,36747],{"class":210},[200,96084,215],{"class":214},[200,96086,36856],{"class":223},[200,96088,227],{"class":235},[200,96090,36861],{"class":2060},[200,96092,416],{"class":235},[200,96094,96095],{"class":202,"line":255},[200,96096,528],{"emptyLinePlaceholder":20},[200,96098,96099,96101,96103,96105,96107,96109,96111],{"class":202,"line":272},[200,96100,3738],{"class":379},[200,96102,36874],{"class":210},[200,96104,239],{"class":214},[200,96106,36879],{"class":1364},[200,96108,509],{"class":214},[200,96110,452],{"class":206},[200,96112,31187],{"class":210},[200,96114,96115,96117,96119,96121,96123],{"class":202,"line":283},[200,96116,3770],{"class":214},[200,96118,36892],{"class":223},[200,96120,227],{"class":235},[200,96122,36897],{"class":210},[200,96124,416],{"class":235},[200,96126,96127,96129,96131,96133,96135,96137,96139,96141,96143],{"class":202,"line":300},[200,96128,3770],{"class":214},[200,96130,36906],{"class":223},[200,96132,227],{"class":235},[200,96134,3546],{"class":210},[200,96136,215],{"class":214},[200,96138,36915],{"class":210},[200,96140,549],{"class":214},[200,96142,36920],{"class":210},[200,96144,416],{"class":235},[200,96146,96147],{"class":202,"line":310},[200,96148,528],{"emptyLinePlaceholder":20},[200,96150,96151,96153,96155,96157,96159,96161,96163],{"class":202,"line":336},[200,96152,36747],{"class":223},[200,96154,227],{"class":235},[200,96156,36935],{"class":210},[200,96158,2104],{"class":235},[200,96160,215],{"class":214},[200,96162,36942],{"class":223},[200,96164,466],{"class":235},[200,96166,96167,96169,96171,96173,96175,96177,96179,96181,96183,96185,96187],{"class":202,"line":342},[200,96168,36747],{"class":223},[200,96170,227],{"class":235},[200,96172,36935],{"class":210},[200,96174,2104],{"class":235},[200,96176,215],{"class":214},[200,96178,36959],{"class":223},[200,96180,227],{"class":235},[200,96182,249],{"class":214},[200,96184,36915],{"class":245},[200,96186,249],{"class":214},[200,96188,416],{"class":235},[200,96190,96191,96193,96195,96197,96199,96201,96203],{"class":202,"line":365},[200,96192,36747],{"class":223},[200,96194,227],{"class":235},[200,96196,36935],{"class":210},[200,96198,2104],{"class":235},[200,96200,215],{"class":214},[200,96202,36959],{"class":223},[200,96204,3130],{"class":235},[200,96206,96207,96209,96211,96213],{"class":202,"line":371},[200,96208,1415],{"class":214},[200,96210,20112],{"class":245},[200,96212,249],{"class":214},[200,96214,252],{"class":214},[200,96216,96217,96219,96221,96223,96225,96227],{"class":202,"line":398},[200,96218,37000],{"class":210},[200,96220,215],{"class":214},[200,96222,20112],{"class":210},[200,96224,215],{"class":214},[200,96226,3847],{"class":223},[200,96228,466],{"class":235},[200,96230,96231],{"class":202,"line":405},[200,96232,29966],{"class":235},[200,96234,96235,96237,96239,96241,96243,96245,96247,96249,96251,96253,96255],{"class":202,"line":410},[200,96236,36747],{"class":223},[200,96238,227],{"class":235},[200,96240,36935],{"class":210},[200,96242,2104],{"class":235},[200,96244,215],{"class":214},[200,96246,36959],{"class":223},[200,96248,227],{"class":235},[200,96250,249],{"class":214},[200,96252,37035],{"class":245},[200,96254,249],{"class":214},[200,96256,416],{"class":235},[200,96258,96259,96261,96263,96265,96267,96269,96271,96273,96275,96277,96279],{"class":202,"line":860},[200,96260,36747],{"class":223},[200,96262,227],{"class":235},[200,96264,36935],{"class":210},[200,96266,2104],{"class":235},[200,96268,215],{"class":214},[200,96270,36959],{"class":223},[200,96272,227],{"class":235},[200,96274,249],{"class":214},[200,96276,37060],{"class":245},[200,96278,249],{"class":214},[200,96280,416],{"class":235},[200,96282,96283,96285],{"class":202,"line":889},[200,96284,413],{"class":214},[200,96286,416],{"class":210},[200,96288,96289],{"class":202,"line":899},[200,96290,528],{"emptyLinePlaceholder":20},[200,96292,96293,96295,96297,96299,96301,96303,96305,96307,96309,96311],{"class":202,"line":904},[200,96294,36725],{"class":223},[200,96296,227],{"class":210},[200,96298,249],{"class":214},[200,96300,37085],{"class":245},[200,96302,249],{"class":214},[200,96304,549],{"class":214},[200,96306,380],{"class":379},[200,96308,3729],{"class":214},[200,96310,393],{"class":379},[200,96312,280],{"class":214},[200,96314,96315,96317,96319,96321,96323,96325],{"class":202,"line":930},[200,96316,36747],{"class":210},[200,96318,215],{"class":214},[200,96320,36856],{"class":223},[200,96322,227],{"class":235},[200,96324,36861],{"class":2060},[200,96326,416],{"class":235},[200,96328,96329],{"class":202,"line":3655},[200,96330,528],{"emptyLinePlaceholder":20},[200,96332,96333,96335,96337,96339,96341,96343,96345],{"class":202,"line":3660},[200,96334,3738],{"class":379},[200,96336,37122],{"class":210},[200,96338,239],{"class":214},[200,96340,36879],{"class":1364},[200,96342,509],{"class":214},[200,96344,452],{"class":206},[200,96346,31187],{"class":210},[200,96348,96349,96351,96353,96355,96357],{"class":202,"line":4035},[200,96350,3770],{"class":214},[200,96352,36892],{"class":223},[200,96354,227],{"class":235},[200,96356,36897],{"class":210},[200,96358,416],{"class":235},[200,96360,96361,96363,96365,96367,96369,96371,96373],{"class":202,"line":4490},[200,96362,3770],{"class":214},[200,96364,37151],{"class":223},[200,96366,227],{"class":235},[200,96368,3546],{"class":210},[200,96370,215],{"class":214},[200,96372,36915],{"class":210},[200,96374,416],{"class":235},[200,96376,96377],{"class":202,"line":4496},[200,96378,528],{"emptyLinePlaceholder":20},[200,96380,96381,96383,96385,96387,96389,96391,96393],{"class":202,"line":4502},[200,96382,36747],{"class":223},[200,96384,227],{"class":235},[200,96386,37174],{"class":210},[200,96388,2104],{"class":235},[200,96390,215],{"class":214},[200,96392,36942],{"class":223},[200,96394,466],{"class":235},[200,96396,96397,96399,96401,96403,96405,96407,96409,96411,96413,96415,96417],{"class":202,"line":4508},[200,96398,36747],{"class":223},[200,96400,227],{"class":235},[200,96402,37174],{"class":210},[200,96404,2104],{"class":235},[200,96406,215],{"class":214},[200,96408,36959],{"class":223},[200,96410,227],{"class":235},[200,96412,249],{"class":214},[200,96414,36915],{"class":245},[200,96416,249],{"class":214},[200,96418,416],{"class":235},[200,96420,96421,96423,96425,96427,96429,96431,96433,96435,96437,96439,96441,96443,96445,96447,96449,96451,96453],{"class":202,"line":5469},[200,96422,36747],{"class":223},[200,96424,227],{"class":235},[200,96426,37174],{"class":210},[200,96428,2104],{"class":235},[200,96430,215],{"class":214},[200,96432,36959],{"class":223},[200,96434,227],{"class":235},[200,96436,249],{"class":214},[200,96438,20112],{"class":245},[200,96440,249],{"class":214},[200,96442,549],{"class":214},[200,96444,3561],{"class":210},[200,96446,215],{"class":214},[200,96448,20112],{"class":210},[200,96450,215],{"class":214},[200,96452,3847],{"class":223},[200,96454,10962],{"class":235},[200,96456,96457,96459,96461,96463,96465,96467,96469,96471,96473,96475,96477],{"class":202,"line":5474},[200,96458,36747],{"class":223},[200,96460,227],{"class":235},[200,96462,37174],{"class":210},[200,96464,2104],{"class":235},[200,96466,215],{"class":214},[200,96468,36959],{"class":223},[200,96470,227],{"class":235},[200,96472,249],{"class":214},[200,96474,37035],{"class":245},[200,96476,249],{"class":214},[200,96478,416],{"class":235},[200,96480,96481,96483,96485,96487,96489,96491,96493,96495,96497,96499,96501],{"class":202,"line":13237},[200,96482,36747],{"class":223},[200,96484,227],{"class":235},[200,96486,37174],{"class":210},[200,96488,2104],{"class":235},[200,96490,215],{"class":214},[200,96492,36959],{"class":223},[200,96494,227],{"class":235},[200,96496,249],{"class":214},[200,96498,37060],{"class":245},[200,96500,249],{"class":214},[200,96502,416],{"class":235},[200,96504,96505,96507],{"class":202,"line":13263},[200,96506,413],{"class":214},[200,96508,416],{"class":210},[35,96510,37299,96511,37303],{},[6862,96512,37302],{},[192,96514,96515],{"className":1380,"code":37306,"language":1383,"meta":13,"style":13},[156,96516,96517,96525,96533,96543,96553,96563,96577,96591,96605,96615,96619,96623,96631,96641,96673,96705,96715,96725,96735,96745,96749],{"__ignoreMap":13},[200,96518,96519,96521,96523],{"class":202,"line":203},[200,96520,1390],{"class":206},[200,96522,1393],{"class":206},[200,96524,280],{"class":214},[200,96526,96527,96529,96531],{"class":202,"line":14},[200,96528,37321],{"class":235},[200,96530,239],{"class":214},[200,96532,280],{"class":214},[200,96534,96535,96537,96539,96541],{"class":202,"line":255},[200,96536,37330],{"class":235},[200,96538,239],{"class":214},[200,96540,27681],{"class":210},[200,96542,252],{"class":214},[200,96544,96545,96547,96549,96551],{"class":202,"line":272},[200,96546,37341],{"class":235},[200,96548,239],{"class":214},[200,96550,27681],{"class":210},[200,96552,252],{"class":214},[200,96554,96555,96557,96559,96561],{"class":202,"line":283},[200,96556,37352],{"class":235},[200,96558,239],{"class":214},[200,96560,27681],{"class":210},[200,96562,252],{"class":214},[200,96564,96565,96567,96569,96571,96573,96575],{"class":202,"line":300},[200,96566,37363],{"class":235},[200,96568,239],{"class":214},[200,96570,350],{"class":210},[200,96572,37370],{"class":223},[200,96574,37373],{"class":210},[200,96576,252],{"class":214},[200,96578,96579,96581,96583,96585,96587,96589],{"class":202,"line":310},[200,96580,37380],{"class":235},[200,96582,239],{"class":214},[200,96584,350],{"class":210},[200,96586,37387],{"class":223},[200,96588,37390],{"class":210},[200,96590,252],{"class":214},[200,96592,96593,96595,96597,96599,96601,96603],{"class":202,"line":336},[200,96594,37397],{"class":235},[200,96596,239],{"class":214},[200,96598,350],{"class":210},[200,96600,37370],{"class":223},[200,96602,37373],{"class":210},[200,96604,252],{"class":214},[200,96606,96607,96609,96611,96613],{"class":202,"line":342},[200,96608,37412],{"class":235},[200,96610,239],{"class":214},[200,96612,27681],{"class":210},[200,96614,252],{"class":214},[200,96616,96617],{"class":202,"line":365},[200,96618,368],{"class":214},[200,96620,96621],{"class":202,"line":371},[200,96622,528],{"emptyLinePlaceholder":20},[200,96624,96625,96627,96629],{"class":202,"line":398},[200,96626,37431],{"class":235},[200,96628,239],{"class":214},[200,96630,280],{"class":214},[200,96632,96633,96635,96637,96639],{"class":202,"line":405},[200,96634,37330],{"class":235},[200,96636,239],{"class":214},[200,96638,27681],{"class":210},[200,96640,252],{"class":214},[200,96642,96643,96645,96647,96649,96651,96653,96655,96657,96659,96661,96663,96665,96667,96669,96671],{"class":202,"line":410},[200,96644,37341],{"class":235},[200,96646,239],{"class":214},[200,96648,350],{"class":210},[200,96650,37456],{"class":223},[200,96652,227],{"class":210},[200,96654,37461],{"class":223},[200,96656,227],{"class":210},[200,96658,249],{"class":214},[200,96660,37468],{"class":245},[200,96662,249],{"class":214},[200,96664,2104],{"class":210},[200,96666,549],{"class":214},[200,96668,37477],{"class":223},[200,96670,37480],{"class":210},[200,96672,252],{"class":214},[200,96674,96675,96677,96679,96681,96683,96685,96687,96689,96691,96693,96695,96697,96699,96701,96703],{"class":202,"line":860},[200,96676,37352],{"class":235},[200,96678,239],{"class":214},[200,96680,350],{"class":210},[200,96682,37456],{"class":223},[200,96684,227],{"class":210},[200,96686,37461],{"class":223},[200,96688,227],{"class":210},[200,96690,249],{"class":214},[200,96692,37468],{"class":245},[200,96694,249],{"class":214},[200,96696,2104],{"class":210},[200,96698,549],{"class":214},[200,96700,37477],{"class":223},[200,96702,37480],{"class":210},[200,96704,252],{"class":214},[200,96706,96707,96709,96711,96713],{"class":202,"line":889},[200,96708,37363],{"class":235},[200,96710,239],{"class":214},[200,96712,27681],{"class":210},[200,96714,252],{"class":214},[200,96716,96717,96719,96721,96723],{"class":202,"line":899},[200,96718,37380],{"class":235},[200,96720,239],{"class":214},[200,96722,27681],{"class":210},[200,96724,252],{"class":214},[200,96726,96727,96729,96731,96733],{"class":202,"line":904},[200,96728,37397],{"class":235},[200,96730,239],{"class":214},[200,96732,27681],{"class":210},[200,96734,252],{"class":214},[200,96736,96737,96739,96741,96743],{"class":202,"line":930},[200,96738,37412],{"class":235},[200,96740,239],{"class":214},[200,96742,27681],{"class":210},[200,96744,252],{"class":214},[200,96746,96747],{"class":202,"line":3655},[200,96748,368],{"class":214},[200,96750,96751],{"class":202,"line":3660},[200,96752,1129],{"class":214},[35,96754,37565,96755],{},[1216,96756,37568],{},[35,96758,37571,96759],{},[1216,96760,37574],{},[35,96762,37577,96763,37583],{},[39,96764,37582],{"href":37580,"rel":96765},[77],[35,96767,37586],{},[35,96769,37589,96770,37595,96773],{},[39,96771,37594],{"href":37592,"rel":96772},[77],[1216,96774,37598],{},[35,96776,96777],{},[1707,96778],{"alt":37603,"src":37604},[35,96780,37607],{},[1593,96782,96783,96785,96787,96789],{},[71,96784,37612],{},[71,96786,37615],{},[71,96788,37618],{},[71,96790,37621],{},[35,96792,37624,96793],{},[1216,96794,37627],{},[35,96796,37630,96797,8048,96799],{},[1216,96798,37633],{},[1216,96800,37636],{},[35,96802,37639,96803],{},[1216,96804,37642],{},[35,96806,96807],{},[1707,96808],{"alt":37647,"src":37648},[35,96810,37651],{},[35,96812,37654,96813,37658],{},[1216,96814,37657],{},[35,96816,37661,96817,37665,96819,37669],{},[156,96818,37664],{},[1216,96820,37668],{},[35,96822,37672],{},[35,96824,37675],{},[35,96826,37678],{},[7918,96828,96829,96831,96841,96848,96852,96856],{"id":20859},[35,96830,37683],{},[35,96832,37686,96833,37692,96836],{},[39,96834,37691],{"href":37689,"rel":96835},[77],[1216,96837,37695,96838,215],{},[39,96839,37700],{"href":37698,"rel":96840},[77],[35,96842,37703,96843,37709,96846,37713],{},[39,96844,37708],{"href":37706,"rel":96845},[77],[1216,96847,37712],{},[35,96849,96850],{},[1707,96851],{"alt":37718,"src":37719},[35,96853,37722,96854],{},[1216,96855,37725],{},[7151,96857,96858,96860,96862,96864,96918,96922,96924],{"icon":37728,"text":37729,"to":33445},[51,96859,37733],{"id":37732},[35,96861,37736],{},[35,96863,37739],{},[68,96865,96866,96868,96872,96876,96880,96884,96888,96892,96896,96906,96910,96914],{},[71,96867,37744],{},[71,96869,37747,96870,215],{},[156,96871,37750],{},[71,96873,37753,96874],{},[1216,96875,37756],{},[71,96877,37759,96878],{},[1216,96879,37762],{},[71,96881,37765,96882],{},[1216,96883,37768],{},[71,96885,37771,96886],{},[1216,96887,37774],{},[71,96889,37777,96890],{},[1216,96891,37780],{},[71,96893,37783,96894],{},[1216,96895,37786],{},[71,96897,37789,96898,3092,96900,1655,96902,106,96904],{},[156,96899,37792],{},[156,96901,37795],{},[156,96903,37798],{},[1216,96905,37801],{},[71,96907,37804,96908,215],{},[156,96909,37798],{},[71,96911,37809,96912],{},[1216,96913,37812],{},[71,96915,37815,96916],{},[1216,96917,37818],{},[35,96919,37821,96920],{},[1216,96921,37824],{},[35,96923,37827],{},[35,96925,96926],{},[1216,96927,37832,96928,215],{},[39,96929,37836],{"href":33445,"rel":96930},[77],[1170,96932,37839],{},{"title":13,"searchDepth":14,"depth":14,"links":96934},[96935],{"id":36787,"depth":14,"text":36788},{},{"title":36700,"description":37845},{"loc":37847,"images":96939},[96940,96941,96942],{"loc":37604},{"loc":37648},{"loc":37719},{"id":37858,"title":37859,"body":96944,"contentId":38094,"date":38095,"description":38096,"extension":18,"meta":97092,"navigation":20,"path":38098,"rawbody":38099,"robots":23,"schemaOrg":23,"seo":97093,"sitemap":97094,"stem":38105,"__hash__":38106},{"type":7,"value":96945,"toc":97088},[96946,96954,96959,96963,96965,96978,96985,96991,96995,96997,96999,97003,97007,97017,97027,97031,97033,97035,97040,97045,97050,97055,97062,97064],[938,96947,96948],{},[35,96949,96950,37868,96952,215],{},[200,96951,944],{},[39,96953,37872],{"href":37871},[35,96955,37875,96956],{},[39,96957,37880],{"href":37878,"rel":96958},[77],[35,96960,96961],{},[1707,96962],{"alt":37885,"src":37886},[35,96964,37889],{},[35,96966,37892,96967,37898,96970,37903,96973,37909,96976],{},[39,96968,37897],{"href":37895,"rel":96969},[77],[39,96971,36579],{"href":37901,"rel":96972},[77],[39,96974,37908],{"href":37906,"rel":96975},[77],[1216,96977,37912],{},[35,96979,37915,96980,37919,96982,37925],{},[6862,96981,37918],{},[39,96983,37924],{"href":37922,"rel":96984},[77],[35,96986,37928,96987,37932,96989],{},[1216,96988,37931],{},[1216,96990,37935],{},[938,96992,96993],{},[35,96994,37940],{},[35,96996,37943],{},[51,96998,37880],{"id":37946},[35,97000,97001],{},[1707,97002],{"alt":37951,"src":37952},[35,97004,37955,97005,37959],{},[1216,97006,37958],{},[35,97008,37962,97009,1655,97011,37969,97013,1655,97015,215],{},[6862,97010,37965],{},[6862,97012,37968],{},[6862,97014,37972],{},[6862,97016,37975],{},[35,97018,37978,97019,3092,97021,37985,97023,106,97025],{},[6862,97020,37981],{},[6862,97022,37984],{},[6862,97024,37988],{},[1216,97026,37991],{},[938,97028,97029],{},[35,97030,37996],{},[51,97032,38000],{"id":37999},[35,97034,38003],{},[35,97036,38006,97037,38010],{},[39,97038,37880],{"href":37878,"rel":97039},[77],[35,97041,38013,97042,38019],{},[39,97043,38018],{"href":38016,"rel":97044},[77],[35,97046,38022,97047,38027],{},[39,97048,38026],{"href":14207,"rel":97049},[77],[35,97051,38030,97052,38036],{},[39,97053,38035],{"href":38033,"rel":97054},[77],[35,97056,38039,97057,38045,97060,38049],{},[39,97058,38044],{"href":38042,"rel":97059},[77],[1216,97061,38048],{},[35,97063,38052],{},[7918,97065,97066],{"id":30599},[7918,97067,97068,97072,97074,97078,97082,97086],{"id":30604},[35,97069,38059,97070],{},[1599,97071,38063],{"repo":38062},[51,97073,38067],{"id":38066},[35,97075,38070,97076,38074],{},[6862,97077,38073],{},[35,97079,38077,97080],{},[1216,97081,38080],{},[35,97083,38083,97084],{},[1599,97085,38086],{"repo":38062},[35,97087,38089],{},{"title":13,"searchDepth":14,"depth":14,"links":97089},[97090,97091],{"id":37946,"depth":14,"text":37880},{"id":37999,"depth":14,"text":38000},{},{"title":37859,"description":38096},{"loc":38098,"images":97095},[97096,97097],{"loc":37886},{"loc":37952},{"id":38108,"title":38109,"body":97099,"contentId":30735,"date":38262,"description":38263,"extension":18,"meta":97207,"navigation":20,"path":38265,"rawbody":38266,"robots":23,"schemaOrg":23,"seo":97208,"sitemap":97209,"stem":38274,"__hash__":38275},{"type":7,"value":97100,"toc":97204},[97101,97115,97119,97124,97126,97128,97130,97132,97139,97143,97153,97157,97159,97166,97168,97172,97174,97176,97178,97182,97196,97200],[938,97102,97103],{},[35,97104,97105,38118,97107,3092,97110,13741,97113,215],{},[200,97106,33274],{},[39,97108,36276],{"href":36274,"rel":97109},[77],[39,97111,7255],{"href":7043,"rel":97112},[77],[39,97114,38127],{"href":20761},[35,97116,38130,97117],{},[1216,97118,38133],{},[35,97120,38136,97121,38141],{},[39,97122,38140],{"href":8217,"rel":97123},[77],[35,97125,38144],{},[35,97127,38147],{},[35,97129,38150],{},[35,97131,38153],{},[35,97133,38156,97134,38160,97137],{},[39,97135,18057],{"href":18055,"rel":97136},[77],[1216,97138,38163],{},[35,97140,97141],{},[1707,97142],{"alt":38168,"src":38169},[35,97144,38172,97145,1655,97148,38179,97151],{},[39,97146,2934],{"href":13829,"rel":97147},[77],[39,97149,30540],{"href":34910,"rel":97150},[77],[1216,97152,38182],{},[35,97154,97155],{},[1707,97156],{"alt":38187,"src":38188},[35,97158,38191],{},[35,97160,38194,97161,38198,97164,38202],{},[39,97162,21156],{"href":21154,"rel":97163},[77],[1216,97165,38201],{},[35,97167,38205],{},[35,97169,97170],{},[1707,97171],{"alt":38210,"src":38211},[35,97173,38214],{},[51,97175,38218],{"id":38217},[35,97177,38221],{},[35,97179,38224,97180],{},[1216,97181,38227],{},[35,97183,38230,97184,38234,97186,38238,97188,3092,97191,13741,97194,215],{},[6862,97185,38233],{},[1216,97187,38237],{},[39,97189,38242],{"href":36274,"rel":97190},[77],[39,97192,7255],{"href":7043,"rel":97193},[77],[39,97195,38127],{"href":20761},[35,97197,97198],{},[1216,97199,38252],{},[35,97201,97202],{},[1707,97203],{"alt":38257,"src":38258},{"title":13,"searchDepth":14,"depth":14,"links":97205},[97206],{"id":38217,"depth":14,"text":38218},{},{"title":38109,"description":38263},{"loc":38265,"images":97210},[97211,97212,97213,97214],{"loc":38169},{"loc":38188},{"loc":38211},{"loc":38258},{"id":38277,"title":38278,"body":97216,"contentId":38632,"date":38633,"description":38634,"extension":18,"meta":97447,"navigation":20,"path":38636,"rawbody":38637,"robots":23,"schemaOrg":23,"seo":97448,"sitemap":97449,"stem":38646,"__hash__":38647},{"type":7,"value":97217,"toc":97438},[97218,97225,97227,97232,97234,97238,97245,97247,97249,97256,97258,97262,97266,97268,97272,97279,97286,97288,97294,97296,97303,97308,97310,97314,97316,97318,97322,97324,97328,97330,97334,97336,97338,97340,97344,97346,97348,97350,97399,97401,97408,97414,97419,97424,97436],[938,97219,97220],{},[35,97221,38285,97222,215],{},[39,97223,38290],{"href":38288,"rel":97224},[77],[51,97226,17294],{"id":17293},[35,97228,38295,97229,38299],{},[39,97230,36579],{"href":37901,"rel":97231},[77],[35,97233,38302],{},[938,97235,97236],{},[35,97237,38307],{},[35,97239,38310,97240],{},[1216,97241,38313,97242,38319],{},[39,97243,38318],{"href":38316,"rel":97244},[77],[35,97246,38322],{},[51,97248,38326],{"id":38325},[35,97250,38329,97251,38338],{},[1216,97252,38332,97253,215],{},[39,97254,38337],{"href":38335,"rel":97255},[77],[35,97257,38341],{},[35,97259,38344,97260],{},[1216,97261,38347],{},[35,97263,38350,97264,38354],{},[1216,97265,38353],{},[51,97267,38358],{"id":38357},[35,97269,38361,97270,38365],{},[1216,97271,38364],{},[35,97273,38368,97274,106,97277],{},[39,97275,38373],{"href":38371,"rel":97276},[77],[1216,97278,38376],{},[35,97280,38379,97281,38385,97284],{},[39,97282,38384],{"href":38382,"rel":97283},[77],[1216,97285,38388],{},[35,97287,38391],{},[35,97289,38394,97290,106,97292],{},[6862,97291,38397],{},[1216,97293,38400],{},[35,97295,38403],{},[35,97297,38406,97298,38410,97301],{},[39,97299,38290],{"href":38288,"rel":97300},[77],[1599,97302,38414],{"repo":38413},[35,97304,38417,97305,38423],{},[39,97306,38422],{"href":38420,"rel":97307},[77],[1929,97309,38427],{"id":38426},[35,97311,97312],{},[1707,97313],{"alt":38397,"src":38432},[35,97315,38435],{},[38437,97317,38440],{"id":38439},[35,97319,97320],{},[1707,97321],{"alt":38445,"src":38446},[35,97323,38449],{},[35,97325,97326],{},[1707,97327],{"alt":38454,"src":38455},[38437,97329,38459],{"id":38458},[35,97331,97332],{},[1707,97333],{"alt":38464,"src":38465},[35,97335,38468],{},[35,97337,38471],{},[38437,97339,38475],{"id":38474},[35,97341,97342],{},[1707,97343],{"alt":38480,"src":38481},[35,97345,38484],{},[1929,97347,38488],{"id":38487},[35,97349,38491],{},[1593,97351,97352,97359,97364,97369,97374,97379,97384,97389,97394],{},[71,97353,97354,8048,97357],{},[39,97355,38499],{"href":23088,"rel":97356},[77],[1216,97358,38502],{},[71,97360,97361,38508],{},[39,97362,9203],{"href":23093,"rel":97363},[77],[71,97365,97366,38514],{},[39,97367,36579],{"href":36577,"rel":97368},[77],[71,97370,97371,38522],{},[39,97372,38521],{"href":38519,"rel":97373},[77],[71,97375,97376,38528],{},[39,97377,9234],{"href":9232,"rel":97378},[77],[71,97380,97381,34220],{},[39,97382,38535],{"href":38533,"rel":97383},[77],[71,97385,97386,38543],{},[39,97387,38542],{"href":38540,"rel":97388},[77],[71,97390,97391,38551],{},[39,97392,38550],{"href":38548,"rel":97393},[77],[71,97395,97396,38559],{},[39,97397,38558],{"href":38556,"rel":97398},[77],[51,97400,38563],{"id":38562},[35,97402,38566,97403,38570,97406,38575],{},[39,97404,18581],{"href":18579,"rel":97405},[77],[39,97407,38574],{"href":38573},[35,97409,38578,97410,1655,97412,215],{},[39,97411,38582],{"href":38581},[39,97413,38586],{"href":38585},[35,97415,38589,97416,38593],{},[39,97417,9234],{"href":9232,"rel":97418},[77],[35,97420,38596,97421,15917],{},[39,97422,38601],{"href":38599,"rel":97423},[77],[35,97425,38604,97426,38611,97431],{},[39,97427,97429],{"href":38288,"rel":97428},[77],[6862,97430,38610],{},[1216,97432,38614,97433,15917],{},[39,97434,38619],{"href":38617,"rel":97435},[77],[35,97437,38622],{},{"title":13,"searchDepth":14,"depth":14,"links":97439},[97440,97441,97442,97446],{"id":17293,"depth":14,"text":17294},{"id":38325,"depth":14,"text":38326},{"id":38357,"depth":14,"text":38358,"children":97443},[97444,97445],{"id":38426,"depth":255,"text":38427},{"id":38487,"depth":255,"text":38488},{"id":38562,"depth":14,"text":38563},{},{"title":38278,"description":38634},{"loc":38636,"images":97450},[97451,97452,97453,97454,97455],{"loc":38432},{"loc":38446},{"loc":38455},{"loc":38465},{"loc":38481},{"id":38649,"title":38650,"body":97457,"contentId":30604,"date":40565,"description":40566,"extension":18,"meta":99124,"navigation":20,"path":40568,"rawbody":40569,"robots":23,"schemaOrg":23,"seo":99125,"sitemap":99126,"stem":40572,"__hash__":40573},{"type":7,"value":97458,"toc":99113},[97459,97467,97475,97481,97483,97485,99111],[35,97460,38655,97461,38659,97463,38663,97465,215],{},[6862,97462,38658],{},[6862,97464,38662],{},[6862,97466,38666],{},[35,97468,38669,97469,38673,97471,38677,97473,38681],{},[6862,97470,38672],{},[6862,97472,38676],{},[6862,97474,38680],{},[35,97476,38684,97477,38688,97479,38692],{},[6862,97478,38687],{},[6862,97480,38691],{},[35,97482,38695],{},[35,97484,38698],{},[7918,97486,97487,97489,97493,97661,97671,97677,97682,97684,97691,97699,97707,97777,97781,97783,97791,97989,97995,98065,98071,98073,98075,98079,98083,98421,98423,98433,98435,98449,98453,98457,98462,98464,98473,98477,98482,98484,98488,98490,98494,98506,98508,98510,98516,98520,98584,98586,98634,98636,98640,98644,98732,98742,98844,98846,98850,98856,98982,98990,99000,99008,99088,99092,99094,99099,99101,99103,99109],{"id":30589},[51,97488,29078],{"id":29077},[35,97490,38705,97491,38709],{},[6862,97492,38708],{},[192,97494,97495],{"className":1380,"code":38712,"language":1383,"meta":13,"style":13},[156,97496,97497,97519,97543,97547,97575,97587,97595,97605,97615,97625,97635,97639,97645,97649,97655],{"__ignoreMap":13},[200,97498,97499,97501,97503,97505,97507,97509,97511,97513,97515,97517],{"class":202,"line":203},[200,97500,1390],{"class":206},[200,97502,1393],{"class":206},[200,97504,38723],{"class":223},[200,97506,227],{"class":210},[200,97508,3726],{"class":379},[200,97510,1094],{"class":214},[200,97512,25426],{"class":386},[200,97514,2104],{"class":214},[200,97516,393],{"class":379},[200,97518,280],{"class":214},[200,97520,97521,97523,97525,97527,97529,97531,97533,97535,97537,97539,97541],{"class":202,"line":14},[200,97522,3738],{"class":379},[200,97524,3239],{"class":210},[200,97526,509],{"class":214},[200,97528,38748],{"class":223},[200,97530,227],{"class":235},[200,97532,25426],{"class":210},[200,97534,549],{"class":214},[200,97536,242],{"class":214},[200,97538,3295],{"class":245},[200,97540,249],{"class":214},[200,97542,416],{"class":235},[200,97544,97545],{"class":202,"line":255},[200,97546,528],{"emptyLinePlaceholder":20},[200,97548,97549,97551,97553,97555,97557,97559,97561,97563,97565,97567,97569,97571,97573],{"class":202,"line":272},[200,97550,3738],{"class":379},[200,97552,38773],{"class":210},[200,97554,509],{"class":214},[200,97556,452],{"class":206},[200,97558,38780],{"class":210},[200,97560,215],{"class":214},[200,97562,3524],{"class":210},[200,97564,215],{"class":214},[200,97566,38789],{"class":210},[200,97568,215],{"class":214},[200,97570,38794],{"class":223},[200,97572,227],{"class":235},[200,97574,230],{"class":214},[200,97576,97577,97579,97581,97583,97585],{"class":202,"line":283},[200,97578,38803],{"class":235},[200,97580,239],{"class":214},[200,97582,318],{"class":214},[200,97584,3239],{"class":210},[200,97586,333],{"class":214},[200,97588,97589,97591,97593],{"class":202,"line":300},[200,97590,38816],{"class":235},[200,97592,239],{"class":214},[200,97594,280],{"class":214},[200,97596,97597,97599,97601,97603],{"class":202,"line":310},[200,97598,3572],{"class":235},[200,97600,239],{"class":214},[200,97602,2439],{"class":2438},[200,97604,252],{"class":214},[200,97606,97607,97609,97611,97613],{"class":202,"line":336},[200,97608,3604],{"class":235},[200,97610,239],{"class":214},[200,97612,2439],{"class":2438},[200,97614,252],{"class":214},[200,97616,97617,97619,97621,97623],{"class":202,"line":342},[200,97618,38845],{"class":235},[200,97620,239],{"class":214},[200,97622,2439],{"class":2438},[200,97624,252],{"class":214},[200,97626,97627,97629,97631,97633],{"class":202,"line":365},[200,97628,38856],{"class":235},[200,97630,239],{"class":214},[200,97632,2439],{"class":2438},[200,97634,252],{"class":214},[200,97636,97637],{"class":202,"line":371},[200,97638,339],{"class":214},[200,97640,97641,97643],{"class":202,"line":398},[200,97642,5095],{"class":214},[200,97644,416],{"class":235},[200,97646,97647],{"class":202,"line":405},[200,97648,528],{"emptyLinePlaceholder":20},[200,97650,97651,97653],{"class":202,"line":410},[200,97652,3758],{"class":206},[200,97654,38883],{"class":210},[200,97656,97657,97659],{"class":202,"line":860},[200,97658,413],{"class":214},[200,97660,416],{"class":210},[35,97662,38892,97663,38896,97665,3092,97667,13741,97669,215],{},[156,97664,38895],{},[156,97666,38899],{},[156,97668,38902],{},[156,97670,38905],{},[35,97672,38908,97673,33751,97675,38915],{},[6862,97674,38911],{},[6862,97676,38914],{},[35,97678,38918,97679,38924],{},[39,97680,38923],{"href":38921,"rel":97681},[77],[51,97683,38928],{"id":38927},[35,97685,38931,97686,38937],{},[39,97687,97689],{"href":38519,"rel":97688},[77],[6862,97690,38035],{},[35,97692,38940,97693,38943,97695,38947,97697,215],{},[6862,97694,38708],{},[6862,97696,38946],{},[156,97698,38950],{},[35,97700,38953,97701,38956,97703,38960,97705,38963],{},[6862,97702,38035],{},[156,97704,38959],{},[156,97706,38950],{},[192,97708,97709],{"className":1380,"code":38966,"language":1383,"meta":13,"style":13},[156,97710,97711,97733,97745,97749,97761,97765,97771],{"__ignoreMap":13},[200,97712,97713,97715,97717,97719,97721,97723,97725,97727,97729,97731],{"class":202,"line":203},[200,97714,1390],{"class":206},[200,97716,1393],{"class":206},[200,97718,38723],{"class":223},[200,97720,227],{"class":210},[200,97722,3726],{"class":379},[200,97724,1094],{"class":214},[200,97726,25426],{"class":386},[200,97728,2104],{"class":214},[200,97730,393],{"class":379},[200,97732,280],{"class":214},[200,97734,97735,97737,97739,97741,97743],{"class":202,"line":14},[200,97736,6177],{"class":206},[200,97738,38997],{"class":223},[200,97740,227],{"class":235},[200,97742,25426],{"class":210},[200,97744,416],{"class":235},[200,97746,97747],{"class":202,"line":255},[200,97748,528],{"emptyLinePlaceholder":20},[200,97750,97751,97753,97755,97757,97759],{"class":202,"line":272},[200,97752,3738],{"class":379},[200,97754,38773],{"class":210},[200,97756,509],{"class":214},[200,97758,39018],{"class":214},[200,97760,39021],{"class":401},[200,97762,97763],{"class":202,"line":283},[200,97764,528],{"emptyLinePlaceholder":20},[200,97766,97767,97769],{"class":202,"line":300},[200,97768,3758],{"class":206},[200,97770,38883],{"class":210},[200,97772,97773,97775],{"class":202,"line":310},[200,97774,413],{"class":214},[200,97776,416],{"class":210},[35,97778,39040,97779,39044],{},[6862,97780,39043],{},[51,97782,39048],{"id":39047},[35,97784,39051,97785,39054,97787,39058,97789,39062],{},[156,97786,38959],{},[156,97788,39057],{},[156,97790,39061],{},[192,97792,97793],{"className":1380,"code":39065,"language":1383,"meta":13,"style":13},[156,97794,97795,97851,97869,97883,97887,97911,97921,97939,97961,97967,97971,97975,97985],{"__ignoreMap":13},[200,97796,97797,97799,97801,97803,97805,97807,97809,97811,97813,97815,97817,97819,97821,97823,97825,97827,97829,97831,97833,97835,97837,97839,97841,97843,97845,97847,97849],{"class":202,"line":203},[200,97798,1390],{"class":206},[200,97800,380],{"class":379},[200,97802,5002],{"class":379},[200,97804,39078],{"class":223},[200,97806,227],{"class":214},[200,97808,25426],{"class":386},[200,97810,239],{"class":214},[200,97812,39087],{"class":1364},[200,97814,549],{"class":214},[200,97816,39092],{"class":386},[200,97818,239],{"class":214},[200,97820,318],{"class":214},[200,97822,39099],{"class":235},[200,97824,39102],{"class":214},[200,97826,20155],{"class":1364},[200,97828,549],{"class":214},[200,97830,39109],{"class":235},[200,97832,39102],{"class":214},[200,97834,22752],{"class":1364},[200,97836,980],{"class":214},[200,97838,509],{"class":214},[200,97840,39120],{"class":214},[200,97842,5011],{"class":1364},[200,97844,599],{"class":214},[200,97846,39127],{"class":1364},[200,97848,674],{"class":214},[200,97850,280],{"class":214},[200,97852,97853,97855,97857,97859,97861,97863,97865,97867],{"class":202,"line":14},[200,97854,3738],{"class":379},[200,97856,39138],{"class":210},[200,97858,509],{"class":214},[200,97860,452],{"class":206},[200,97862,38997],{"class":223},[200,97864,227],{"class":235},[200,97866,25426],{"class":210},[200,97868,416],{"class":235},[200,97870,97871,97873,97875,97877,97879,97881],{"class":202,"line":255},[200,97872,3738],{"class":379},[200,97874,318],{"class":214},[200,97876,34533],{"class":210},[200,97878,980],{"class":214},[200,97880,509],{"class":214},[200,97882,39165],{"class":210},[200,97884,97885],{"class":202,"line":272},[200,97886,528],{"emptyLinePlaceholder":20},[200,97888,97889,97891,97893,97895,97897,97899,97901,97903,97905,97907,97909],{"class":202,"line":283},[200,97890,20098],{"class":206},[200,97892,1094],{"class":235},[200,97894,26847],{"class":210},[200,97896,215],{"class":214},[200,97898,39182],{"class":210},[200,97900,13398],{"class":214},[200,97902,242],{"class":214},[200,97904,39189],{"class":245},[200,97906,249],{"class":214},[200,97908,5282],{"class":235},[200,97910,230],{"class":214},[200,97912,97913,97915,97917,97919],{"class":202,"line":300},[200,97914,39200],{"class":206},[200,97916,39203],{"class":223},[200,97918,227],{"class":235},[200,97920,230],{"class":214},[200,97922,97923,97925,97927,97929,97931,97933,97935,97937],{"class":202,"line":310},[200,97924,39212],{"class":235},[200,97926,239],{"class":214},[200,97928,39092],{"class":210},[200,97930,215],{"class":214},[200,97932,39221],{"class":210},[200,97934,15013],{"class":214},[200,97936,39226],{"class":2060},[200,97938,252],{"class":214},[200,97940,97941,97943,97945,97947,97949,97951,97953,97955,97957,97959],{"class":202,"line":336},[200,97942,39233],{"class":235},[200,97944,239],{"class":214},[200,97946,39092],{"class":210},[200,97948,215],{"class":214},[200,97950,29303],{"class":210},[200,97952,15013],{"class":214},[200,97954,242],{"class":214},[200,97956,39248],{"class":245},[200,97958,249],{"class":214},[200,97960,252],{"class":214},[200,97962,97963,97965],{"class":202,"line":342},[200,97964,2111],{"class":214},[200,97966,416],{"class":235},[200,97968,97969],{"class":202,"line":365},[200,97970,4505],{"class":214},[200,97972,97973],{"class":202,"line":371},[200,97974,528],{"emptyLinePlaceholder":20},[200,97976,97977,97979,97981,97983],{"class":202,"line":398},[200,97978,3758],{"class":206},[200,97980,39138],{"class":210},[200,97982,14678],{"class":206},[200,97984,39277],{"class":1364},[200,97986,97987],{"class":202,"line":405},[200,97988,1129],{"class":214},[35,97990,39284,97991,39287,97993,39290],{},[156,97992,38959],{},[156,97994,39057],{},[192,97996,97997],{"className":1380,"code":39293,"language":1383,"meta":13,"style":13},[156,97998,97999,98021,98033,98037,98049,98053,98059],{"__ignoreMap":13},[200,98000,98001,98003,98005,98007,98009,98011,98013,98015,98017,98019],{"class":202,"line":203},[200,98002,1390],{"class":206},[200,98004,1393],{"class":206},[200,98006,38723],{"class":223},[200,98008,227],{"class":210},[200,98010,3726],{"class":379},[200,98012,1094],{"class":214},[200,98014,25426],{"class":386},[200,98016,2104],{"class":214},[200,98018,393],{"class":379},[200,98020,280],{"class":214},[200,98022,98023,98025,98027,98029,98031],{"class":202,"line":14},[200,98024,6177],{"class":206},[200,98026,39078],{"class":223},[200,98028,227],{"class":235},[200,98030,25426],{"class":210},[200,98032,416],{"class":235},[200,98034,98035],{"class":202,"line":255},[200,98036,528],{"emptyLinePlaceholder":20},[200,98038,98039,98041,98043,98045,98047],{"class":202,"line":272},[200,98040,3738],{"class":379},[200,98042,38773],{"class":210},[200,98044,509],{"class":214},[200,98046,39018],{"class":214},[200,98048,39021],{"class":401},[200,98050,98051],{"class":202,"line":283},[200,98052,528],{"emptyLinePlaceholder":20},[200,98054,98055,98057],{"class":202,"line":300},[200,98056,3758],{"class":206},[200,98058,38883],{"class":210},[200,98060,98061,98063],{"class":202,"line":310},[200,98062,413],{"class":214},[200,98064,416],{"class":210},[35,98066,39364,98067,39368,98069,39371],{},[6862,98068,39367],{},[156,98070,39189],{},[35,98072,39374],{},[51,98074,39378],{"id":39377},[35,98076,39381,98077,39384],{},[6862,98078,38666],{},[35,98080,39387,98081,2186],{},[156,98082,39390],{},[192,98084,98085],{"className":1380,"code":39393,"language":1383,"meta":13,"style":13},[156,98086,98087,98151,98169,98183,98187,98209,98215,98219,98233,98243,98261,98283,98289,98293,98297,98319,98325,98329,98351,98357,98361,98371,98389,98411,98417],{"__ignoreMap":13},[200,98088,98089,98091,98093,98095,98097,98099,98101,98103,98105,98107,98109,98111,98113,98115,98117,98119,98121,98123,98125,98127,98129,98131,98133,98135,98137,98139,98141,98143,98145,98147,98149],{"class":202,"line":203},[200,98090,1390],{"class":206},[200,98092,380],{"class":379},[200,98094,5002],{"class":379},[200,98096,39406],{"class":223},[200,98098,227],{"class":214},[200,98100,25426],{"class":386},[200,98102,239],{"class":214},[200,98104,39087],{"class":1364},[200,98106,549],{"class":214},[200,98108,38773],{"class":386},[200,98110,239],{"class":214},[200,98112,39423],{"class":1364},[200,98114,549],{"class":214},[200,98116,39092],{"class":386},[200,98118,239],{"class":214},[200,98120,318],{"class":214},[200,98122,39099],{"class":235},[200,98124,39102],{"class":214},[200,98126,20155],{"class":1364},[200,98128,549],{"class":214},[200,98130,39109],{"class":235},[200,98132,39102],{"class":214},[200,98134,22752],{"class":1364},[200,98136,980],{"class":214},[200,98138,509],{"class":214},[200,98140,39120],{"class":214},[200,98142,5011],{"class":1364},[200,98144,599],{"class":214},[200,98146,39127],{"class":1364},[200,98148,674],{"class":214},[200,98150,280],{"class":214},[200,98152,98153,98155,98157,98159,98161,98163,98165,98167],{"class":202,"line":14},[200,98154,3738],{"class":379},[200,98156,39138],{"class":210},[200,98158,509],{"class":214},[200,98160,452],{"class":206},[200,98162,39474],{"class":223},[200,98164,227],{"class":235},[200,98166,25426],{"class":210},[200,98168,416],{"class":235},[200,98170,98171,98173,98175,98177,98179,98181],{"class":202,"line":255},[200,98172,3738],{"class":379},[200,98174,318],{"class":214},[200,98176,34533],{"class":210},[200,98178,980],{"class":214},[200,98180,509],{"class":214},[200,98182,39165],{"class":210},[200,98184,98185],{"class":202,"line":272},[200,98186,528],{"emptyLinePlaceholder":20},[200,98188,98189,98191,98193,98195,98197,98199,98201,98203,98205,98207],{"class":202,"line":283},[200,98190,20098],{"class":206},[200,98192,1094],{"class":235},[200,98194,39507],{"class":210},[200,98196,215],{"class":214},[200,98198,39512],{"class":210},[200,98200,3298],{"class":214},[200,98202,242],{"class":214},[200,98204,39519],{"class":245},[200,98206,249],{"class":214},[200,98208,416],{"class":235},[200,98210,98211,98213],{"class":202,"line":300},[200,98212,3311],{"class":206},[200,98214,39165],{"class":210},[200,98216,98217],{"class":202,"line":310},[200,98218,528],{"emptyLinePlaceholder":20},[200,98220,98221,98223,98225,98227,98229,98231],{"class":202,"line":336},[200,98222,20098],{"class":206},[200,98224,1094],{"class":235},[200,98226,11852],{"class":214},[200,98228,39544],{"class":210},[200,98230,5282],{"class":235},[200,98232,230],{"class":214},[200,98234,98235,98237,98239,98241],{"class":202,"line":342},[200,98236,39200],{"class":206},[200,98238,39203],{"class":223},[200,98240,227],{"class":235},[200,98242,230],{"class":214},[200,98244,98245,98247,98249,98251,98253,98255,98257,98259],{"class":202,"line":365},[200,98246,39212],{"class":235},[200,98248,239],{"class":214},[200,98250,39092],{"class":210},[200,98252,215],{"class":214},[200,98254,39221],{"class":210},[200,98256,15013],{"class":214},[200,98258,39575],{"class":2060},[200,98260,252],{"class":214},[200,98262,98263,98265,98267,98269,98271,98273,98275,98277,98279,98281],{"class":202,"line":371},[200,98264,39233],{"class":235},[200,98266,239],{"class":214},[200,98268,39092],{"class":210},[200,98270,215],{"class":214},[200,98272,29303],{"class":210},[200,98274,15013],{"class":214},[200,98276,242],{"class":214},[200,98278,39248],{"class":245},[200,98280,249],{"class":214},[200,98282,252],{"class":214},[200,98284,98285,98287],{"class":202,"line":398},[200,98286,2111],{"class":214},[200,98288,416],{"class":235},[200,98290,98291],{"class":202,"line":405},[200,98292,4505],{"class":214},[200,98294,98295],{"class":202,"line":410},[200,98296,528],{"emptyLinePlaceholder":20},[200,98298,98299,98301,98303,98305,98307,98309,98311,98313,98315,98317],{"class":202,"line":860},[200,98300,20098],{"class":206},[200,98302,1094],{"class":235},[200,98304,39507],{"class":210},[200,98306,215],{"class":214},[200,98308,39626],{"class":210},[200,98310,3298],{"class":214},[200,98312,34533],{"class":210},[200,98314,215],{"class":214},[200,98316,3295],{"class":210},[200,98318,416],{"class":235},[200,98320,98321,98323],{"class":202,"line":889},[200,98322,3311],{"class":206},[200,98324,39165],{"class":210},[200,98326,98327],{"class":202,"line":899},[200,98328,528],{"emptyLinePlaceholder":20},[200,98330,98331,98333,98335,98337,98339,98341,98343,98345,98347,98349],{"class":202,"line":904},[200,98332,20098],{"class":206},[200,98334,1094],{"class":235},[200,98336,26847],{"class":210},[200,98338,215],{"class":214},[200,98340,39182],{"class":210},[200,98342,3298],{"class":214},[200,98344,242],{"class":214},[200,98346,39189],{"class":245},[200,98348,249],{"class":214},[200,98350,416],{"class":235},[200,98352,98353,98355],{"class":202,"line":930},[200,98354,3311],{"class":206},[200,98356,39165],{"class":210},[200,98358,98359],{"class":202,"line":3655},[200,98360,528],{"emptyLinePlaceholder":20},[200,98362,98363,98365,98367,98369],{"class":202,"line":3660},[200,98364,20287],{"class":206},[200,98366,39203],{"class":223},[200,98368,227],{"class":235},[200,98370,230],{"class":214},[200,98372,98373,98375,98377,98379,98381,98383,98385,98387],{"class":202,"line":4035},[200,98374,39693],{"class":235},[200,98376,239],{"class":214},[200,98378,39092],{"class":210},[200,98380,215],{"class":214},[200,98382,39221],{"class":210},[200,98384,15013],{"class":214},[200,98386,39226],{"class":2060},[200,98388,252],{"class":214},[200,98390,98391,98393,98395,98397,98399,98401,98403,98405,98407,98409],{"class":202,"line":4490},[200,98392,39712],{"class":235},[200,98394,239],{"class":214},[200,98396,39092],{"class":210},[200,98398,215],{"class":214},[200,98400,29303],{"class":210},[200,98402,15013],{"class":214},[200,98404,242],{"class":214},[200,98406,39727],{"class":245},[200,98408,249],{"class":214},[200,98410,252],{"class":214},[200,98412,98413,98415],{"class":202,"line":4496},[200,98414,5095],{"class":214},[200,98416,416],{"class":235},[200,98418,98419],{"class":202,"line":4502},[200,98420,1129],{"class":214},[35,98422,39744],{},[35,98424,39747,98425,39751,98427,39755,98429,1655,98431,215],{},[6862,98426,39750],{},[39,98428,39754],{"href":38581},[156,98430,38959],{},[156,98432,39760],{},[35,98434,39763],{},[1593,98436,98437,98441,98445],{},[71,98438,98439,39771],{},[6862,98440,39770],{},[71,98442,98443,39771],{},[6862,98444,39776],{},[71,98446,98447,39771],{},[6862,98448,39781],{},[35,98450,39784,98451,39787],{},[156,98452,39781],{},[35,98454,39790,98455,215],{},[6862,98456,39793],{},[35,98458,39796,98459,215],{},[39,98460,39800],{"href":38599,"rel":98461},[77],[51,98463,39804],{"id":39803},[35,98465,39807,98466,39810,98468,215],{},[6862,98467,38666],{},[39,98469,98471],{"href":38519,"rel":98470},[77],[6862,98472,38035],{},[35,98474,39818,98475],{},[1599,98476,39822],{"repo":39821},[35,98478,39825,98479,215],{},[39,98480,38242],{"href":39828,"rel":98481},[77],[1929,98483,39833],{"id":39832},[35,98485,39836,98486,39839],{},[6862,98487,39793],{},[35,98489,39842],{},[35,98491,39845,98492,39849],{},[6862,98493,39848],{},[35,98495,39852,98496,13741,98498,39857,98500,39860,98502,39863,98504,215],{},[156,98497,39770],{},[156,98499,39776],{},[156,98501,39781],{},[156,98503,39781],{},[156,98505,39061],{},[35,98507,39868],{},[1929,98509,39872],{"id":39871},[35,98511,39875,98512,39879,98514,215],{},[6862,98513,39878],{},[6862,98515,39882],{},[35,98517,39885,98518,39888],{},[6862,98519,39848],{},[192,98521,98522],{"className":1380,"code":39891,"language":1383,"meta":13,"style":13},[156,98523,98524,98560,98578],{"__ignoreMap":13},[200,98525,98526,98528,98530,98532,98534,98536,98538,98540,98542,98544,98546,98548,98550,98552,98554,98556,98558],{"class":202,"line":203},[200,98527,1390],{"class":206},[200,98529,14305],{"class":379},[200,98531,39902],{"class":210},[200,98533,449],{"class":214},[200,98535,39907],{"class":223},[200,98537,227],{"class":210},[200,98539,227],{"class":214},[200,98541,26847],{"class":386},[200,98543,239],{"class":214},[200,98545,34635],{"class":1364},[200,98547,549],{"class":214},[200,98549,39922],{"class":386},[200,98551,239],{"class":214},[200,98553,39927],{"class":1364},[200,98555,2104],{"class":214},[200,98557,393],{"class":379},[200,98559,280],{"class":214},[200,98561,98562,98564,98566,98568,98570,98572,98574,98576],{"class":202,"line":14},[200,98563,3758],{"class":206},[200,98565,34533],{"class":210},[200,98567,215],{"class":214},[200,98569,3295],{"class":210},[200,98571,3298],{"class":214},[200,98573,39922],{"class":210},[200,98575,215],{"class":214},[200,98577,39952],{"class":210},[200,98579,98580,98582],{"class":202,"line":255},[200,98581,413],{"class":214},[200,98583,416],{"class":210},[35,98585,39961],{},[192,98587,98588],{"className":1380,"code":39964,"language":1383,"meta":13,"style":13},[156,98589,98590,98622,98628],{"__ignoreMap":13},[200,98591,98592,98594,98596,98598,98600,98602,98604,98606,98608,98610,98612,98614,98616,98618,98620],{"class":202,"line":203},[200,98593,1390],{"class":206},[200,98595,14305],{"class":379},[200,98597,39975],{"class":210},[200,98599,449],{"class":214},[200,98601,39907],{"class":223},[200,98603,227],{"class":210},[200,98605,227],{"class":214},[200,98607,26847],{"class":386},[200,98609,239],{"class":214},[200,98611,34635],{"class":1364},[200,98613,39992],{"class":214},[200,98615,12432],{"class":1364},[200,98617,2104],{"class":214},[200,98619,393],{"class":379},[200,98621,280],{"class":214},[200,98623,98624,98626],{"class":202,"line":14},[200,98625,3758],{"class":206},[200,98627,22236],{"class":2438},[200,98629,98630,98632],{"class":202,"line":255},[200,98631,413],{"class":214},[200,98633,416],{"class":210},[35,98635,40015],{},[35,98637,15736,98638,40021],{},[156,98639,40020],{},[35,98641,40024,98642,40027],{},[156,98643,39781],{},[192,98645,98646],{"className":1380,"code":40030,"language":1383,"meta":13,"style":13},[156,98647,98648,98670,98686,98690,98716,98720,98726],{"__ignoreMap":13},[200,98649,98650,98652,98654,98656,98658,98660,98662,98664,98666,98668],{"class":202,"line":203},[200,98651,1390],{"class":206},[200,98653,1393],{"class":206},[200,98655,38723],{"class":223},[200,98657,227],{"class":210},[200,98659,3726],{"class":379},[200,98661,1094],{"class":214},[200,98663,25426],{"class":386},[200,98665,2104],{"class":214},[200,98667,393],{"class":379},[200,98669,280],{"class":214},[200,98671,98672,98674,98676,98678,98680,98682,98684],{"class":202,"line":14},[200,98673,6177],{"class":206},[200,98675,40061],{"class":223},[200,98677,227],{"class":235},[200,98679,25426],{"class":210},[200,98681,549],{"class":214},[200,98683,40070],{"class":210},[200,98685,416],{"class":235},[200,98687,98688],{"class":202,"line":255},[200,98689,528],{"emptyLinePlaceholder":20},[200,98691,98692,98694,98696,98698,98700,98702,98704,98706,98708,98710,98712,98714],{"class":202,"line":272},[200,98693,3738],{"class":379},[200,98695,40083],{"class":210},[200,98697,509],{"class":214},[200,98699,452],{"class":206},[200,98701,38780],{"class":210},[200,98703,215],{"class":214},[200,98705,3524],{"class":210},[200,98707,215],{"class":214},[200,98709,40098],{"class":210},[200,98711,215],{"class":214},[200,98713,40103],{"class":223},[200,98715,466],{"class":235},[200,98717,98718],{"class":202,"line":283},[200,98719,528],{"emptyLinePlaceholder":20},[200,98721,98722,98724],{"class":202,"line":300},[200,98723,3758],{"class":206},[200,98725,40116],{"class":210},[200,98727,98728,98730],{"class":202,"line":310},[200,98729,413],{"class":214},[200,98731,416],{"class":210},[35,98733,15736,98734,40127,98736,40130,98738,13741,98740,215],{},[156,98735,39781],{},[156,98737,39061],{},[156,98739,40133],{},[156,98741,40136],{},[192,98743,98744],{"className":1380,"code":40139,"language":1383,"meta":13,"style":13},[156,98745,98746,98768,98792,98798,98802,98828,98832,98838],{"__ignoreMap":13},[200,98747,98748,98750,98752,98754,98756,98758,98760,98762,98764,98766],{"class":202,"line":203},[200,98749,1390],{"class":206},[200,98751,1393],{"class":206},[200,98753,38723],{"class":223},[200,98755,227],{"class":210},[200,98757,3726],{"class":379},[200,98759,1094],{"class":214},[200,98761,25426],{"class":386},[200,98763,2104],{"class":214},[200,98765,393],{"class":379},[200,98767,280],{"class":214},[200,98769,98770,98772,98774,98776,98778,98780,98782,98784,98786,98788,98790],{"class":202,"line":14},[200,98771,20098],{"class":206},[200,98773,1094],{"class":235},[200,98775,207],{"class":206},[200,98777,40174],{"class":223},[200,98779,227],{"class":235},[200,98781,25426],{"class":210},[200,98783,549],{"class":214},[200,98785,40070],{"class":210},[200,98787,549],{"class":214},[200,98789,39922],{"class":210},[200,98791,584],{"class":235},[200,98793,98794,98796],{"class":202,"line":255},[200,98795,3311],{"class":206},[200,98797,6433],{"class":235},[200,98799,98800],{"class":202,"line":272},[200,98801,528],{"emptyLinePlaceholder":20},[200,98803,98804,98806,98808,98810,98812,98814,98816,98818,98820,98822,98824,98826],{"class":202,"line":283},[200,98805,3738],{"class":379},[200,98807,40083],{"class":210},[200,98809,509],{"class":214},[200,98811,452],{"class":206},[200,98813,38780],{"class":210},[200,98815,215],{"class":214},[200,98817,3524],{"class":210},[200,98819,215],{"class":214},[200,98821,40098],{"class":210},[200,98823,215],{"class":214},[200,98825,40103],{"class":223},[200,98827,466],{"class":235},[200,98829,98830],{"class":202,"line":300},[200,98831,528],{"emptyLinePlaceholder":20},[200,98833,98834,98836],{"class":202,"line":310},[200,98835,3758],{"class":206},[200,98837,40116],{"class":210},[200,98839,98840,98842],{"class":202,"line":336},[200,98841,413],{"class":214},[200,98843,416],{"class":210},[35,98845,40243],{},[35,98847,40246,98848,40250],{},[156,98849,40249],{},[35,98851,40253,98852,1655,98854,215],{},[156,98853,40256],{},[156,98855,40259],{},[192,98857,98858],{"className":1380,"code":40262,"language":1383,"meta":13,"style":13},[156,98859,98860,98896,98920,98930,98940,98954,98960,98964,98968,98976],{"__ignoreMap":13},[200,98861,98862,98864,98866,98868,98870,98872,98874,98876,98878,98880,98882,98884,98886,98888,98890,98892,98894],{"class":202,"line":203},[200,98863,1390],{"class":206},[200,98865,14305],{"class":379},[200,98867,40273],{"class":210},[200,98869,449],{"class":214},[200,98871,39907],{"class":223},[200,98873,227],{"class":210},[200,98875,227],{"class":214},[200,98877,26847],{"class":386},[200,98879,239],{"class":214},[200,98881,34635],{"class":1364},[200,98883,549],{"class":214},[200,98885,39922],{"class":386},[200,98887,239],{"class":214},[200,98889,39927],{"class":1364},[200,98891,2104],{"class":214},[200,98893,393],{"class":379},[200,98895,280],{"class":214},[200,98897,98898,98900,98902,98904,98906,98908,98910,98912,98914,98916,98918],{"class":202,"line":14},[200,98899,20098],{"class":206},[200,98901,1094],{"class":235},[200,98903,40310],{"class":210},[200,98905,215],{"class":214},[200,98907,39512],{"class":210},[200,98909,3298],{"class":214},[200,98911,242],{"class":214},[200,98913,40321],{"class":245},[200,98915,249],{"class":214},[200,98917,5282],{"class":235},[200,98919,230],{"class":214},[200,98921,98922,98924,98926,98928],{"class":202,"line":255},[200,98923,3311],{"class":206},[200,98925,40334],{"class":223},[200,98927,227],{"class":235},[200,98929,230],{"class":214},[200,98931,98932,98934,98936,98938],{"class":202,"line":272},[200,98933,39212],{"class":235},[200,98935,239],{"class":214},[200,98937,40347],{"class":2060},[200,98939,252],{"class":214},[200,98941,98942,98944,98946,98948,98950,98952],{"class":202,"line":283},[200,98943,39233],{"class":235},[200,98945,239],{"class":214},[200,98947,242],{"class":214},[200,98949,40360],{"class":245},[200,98951,249],{"class":214},[200,98953,252],{"class":214},[200,98955,98956,98958],{"class":202,"line":300},[200,98957,2111],{"class":214},[200,98959,416],{"class":235},[200,98961,98962],{"class":202,"line":310},[200,98963,4505],{"class":214},[200,98965,98966],{"class":202,"line":336},[200,98967,528],{"emptyLinePlaceholder":20},[200,98969,98970,98972,98974],{"class":202,"line":342},[200,98971,3758],{"class":206},[200,98973,40385],{"class":223},[200,98975,466],{"class":235},[200,98977,98978,98980],{"class":202,"line":365},[200,98979,413],{"class":214},[200,98981,416],{"class":210},[35,98983,40396,98984,40399,98986,40402,98988,40405],{},[156,98985,39781],{},[156,98987,40249],{},[156,98989,39061],{},[35,98991,40408,98992,3092,98994,1655,98996,40416,98998,215],{},[156,98993,40133],{},[156,98995,40136],{},[156,98997,40415],{},[6862,98999,39848],{},[35,99001,40421,99002,1655,99004,40428,99006,215],{},[156,99003,40424],{},[156,99005,40427],{},[6862,99007,39848],{},[192,99009,99010],{"className":21457,"code":40433,"language":12075,"meta":13,"style":13},[156,99011,99012,99020,99056,99072,99080],{"__ignoreMap":13},[200,99013,99014,99016,99018],{"class":202,"line":203},[200,99015,599],{"class":214},[200,99017,21468],{"class":235},[200,99019,647],{"class":214},[200,99021,99022,99024,99026,99028,99030,99032,99034,99036,99038,99040,99042,99044,99046,99048,99050,99052,99054],{"class":202,"line":14},[200,99023,656],{"class":214},[200,99025,40424],{"class":235},[200,99027,2186],{"class":214},[200,99029,40454],{"class":379},[200,99031,449],{"class":214},[200,99033,610],{"class":214},[200,99035,40461],{"class":210},[200,99037,610],{"class":214},[200,99039,2186],{"class":214},[200,99041,28348],{"class":379},[200,99043,449],{"class":214},[200,99045,610],{"class":214},[200,99047,9872],{"class":214},[200,99049,40310],{"class":210},[200,99051,360],{"class":214},[200,99053,610],{"class":214},[200,99055,647],{"class":214},[200,99057,99058,99060,99062,99064,99066,99068,99070],{"class":202,"line":255},[200,99059,803],{"class":214},[200,99061,909],{"class":235},[200,99063,674],{"class":214},[200,99065,40492],{"class":210},[200,99067,680],{"class":214},[200,99069,909],{"class":235},[200,99071,647],{"class":214},[200,99073,99074,99076,99078],{"class":202,"line":272},[200,99075,892],{"class":214},[200,99077,40424],{"class":235},[200,99079,647],{"class":214},[200,99081,99082,99084,99086],{"class":202,"line":283},[200,99083,680],{"class":214},[200,99085,21468],{"class":235},[200,99087,647],{"class":214},[35,99089,40517,99090],{},[1599,99091,215],{"repo":39821},[1929,99093,40522],{"id":28407},[35,99095,40525,99096,40531],{},[39,99097,40530],{"href":40528,"rel":99098},[77],[35,99100,40534],{},[35,99102,40537],{},[35,99104,40540,99105,40543,99107,40547],{},[6862,99106,38666],{},[6862,99108,40546],{},[35,99110,40550],{},[1170,99112,40553],{},{"title":13,"searchDepth":14,"depth":14,"links":99114},[99115,99116,99117,99118,99119],{"id":29077,"depth":14,"text":29078},{"id":38927,"depth":14,"text":38928},{"id":39047,"depth":14,"text":39048},{"id":39377,"depth":14,"text":39378},{"id":39803,"depth":14,"text":39804,"children":99120},[99121,99122,99123],{"id":39832,"depth":255,"text":39833},{"id":39871,"depth":255,"text":39872},{"id":28407,"depth":255,"text":40522},{},{"title":38650,"description":40566},{"loc":40568},{"id":40575,"title":40576,"body":99128,"contentId":30599,"date":45809,"description":45810,"extension":18,"meta":104091,"navigation":20,"path":45812,"rawbody":45813,"robots":23,"schemaOrg":23,"seo":104092,"sitemap":104093,"stem":45816,"__hash__":45817},{"type":7,"value":99129,"toc":104085},[99130,99137,99141,104083],[35,99131,40581,99132,40585,99135,40589],{},[39,99133,36579],{"href":37901,"rel":99134},[77],[6862,99136,40588],{},[35,99138,99139],{},[1216,99140,40594],{},[7918,99142,99143,99150,99154,99156,99166,99172,99174,99176,99292,99294,99372,99376,99380,99382,99391,99529,99533],{"id":30589},[35,99144,99145],{},[1216,99146,40601,99147,40605],{},[39,99148,36579],{"href":37901,"rel":99149},[77],[35,99151,40608,99152],{},[1599,99153,215],{"repo":40611},[51,99155,18647],{"id":18646},[35,99157,40616,99158,40620,99160,40624,99162,1655,99164,40631],{},[6862,99159,40619],{},[6862,99161,40623],{},[6862,99163,40627],{},[6862,99165,40630],{},[35,99167,40634,99168,40638,99170,40642],{},[1216,99169,40637],{},[6862,99171,40641],{},[51,99173,40646],{"id":40645},[35,99175,40649],{},[192,99177,99178],{"className":21457,"code":40652,"language":12075,"meta":13,"style":13},[156,99179,99180,99188,99216,99246,99264,99268,99276,99284],{"__ignoreMap":13},[200,99181,99182,99184,99186],{"class":202,"line":203},[200,99183,599],{"class":214},[200,99185,21468],{"class":235},[200,99187,647],{"class":214},[200,99189,99190,99192,99194,99196,99198,99200,99202,99204,99206,99208,99210,99212,99214],{"class":202,"line":14},[200,99191,656],{"class":214},[200,99193,602],{"class":235},[200,99195,6589],{"class":379},[200,99197,449],{"class":214},[200,99199,610],{"class":214},[200,99201,3793],{"class":245},[200,99203,610],{"class":214},[200,99205,40681],{"class":379},[200,99207,449],{"class":214},[200,99209,610],{"class":214},[200,99211,40688],{"class":245},[200,99213,610],{"class":214},[200,99215,647],{"class":214},[200,99217,99218,99220,99222,99224,99226,99228,99230,99232,99234,99236,99238,99240,99242,99244],{"class":202,"line":255},[200,99219,803],{"class":214},[200,99221,355],{"class":235},[200,99223,321],{"class":379},[200,99225,449],{"class":214},[200,99227,610],{"class":214},[200,99229,697],{"class":245},[200,99231,610],{"class":214},[200,99233,40711],{"class":379},[200,99235,449],{"class":214},[200,99237,610],{"class":214},[200,99239,40718],{"class":245},[200,99241,610],{"class":214},[200,99243,40723],{"class":379},[200,99245,647],{"class":214},[200,99247,99248,99250,99252,99254,99256,99258,99260,99262],{"class":202,"line":272},[200,99249,803],{"class":214},[200,99251,909],{"class":235},[200,99253,321],{"class":379},[200,99255,449],{"class":214},[200,99257,610],{"class":214},[200,99259,916],{"class":245},[200,99261,610],{"class":214},[200,99263,647],{"class":214},[200,99265,99266],{"class":202,"line":283},[200,99267,40748],{"class":210},[200,99269,99270,99272,99274],{"class":202,"line":300},[200,99271,15078],{"class":214},[200,99273,909],{"class":235},[200,99275,647],{"class":214},[200,99277,99278,99280,99282],{"class":202,"line":310},[200,99279,892],{"class":214},[200,99281,602],{"class":235},[200,99283,647],{"class":214},[200,99285,99286,99288,99290],{"class":202,"line":336},[200,99287,680],{"class":214},[200,99289,21468],{"class":235},[200,99291,647],{"class":214},[35,99293,40775],{},[192,99295,99296],{"className":1380,"code":40778,"language":1383,"meta":13,"style":13},[156,99297,99298,99320,99338,99342,99346,99350,99366],{"__ignoreMap":13},[200,99299,99300,99302,99304,99306,99308,99310,99312,99314,99316,99318],{"class":202,"line":203},[200,99301,1390],{"class":206},[200,99303,1393],{"class":206},[200,99305,38723],{"class":223},[200,99307,227],{"class":210},[200,99309,3726],{"class":379},[200,99311,1094],{"class":214},[200,99313,25426],{"class":386},[200,99315,2104],{"class":214},[200,99317,393],{"class":379},[200,99319,280],{"class":214},[200,99321,99322,99324,99326,99328,99330,99332,99334,99336],{"class":202,"line":14},[200,99323,3738],{"class":379},[200,99325,40809],{"class":210},[200,99327,509],{"class":214},[200,99329,452],{"class":206},[200,99331,40816],{"class":223},[200,99333,227],{"class":235},[200,99335,25426],{"class":210},[200,99337,416],{"class":235},[200,99339,99340],{"class":202,"line":255},[200,99341,528],{"emptyLinePlaceholder":20},[200,99343,99344],{"class":202,"line":272},[200,99345,40831],{"class":401},[200,99347,99348],{"class":202,"line":283},[200,99349,528],{"emptyLinePlaceholder":20},[200,99351,99352,99354,99356,99358,99360,99362,99364],{"class":202,"line":300},[200,99353,3758],{"class":206},[200,99355,40842],{"class":223},[200,99357,227],{"class":235},[200,99359,25426],{"class":210},[200,99361,549],{"class":214},[200,99363,40851],{"class":2060},[200,99365,416],{"class":235},[200,99367,99368,99370],{"class":202,"line":310},[200,99369,413],{"class":214},[200,99371,416],{"class":210},[35,99373,40862,99374],{},[1216,99375,40865],{},[35,99377,99378],{},[1216,99379,40870],{},[1929,99381,40874],{"id":40873},[35,99383,40877,99384,106,99387,40886,99389,215],{},[39,99385,40882],{"href":40880,"rel":99386},[77],[1216,99388,40885],{},[156,99390,40889],{},[192,99392,99393],{"className":1380,"code":40892,"language":1383,"meta":13,"style":13},[156,99394,99395,99413,99417,99439,99467,99483,99495,99499,99503,99507,99523],{"__ignoreMap":13},[200,99396,99397,99399,99401,99403,99405,99407,99409,99411],{"class":202,"line":203},[200,99398,972],{"class":206},[200,99400,318],{"class":214},[200,99402,3180],{"class":210},[200,99404,980],{"class":214},[200,99406,983],{"class":206},[200,99408,242],{"class":214},[200,99410,19813],{"class":245},[200,99412,991],{"class":214},[200,99414,99415],{"class":202,"line":14},[200,99416,528],{"emptyLinePlaceholder":20},[200,99418,99419,99421,99423,99425,99427,99429,99431,99433,99435,99437],{"class":202,"line":255},[200,99420,1390],{"class":206},[200,99422,1393],{"class":206},[200,99424,38723],{"class":223},[200,99426,227],{"class":210},[200,99428,3726],{"class":379},[200,99430,1094],{"class":214},[200,99432,25426],{"class":386},[200,99434,2104],{"class":214},[200,99436,393],{"class":379},[200,99438,280],{"class":214},[200,99440,99441,99443,99445,99447,99449,99451,99453,99455,99457,99459,99461,99463,99465],{"class":202,"line":272},[200,99442,3738],{"class":379},[200,99444,40809],{"class":210},[200,99446,509],{"class":214},[200,99448,452],{"class":206},[200,99450,40951],{"class":223},[200,99452,227],{"class":235},[200,99454,25426],{"class":210},[200,99456,549],{"class":214},[200,99458,3180],{"class":210},[200,99460,215],{"class":214},[200,99462,293],{"class":223},[200,99464,227],{"class":235},[200,99466,230],{"class":214},[200,99468,99469,99471,99473,99475,99477,99479,99481],{"class":202,"line":283},[200,99470,11812],{"class":235},[200,99472,239],{"class":214},[200,99474,3180],{"class":210},[200,99476,215],{"class":214},[200,99478,328],{"class":223},[200,99480,1549],{"class":235},[200,99482,252],{"class":214},[200,99484,99485,99487,99489,99491,99493],{"class":202,"line":300},[200,99486,5095],{"class":214},[200,99488,2104],{"class":235},[200,99490,215],{"class":214},[200,99492,40994],{"class":210},[200,99494,416],{"class":235},[200,99496,99497],{"class":202,"line":310},[200,99498,528],{"emptyLinePlaceholder":20},[200,99500,99501],{"class":202,"line":336},[200,99502,40831],{"class":401},[200,99504,99505],{"class":202,"line":342},[200,99506,528],{"emptyLinePlaceholder":20},[200,99508,99509,99511,99513,99515,99517,99519,99521],{"class":202,"line":365},[200,99510,3758],{"class":206},[200,99512,40842],{"class":223},[200,99514,227],{"class":235},[200,99516,25426],{"class":210},[200,99518,549],{"class":214},[200,99520,40851],{"class":2060},[200,99522,416],{"class":235},[200,99524,99525,99527],{"class":202,"line":371},[200,99526,413],{"class":214},[200,99528,416],{"class":210},[35,99530,41033,99531,41036],{},[156,99532,40889],{},[7151,99534,99535,99537,99541,99791,99795,100067,100073,100075,100077,100547,100560,100562,100564,100566,100572,100656,100662,100664,100666,100756,100758,100866,100868,100870,100874,100878,100880,100882,101008,101014,101024,101496,101498,101502,101768,101770,102182,102186,102188,102278,102280,102286,102460,102462,102464,102468,102470,102646,102648,102946,102948,102952,103298,103302,103562,103568,103910,103914,104068,104070,104079,104081],{"icon":41039,"text":41040,"to":41041},[35,99536,41044],{},[35,99538,41047,99539,41051],{},[156,99540,41050],{},[192,99542,99543],{"className":1380,"code":41054,"language":1383,"meta":13,"style":13},[156,99544,99545,99563,99567,99589,99617,99633,99645,99649,99667,99677,99687,99701,99729,99735,99739,99743,99757,99761,99765,99769,99785],{"__ignoreMap":13},[200,99546,99547,99549,99551,99553,99555,99557,99559,99561],{"class":202,"line":203},[200,99548,972],{"class":206},[200,99550,318],{"class":214},[200,99552,3180],{"class":210},[200,99554,980],{"class":214},[200,99556,983],{"class":206},[200,99558,242],{"class":214},[200,99560,19813],{"class":245},[200,99562,991],{"class":214},[200,99564,99565],{"class":202,"line":14},[200,99566,528],{"emptyLinePlaceholder":20},[200,99568,99569,99571,99573,99575,99577,99579,99581,99583,99585,99587],{"class":202,"line":255},[200,99570,1390],{"class":206},[200,99572,1393],{"class":206},[200,99574,38723],{"class":223},[200,99576,227],{"class":210},[200,99578,3726],{"class":379},[200,99580,1094],{"class":214},[200,99582,25426],{"class":386},[200,99584,2104],{"class":214},[200,99586,393],{"class":379},[200,99588,280],{"class":214},[200,99590,99591,99593,99595,99597,99599,99601,99603,99605,99607,99609,99611,99613,99615],{"class":202,"line":272},[200,99592,3738],{"class":379},[200,99594,3561],{"class":210},[200,99596,509],{"class":214},[200,99598,452],{"class":206},[200,99600,40951],{"class":223},[200,99602,227],{"class":235},[200,99604,25426],{"class":210},[200,99606,549],{"class":214},[200,99608,3180],{"class":210},[200,99610,215],{"class":214},[200,99612,293],{"class":223},[200,99614,227],{"class":235},[200,99616,230],{"class":214},[200,99618,99619,99621,99623,99625,99627,99629,99631],{"class":202,"line":283},[200,99620,11812],{"class":235},[200,99622,239],{"class":214},[200,99624,3180],{"class":210},[200,99626,215],{"class":214},[200,99628,328],{"class":223},[200,99630,1549],{"class":235},[200,99632,252],{"class":214},[200,99634,99635,99637,99639,99641,99643],{"class":202,"line":300},[200,99636,5095],{"class":214},[200,99638,2104],{"class":235},[200,99640,215],{"class":214},[200,99642,41050],{"class":210},[200,99644,416],{"class":235},[200,99646,99647],{"class":202,"line":310},[200,99648,528],{"emptyLinePlaceholder":20},[200,99650,99651,99653,99655,99657,99659,99661,99663,99665],{"class":202,"line":336},[200,99652,20098],{"class":206},[200,99654,1094],{"class":235},[200,99656,11852],{"class":214},[200,99658,3546],{"class":210},[200,99660,215],{"class":214},[200,99662,41175],{"class":210},[200,99664,5282],{"class":235},[200,99666,230],{"class":214},[200,99668,99669,99671,99673,99675],{"class":202,"line":342},[200,99670,39200],{"class":206},[200,99672,39203],{"class":223},[200,99674,227],{"class":235},[200,99676,230],{"class":214},[200,99678,99679,99681,99683,99685],{"class":202,"line":365},[200,99680,38845],{"class":235},[200,99682,239],{"class":214},[200,99684,41198],{"class":2060},[200,99686,252],{"class":214},[200,99688,99689,99691,99693,99695,99697,99699],{"class":202,"line":371},[200,99690,39233],{"class":235},[200,99692,239],{"class":214},[200,99694,242],{"class":214},[200,99696,41211],{"class":245},[200,99698,249],{"class":214},[200,99700,252],{"class":214},[200,99702,99703,99705,99707,99709,99711,99713,99715,99717,99719,99721,99723,99725,99727],{"class":202,"line":398},[200,99704,41220],{"class":235},[200,99706,239],{"class":214},[200,99708,3561],{"class":210},[200,99710,215],{"class":214},[200,99712,19275],{"class":210},[200,99714,215],{"class":214},[200,99716,26828],{"class":210},[200,99718,9872],{"class":235},[200,99720,3948],{"class":2060},[200,99722,360],{"class":235},[200,99724,215],{"class":214},[200,99726,29303],{"class":210},[200,99728,252],{"class":214},[200,99730,99731,99733],{"class":202,"line":405},[200,99732,2111],{"class":214},[200,99734,416],{"class":235},[200,99736,99737],{"class":202,"line":410},[200,99738,4505],{"class":214},[200,99740,99741],{"class":202,"line":860},[200,99742,528],{"emptyLinePlaceholder":20},[200,99744,99745,99747,99749,99751,99753,99755],{"class":202,"line":889},[200,99746,3738],{"class":379},[200,99748,40809],{"class":210},[200,99750,509],{"class":214},[200,99752,3561],{"class":210},[200,99754,215],{"class":214},[200,99756,3765],{"class":210},[200,99758,99759],{"class":202,"line":899},[200,99760,528],{"emptyLinePlaceholder":20},[200,99762,99763],{"class":202,"line":904},[200,99764,40831],{"class":401},[200,99766,99767],{"class":202,"line":930},[200,99768,528],{"emptyLinePlaceholder":20},[200,99770,99771,99773,99775,99777,99779,99781,99783],{"class":202,"line":3655},[200,99772,3758],{"class":206},[200,99774,40842],{"class":223},[200,99776,227],{"class":235},[200,99778,25426],{"class":210},[200,99780,549],{"class":214},[200,99782,40851],{"class":2060},[200,99784,416],{"class":235},[200,99786,99787,99789],{"class":202,"line":3660},[200,99788,413],{"class":214},[200,99790,416],{"class":210},[35,99792,41309,99793,41313],{},[156,99794,41312],{},[192,99796,99797],{"className":1380,"code":41316,"language":1383,"meta":13,"style":13},[156,99798,99799,99819,99837,99841,99863,99895,99911,99943,99959,99969,99979,99993,100017,100023,100027,100033,100037,100041,100045,100061],{"__ignoreMap":13},[200,99800,99801,99803,99805,99807,99809,99811,99813,99815,99817],{"class":202,"line":203},[200,99802,972],{"class":206},[200,99804,321],{"class":206},[200,99806,318],{"class":214},[200,99808,41329],{"class":210},[200,99810,980],{"class":214},[200,99812,983],{"class":206},[200,99814,242],{"class":214},[200,99816,19813],{"class":245},[200,99818,991],{"class":214},[200,99820,99821,99823,99825,99827,99829,99831,99833,99835],{"class":202,"line":14},[200,99822,972],{"class":206},[200,99824,318],{"class":214},[200,99826,3180],{"class":210},[200,99828,980],{"class":214},[200,99830,983],{"class":206},[200,99832,242],{"class":214},[200,99834,19813],{"class":245},[200,99836,991],{"class":214},[200,99838,99839],{"class":202,"line":255},[200,99840,528],{"emptyLinePlaceholder":20},[200,99842,99843,99845,99847,99849,99851,99853,99855,99857,99859,99861],{"class":202,"line":272},[200,99844,1390],{"class":206},[200,99846,1393],{"class":206},[200,99848,38723],{"class":223},[200,99850,227],{"class":210},[200,99852,3726],{"class":379},[200,99854,1094],{"class":214},[200,99856,25426],{"class":386},[200,99858,2104],{"class":214},[200,99860,393],{"class":379},[200,99862,280],{"class":214},[200,99864,99865,99867,99869,99871,99873,99875,99877,99879,99881,99883,99885,99887,99889,99891,99893],{"class":202,"line":283},[200,99866,3738],{"class":379},[200,99868,40809],{"class":210},[200,99870,509],{"class":214},[200,99872,452],{"class":206},[200,99874,40951],{"class":223},[200,99876,227],{"class":235},[200,99878,25426],{"class":210},[200,99880,549],{"class":214},[200,99882,5056],{"class":386},[200,99884,393],{"class":379},[200,99886,3180],{"class":210},[200,99888,215],{"class":214},[200,99890,293],{"class":223},[200,99892,227],{"class":235},[200,99894,230],{"class":214},[200,99896,99897,99899,99901,99903,99905,99907,99909],{"class":202,"line":300},[200,99898,11812],{"class":235},[200,99900,239],{"class":214},[200,99902,3180],{"class":210},[200,99904,215],{"class":214},[200,99906,328],{"class":223},[200,99908,1549],{"class":235},[200,99910,252],{"class":214},[200,99912,99913,99915,99917,99919,99921,99923,99925,99927,99929,99931,99933,99935,99937,99939,99941],{"class":202,"line":310},[200,99914,5095],{"class":214},[200,99916,2104],{"class":235},[200,99918,215],{"class":214},[200,99920,41312],{"class":223},[200,99922,227],{"class":235},[200,99924,12736],{"class":210},[200,99926,2104],{"class":235},[200,99928,215],{"class":214},[200,99930,41452],{"class":223},[200,99932,227],{"class":235},[200,99934,227],{"class":214},[200,99936,19275],{"class":386},[200,99938,2104],{"class":214},[200,99940,393],{"class":379},[200,99942,280],{"class":214},[200,99944,99945,99947,99949,99951,99953,99955,99957],{"class":202,"line":336},[200,99946,5267],{"class":206},[200,99948,1094],{"class":235},[200,99950,19275],{"class":210},[200,99952,41475],{"class":214},[200,99954,41329],{"class":1364},[200,99956,5282],{"class":235},[200,99958,230],{"class":214},[200,99960,99961,99963,99965,99967],{"class":202,"line":342},[200,99962,20250],{"class":206},[200,99964,39203],{"class":223},[200,99966,227],{"class":235},[200,99968,230],{"class":214},[200,99970,99971,99973,99975,99977],{"class":202,"line":365},[200,99972,41496],{"class":235},[200,99974,239],{"class":214},[200,99976,41198],{"class":2060},[200,99978,252],{"class":214},[200,99980,99981,99983,99985,99987,99989,99991],{"class":202,"line":371},[200,99982,41507],{"class":235},[200,99984,239],{"class":214},[200,99986,242],{"class":214},[200,99988,41211],{"class":245},[200,99990,249],{"class":214},[200,99992,252],{"class":214},[200,99994,99995,99997,99999,100001,100003,100005,100007,100009,100011,100013,100015],{"class":202,"line":398},[200,99996,41522],{"class":235},[200,99998,239],{"class":214},[200,100000,19220],{"class":210},[200,100002,215],{"class":214},[200,100004,26828],{"class":210},[200,100006,9872],{"class":235},[200,100008,3948],{"class":2060},[200,100010,360],{"class":235},[200,100012,215],{"class":214},[200,100014,29303],{"class":210},[200,100016,252],{"class":214},[200,100018,100019,100021],{"class":202,"line":405},[200,100020,2647],{"class":214},[200,100022,416],{"class":235},[200,100024,100025],{"class":202,"line":410},[200,100026,4977],{"class":214},[200,100028,100029,100031],{"class":202,"line":860},[200,100030,5095],{"class":214},[200,100032,584],{"class":235},[200,100034,100035],{"class":202,"line":889},[200,100036,528],{"emptyLinePlaceholder":20},[200,100038,100039],{"class":202,"line":899},[200,100040,40831],{"class":401},[200,100042,100043],{"class":202,"line":904},[200,100044,528],{"emptyLinePlaceholder":20},[200,100046,100047,100049,100051,100053,100055,100057,100059],{"class":202,"line":930},[200,100048,3758],{"class":206},[200,100050,40842],{"class":223},[200,100052,227],{"class":235},[200,100054,25426],{"class":210},[200,100056,549],{"class":214},[200,100058,40851],{"class":2060},[200,100060,416],{"class":235},[200,100062,100063,100065],{"class":202,"line":3655},[200,100064,413],{"class":214},[200,100066,416],{"class":210},[35,100068,100069],{},[1216,100070,41597,100071,41600],{},[156,100072,26285],{},[1929,100074,41604],{"id":41603},[35,100076,41607],{},[192,100078,100079],{"className":21457,"code":41610,"language":12075,"meta":13,"style":13},[156,100080,100081,100101,100121,100139,100143,100161,100195,100201,100205,100219,100229,100235,100239,100251,100257,100277,100295,100309,100319,100325,100329,100341,100357,100367,100377,100391,100405,100411,100415,100419,100423,100431,100435,100443,100461,100501,100519,100523,100531,100539],{"__ignoreMap":13},[200,100082,100083,100085,100087,100089,100091,100093,100095,100097,100099],{"class":202,"line":203},[200,100084,599],{"class":214},[200,100086,14896],{"class":235},[200,100088,14594],{"class":379},[200,100090,449],{"class":214},[200,100092,610],{"class":214},[200,100094,1383],{"class":245},[200,100096,610],{"class":214},[200,100098,21825],{"class":379},[200,100100,647],{"class":214},[200,100102,100103,100105,100107,100109,100111,100113,100115,100117,100119],{"class":202,"line":14},[200,100104,972],{"class":206},[200,100106,321],{"class":206},[200,100108,318],{"class":214},[200,100110,41329],{"class":210},[200,100112,980],{"class":214},[200,100114,983],{"class":206},[200,100116,242],{"class":214},[200,100118,19813],{"class":245},[200,100120,991],{"class":214},[200,100122,100123,100125,100127,100129,100131,100133,100135,100137],{"class":202,"line":255},[200,100124,972],{"class":206},[200,100126,318],{"class":214},[200,100128,3180],{"class":210},[200,100130,980],{"class":214},[200,100132,983],{"class":206},[200,100134,242],{"class":214},[200,100136,19813],{"class":245},[200,100138,991],{"class":214},[200,100140,100141],{"class":202,"line":272},[200,100142,528],{"emptyLinePlaceholder":20},[200,100144,100145,100147,100149,100151,100153,100155,100157,100159],{"class":202,"line":283},[200,100146,443],{"class":379},[200,100148,41681],{"class":210},[200,100150,449],{"class":214},[200,100152,3180],{"class":210},[200,100154,215],{"class":214},[200,100156,293],{"class":223},[200,100158,227],{"class":210},[200,100160,230],{"class":214},[200,100162,100163,100165,100167,100169,100171,100173,100175,100177,100179,100181,100183,100185,100187,100189,100191,100193],{"class":202,"line":300},[200,100164,236],{"class":235},[200,100166,239],{"class":214},[200,100168,3180],{"class":210},[200,100170,215],{"class":214},[200,100172,328],{"class":223},[200,100174,227],{"class":210},[200,100176,2767],{"class":214},[200,100178,39109],{"class":235},[200,100180,239],{"class":214},[200,100182,242],{"class":214},[200,100184,41718],{"class":245},[200,100186,249],{"class":214},[200,100188,980],{"class":214},[200,100190,2104],{"class":210},[200,100192,549],{"class":214},[200,100194,41729],{"class":401},[200,100196,100197,100199],{"class":202,"line":310},[200,100198,413],{"class":214},[200,100200,416],{"class":210},[200,100202,100203],{"class":202,"line":336},[200,100204,528],{"emptyLinePlaceholder":20},[200,100206,100207,100209,100211,100213,100215,100217],{"class":202,"line":342},[200,100208,443],{"class":379},[200,100210,18960],{"class":210},[200,100212,449],{"class":214},[200,100214,41750],{"class":223},[200,100216,227],{"class":210},[200,100218,230],{"class":214},[200,100220,100221,100223,100225,100227],{"class":202,"line":365},[200,100222,236],{"class":235},[200,100224,239],{"class":214},[200,100226,3909],{"class":214},[200,100228,252],{"class":214},[200,100230,100231,100233],{"class":202,"line":371},[200,100232,413],{"class":214},[200,100234,416],{"class":210},[200,100236,100237],{"class":202,"line":398},[200,100238,528],{"emptyLinePlaceholder":20},[200,100240,100241,100243,100245,100247,100249],{"class":202,"line":405},[200,100242,3726],{"class":379},[200,100244,5002],{"class":379},[200,100246,41783],{"class":223},[200,100248,1549],{"class":214},[200,100250,280],{"class":214},[200,100252,100253,100255],{"class":202,"line":410},[200,100254,41792],{"class":206},[200,100256,280],{"class":214},[200,100258,100259,100261,100263,100265,100267,100269,100271,100273,100275],{"class":202,"line":860},[200,100260,2014],{"class":379},[200,100262,5056],{"class":210},[200,100264,509],{"class":214},[200,100266,41805],{"class":210},[200,100268,215],{"class":214},[200,100270,40994],{"class":223},[200,100272,227],{"class":235},[200,100274,26875],{"class":210},[200,100276,416],{"class":235},[200,100278,100279,100281,100283,100285,100287,100289,100291,100293],{"class":202,"line":889},[200,100280,19231],{"class":206},[200,100282,18754],{"class":223},[200,100284,227],{"class":235},[200,100286,249],{"class":214},[200,100288,40688],{"class":245},[200,100290,249],{"class":214},[200,100292,549],{"class":214},[200,100294,280],{"class":214},[200,100296,100297,100299,100301,100303,100305,100307],{"class":202,"line":899},[200,100298,41838],{"class":235},[200,100300,239],{"class":214},[200,100302,242],{"class":214},[200,100304,3793],{"class":245},[200,100306,249],{"class":214},[200,100308,252],{"class":214},[200,100310,100311,100313,100315,100317],{"class":202,"line":904},[200,100312,41853],{"class":235},[200,100314,239],{"class":214},[200,100316,5056],{"class":210},[200,100318,252],{"class":214},[200,100320,100321,100323],{"class":202,"line":930},[200,100322,2111],{"class":214},[200,100324,416],{"class":235},[200,100326,100327],{"class":202,"line":3655},[200,100328,4505],{"class":214},[200,100330,100331,100333,100335,100337,100339],{"class":202,"line":3660},[200,100332,41874],{"class":206},[200,100334,1094],{"class":235},[200,100336,19275],{"class":210},[200,100338,5282],{"class":235},[200,100340,230],{"class":214},[200,100342,100343,100345,100347,100349,100351,100353,100355],{"class":202,"line":4035},[200,100344,5267],{"class":206},[200,100346,1094],{"class":235},[200,100348,19275],{"class":210},[200,100350,41475],{"class":214},[200,100352,41329],{"class":1364},[200,100354,5282],{"class":235},[200,100356,230],{"class":214},[200,100358,100359,100361,100363,100365],{"class":202,"line":4490},[200,100360,20250],{"class":206},[200,100362,39203],{"class":223},[200,100364,227],{"class":235},[200,100366,230],{"class":214},[200,100368,100369,100371,100373,100375],{"class":202,"line":4496},[200,100370,41496],{"class":235},[200,100372,239],{"class":214},[200,100374,41198],{"class":2060},[200,100376,252],{"class":214},[200,100378,100379,100381,100383,100385,100387,100389],{"class":202,"line":4502},[200,100380,41507],{"class":235},[200,100382,239],{"class":214},[200,100384,242],{"class":214},[200,100386,41211],{"class":245},[200,100388,249],{"class":214},[200,100390,252],{"class":214},[200,100392,100393,100395,100397,100399,100401,100403],{"class":202,"line":4508},[200,100394,41522],{"class":235},[200,100396,239],{"class":214},[200,100398,19220],{"class":210},[200,100400,215],{"class":214},[200,100402,26828],{"class":210},[200,100404,252],{"class":214},[200,100406,100407,100409],{"class":202,"line":5469},[200,100408,2647],{"class":214},[200,100410,416],{"class":235},[200,100412,100413],{"class":202,"line":5474},[200,100414,4977],{"class":214},[200,100416,100417],{"class":202,"line":13237},[200,100418,4505],{"class":214},[200,100420,100421],{"class":202,"line":13263},[200,100422,1129],{"class":214},[200,100424,100425,100427,100429],{"class":202,"line":13279},[200,100426,680],{"class":214},[200,100428,14896],{"class":235},[200,100430,647],{"class":214},[200,100432,100433],{"class":202,"line":13323},[200,100434,528],{"emptyLinePlaceholder":20},[200,100436,100437,100439,100441],{"class":202,"line":13329},[200,100438,599],{"class":214},[200,100440,21468],{"class":235},[200,100442,647],{"class":214},[200,100444,100445,100447,100449,100451,100453,100455,100457,100459],{"class":202,"line":13334},[200,100446,656],{"class":214},[200,100448,602],{"class":235},[200,100450,41993],{"class":379},[200,100452,449],{"class":214},[200,100454,610],{"class":214},[200,100456,42000],{"class":245},[200,100458,610],{"class":214},[200,100460,647],{"class":214},[200,100462,100463,100465,100467,100469,100471,100473,100475,100477,100479,100481,100483,100485,100487,100489,100491,100493,100495,100497,100499],{"class":202,"line":13360},[200,100464,803],{"class":214},[200,100466,355],{"class":235},[200,100468,42013],{"class":379},[200,100470,449],{"class":214},[200,100472,610],{"class":214},[200,100474,42020],{"class":245},[200,100476,610],{"class":214},[200,100478,321],{"class":379},[200,100480,449],{"class":214},[200,100482,610],{"class":214},[200,100484,697],{"class":245},[200,100486,610],{"class":214},[200,100488,40711],{"class":379},[200,100490,449],{"class":214},[200,100492,610],{"class":214},[200,100494,40718],{"class":245},[200,100496,610],{"class":214},[200,100498,40723],{"class":379},[200,100500,647],{"class":214},[200,100502,100503,100505,100507,100509,100511,100513,100515,100517],{"class":202,"line":13365},[200,100504,803],{"class":214},[200,100506,909],{"class":235},[200,100508,321],{"class":379},[200,100510,449],{"class":214},[200,100512,610],{"class":214},[200,100514,916],{"class":245},[200,100516,610],{"class":214},[200,100518,647],{"class":214},[200,100520,100521],{"class":202,"line":13385},[200,100522,40748],{"class":210},[200,100524,100525,100527,100529],{"class":202,"line":13405},[200,100526,15078],{"class":214},[200,100528,909],{"class":235},[200,100530,647],{"class":214},[200,100532,100533,100535,100537],{"class":202,"line":13410},[200,100534,892],{"class":214},[200,100536,602],{"class":235},[200,100538,647],{"class":214},[200,100540,100541,100543,100545],{"class":202,"line":13418},[200,100542,680],{"class":214},[200,100544,21468],{"class":235},[200,100546,647],{"class":214},[35,100548,42095,100549,17891,100551,42102,100554,13741,100557,215],{},[156,100550,42098],{},[39,100552,9203],{"href":23093,"rel":100553},[77],[39,100555,42107],{"href":42105,"rel":100556},[77],[39,100558,42112],{"href":42110,"rel":100559},[77],[51,100561,42116],{"id":42115},[35,100563,42119],{},[35,100565,42122],{},[35,100567,42125,100568,35146,100570,42132],{},[156,100569,42128],{},[156,100571,42131],{},[192,100573,100574],{"className":1380,"code":42135,"language":1383,"meta":13,"style":13},[156,100575,100576,100594,100598,100618,100650],{"__ignoreMap":13},[200,100577,100578,100580,100582,100584,100586,100588,100590,100592],{"class":202,"line":203},[200,100579,972],{"class":206},[200,100581,318],{"class":214},[200,100583,3180],{"class":210},[200,100585,980],{"class":214},[200,100587,983],{"class":206},[200,100589,242],{"class":214},[200,100591,19813],{"class":245},[200,100593,991],{"class":214},[200,100595,100596],{"class":202,"line":14},[200,100597,528],{"emptyLinePlaceholder":20},[200,100599,100600,100602,100604,100606,100608,100610,100612,100614,100616],{"class":202,"line":255},[200,100601,1390],{"class":206},[200,100603,14305],{"class":379},[200,100605,42168],{"class":210},[200,100607,449],{"class":214},[200,100609,3180],{"class":210},[200,100611,215],{"class":214},[200,100613,293],{"class":223},[200,100615,227],{"class":210},[200,100617,230],{"class":214},[200,100619,100620,100622,100624,100626,100628,100630,100632,100634,100636,100638,100640,100642,100644,100646,100648],{"class":202,"line":272},[200,100621,236],{"class":235},[200,100623,239],{"class":214},[200,100625,3180],{"class":210},[200,100627,215],{"class":214},[200,100629,328],{"class":223},[200,100631,227],{"class":210},[200,100633,2767],{"class":214},[200,100635,39109],{"class":235},[200,100637,239],{"class":214},[200,100639,242],{"class":214},[200,100641,41718],{"class":245},[200,100643,249],{"class":214},[200,100645,980],{"class":214},[200,100647,2104],{"class":210},[200,100649,252],{"class":214},[200,100651,100652,100654],{"class":202,"line":283},[200,100653,413],{"class":214},[200,100655,416],{"class":210},[938,100657,100658],{},[35,100659,100660,42225],{},[200,100661,31019],{},[35,100663,42228],{},[35,100665,42231],{},[192,100667,100668],{"className":21457,"code":42234,"language":12075,"meta":13,"style":13},[156,100669,100670,100690,100700,100704,100708,100716,100720,100728,100736,100740,100748],{"__ignoreMap":13},[200,100671,100672,100674,100676,100678,100680,100682,100684,100686,100688],{"class":202,"line":203},[200,100673,599],{"class":214},[200,100675,14896],{"class":235},[200,100677,14594],{"class":379},[200,100679,449],{"class":214},[200,100681,610],{"class":214},[200,100683,1383],{"class":245},[200,100685,610],{"class":214},[200,100687,21825],{"class":379},[200,100689,647],{"class":214},[200,100691,100692,100694,100696,100698],{"class":202,"line":14},[200,100693,443],{"class":379},[200,100695,41681],{"class":210},[200,100697,449],{"class":214},[200,100699,42267],{"class":210},[200,100701,100702],{"class":202,"line":255},[200,100703,528],{"emptyLinePlaceholder":20},[200,100705,100706],{"class":202,"line":272},[200,100707,32897],{"class":401},[200,100709,100710,100712,100714],{"class":202,"line":283},[200,100711,680],{"class":214},[200,100713,14896],{"class":235},[200,100715,647],{"class":214},[200,100717,100718],{"class":202,"line":300},[200,100719,528],{"emptyLinePlaceholder":20},[200,100721,100722,100724,100726],{"class":202,"line":310},[200,100723,599],{"class":214},[200,100725,21468],{"class":235},[200,100727,647],{"class":214},[200,100729,100730,100732,100734],{"class":202,"line":336},[200,100731,656],{"class":214},[200,100733,602],{"class":235},[200,100735,647],{"class":214},[200,100737,100738],{"class":202,"line":342},[200,100739,42308],{"class":401},[200,100741,100742,100744,100746],{"class":202,"line":365},[200,100743,892],{"class":214},[200,100745,602],{"class":235},[200,100747,647],{"class":214},[200,100749,100750,100752,100754],{"class":202,"line":371},[200,100751,680],{"class":214},[200,100753,21468],{"class":235},[200,100755,647],{"class":214},[35,100757,42327],{},[192,100759,100760],{"className":1380,"code":42330,"language":1383,"meta":13,"style":13},[156,100761,100762,100780,100784,100806,100832,100836,100840,100844,100860],{"__ignoreMap":13},[200,100763,100764,100766,100768,100770,100772,100774,100776,100778],{"class":202,"line":203},[200,100765,972],{"class":206},[200,100767,318],{"class":214},[200,100769,42341],{"class":210},[200,100771,980],{"class":214},[200,100773,983],{"class":206},[200,100775,242],{"class":214},[200,100777,42350],{"class":245},[200,100779,991],{"class":214},[200,100781,100782],{"class":202,"line":14},[200,100783,528],{"emptyLinePlaceholder":20},[200,100785,100786,100788,100790,100792,100794,100796,100798,100800,100802,100804],{"class":202,"line":255},[200,100787,1390],{"class":206},[200,100789,1393],{"class":206},[200,100791,38723],{"class":223},[200,100793,227],{"class":210},[200,100795,3726],{"class":379},[200,100797,1094],{"class":214},[200,100799,25426],{"class":386},[200,100801,2104],{"class":214},[200,100803,393],{"class":379},[200,100805,280],{"class":214},[200,100807,100808,100810,100812,100814,100816,100818,100820,100822,100824,100826,100828,100830],{"class":202,"line":272},[200,100809,3738],{"class":379},[200,100811,40809],{"class":210},[200,100813,509],{"class":214},[200,100815,452],{"class":206},[200,100817,40951],{"class":223},[200,100819,227],{"class":235},[200,100821,25426],{"class":210},[200,100823,549],{"class":214},[200,100825,42341],{"class":210},[200,100827,215],{"class":214},[200,100829,40994],{"class":210},[200,100831,416],{"class":235},[200,100833,100834],{"class":202,"line":283},[200,100835,528],{"emptyLinePlaceholder":20},[200,100837,100838],{"class":202,"line":300},[200,100839,40831],{"class":401},[200,100841,100842],{"class":202,"line":310},[200,100843,528],{"emptyLinePlaceholder":20},[200,100845,100846,100848,100850,100852,100854,100856,100858],{"class":202,"line":336},[200,100847,3758],{"class":206},[200,100849,40842],{"class":223},[200,100851,227],{"class":235},[200,100853,25426],{"class":210},[200,100855,549],{"class":214},[200,100857,40851],{"class":2060},[200,100859,416],{"class":235},[200,100861,100862,100864],{"class":202,"line":342},[200,100863,413],{"class":214},[200,100865,416],{"class":210},[35,100867,42441],{},[51,100869,42445],{"id":42444},[35,100871,42448,100872,42452],{},[156,100873,42451],{},[35,100875,100876],{},[1216,100877,42457],{},[1929,100879,42461],{"id":42460},[35,100881,42464],{},[192,100883,100884],{"className":21457,"code":42467,"language":12075,"meta":13,"style":13},[156,100885,100886,100894,100932,100962,100980,100984,100992,101000],{"__ignoreMap":13},[200,100887,100888,100890,100892],{"class":202,"line":203},[200,100889,599],{"class":214},[200,100891,21468],{"class":235},[200,100893,647],{"class":214},[200,100895,100896,100898,100900,100902,100904,100906,100908,100910,100912,100914,100916,100918,100920,100922,100924,100926,100928,100930],{"class":202,"line":14},[200,100897,656],{"class":214},[200,100899,602],{"class":235},[200,100901,40681],{"class":379},[200,100903,449],{"class":214},[200,100905,610],{"class":214},[200,100907,42492],{"class":245},[200,100909,610],{"class":214},[200,100911,42497],{"class":379},[200,100913,449],{"class":214},[200,100915,610],{"class":214},[200,100917,42504],{"class":245},[200,100919,610],{"class":214},[200,100921,6589],{"class":379},[200,100923,449],{"class":214},[200,100925,610],{"class":214},[200,100927,3793],{"class":245},[200,100929,610],{"class":214},[200,100931,647],{"class":214},[200,100933,100934,100936,100938,100940,100942,100944,100946,100948,100950,100952,100954,100956,100958,100960],{"class":202,"line":255},[200,100935,803],{"class":214},[200,100937,355],{"class":235},[200,100939,321],{"class":379},[200,100941,449],{"class":214},[200,100943,610],{"class":214},[200,100945,42533],{"class":245},[200,100947,610],{"class":214},[200,100949,42538],{"class":379},[200,100951,449],{"class":214},[200,100953,610],{"class":214},[200,100955,42545],{"class":245},[200,100957,610],{"class":214},[200,100959,40723],{"class":379},[200,100961,647],{"class":214},[200,100963,100964,100966,100968,100970,100972,100974,100976,100978],{"class":202,"line":272},[200,100965,803],{"class":214},[200,100967,909],{"class":235},[200,100969,321],{"class":379},[200,100971,449],{"class":214},[200,100973,610],{"class":214},[200,100975,916],{"class":245},[200,100977,610],{"class":214},[200,100979,647],{"class":214},[200,100981,100982],{"class":202,"line":283},[200,100983,40748],{"class":210},[200,100985,100986,100988,100990],{"class":202,"line":300},[200,100987,15078],{"class":214},[200,100989,909],{"class":235},[200,100991,647],{"class":214},[200,100993,100994,100996,100998],{"class":202,"line":310},[200,100995,892],{"class":214},[200,100997,602],{"class":235},[200,100999,647],{"class":214},[200,101001,101002,101004,101006],{"class":202,"line":336},[200,101003,680],{"class":214},[200,101005,21468],{"class":235},[200,101007,647],{"class":214},[35,101009,42600,101010,42604,101012,42608],{},[156,101011,42603],{},[156,101013,42607],{},[35,101015,42611,101016,42614,101018,42618,101020,26733,101022,42623],{},[156,101017,42603],{},[156,101019,42617],{},[156,101021,9752],{},[156,101023,42603],{},[192,101025,101026],{"className":1380,"code":42626,"language":1383,"meta":13,"style":13},[156,101027,101028,101050,101054,101076,101094,101098,101122,101132,101142,101182,101230,101284,101358,101394,101404,101414,101428,101450,101456,101462,101466,101470,101474,101490],{"__ignoreMap":13},[200,101029,101030,101032,101034,101036,101038,101040,101042,101044,101046,101048],{"class":202,"line":203},[200,101031,972],{"class":206},[200,101033,318],{"class":214},[200,101035,42637],{"class":210},[200,101037,549],{"class":214},[200,101039,42642],{"class":210},[200,101041,980],{"class":214},[200,101043,983],{"class":206},[200,101045,242],{"class":214},[200,101047,19813],{"class":245},[200,101049,991],{"class":214},[200,101051,101052],{"class":202,"line":14},[200,101053,528],{"emptyLinePlaceholder":20},[200,101055,101056,101058,101060,101062,101064,101066,101068,101070,101072,101074],{"class":202,"line":255},[200,101057,1390],{"class":206},[200,101059,1393],{"class":206},[200,101061,38723],{"class":223},[200,101063,227],{"class":210},[200,101065,3726],{"class":379},[200,101067,1094],{"class":214},[200,101069,25426],{"class":386},[200,101071,2104],{"class":214},[200,101073,393],{"class":379},[200,101075,280],{"class":214},[200,101077,101078,101080,101082,101084,101086,101088,101090,101092],{"class":202,"line":272},[200,101079,3738],{"class":379},[200,101081,42685],{"class":210},[200,101083,509],{"class":214},[200,101085,452],{"class":206},[200,101087,42692],{"class":223},[200,101089,227],{"class":235},[200,101091,25426],{"class":210},[200,101093,416],{"class":235},[200,101095,101096],{"class":202,"line":283},[200,101097,528],{"emptyLinePlaceholder":20},[200,101099,101100,101102,101104,101106,101108,101110,101112,101114,101116,101118,101120],{"class":202,"line":300},[200,101101,3738],{"class":379},[200,101103,42709],{"class":210},[200,101105,509],{"class":214},[200,101107,42685],{"class":210},[200,101109,215],{"class":214},[200,101111,42718],{"class":223},[200,101113,227],{"class":235},[200,101115,249],{"class":214},[200,101117,9752],{"class":245},[200,101119,249],{"class":214},[200,101121,416],{"class":235},[200,101123,101124,101126,101128,101130],{"class":202,"line":310},[200,101125,6177],{"class":206},[200,101127,42642],{"class":223},[200,101129,227],{"class":235},[200,101131,230],{"class":214},[200,101133,101134,101136,101138,101140],{"class":202,"line":336},[200,101135,42743],{"class":235},[200,101137,239],{"class":214},[200,101139,42637],{"class":223},[200,101141,466],{"class":235},[200,101143,101144,101146,101148,101150,101152,101154,101156,101158,101160,101162,101164,101166,101168,101170,101172,101174,101176,101178,101180],{"class":202,"line":342},[200,101145,5066],{"class":214},[200,101147,42756],{"class":223},[200,101149,227],{"class":235},[200,101151,9752],{"class":386},[200,101153,393],{"class":379},[200,101155,42709],{"class":210},[200,101157,41475],{"class":214},[200,101159,42769],{"class":1364},[200,101161,549],{"class":214},[200,101163,318],{"class":214},[200,101165,39109],{"class":235},[200,101167,239],{"class":214},[200,101169,242],{"class":214},[200,101171,42782],{"class":245},[200,101173,16056],{"class":210},[200,101175,42787],{"class":245},[200,101177,249],{"class":214},[200,101179,980],{"class":214},[200,101181,416],{"class":235},[200,101183,101184,101186,101188,101190,101192,101194,101196,101198,101200,101202,101204,101206,101208,101210,101212,101214,101216,101218,101220,101222,101224,101226,101228],{"class":202,"line":365},[200,101185,5066],{"class":214},[200,101187,42756],{"class":223},[200,101189,227],{"class":235},[200,101191,9752],{"class":386},[200,101193,393],{"class":379},[200,101195,42709],{"class":210},[200,101197,215],{"class":214},[200,101199,35121],{"class":210},[200,101201,31004],{"class":214},[200,101203,42816],{"class":2060},[200,101205,29668],{"class":214},[200,101207,42816],{"class":2060},[200,101209,549],{"class":214},[200,101211,318],{"class":214},[200,101213,39109],{"class":235},[200,101215,239],{"class":214},[200,101217,242],{"class":214},[200,101219,42782],{"class":245},[200,101221,16056],{"class":210},[200,101223,42837],{"class":245},[200,101225,249],{"class":214},[200,101227,980],{"class":214},[200,101229,416],{"class":235},[200,101231,101232,101234,101236,101238,101240,101242,101244,101246,101248,101250,101252,101254,101256,101258,101260,101262,101264,101266,101268,101270,101272,101274,101276,101278,101280,101282],{"class":202,"line":371},[200,101233,5066],{"class":214},[200,101235,42756],{"class":223},[200,101237,227],{"class":235},[200,101239,9752],{"class":386},[200,101241,393],{"class":379},[200,101243,42709],{"class":210},[200,101245,215],{"class":214},[200,101247,3786],{"class":210},[200,101249,215],{"class":214},[200,101251,6120],{"class":223},[200,101253,227],{"class":235},[200,101255,249],{"class":214},[200,101257,42872],{"class":245},[200,101259,249],{"class":214},[200,101261,2104],{"class":235},[200,101263,549],{"class":214},[200,101265,318],{"class":214},[200,101267,39109],{"class":235},[200,101269,239],{"class":214},[200,101271,242],{"class":214},[200,101273,42782],{"class":245},[200,101275,16056],{"class":210},[200,101277,42893],{"class":245},[200,101279,249],{"class":214},[200,101281,980],{"class":214},[200,101283,416],{"class":235},[200,101285,101286,101288,101290,101292,101294,101296,101298,101300,101302,101304,101306,101308,101310,101312,101314,101316,101318,101320,101322,101324,101326,101328,101330,101332,101334,101336,101338,101340,101342,101344,101346,101348,101350,101352,101354,101356],{"class":202,"line":398},[200,101287,5066],{"class":214},[200,101289,42756],{"class":223},[200,101291,227],{"class":235},[200,101293,9752],{"class":386},[200,101295,393],{"class":379},[200,101297,350],{"class":235},[200,101299,249],{"class":214},[200,101301,42918],{"class":245},[200,101303,249],{"class":214},[200,101305,549],{"class":214},[200,101307,242],{"class":214},[200,101309,42927],{"class":245},[200,101311,249],{"class":214},[200,101313,549],{"class":214},[200,101315,242],{"class":214},[200,101317,42936],{"class":245},[200,101319,249],{"class":214},[200,101321,360],{"class":235},[200,101323,215],{"class":214},[200,101325,3856],{"class":223},[200,101327,227],{"class":235},[200,101329,9752],{"class":210},[200,101331,215],{"class":214},[200,101333,3786],{"class":210},[200,101335,2104],{"class":235},[200,101337,549],{"class":214},[200,101339,318],{"class":214},[200,101341,39109],{"class":235},[200,101343,239],{"class":214},[200,101345,242],{"class":214},[200,101347,42782],{"class":245},[200,101349,16056],{"class":210},[200,101351,42971],{"class":245},[200,101353,249],{"class":214},[200,101355,980],{"class":214},[200,101357,416],{"class":235},[200,101359,101360,101362,101364,101366,101368,101370,101372,101374,101376,101378,101380,101382,101384,101386,101388,101390,101392],{"class":202,"line":405},[200,101361,5095],{"class":214},[200,101363,2104],{"class":235},[200,101365,215],{"class":214},[200,101367,41312],{"class":223},[200,101369,227],{"class":235},[200,101371,2767],{"class":214},[200,101373,42709],{"class":210},[200,101375,980],{"class":214},[200,101377,2104],{"class":235},[200,101379,215],{"class":214},[200,101381,41452],{"class":223},[200,101383,227],{"class":235},[200,101385,227],{"class":214},[200,101387,19275],{"class":386},[200,101389,2104],{"class":214},[200,101391,393],{"class":379},[200,101393,280],{"class":214},[200,101395,101396,101398,101400,101402],{"class":202,"line":410},[200,101397,39200],{"class":206},[200,101399,39203],{"class":223},[200,101401,227],{"class":235},[200,101403,230],{"class":214},[200,101405,101406,101408,101410,101412],{"class":202,"line":860},[200,101407,38845],{"class":235},[200,101409,239],{"class":214},[200,101411,41198],{"class":2060},[200,101413,252],{"class":214},[200,101415,101416,101418,101420,101422,101424,101426],{"class":202,"line":889},[200,101417,39233],{"class":235},[200,101419,239],{"class":214},[200,101421,242],{"class":214},[200,101423,43044],{"class":245},[200,101425,249],{"class":214},[200,101427,252],{"class":214},[200,101429,101430,101432,101434,101436,101438,101440,101442,101444,101446,101448],{"class":202,"line":899},[200,101431,41220],{"class":235},[200,101433,239],{"class":214},[200,101435,19220],{"class":210},[200,101437,215],{"class":214},[200,101439,26828],{"class":210},[200,101441,9872],{"class":235},[200,101443,3948],{"class":2060},[200,101445,360],{"class":235},[200,101447,215],{"class":214},[200,101449,43071],{"class":210},[200,101451,101452,101454],{"class":202,"line":904},[200,101453,2111],{"class":214},[200,101455,416],{"class":235},[200,101457,101458,101460],{"class":202,"line":930},[200,101459,5095],{"class":214},[200,101461,416],{"class":235},[200,101463,101464],{"class":202,"line":3655},[200,101465,528],{"emptyLinePlaceholder":20},[200,101467,101468],{"class":202,"line":3660},[200,101469,43092],{"class":401},[200,101471,101472],{"class":202,"line":4035},[200,101473,528],{"emptyLinePlaceholder":20},[200,101475,101476,101478,101480,101482,101484,101486,101488],{"class":202,"line":4490},[200,101477,3758],{"class":206},[200,101479,40842],{"class":223},[200,101481,227],{"class":235},[200,101483,25426],{"class":210},[200,101485,549],{"class":214},[200,101487,40851],{"class":2060},[200,101489,416],{"class":235},[200,101491,101492,101494],{"class":202,"line":4496},[200,101493,413],{"class":214},[200,101495,416],{"class":210},[35,101497,43121],{},[35,101499,43124,101500,2186],{},[156,101501,43127],{},[192,101503,101504],{"className":1380,"code":43130,"language":1383,"meta":13,"style":13},[156,101505,101506,101528,101532,101548,101558,101598,101646,101700,101762],{"__ignoreMap":13},[200,101507,101508,101510,101512,101514,101516,101518,101520,101522,101524,101526],{"class":202,"line":203},[200,101509,972],{"class":206},[200,101511,318],{"class":214},[200,101513,42637],{"class":210},[200,101515,549],{"class":214},[200,101517,42642],{"class":210},[200,101519,980],{"class":214},[200,101521,983],{"class":206},[200,101523,242],{"class":214},[200,101525,19813],{"class":245},[200,101527,991],{"class":214},[200,101529,101530],{"class":202,"line":14},[200,101531,528],{"emptyLinePlaceholder":20},[200,101533,101534,101536,101538,101540,101542,101544,101546],{"class":202,"line":255},[200,101535,1390],{"class":206},[200,101537,14305],{"class":379},[200,101539,43167],{"class":210},[200,101541,449],{"class":214},[200,101543,42642],{"class":223},[200,101545,227],{"class":210},[200,101547,230],{"class":214},[200,101549,101550,101552,101554,101556],{"class":202,"line":272},[200,101551,43180],{"class":235},[200,101553,239],{"class":214},[200,101555,42637],{"class":223},[200,101557,466],{"class":210},[200,101559,101560,101562,101564,101566,101568,101570,101572,101574,101576,101578,101580,101582,101584,101586,101588,101590,101592,101594,101596],{"class":202,"line":283},[200,101561,3770],{"class":214},[200,101563,42756],{"class":223},[200,101565,227],{"class":210},[200,101567,9752],{"class":386},[200,101569,393],{"class":379},[200,101571,43201],{"class":210},[200,101573,43204],{"class":214},[200,101575,42769],{"class":1364},[200,101577,549],{"class":214},[200,101579,318],{"class":214},[200,101581,39109],{"class":235},[200,101583,239],{"class":214},[200,101585,242],{"class":214},[200,101587,42782],{"class":245},[200,101589,16056],{"class":210},[200,101591,42787],{"class":245},[200,101593,249],{"class":214},[200,101595,980],{"class":214},[200,101597,416],{"class":210},[200,101599,101600,101602,101604,101606,101608,101610,101612,101614,101616,101618,101620,101622,101624,101626,101628,101630,101632,101634,101636,101638,101640,101642,101644],{"class":202,"line":300},[200,101601,3770],{"class":214},[200,101603,42756],{"class":223},[200,101605,227],{"class":210},[200,101607,9752],{"class":386},[200,101609,393],{"class":379},[200,101611,42709],{"class":210},[200,101613,215],{"class":214},[200,101615,43247],{"class":210},[200,101617,599],{"class":214},[200,101619,42816],{"class":2060},[200,101621,29668],{"class":214},[200,101623,42816],{"class":2060},[200,101625,549],{"class":214},[200,101627,318],{"class":214},[200,101629,39109],{"class":235},[200,101631,239],{"class":214},[200,101633,242],{"class":214},[200,101635,42782],{"class":245},[200,101637,16056],{"class":210},[200,101639,42837],{"class":245},[200,101641,249],{"class":214},[200,101643,980],{"class":214},[200,101645,416],{"class":210},[200,101647,101648,101650,101652,101654,101656,101658,101660,101662,101664,101666,101668,101670,101672,101674,101676,101678,101680,101682,101684,101686,101688,101690,101692,101694,101696,101698],{"class":202,"line":310},[200,101649,3770],{"class":214},[200,101651,42756],{"class":223},[200,101653,227],{"class":210},[200,101655,9752],{"class":386},[200,101657,393],{"class":379},[200,101659,42709],{"class":210},[200,101661,215],{"class":214},[200,101663,3786],{"class":210},[200,101665,215],{"class":214},[200,101667,6120],{"class":223},[200,101669,227],{"class":210},[200,101671,249],{"class":214},[200,101673,42872],{"class":245},[200,101675,249],{"class":214},[200,101677,2104],{"class":210},[200,101679,549],{"class":214},[200,101681,318],{"class":214},[200,101683,39109],{"class":235},[200,101685,239],{"class":214},[200,101687,242],{"class":214},[200,101689,42782],{"class":245},[200,101691,16056],{"class":210},[200,101693,42893],{"class":245},[200,101695,249],{"class":214},[200,101697,980],{"class":214},[200,101699,416],{"class":210},[200,101701,101702,101704,101706,101708,101710,101712,101714,101716,101718,101720,101722,101724,101726,101728,101730,101732,101734,101736,101738,101740,101742,101744,101746,101748,101750,101752,101754,101756,101758,101760],{"class":202,"line":336},[200,101703,3770],{"class":214},[200,101705,42756],{"class":223},[200,101707,227],{"class":210},[200,101709,9752],{"class":386},[200,101711,393],{"class":379},[200,101713,350],{"class":210},[200,101715,249],{"class":214},[200,101717,42927],{"class":245},[200,101719,249],{"class":214},[200,101721,549],{"class":214},[200,101723,242],{"class":214},[200,101725,42918],{"class":245},[200,101727,249],{"class":214},[200,101729,360],{"class":210},[200,101731,215],{"class":214},[200,101733,3856],{"class":223},[200,101735,43368],{"class":210},[200,101737,215],{"class":214},[200,101739,43373],{"class":210},[200,101741,549],{"class":214},[200,101743,318],{"class":214},[200,101745,39109],{"class":235},[200,101747,239],{"class":214},[200,101749,242],{"class":214},[200,101751,42782],{"class":245},[200,101753,16056],{"class":210},[200,101755,42971],{"class":245},[200,101757,249],{"class":214},[200,101759,980],{"class":214},[200,101761,416],{"class":210},[200,101763,101764,101766],{"class":202,"line":342},[200,101765,413],{"class":214},[200,101767,416],{"class":210},[35,101769,43404],{},[192,101771,101772],{"className":21457,"code":43407,"language":12075,"meta":13,"style":13},[156,101773,101774,101794,101814,101832,101850,101854,101878,101884,101904,101908,101926,101940,101950,101956,101960,101972,101986,101990,101994,102002,102006,102014,102022,102038,102042,102050,102054,102062,102090,102128,102146,102150,102158,102166,102174],{"__ignoreMap":13},[200,101775,101776,101778,101780,101782,101784,101786,101788,101790,101792],{"class":202,"line":203},[200,101777,599],{"class":214},[200,101779,14896],{"class":235},[200,101781,14594],{"class":379},[200,101783,449],{"class":214},[200,101785,610],{"class":214},[200,101787,1383],{"class":245},[200,101789,610],{"class":214},[200,101791,21825],{"class":379},[200,101793,647],{"class":214},[200,101795,101796,101798,101800,101802,101804,101806,101808,101810,101812],{"class":202,"line":14},[200,101797,3726],{"class":379},[200,101799,5002],{"class":379},[200,101801,43438],{"class":223},[200,101803,227],{"class":214},[200,101805,25426],{"class":386},[200,101807,239],{"class":214},[200,101809,43447],{"class":1364},[200,101811,2104],{"class":214},[200,101813,280],{"class":214},[200,101815,101816,101818,101820,101822,101824,101826,101828,101830],{"class":202,"line":255},[200,101817,3738],{"class":379},[200,101819,43458],{"class":210},[200,101821,509],{"class":214},[200,101823,43463],{"class":210},[200,101825,215],{"class":214},[200,101827,43468],{"class":210},[200,101829,14678],{"class":206},[200,101831,43473],{"class":1364},[200,101833,101834,101836,101838,101840,101842,101844,101846,101848],{"class":202,"line":272},[200,101835,3738],{"class":379},[200,101837,42685],{"class":210},[200,101839,509],{"class":214},[200,101841,13007],{"class":214},[200,101843,43486],{"class":223},[200,101845,227],{"class":235},[200,101847,43468],{"class":210},[200,101849,416],{"class":235},[200,101851,101852],{"class":202,"line":283},[200,101853,528],{"emptyLinePlaceholder":20},[200,101855,101856,101858,101860,101862,101864,101866,101868,101870,101872,101874,101876],{"class":202,"line":300},[200,101857,3738],{"class":379},[200,101859,42709],{"class":210},[200,101861,509],{"class":214},[200,101863,42685],{"class":210},[200,101865,215],{"class":214},[200,101867,42718],{"class":223},[200,101869,227],{"class":235},[200,101871,249],{"class":214},[200,101873,9752],{"class":245},[200,101875,249],{"class":214},[200,101877,416],{"class":235},[200,101879,101880,101882],{"class":202,"line":310},[200,101881,41792],{"class":206},[200,101883,280],{"class":214},[200,101885,101886,101888,101890,101892,101894,101896,101898,101900,101902],{"class":202,"line":336},[200,101887,19231],{"class":206},[200,101889,43533],{"class":210},[200,101891,215],{"class":214},[200,101893,41312],{"class":223},[200,101895,227],{"class":235},[200,101897,2767],{"class":214},[200,101899,42709],{"class":210},[200,101901,980],{"class":214},[200,101903,416],{"class":235},[200,101905,101906],{"class":202,"line":342},[200,101907,528],{"emptyLinePlaceholder":20},[200,101909,101910,101912,101914,101916,101918,101920,101922,101924],{"class":202,"line":365},[200,101911,19231],{"class":206},[200,101913,18754],{"class":223},[200,101915,227],{"class":235},[200,101917,249],{"class":214},[200,101919,42492],{"class":245},[200,101921,249],{"class":214},[200,101923,549],{"class":214},[200,101925,280],{"class":214},[200,101927,101928,101930,101932,101934,101936,101938],{"class":202,"line":371},[200,101929,41838],{"class":235},[200,101931,239],{"class":214},[200,101933,242],{"class":214},[200,101935,18779],{"class":245},[200,101937,249],{"class":214},[200,101939,252],{"class":214},[200,101941,101942,101944,101946,101948],{"class":202,"line":398},[200,101943,41853],{"class":235},[200,101945,239],{"class":214},[200,101947,42685],{"class":210},[200,101949,252],{"class":214},[200,101951,101952,101954],{"class":202,"line":405},[200,101953,2111],{"class":214},[200,101955,416],{"class":235},[200,101957,101958],{"class":202,"line":410},[200,101959,4505],{"class":214},[200,101961,101962,101964,101966,101968,101970],{"class":202,"line":860},[200,101963,41874],{"class":206},[200,101965,1094],{"class":235},[200,101967,19275],{"class":210},[200,101969,5282],{"class":235},[200,101971,230],{"class":214},[200,101973,101974,101976,101978,101980,101982,101984],{"class":202,"line":889},[200,101975,43620],{"class":210},[200,101977,215],{"class":214},[200,101979,19275],{"class":223},[200,101981,227],{"class":235},[200,101983,19275],{"class":210},[200,101985,416],{"class":235},[200,101987,101988],{"class":202,"line":899},[200,101989,4505],{"class":214},[200,101991,101992],{"class":202,"line":904},[200,101993,1129],{"class":214},[200,101995,101996,101998,102000],{"class":202,"line":930},[200,101997,680],{"class":214},[200,101999,14896],{"class":235},[200,102001,647],{"class":214},[200,102003,102004],{"class":202,"line":3655},[200,102005,528],{"emptyLinePlaceholder":20},[200,102007,102008,102010,102012],{"class":202,"line":3660},[200,102009,599],{"class":214},[200,102011,21468],{"class":235},[200,102013,647],{"class":214},[200,102015,102016,102018,102020],{"class":202,"line":4035},[200,102017,656],{"class":214},[200,102019,11400],{"class":235},[200,102021,647],{"class":214},[200,102023,102024,102026,102028,102030,102032,102034,102036],{"class":202,"line":4490},[200,102025,803],{"class":214},[200,102027,10095],{"class":235},[200,102029,674],{"class":214},[200,102031,43677],{"class":210},[200,102033,680],{"class":214},[200,102035,10095],{"class":235},[200,102037,647],{"class":214},[200,102039,102040],{"class":202,"line":4496},[200,102041,528],{"emptyLinePlaceholder":20},[200,102043,102044,102046,102048],{"class":202,"line":4502},[200,102045,803],{"class":214},[200,102047,51],{"class":235},[200,102049,647],{"class":214},[200,102051,102052],{"class":202,"line":4508},[200,102053,43700],{"class":210},[200,102055,102056,102058,102060],{"class":202,"line":5469},[200,102057,15078],{"class":214},[200,102059,51],{"class":235},[200,102061,647],{"class":214},[200,102063,102064,102066,102068,102070,102072,102074,102076,102078,102080,102082,102084,102086,102088],{"class":202,"line":5474},[200,102065,803],{"class":214},[200,102067,602],{"class":235},[200,102069,42497],{"class":379},[200,102071,449],{"class":214},[200,102073,610],{"class":214},[200,102075,42504],{"class":245},[200,102077,610],{"class":214},[200,102079,41993],{"class":379},[200,102081,449],{"class":214},[200,102083,610],{"class":214},[200,102085,43733],{"class":245},[200,102087,610],{"class":214},[200,102089,647],{"class":214},[200,102091,102092,102094,102096,102098,102100,102102,102104,102106,102108,102110,102112,102114,102116,102118,102120,102122,102124,102126],{"class":202,"line":13237},[200,102093,22071],{"class":214},[200,102095,355],{"class":235},[200,102097,321],{"class":379},[200,102099,449],{"class":214},[200,102101,610],{"class":214},[200,102103,42533],{"class":245},[200,102105,610],{"class":214},[200,102107,700],{"class":379},[200,102109,449],{"class":214},[200,102111,610],{"class":214},[200,102113,9752],{"class":245},[200,102115,610],{"class":214},[200,102117,42538],{"class":379},[200,102119,449],{"class":214},[200,102121,610],{"class":214},[200,102123,42545],{"class":245},[200,102125,610],{"class":214},[200,102127,647],{"class":214},[200,102129,102130,102132,102134,102136,102138,102140,102142,102144],{"class":202,"line":13263},[200,102131,22071],{"class":214},[200,102133,909],{"class":235},[200,102135,321],{"class":379},[200,102137,449],{"class":214},[200,102139,610],{"class":214},[200,102141,916],{"class":245},[200,102143,610],{"class":214},[200,102145,647],{"class":214},[200,102147,102148],{"class":202,"line":13279},[200,102149,43798],{"class":210},[200,102151,102152,102154,102156],{"class":202,"line":13323},[200,102153,43803],{"class":214},[200,102155,909],{"class":235},[200,102157,647],{"class":214},[200,102159,102160,102162,102164],{"class":202,"line":13329},[200,102161,15078],{"class":214},[200,102163,602],{"class":235},[200,102165,647],{"class":214},[200,102167,102168,102170,102172],{"class":202,"line":13334},[200,102169,892],{"class":214},[200,102171,11400],{"class":235},[200,102173,647],{"class":214},[200,102175,102176,102178,102180],{"class":202,"line":13360},[200,102177,680],{"class":214},[200,102179,21468],{"class":235},[200,102181,647],{"class":214},[35,102183,43834,102184,43838],{},[156,102185,43837],{},[35,102187,43841],{},[192,102189,102190],{"className":1380,"code":43844,"language":1383,"meta":13,"style":13},[156,102191,102192,102210,102214,102236,102240,102244,102264,102268,102272],{"__ignoreMap":13},[200,102193,102194,102196,102198,102200,102202,102204,102206,102208],{"class":202,"line":203},[200,102195,972],{"class":206},[200,102197,318],{"class":214},[200,102199,43855],{"class":210},[200,102201,980],{"class":214},[200,102203,983],{"class":206},[200,102205,242],{"class":214},[200,102207,42350],{"class":245},[200,102209,991],{"class":214},[200,102211,102212],{"class":202,"line":14},[200,102213,528],{"emptyLinePlaceholder":20},[200,102215,102216,102218,102220,102222,102224,102226,102228,102230,102232,102234],{"class":202,"line":255},[200,102217,1390],{"class":206},[200,102219,1393],{"class":206},[200,102221,38723],{"class":223},[200,102223,227],{"class":210},[200,102225,3726],{"class":379},[200,102227,1094],{"class":214},[200,102229,25426],{"class":386},[200,102231,2104],{"class":214},[200,102233,393],{"class":379},[200,102235,280],{"class":214},[200,102237,102238],{"class":202,"line":272},[200,102239,10648],{"class":401},[200,102241,102242],{"class":202,"line":283},[200,102243,528],{"emptyLinePlaceholder":20},[200,102245,102246,102248,102250,102252,102254,102256,102258,102260,102262],{"class":202,"line":300},[200,102247,6177],{"class":206},[200,102249,43855],{"class":210},[200,102251,215],{"class":214},[200,102253,41312],{"class":223},[200,102255,227],{"class":235},[200,102257,2767],{"class":214},[200,102259,42709],{"class":210},[200,102261,980],{"class":214},[200,102263,416],{"class":235},[200,102265,102266],{"class":202,"line":310},[200,102267,528],{"emptyLinePlaceholder":20},[200,102269,102270],{"class":202,"line":336},[200,102271,10648],{"class":401},[200,102273,102274,102276],{"class":202,"line":342},[200,102275,413],{"class":214},[200,102277,416],{"class":210},[1929,102279,43937],{"id":43936},[35,102281,43940,102282,43943,102284],{},[156,102283,42451],{},[1216,102285,43946],{},[192,102287,102288],{"className":1380,"code":43949,"language":1383,"meta":13,"style":13},[156,102289,102290,102312,102330,102334,102362,102374,102388,102420,102426,102430,102434,102438,102454],{"__ignoreMap":13},[200,102291,102292,102294,102296,102298,102300,102302,102304,102306,102308,102310],{"class":202,"line":203},[200,102293,1390],{"class":206},[200,102295,1393],{"class":206},[200,102297,38723],{"class":223},[200,102299,227],{"class":210},[200,102301,3726],{"class":379},[200,102303,1094],{"class":214},[200,102305,25426],{"class":386},[200,102307,2104],{"class":214},[200,102309,393],{"class":379},[200,102311,280],{"class":214},[200,102313,102314,102316,102318,102320,102322,102324,102326,102328],{"class":202,"line":14},[200,102315,3738],{"class":379},[200,102317,42685],{"class":210},[200,102319,509],{"class":214},[200,102321,452],{"class":206},[200,102323,42692],{"class":223},[200,102325,227],{"class":235},[200,102327,25426],{"class":210},[200,102329,416],{"class":235},[200,102331,102332],{"class":202,"line":255},[200,102333,528],{"emptyLinePlaceholder":20},[200,102335,102336,102338,102340,102342,102344,102346,102348,102350,102352,102354,102356,102358,102360],{"class":202,"line":272},[200,102337,3738],{"class":379},[200,102339,42709],{"class":210},[200,102341,509],{"class":214},[200,102343,42685],{"class":210},[200,102345,215],{"class":214},[200,102347,42718],{"class":223},[200,102349,227],{"class":235},[200,102351,249],{"class":214},[200,102353,9752],{"class":245},[200,102355,249],{"class":214},[200,102357,5282],{"class":235},[200,102359,44022],{"class":206},[200,102361,44025],{"class":1364},[200,102363,102364,102366,102368,102370,102372],{"class":202,"line":283},[200,102365,44030],{"class":223},[200,102367,227],{"class":235},[200,102369,9752],{"class":210},[200,102371,549],{"class":214},[200,102373,280],{"class":214},[200,102375,102376,102378,102380,102382,102384,102386],{"class":202,"line":300},[200,102377,44043],{"class":235},[200,102379,239],{"class":214},[200,102381,242],{"class":214},[200,102383,44050],{"class":245},[200,102385,249],{"class":214},[200,102387,252],{"class":214},[200,102389,102390,102392,102394,102396,102398,102400,102402,102404,102406,102408,102410,102412,102414,102416,102418],{"class":202,"line":310},[200,102391,44059],{"class":235},[200,102393,239],{"class":214},[200,102395,350],{"class":235},[200,102397,249],{"class":214},[200,102399,42918],{"class":245},[200,102401,249],{"class":214},[200,102403,549],{"class":214},[200,102405,242],{"class":214},[200,102407,42927],{"class":245},[200,102409,249],{"class":214},[200,102411,549],{"class":214},[200,102413,242],{"class":214},[200,102415,42936],{"class":245},[200,102417,249],{"class":214},[200,102419,4511],{"class":235},[200,102421,102422,102424],{"class":202,"line":336},[200,102423,5095],{"class":214},[200,102425,416],{"class":235},[200,102427,102428],{"class":202,"line":342},[200,102429,528],{"emptyLinePlaceholder":20},[200,102431,102432],{"class":202,"line":365},[200,102433,43092],{"class":401},[200,102435,102436],{"class":202,"line":371},[200,102437,528],{"emptyLinePlaceholder":20},[200,102439,102440,102442,102444,102446,102448,102450,102452],{"class":202,"line":398},[200,102441,3758],{"class":206},[200,102443,40842],{"class":223},[200,102445,227],{"class":235},[200,102447,25426],{"class":210},[200,102449,549],{"class":214},[200,102451,40851],{"class":2060},[200,102453,416],{"class":235},[200,102455,102456,102458],{"class":202,"line":405},[200,102457,413],{"class":214},[200,102459,416],{"class":210},[51,102461,44131],{"id":44130},[35,102463,44134],{},[35,102465,44137,102466,44140],{},[156,102467,42603],{},[35,102469,44143],{},[192,102471,102472],{"className":21457,"code":44146,"language":12075,"meta":13,"style":13},[156,102473,102474,102482,102520,102560,102600,102618,102622,102630,102638],{"__ignoreMap":13},[200,102475,102476,102478,102480],{"class":202,"line":203},[200,102477,599],{"class":214},[200,102479,21468],{"class":235},[200,102481,647],{"class":214},[200,102483,102484,102486,102488,102490,102492,102494,102496,102498,102500,102502,102504,102506,102508,102510,102512,102514,102516,102518],{"class":202,"line":14},[200,102485,656],{"class":214},[200,102487,602],{"class":235},[200,102489,40681],{"class":379},[200,102491,449],{"class":214},[200,102493,610],{"class":214},[200,102495,44171],{"class":245},[200,102497,610],{"class":214},[200,102499,42497],{"class":379},[200,102501,449],{"class":214},[200,102503,610],{"class":214},[200,102505,42504],{"class":245},[200,102507,610],{"class":214},[200,102509,6589],{"class":379},[200,102511,449],{"class":214},[200,102513,610],{"class":214},[200,102515,3793],{"class":245},[200,102517,610],{"class":214},[200,102519,647],{"class":214},[200,102521,102522,102524,102526,102528,102530,102532,102534,102536,102538,102540,102542,102544,102546,102548,102550,102552,102554,102556,102558],{"class":202,"line":255},[200,102523,803],{"class":214},[200,102525,355],{"class":235},[200,102527,321],{"class":379},[200,102529,449],{"class":214},[200,102531,610],{"class":214},[200,102533,697],{"class":245},[200,102535,610],{"class":214},[200,102537,700],{"class":379},[200,102539,449],{"class":214},[200,102541,610],{"class":214},[200,102543,20112],{"class":245},[200,102545,610],{"class":214},[200,102547,40711],{"class":379},[200,102549,449],{"class":214},[200,102551,610],{"class":214},[200,102553,40718],{"class":245},[200,102555,610],{"class":214},[200,102557,40723],{"class":379},[200,102559,647],{"class":214},[200,102561,102562,102564,102566,102568,102570,102572,102574,102576,102578,102580,102582,102584,102586,102588,102590,102592,102594,102596,102598],{"class":202,"line":272},[200,102563,803],{"class":214},[200,102565,355],{"class":235},[200,102567,321],{"class":379},[200,102569,449],{"class":214},[200,102571,610],{"class":214},[200,102573,42533],{"class":245},[200,102575,610],{"class":214},[200,102577,700],{"class":379},[200,102579,449],{"class":214},[200,102581,610],{"class":214},[200,102583,9752],{"class":245},[200,102585,610],{"class":214},[200,102587,42538],{"class":379},[200,102589,449],{"class":214},[200,102591,610],{"class":214},[200,102593,42545],{"class":245},[200,102595,610],{"class":214},[200,102597,40723],{"class":379},[200,102599,647],{"class":214},[200,102601,102602,102604,102606,102608,102610,102612,102614,102616],{"class":202,"line":283},[200,102603,803],{"class":214},[200,102605,909],{"class":235},[200,102607,321],{"class":379},[200,102609,449],{"class":214},[200,102611,610],{"class":214},[200,102613,916],{"class":245},[200,102615,610],{"class":214},[200,102617,647],{"class":214},[200,102619,102620],{"class":202,"line":300},[200,102621,40748],{"class":210},[200,102623,102624,102626,102628],{"class":202,"line":310},[200,102625,15078],{"class":214},[200,102627,909],{"class":235},[200,102629,647],{"class":214},[200,102631,102632,102634,102636],{"class":202,"line":336},[200,102633,892],{"class":214},[200,102635,602],{"class":235},[200,102637,647],{"class":214},[200,102639,102640,102642,102644],{"class":202,"line":342},[200,102641,680],{"class":214},[200,102643,21468],{"class":235},[200,102645,647],{"class":214},[35,102647,44324],{},[192,102649,102650],{"className":1380,"code":44327,"language":1383,"meta":13,"style":13},[156,102651,102652,102678,102682,102698,102726,102940],{"__ignoreMap":13},[200,102653,102654,102656,102658,102660,102662,102664,102666,102668,102670,102672,102674,102676],{"class":202,"line":203},[200,102655,972],{"class":206},[200,102657,318],{"class":214},[200,102659,42637],{"class":210},[200,102661,549],{"class":214},[200,102663,42642],{"class":210},[200,102665,549],{"class":214},[200,102667,22752],{"class":210},[200,102669,980],{"class":214},[200,102671,983],{"class":206},[200,102673,242],{"class":214},[200,102675,19813],{"class":245},[200,102677,991],{"class":214},[200,102679,102680],{"class":202,"line":14},[200,102681,528],{"emptyLinePlaceholder":20},[200,102683,102684,102686,102688,102690,102692,102694,102696],{"class":202,"line":255},[200,102685,1390],{"class":206},[200,102687,14305],{"class":379},[200,102689,44368],{"class":210},[200,102691,449],{"class":214},[200,102693,42642],{"class":223},[200,102695,227],{"class":210},[200,102697,230],{"class":214},[200,102699,102700,102702,102704,102706,102708,102710,102712,102714,102716,102718,102720,102722,102724],{"class":202,"line":272},[200,102701,236],{"class":235},[200,102703,239],{"class":214},[200,102705,22752],{"class":223},[200,102707,227],{"class":210},[200,102709,2767],{"class":214},[200,102711,39109],{"class":235},[200,102713,239],{"class":214},[200,102715,242],{"class":214},[200,102717,41718],{"class":245},[200,102719,249],{"class":214},[200,102721,980],{"class":214},[200,102723,2104],{"class":210},[200,102725,252],{"class":214},[200,102727,102728,102730,102732,102734,102736,102738,102740,102742,102744,102746,102748,102750,102752,102754,102756,102758,102760,102762,102764,102766,102768,102770,102772,102774,102776,102778,102780,102782,102784,102786,102788,102790,102792,102794,102796,102798,102800,102802,102804,102806,102808,102810,102812,102814,102816,102818,102820,102822,102824,102826,102828,102830,102832,102834,102836,102838,102840,102842,102844,102846,102848,102850,102852,102854,102856,102858,102860,102862,102864,102866,102868,102870,102872,102874,102876,102878,102880,102882,102884,102886,102888,102890,102892,102894,102896,102898,102900,102902,102904,102906,102908,102910,102912,102914,102916,102918,102920,102922,102924,102926,102928,102930,102932,102934,102936,102938],{"class":202,"line":283},[200,102729,43180],{"class":235},[200,102731,239],{"class":214},[200,102733,42637],{"class":223},[200,102735,1549],{"class":210},[200,102737,215],{"class":214},[200,102739,42756],{"class":223},[200,102741,227],{"class":210},[200,102743,9752],{"class":386},[200,102745,393],{"class":379},[200,102747,43201],{"class":210},[200,102749,43204],{"class":214},[200,102751,42769],{"class":1364},[200,102753,549],{"class":214},[200,102755,318],{"class":214},[200,102757,39109],{"class":235},[200,102759,239],{"class":214},[200,102761,242],{"class":214},[200,102763,42782],{"class":245},[200,102765,16056],{"class":210},[200,102767,42787],{"class":245},[200,102769,249],{"class":214},[200,102771,980],{"class":214},[200,102773,2104],{"class":210},[200,102775,215],{"class":214},[200,102777,42756],{"class":223},[200,102779,227],{"class":210},[200,102781,9752],{"class":386},[200,102783,393],{"class":379},[200,102785,42709],{"class":210},[200,102787,215],{"class":214},[200,102789,43247],{"class":210},[200,102791,599],{"class":214},[200,102793,42816],{"class":2060},[200,102795,29668],{"class":214},[200,102797,42816],{"class":2060},[200,102799,549],{"class":214},[200,102801,318],{"class":214},[200,102803,39109],{"class":235},[200,102805,239],{"class":214},[200,102807,242],{"class":214},[200,102809,42782],{"class":245},[200,102811,16056],{"class":210},[200,102813,42837],{"class":245},[200,102815,249],{"class":214},[200,102817,980],{"class":214},[200,102819,2104],{"class":210},[200,102821,215],{"class":214},[200,102823,42756],{"class":223},[200,102825,227],{"class":210},[200,102827,9752],{"class":386},[200,102829,393],{"class":379},[200,102831,42709],{"class":210},[200,102833,215],{"class":214},[200,102835,3786],{"class":210},[200,102837,215],{"class":214},[200,102839,6120],{"class":223},[200,102841,227],{"class":210},[200,102843,249],{"class":214},[200,102845,42872],{"class":245},[200,102847,249],{"class":214},[200,102849,2104],{"class":210},[200,102851,549],{"class":214},[200,102853,318],{"class":214},[200,102855,39109],{"class":235},[200,102857,239],{"class":214},[200,102859,242],{"class":214},[200,102861,42782],{"class":245},[200,102863,16056],{"class":210},[200,102865,42893],{"class":245},[200,102867,249],{"class":214},[200,102869,980],{"class":214},[200,102871,2104],{"class":210},[200,102873,215],{"class":214},[200,102875,42756],{"class":223},[200,102877,227],{"class":210},[200,102879,9752],{"class":386},[200,102881,393],{"class":379},[200,102883,350],{"class":210},[200,102885,249],{"class":214},[200,102887,42918],{"class":245},[200,102889,249],{"class":214},[200,102891,549],{"class":214},[200,102893,242],{"class":214},[200,102895,42927],{"class":245},[200,102897,249],{"class":214},[200,102899,549],{"class":214},[200,102901,242],{"class":214},[200,102903,42936],{"class":245},[200,102905,249],{"class":214},[200,102907,360],{"class":210},[200,102909,215],{"class":214},[200,102911,3856],{"class":223},[200,102913,43368],{"class":210},[200,102915,215],{"class":214},[200,102917,43373],{"class":210},[200,102919,549],{"class":214},[200,102921,318],{"class":214},[200,102923,39109],{"class":235},[200,102925,239],{"class":214},[200,102927,242],{"class":214},[200,102929,42782],{"class":245},[200,102931,16056],{"class":210},[200,102933,42971],{"class":245},[200,102935,249],{"class":214},[200,102937,980],{"class":214},[200,102939,416],{"class":210},[200,102941,102942,102944],{"class":202,"line":300},[200,102943,413],{"class":214},[200,102945,416],{"class":210},[35,102947,44627],{},[35,102949,44630,102950,44633],{},[156,102951,42451],{},[192,102953,102954],{"className":1380,"code":44636,"language":1383,"meta":13,"style":13},[156,102955,102956,102982,102986,103002,103030,103036,103040,103056,103270,103276,103280],{"__ignoreMap":13},[200,102957,102958,102960,102962,102964,102966,102968,102970,102972,102974,102976,102978,102980],{"class":202,"line":203},[200,102959,972],{"class":206},[200,102961,318],{"class":214},[200,102963,42637],{"class":210},[200,102965,549],{"class":214},[200,102967,42642],{"class":210},[200,102969,549],{"class":214},[200,102971,22752],{"class":210},[200,102973,980],{"class":214},[200,102975,983],{"class":206},[200,102977,242],{"class":214},[200,102979,19813],{"class":245},[200,102981,991],{"class":214},[200,102983,102984],{"class":202,"line":14},[200,102985,528],{"emptyLinePlaceholder":20},[200,102987,102988,102990,102992,102994,102996,102998,103000],{"class":202,"line":255},[200,102989,1390],{"class":206},[200,102991,14305],{"class":379},[200,102993,44677],{"class":210},[200,102995,449],{"class":214},[200,102997,42642],{"class":223},[200,102999,227],{"class":210},[200,103001,230],{"class":214},[200,103003,103004,103006,103008,103010,103012,103014,103016,103018,103020,103022,103024,103026,103028],{"class":202,"line":272},[200,103005,236],{"class":235},[200,103007,239],{"class":214},[200,103009,22752],{"class":223},[200,103011,227],{"class":210},[200,103013,2767],{"class":214},[200,103015,39109],{"class":235},[200,103017,239],{"class":214},[200,103019,242],{"class":214},[200,103021,41718],{"class":245},[200,103023,249],{"class":214},[200,103025,980],{"class":214},[200,103027,2104],{"class":210},[200,103029,252],{"class":214},[200,103031,103032,103034],{"class":202,"line":283},[200,103033,413],{"class":214},[200,103035,416],{"class":210},[200,103037,103038],{"class":202,"line":300},[200,103039,528],{"emptyLinePlaceholder":20},[200,103041,103042,103044,103046,103048,103050,103052,103054],{"class":202,"line":310},[200,103043,1390],{"class":206},[200,103045,14305],{"class":379},[200,103047,43167],{"class":210},[200,103049,449],{"class":214},[200,103051,42642],{"class":223},[200,103053,227],{"class":210},[200,103055,230],{"class":214},[200,103057,103058,103060,103062,103064,103066,103068,103070,103072,103074,103076,103078,103080,103082,103084,103086,103088,103090,103092,103094,103096,103098,103100,103102,103104,103106,103108,103110,103112,103114,103116,103118,103120,103122,103124,103126,103128,103130,103132,103134,103136,103138,103140,103142,103144,103146,103148,103150,103152,103154,103156,103158,103160,103162,103164,103166,103168,103170,103172,103174,103176,103178,103180,103182,103184,103186,103188,103190,103192,103194,103196,103198,103200,103202,103204,103206,103208,103210,103212,103214,103216,103218,103220,103222,103224,103226,103228,103230,103232,103234,103236,103238,103240,103242,103244,103246,103248,103250,103252,103254,103256,103258,103260,103262,103264,103266,103268],{"class":202,"line":336},[200,103059,43180],{"class":235},[200,103061,239],{"class":214},[200,103063,42637],{"class":223},[200,103065,1549],{"class":210},[200,103067,215],{"class":214},[200,103069,42756],{"class":223},[200,103071,227],{"class":210},[200,103073,9752],{"class":386},[200,103075,393],{"class":379},[200,103077,43201],{"class":210},[200,103079,43204],{"class":214},[200,103081,42769],{"class":1364},[200,103083,549],{"class":214},[200,103085,318],{"class":214},[200,103087,39109],{"class":235},[200,103089,239],{"class":214},[200,103091,242],{"class":214},[200,103093,42782],{"class":245},[200,103095,16056],{"class":210},[200,103097,42787],{"class":245},[200,103099,249],{"class":214},[200,103101,980],{"class":214},[200,103103,2104],{"class":210},[200,103105,215],{"class":214},[200,103107,42756],{"class":223},[200,103109,227],{"class":210},[200,103111,9752],{"class":386},[200,103113,393],{"class":379},[200,103115,42709],{"class":210},[200,103117,215],{"class":214},[200,103119,43247],{"class":210},[200,103121,599],{"class":214},[200,103123,42816],{"class":2060},[200,103125,29668],{"class":214},[200,103127,42816],{"class":2060},[200,103129,549],{"class":214},[200,103131,318],{"class":214},[200,103133,39109],{"class":235},[200,103135,239],{"class":214},[200,103137,242],{"class":214},[200,103139,42782],{"class":245},[200,103141,16056],{"class":210},[200,103143,42837],{"class":245},[200,103145,249],{"class":214},[200,103147,980],{"class":214},[200,103149,2104],{"class":210},[200,103151,215],{"class":214},[200,103153,42756],{"class":223},[200,103155,227],{"class":210},[200,103157,9752],{"class":386},[200,103159,393],{"class":379},[200,103161,42709],{"class":210},[200,103163,215],{"class":214},[200,103165,3786],{"class":210},[200,103167,215],{"class":214},[200,103169,6120],{"class":223},[200,103171,227],{"class":210},[200,103173,249],{"class":214},[200,103175,42872],{"class":245},[200,103177,249],{"class":214},[200,103179,2104],{"class":210},[200,103181,549],{"class":214},[200,103183,318],{"class":214},[200,103185,39109],{"class":235},[200,103187,239],{"class":214},[200,103189,242],{"class":214},[200,103191,42782],{"class":245},[200,103193,16056],{"class":210},[200,103195,42893],{"class":245},[200,103197,249],{"class":214},[200,103199,980],{"class":214},[200,103201,2104],{"class":210},[200,103203,215],{"class":214},[200,103205,42756],{"class":223},[200,103207,227],{"class":210},[200,103209,9752],{"class":386},[200,103211,393],{"class":379},[200,103213,350],{"class":210},[200,103215,249],{"class":214},[200,103217,42918],{"class":245},[200,103219,249],{"class":214},[200,103221,549],{"class":214},[200,103223,242],{"class":214},[200,103225,42927],{"class":245},[200,103227,249],{"class":214},[200,103229,549],{"class":214},[200,103231,242],{"class":214},[200,103233,42936],{"class":245},[200,103235,249],{"class":214},[200,103237,360],{"class":210},[200,103239,215],{"class":214},[200,103241,3856],{"class":223},[200,103243,43368],{"class":210},[200,103245,215],{"class":214},[200,103247,43373],{"class":210},[200,103249,549],{"class":214},[200,103251,318],{"class":214},[200,103253,39109],{"class":235},[200,103255,239],{"class":214},[200,103257,242],{"class":214},[200,103259,42782],{"class":245},[200,103261,16056],{"class":210},[200,103263,42971],{"class":245},[200,103265,249],{"class":214},[200,103267,980],{"class":214},[200,103269,416],{"class":210},[200,103271,103272,103274],{"class":202,"line":342},[200,103273,413],{"class":214},[200,103275,416],{"class":210},[200,103277,103278],{"class":202,"line":365},[200,103279,528],{"emptyLinePlaceholder":20},[200,103281,103282,103284,103286,103288,103290,103292,103294,103296],{"class":202,"line":371},[200,103283,1390],{"class":206},[200,103285,14305],{"class":379},[200,103287,44368],{"class":210},[200,103289,449],{"class":214},[200,103291,44976],{"class":210},[200,103293,215],{"class":214},[200,103295,44981],{"class":223},[200,103297,44984],{"class":210},[35,103299,44987,103300,44991],{},[156,103301,44990],{},[192,103303,103304],{"className":21457,"code":44994,"language":12075,"meta":13,"style":13},[156,103305,103306,103326,103346,103364,103382,103386,103410,103434,103440,103464,103468,103486,103500,103510,103516,103520,103532,103546,103550,103554],{"__ignoreMap":13},[200,103307,103308,103310,103312,103314,103316,103318,103320,103322,103324],{"class":202,"line":203},[200,103309,599],{"class":214},[200,103311,14896],{"class":235},[200,103313,14594],{"class":379},[200,103315,449],{"class":214},[200,103317,610],{"class":214},[200,103319,1383],{"class":245},[200,103321,610],{"class":214},[200,103323,21825],{"class":379},[200,103325,647],{"class":214},[200,103327,103328,103330,103332,103334,103336,103338,103340,103342,103344],{"class":202,"line":14},[200,103329,3726],{"class":379},[200,103331,5002],{"class":379},[200,103333,41783],{"class":223},[200,103335,227],{"class":214},[200,103337,25426],{"class":386},[200,103339,239],{"class":214},[200,103341,43447],{"class":1364},[200,103343,2104],{"class":214},[200,103345,280],{"class":214},[200,103347,103348,103350,103352,103354,103356,103358,103360,103362],{"class":202,"line":255},[200,103349,3738],{"class":379},[200,103351,43458],{"class":210},[200,103353,509],{"class":214},[200,103355,43463],{"class":210},[200,103357,215],{"class":214},[200,103359,43468],{"class":210},[200,103361,14678],{"class":206},[200,103363,43473],{"class":1364},[200,103365,103366,103368,103370,103372,103374,103376,103378,103380],{"class":202,"line":272},[200,103367,3738],{"class":379},[200,103369,42685],{"class":210},[200,103371,509],{"class":214},[200,103373,13007],{"class":214},[200,103375,43486],{"class":223},[200,103377,227],{"class":235},[200,103379,43468],{"class":210},[200,103381,416],{"class":235},[200,103383,103384],{"class":202,"line":283},[200,103385,528],{"emptyLinePlaceholder":20},[200,103387,103388,103390,103392,103394,103396,103398,103400,103402,103404,103406,103408],{"class":202,"line":300},[200,103389,3738],{"class":379},[200,103391,700],{"class":210},[200,103393,509],{"class":214},[200,103395,42685],{"class":210},[200,103397,215],{"class":214},[200,103399,42718],{"class":223},[200,103401,227],{"class":235},[200,103403,249],{"class":214},[200,103405,20112],{"class":245},[200,103407,249],{"class":214},[200,103409,416],{"class":235},[200,103411,103412,103414,103416,103418,103420,103422,103424,103426,103428,103430,103432],{"class":202,"line":310},[200,103413,3738],{"class":379},[200,103415,42709],{"class":210},[200,103417,509],{"class":214},[200,103419,42685],{"class":210},[200,103421,215],{"class":214},[200,103423,42718],{"class":223},[200,103425,227],{"class":235},[200,103427,249],{"class":214},[200,103429,9752],{"class":245},[200,103431,249],{"class":214},[200,103433,416],{"class":235},[200,103435,103436,103438],{"class":202,"line":336},[200,103437,41792],{"class":206},[200,103439,280],{"class":214},[200,103441,103442,103444,103446,103448,103450,103452,103454,103456,103458,103460,103462],{"class":202,"line":342},[200,103443,19231],{"class":206},[200,103445,45137],{"class":210},[200,103447,215],{"class":214},[200,103449,41312],{"class":223},[200,103451,227],{"class":235},[200,103453,2767],{"class":214},[200,103455,700],{"class":210},[200,103457,549],{"class":214},[200,103459,42709],{"class":210},[200,103461,980],{"class":214},[200,103463,416],{"class":235},[200,103465,103466],{"class":202,"line":365},[200,103467,528],{"emptyLinePlaceholder":20},[200,103469,103470,103472,103474,103476,103478,103480,103482,103484],{"class":202,"line":371},[200,103471,19231],{"class":206},[200,103473,18754],{"class":223},[200,103475,227],{"class":235},[200,103477,249],{"class":214},[200,103479,44171],{"class":245},[200,103481,249],{"class":214},[200,103483,549],{"class":214},[200,103485,280],{"class":214},[200,103487,103488,103490,103492,103494,103496,103498],{"class":202,"line":398},[200,103489,41838],{"class":235},[200,103491,239],{"class":214},[200,103493,242],{"class":214},[200,103495,18779],{"class":245},[200,103497,249],{"class":214},[200,103499,252],{"class":214},[200,103501,103502,103504,103506,103508],{"class":202,"line":405},[200,103503,41853],{"class":235},[200,103505,239],{"class":214},[200,103507,42685],{"class":210},[200,103509,252],{"class":214},[200,103511,103512,103514],{"class":202,"line":410},[200,103513,2111],{"class":214},[200,103515,416],{"class":235},[200,103517,103518],{"class":202,"line":860},[200,103519,4505],{"class":214},[200,103521,103522,103524,103526,103528,103530],{"class":202,"line":889},[200,103523,41874],{"class":206},[200,103525,1094],{"class":235},[200,103527,19275],{"class":210},[200,103529,5282],{"class":235},[200,103531,230],{"class":214},[200,103533,103534,103536,103538,103540,103542,103544],{"class":202,"line":899},[200,103535,43620],{"class":210},[200,103537,215],{"class":214},[200,103539,19275],{"class":223},[200,103541,227],{"class":235},[200,103543,19275],{"class":210},[200,103545,416],{"class":235},[200,103547,103548],{"class":202,"line":904},[200,103549,4505],{"class":214},[200,103551,103552],{"class":202,"line":930},[200,103553,1129],{"class":214},[200,103555,103556,103558,103560],{"class":202,"line":3655},[200,103557,680],{"class":214},[200,103559,14896],{"class":235},[200,103561,647],{"class":214},[35,103563,45256,103564,45260,103566,45263],{},[156,103565,45259],{},[156,103567,42451],{},[192,103569,103570],{"className":1380,"code":45266,"language":1383,"meta":13,"style":13},[156,103571,103572,103590,103594,103616,103634,103638,103662,103704,103714,103724,103738,103760,103766,103772,103776,103780,103784,103812,103824,103838,103870,103876,103880,103884,103888,103904],{"__ignoreMap":13},[200,103573,103574,103576,103578,103580,103582,103584,103586,103588],{"class":202,"line":203},[200,103575,972],{"class":206},[200,103577,318],{"class":214},[200,103579,44976],{"class":210},[200,103581,980],{"class":214},[200,103583,983],{"class":206},[200,103585,242],{"class":214},[200,103587,42350],{"class":245},[200,103589,991],{"class":214},[200,103591,103592],{"class":202,"line":14},[200,103593,528],{"emptyLinePlaceholder":20},[200,103595,103596,103598,103600,103602,103604,103606,103608,103610,103612,103614],{"class":202,"line":255},[200,103597,1390],{"class":206},[200,103599,1393],{"class":206},[200,103601,38723],{"class":223},[200,103603,227],{"class":210},[200,103605,3726],{"class":379},[200,103607,1094],{"class":214},[200,103609,25426],{"class":386},[200,103611,2104],{"class":214},[200,103613,393],{"class":379},[200,103615,280],{"class":214},[200,103617,103618,103620,103622,103624,103626,103628,103630,103632],{"class":202,"line":272},[200,103619,3738],{"class":379},[200,103621,42685],{"class":210},[200,103623,509],{"class":214},[200,103625,452],{"class":206},[200,103627,42692],{"class":223},[200,103629,227],{"class":235},[200,103631,25426],{"class":210},[200,103633,416],{"class":235},[200,103635,103636],{"class":202,"line":283},[200,103637,528],{"emptyLinePlaceholder":20},[200,103639,103640,103642,103644,103646,103648,103650,103652,103654,103656,103658,103660],{"class":202,"line":300},[200,103641,3738],{"class":379},[200,103643,700],{"class":210},[200,103645,509],{"class":214},[200,103647,42685],{"class":210},[200,103649,215],{"class":214},[200,103651,42718],{"class":223},[200,103653,227],{"class":235},[200,103655,249],{"class":214},[200,103657,20112],{"class":245},[200,103659,249],{"class":214},[200,103661,416],{"class":235},[200,103663,103664,103666,103668,103670,103672,103674,103676,103678,103680,103682,103684,103686,103688,103690,103692,103694,103696,103698,103700,103702],{"class":202,"line":310},[200,103665,3738],{"class":379},[200,103667,5056],{"class":210},[200,103669,509],{"class":214},[200,103671,452],{"class":206},[200,103673,44976],{"class":210},[200,103675,215],{"class":214},[200,103677,41312],{"class":223},[200,103679,227],{"class":235},[200,103681,2767],{"class":214},[200,103683,700],{"class":210},[200,103685,980],{"class":214},[200,103687,2104],{"class":235},[200,103689,215],{"class":214},[200,103691,41452],{"class":223},[200,103693,227],{"class":235},[200,103695,227],{"class":214},[200,103697,19275],{"class":386},[200,103699,2104],{"class":214},[200,103701,393],{"class":379},[200,103703,280],{"class":214},[200,103705,103706,103708,103710,103712],{"class":202,"line":336},[200,103707,39200],{"class":206},[200,103709,39203],{"class":223},[200,103711,227],{"class":235},[200,103713,230],{"class":214},[200,103715,103716,103718,103720,103722],{"class":202,"line":342},[200,103717,38845],{"class":235},[200,103719,239],{"class":214},[200,103721,41198],{"class":2060},[200,103723,252],{"class":214},[200,103725,103726,103728,103730,103732,103734,103736],{"class":202,"line":365},[200,103727,39233],{"class":235},[200,103729,239],{"class":214},[200,103731,242],{"class":214},[200,103733,41211],{"class":245},[200,103735,249],{"class":214},[200,103737,252],{"class":214},[200,103739,103740,103742,103744,103746,103748,103750,103752,103754,103756,103758],{"class":202,"line":371},[200,103741,41220],{"class":235},[200,103743,239],{"class":214},[200,103745,19220],{"class":210},[200,103747,215],{"class":214},[200,103749,26828],{"class":210},[200,103751,9872],{"class":235},[200,103753,3948],{"class":2060},[200,103755,360],{"class":235},[200,103757,215],{"class":214},[200,103759,43071],{"class":210},[200,103761,103762,103764],{"class":202,"line":398},[200,103763,2111],{"class":214},[200,103765,416],{"class":235},[200,103767,103768,103770],{"class":202,"line":405},[200,103769,5095],{"class":214},[200,103771,416],{"class":235},[200,103773,103774],{"class":202,"line":410},[200,103775,528],{"emptyLinePlaceholder":20},[200,103777,103778],{"class":202,"line":860},[200,103779,45477],{"class":401},[200,103781,103782],{"class":202,"line":889},[200,103783,528],{"emptyLinePlaceholder":20},[200,103785,103786,103788,103790,103792,103794,103796,103798,103800,103802,103804,103806,103808,103810],{"class":202,"line":899},[200,103787,3738],{"class":379},[200,103789,42709],{"class":210},[200,103791,509],{"class":214},[200,103793,42685],{"class":210},[200,103795,215],{"class":214},[200,103797,42718],{"class":223},[200,103799,227],{"class":235},[200,103801,249],{"class":214},[200,103803,9752],{"class":245},[200,103805,249],{"class":214},[200,103807,5282],{"class":235},[200,103809,44022],{"class":206},[200,103811,44025],{"class":1364},[200,103813,103814,103816,103818,103820,103822],{"class":202,"line":904},[200,103815,44030],{"class":223},[200,103817,227],{"class":235},[200,103819,9752],{"class":210},[200,103821,549],{"class":214},[200,103823,280],{"class":214},[200,103825,103826,103828,103830,103832,103834,103836],{"class":202,"line":930},[200,103827,44043],{"class":235},[200,103829,239],{"class":214},[200,103831,242],{"class":214},[200,103833,45532],{"class":245},[200,103835,249],{"class":214},[200,103837,252],{"class":214},[200,103839,103840,103842,103844,103846,103848,103850,103852,103854,103856,103858,103860,103862,103864,103866,103868],{"class":202,"line":3655},[200,103841,44059],{"class":235},[200,103843,239],{"class":214},[200,103845,350],{"class":235},[200,103847,249],{"class":214},[200,103849,42918],{"class":245},[200,103851,249],{"class":214},[200,103853,549],{"class":214},[200,103855,242],{"class":214},[200,103857,42927],{"class":245},[200,103859,249],{"class":214},[200,103861,549],{"class":214},[200,103863,242],{"class":214},[200,103865,42936],{"class":245},[200,103867,249],{"class":214},[200,103869,4511],{"class":235},[200,103871,103872,103874],{"class":202,"line":3660},[200,103873,5095],{"class":214},[200,103875,416],{"class":235},[200,103877,103878],{"class":202,"line":4035},[200,103879,528],{"emptyLinePlaceholder":20},[200,103881,103882],{"class":202,"line":4490},[200,103883,43092],{"class":401},[200,103885,103886],{"class":202,"line":4496},[200,103887,528],{"emptyLinePlaceholder":20},[200,103889,103890,103892,103894,103896,103898,103900,103902],{"class":202,"line":4502},[200,103891,3758],{"class":206},[200,103893,40842],{"class":223},[200,103895,227],{"class":235},[200,103897,25426],{"class":210},[200,103899,549],{"class":214},[200,103901,40851],{"class":2060},[200,103903,416],{"class":235},[200,103905,103906,103908],{"class":202,"line":4508},[200,103907,413],{"class":214},[200,103909,416],{"class":210},[35,103911,45611,103912,1569],{},[156,103913,42451],{},[192,103915,103916],{"className":1380,"code":45616,"language":1383,"meta":13,"style":13},[156,103917,103918,103928,103958,103978,104002,104006,104010,104032,104036,104040,104046,104060,104064],{"__ignoreMap":13},[200,103919,103920,103922,103924,103926],{"class":202,"line":203},[200,103921,443],{"class":379},[200,103923,10969],{"class":210},[200,103925,449],{"class":214},[200,103927,19914],{"class":214},[200,103929,103930,103932,103934,103936,103938,103940,103942,103944,103946,103948,103950,103952,103954,103956],{"class":202,"line":14},[200,103931,1089],{"class":206},[200,103933,1094],{"class":210},[200,103935,443],{"class":379},[200,103937,350],{"class":214},[200,103939,24627],{"class":210},[200,103941,549],{"class":214},[200,103943,809],{"class":210},[200,103945,360],{"class":214},[200,103947,6457],{"class":214},[200,103949,42685],{"class":210},[200,103951,215],{"class":214},[200,103953,6465],{"class":223},[200,103955,45657],{"class":210},[200,103957,230],{"class":214},[200,103959,103960,103962,103964,103966,103968,103970,103972,103974,103976],{"class":202,"line":255},[200,103961,20098],{"class":206},[200,103963,1094],{"class":235},[200,103965,24627],{"class":210},[200,103967,3298],{"class":214},[200,103969,242],{"class":214},[200,103971,45674],{"class":245},[200,103973,249],{"class":214},[200,103975,5282],{"class":235},[200,103977,230],{"class":214},[200,103979,103980,103982,103984,103986,103988,103990,103992,103994,103996,103998,104000],{"class":202,"line":272},[200,103981,45685],{"class":210},[200,103983,9872],{"class":235},[200,103985,24627],{"class":210},[200,103987,18849],{"class":235},[200,103989,449],{"class":214},[200,103991,42685],{"class":210},[200,103993,215],{"class":214},[200,103995,45700],{"class":223},[200,103997,227],{"class":235},[200,103999,24627],{"class":210},[200,104001,416],{"class":235},[200,104003,104004],{"class":202,"line":283},[200,104005,45711],{"class":206},[200,104007,104008],{"class":202,"line":300},[200,104009,4505],{"class":214},[200,104011,104012,104014,104016,104018,104020,104022,104024,104026,104028,104030],{"class":202,"line":310},[200,104013,45720],{"class":206},[200,104015,45723],{"class":206},[200,104017,1094],{"class":235},[200,104019,24627],{"class":210},[200,104021,3298],{"class":214},[200,104023,242],{"class":214},[200,104025,9752],{"class":245},[200,104027,249],{"class":214},[200,104029,5282],{"class":235},[200,104031,230],{"class":214},[200,104033,104034],{"class":202,"line":336},[200,104035,45711],{"class":206},[200,104037,104038],{"class":202,"line":342},[200,104039,4505],{"class":214},[200,104041,104042,104044],{"class":202,"line":365},[200,104043,45720],{"class":206},[200,104045,280],{"class":214},[200,104047,104048,104050,104052,104054,104056,104058],{"class":202,"line":371},[200,104049,45685],{"class":210},[200,104051,9872],{"class":235},[200,104053,24627],{"class":210},[200,104055,18849],{"class":235},[200,104057,449],{"class":214},[200,104059,45768],{"class":210},[200,104061,104062],{"class":202,"line":398},[200,104063,4505],{"class":214},[200,104065,104066],{"class":202,"line":405},[200,104067,1129],{"class":214},[51,104069,24146],{"id":24145},[35,104071,28985,104072,45784,104074],{},[1216,104073,45783],{},[1216,104075,45787,104076,45793],{},[39,104077,45792],{"href":45790,"rel":104078},[77],[35,104080,45796],{},[35,104082,45799],{},[1170,104084,45802],{},{"title":13,"searchDepth":14,"depth":14,"links":104086},[104087,104088],{"id":18646,"depth":14,"text":18647},{"id":40645,"depth":14,"text":40646,"children":104089},[104090],{"id":40873,"depth":255,"text":40874},{},{"title":40576,"description":45810},{"loc":45812},{"id":45819,"title":45820,"body":104095,"contentId":30589,"date":60222,"description":60223,"extension":18,"meta":117206,"navigation":20,"path":60225,"rawbody":60226,"robots":23,"schemaOrg":23,"seo":117207,"sitemap":117208,"stem":60254,"__hash__":60255},{"type":7,"value":104096,"toc":117202},[104097,104101,104103,104114,104116,104120,104122,104124,104126,104130,104146,104150,104154,104159,104166,104176,104322,104324,104331,104345,104347,104351,104355,104357,104361,104377,104379,104381,117200],[35,104098,104099],{},[6862,104100,45827],{},[35,104102,45830],{},[11400,104104,132,104106],{"className":104105},[45834,45835,45836,45837],[45839,104107,132,104108],{},[7151,104109,132,104110],{"icon":7153,"text":45843,"href":45844},[7151,104111,132,104112],{"icon":45847,"text":45848,"href":36274},[7151,104113],{"icon":45851,"text":45852,"href":45853},[35,104115,45856],{},[35,104117,104118],{},[6862,104119,45861],{},[35,104121,45864],{},[35,104123,45867],{},[51,104125,45871],{"id":45870},[35,104127,104128],{},[1707,104129],{"alt":45876,"src":45877},[35,104131,104132],{},[1216,104133,104134,45885,104137,45890,104140,45894,104143,215],{},[39,104135,38397],{"href":45853,"rel":104136},[77],[39,104138,45889],{"href":18579,"rel":104139},[77],[39,104141,19427],{"href":45844,"rel":104142},[77],[39,104144,11636],{"href":45853,"rel":104145},[77],[35,104147,45900,104148],{},[1216,104149,45903],{},[35,104151,104152],{},[1707,104153],{"alt":45908,"src":45909},[35,104155,45912,104156,45916],{},[39,104157,36579],{"href":37901,"rel":104158},[77],[35,104160,45919,104161,45929],{},[1216,104162,45922,104163,45928],{},[39,104164,45927],{"href":45925,"rel":104165},[77],[35,104167,45932,104168,17891,104173,45942],{},[39,104169,104171],{"href":38033,"rel":104170},[77],[156,104172,38035],{},[39,104174,37897],{"href":45940,"rel":104175},[77],[192,104177,104178],{"className":1380,"code":45945,"language":1383,"meta":13,"style":13},[156,104179,104180,104196,104204,104214,104218,104242,104256,104264,104278,104282,104288,104292,104312,104316],{"__ignoreMap":13},[200,104181,104182,104184,104186,104188,104190,104192,104194],{"class":202,"line":203},[200,104183,1390],{"class":206},[200,104185,1393],{"class":206},[200,104187,45956],{"class":210},[200,104189,215],{"class":214},[200,104191,45961],{"class":223},[200,104193,227],{"class":210},[200,104195,230],{"class":214},[200,104197,104198,104200,104202],{"class":202,"line":14},[200,104199,45970],{"class":235},[200,104201,239],{"class":214},[200,104203,280],{"class":214},[200,104205,104206,104208,104210,104212],{"class":202,"line":255},[200,104207,45979],{"class":235},[200,104209,239],{"class":214},[200,104211,2439],{"class":2438},[200,104213,252],{"class":214},[200,104215,104216],{"class":202,"line":272},[200,104217,368],{"class":214},[200,104219,104220,104222,104224,104226,104228,104230,104232,104234,104236,104238,104240],{"class":202,"line":283},[200,104221,3234],{"class":379},[200,104223,45996],{"class":235},[200,104225,227],{"class":214},[200,104227,25426],{"class":386},[200,104229,549],{"class":214},[200,104231,318],{"class":214},[200,104233,34533],{"class":386},[200,104235,549],{"class":214},[200,104237,9869],{"class":386},[200,104239,390],{"class":214},[200,104241,280],{"class":214},[200,104243,104244,104246,104248,104250,104252,104254],{"class":202,"line":300},[200,104245,19231],{"class":206},[200,104247,46021],{"class":223},[200,104249,227],{"class":235},[200,104251,25426],{"class":210},[200,104253,549],{"class":214},[200,104255,280],{"class":214},[200,104257,104258,104260,104262],{"class":202,"line":310},[200,104259,46034],{"class":235},[200,104261,239],{"class":214},[200,104263,280],{"class":214},[200,104265,104266,104268,104270,104272,104274,104276],{"class":202,"line":336},[200,104267,46043],{"class":235},[200,104269,239],{"class":214},[200,104271,34533],{"class":210},[200,104273,215],{"class":214},[200,104275,46052],{"class":210},[200,104277,252],{"class":214},[200,104279,104280],{"class":202,"line":342},[200,104281,12098],{"class":214},[200,104283,104284,104286],{"class":202,"line":365},[200,104285,2111],{"class":214},[200,104287,416],{"class":235},[200,104289,104290],{"class":202,"line":371},[200,104291,528],{"emptyLinePlaceholder":20},[200,104293,104294,104296,104298,104300,104302,104304,104306,104308,104310],{"class":202,"line":398},[200,104295,3311],{"class":206},[200,104297,46075],{"class":223},[200,104299,227],{"class":235},[200,104301,25426],{"class":210},[200,104303,549],{"class":214},[200,104305,242],{"class":214},[200,104307,2898],{"class":245},[200,104309,249],{"class":214},[200,104311,416],{"class":235},[200,104313,104314],{"class":202,"line":405},[200,104315,368],{"class":214},[200,104317,104318,104320],{"class":202,"line":410},[200,104319,413],{"class":214},[200,104321,416],{"class":210},[35,104323,46102],{},[35,104325,46105,104326,46112],{},[1216,104327,104328],{},[39,104329,45927],{"href":46110,"rel":104330},[77],[35,104332,46115,104333,46122,104338,46126,104340,46132,104343],{},[1216,104334,104335],{},[39,104336,45927],{"href":46120,"rel":104337},[77],[1216,104339,46125],{},[39,104341,46131],{"href":46129,"rel":104342},[77],[1216,104344,46135],{},[35,104346,46138],{},[35,104348,104349],{},[1216,104350,46143],{},[35,104352,46146,104353],{},[1599,104354,46149],{"repo":38413},[51,104356,46153],{"id":46152},[35,104358,104359],{},[1707,104360],{"alt":46158,"src":46159},[35,104362,104363],{},[1216,104364,104365,45885,104368,45890,104371,45894,104374,215],{},[39,104366,38397],{"href":45853,"rel":104367},[77],[39,104369,45889],{"href":18579,"rel":104370},[77],[39,104372,19427],{"href":45844,"rel":104373},[77],[39,104375,11636],{"href":45853,"rel":104376},[77],[35,104378,46178],{},[35,104380,46181],{},[7918,104382,104383],{"id":46184},[7918,104384,104385,104387,104389,104391,104393,104397,104399,104404,104412,104414,104416,104443,104445,104447,104451,104453,104457,104461,104468,104472,104474,104476,104492,104494,104498,104500,104504,104524,104526,104528,104532,104537,105131,105133,105135,105137,105141,105143,105147,105163,105165,105167,105169,105173,105175,105179,105184,105186,105208,105210,105212,105222,105226,105228,105230,105236,105280,105282,105284,105288,105290,105296,105428,105440,105444,105584,105588,105590,105592,105708,105712,105716,105988,105992,105994,106110,106112,106558,106566,106570,106574,106576],{"id":20859},[35,104386,46189],{},[1929,104388,46193],{"id":46192},[35,104390,46196],{},[35,104392,46199],{},[35,104394,104395],{},[1707,104396],{"alt":46204,"src":46205},[35,104398,46208],{},[35,104400,46211,104401,46217],{},[39,104402,46216],{"href":46214,"rel":104403},[77],[938,104405,104406,104408],{},[35,104407,46222],{},[35,104409,104410],{},[6862,104411,46227],{},[35,104413,46230],{},[35,104415,46233],{},[1593,104417,104418,104423,104428,104433,104438],{},[71,104419,104420],{},[39,104421,46242],{"href":46240,"rel":104422},[77],[71,104424,104425],{},[39,104426,46249],{"href":46247,"rel":104427},[77],[71,104429,104430],{},[39,104431,46256],{"href":46254,"rel":104432},[77],[71,104434,104435],{},[39,104436,46263],{"href":46261,"rel":104437},[77],[71,104439,104440],{},[39,104441,46270],{"href":46268,"rel":104442},[77],[35,104444,46273],{},[1929,104446,36579],{"id":46276},[35,104448,104449],{},[1707,104450],{"alt":46281,"src":46282},[35,104452,46285],{},[35,104454,46288,104455,46292],{},[1216,104456,46291],{},[35,104458,104459],{},[1707,104460],{"alt":46297,"src":46298},[35,104462,46301,104463,215],{},[39,104464,104466],{"href":46304,"rel":104465},[77],[156,104467,46308],{},[35,104469,104470],{},[1707,104471],{"alt":46313,"src":46314},[35,104473,46317],{},[35,104475,46320],{},[938,104477,104478,104480],{},[35,104479,46325],{},[1593,104481,104482,104484,104486,104488,104490],{},[71,104483,46330],{},[71,104485,46333],{},[71,104487,46336],{},[71,104489,46339],{},[71,104491,46342],{},[1929,104493,46346],{"id":46345},[35,104495,46349,104496,46352],{},[156,104497,38035],{},[35,104499,46355],{},[35,104501,104502],{},[1707,104503],{"alt":46360,"src":46361},[68,104505,104506,104510,104512,104514,104516,104518,104520,104522],{},[71,104507,46366,104508,215],{},[156,104509,46369],{},[71,104511,46372],{},[71,104513,46375],{},[71,104515,46378],{},[71,104517,46381],{},[71,104519,46384],{},[71,104521,46387],{},[71,104523,46390],{},[35,104525,46393],{},[38437,104527,46397],{"id":46396},[35,104529,46400,104530,46403],{},[156,104531,38035],{},[35,104533,46406,104534,215],{},[39,104535,2160],{"href":38519,"rel":104536},[77],[192,104538,104539],{"className":1380,"code":46412,"language":1383,"meta":13,"style":13},[156,104540,104541,104545,104549,104553,104557,104561,104589,104613,104625,104629,104633,104637,104641,104657,104661,104677,104681,104685,104689,104693,104711,104715,104719,104723,104727,104731,104751,104759,104765,104785,104799,104809,104835,104847,104855,104859,104863,104867,104871,104875,104879,104897,104911,104915,104929,104937,104951,104955,104959,104963,104967,104981,104985,104989,104993,104997,105025,105033,105051,105055,105061,105065,105069,105073,105077,105081,105085,105089,105103,105109,105115,105121,105127],{"__ignoreMap":13},[200,104542,104543],{"class":202,"line":203},[200,104544,26119],{"class":401},[200,104546,104547],{"class":202,"line":14},[200,104548,46423],{"class":401},[200,104550,104551],{"class":202,"line":255},[200,104552,46428],{"class":401},[200,104554,104555],{"class":202,"line":272},[200,104556,46433],{"class":401},[200,104558,104559],{"class":202,"line":283},[200,104560,26134],{"class":401},[200,104562,104563,104565,104567,104569,104571,104573,104575,104577,104579,104581,104583,104585,104587],{"class":202,"line":300},[200,104564,1390],{"class":206},[200,104566,5002],{"class":379},[200,104568,46446],{"class":223},[200,104570,3966],{"class":214},[200,104572,46451],{"class":386},[200,104574,549],{"class":214},[200,104576,45996],{"class":386},[200,104578,46458],{"class":214},[200,104580,46461],{"class":1364},[200,104582,599],{"class":214},[200,104584,46466],{"class":1364},[200,104586,46469],{"class":214},[200,104588,280],{"class":214},[200,104590,104591,104593,104595,104597,104599,104601,104603,104605,104607,104609,104611],{"class":202,"line":310},[200,104592,3758],{"class":206},[200,104594,46478],{"class":223},[200,104596,227],{"class":235},[200,104598,3726],{"class":379},[200,104600,1094],{"class":214},[200,104602,25426],{"class":386},[200,104604,239],{"class":214},[200,104606,39087],{"class":1364},[200,104608,2104],{"class":214},[200,104610,393],{"class":379},[200,104612,280],{"class":214},[200,104614,104615,104617,104619,104621,104623],{"class":202,"line":336},[200,104616,24973],{"class":210},[200,104618,509],{"class":214},[200,104620,318],{"class":214},[200,104622,46507],{"class":401},[200,104624,1129],{"class":214},[200,104626,104627],{"class":202,"line":342},[200,104628,528],{"emptyLinePlaceholder":20},[200,104630,104631],{"class":202,"line":365},[200,104632,19641],{"class":401},[200,104634,104635],{"class":202,"line":371},[200,104636,46522],{"class":401},[200,104638,104639],{"class":202,"line":398},[200,104640,19651],{"class":401},[200,104642,104643,104645,104647,104649,104651,104653,104655],{"class":202,"line":405},[200,104644,2014],{"class":379},[200,104646,3499],{"class":210},[200,104648,509],{"class":214},[200,104650,46537],{"class":223},[200,104652,227],{"class":235},[200,104654,25426],{"class":210},[200,104656,416],{"class":235},[200,104658,104659],{"class":202,"line":410},[200,104660,528],{"emptyLinePlaceholder":20},[200,104662,104663,104665,104667,104669,104671,104673,104675],{"class":202,"line":860},[200,104664,5267],{"class":206},[200,104666,1094],{"class":235},[200,104668,3524],{"class":210},[200,104670,215],{"class":214},[200,104672,19275],{"class":210},[200,104674,5282],{"class":235},[200,104676,230],{"class":214},[200,104678,104679],{"class":202,"line":889},[200,104680,46568],{"class":401},[200,104682,104683],{"class":202,"line":899},[200,104684,46573],{"class":206},[200,104686,104687],{"class":202,"line":904},[200,104688,4977],{"class":214},[200,104690,104691],{"class":202,"line":930},[200,104692,528],{"emptyLinePlaceholder":20},[200,104694,104695,104697,104699,104701,104703,104705,104707,104709],{"class":202,"line":3655},[200,104696,5267],{"class":206},[200,104698,1094],{"class":235},[200,104700,11852],{"class":214},[200,104702,3524],{"class":210},[200,104704,215],{"class":214},[200,104706,156],{"class":210},[200,104708,5282],{"class":235},[200,104710,230],{"class":214},[200,104712,104713],{"class":202,"line":3660},[200,104714,46604],{"class":401},[200,104716,104717],{"class":202,"line":4035},[200,104718,46609],{"class":401},[200,104720,104721],{"class":202,"line":4490},[200,104722,46614],{"class":401},[200,104724,104725],{"class":202,"line":4496},[200,104726,46619],{"class":401},[200,104728,104729],{"class":202,"line":4502},[200,104730,46624],{"class":401},[200,104732,104733,104735,104737,104739,104741,104743,104745,104747,104749],{"class":202,"line":4508},[200,104734,20136],{"class":379},[200,104736,46631],{"class":210},[200,104738,509],{"class":214},[200,104740,46636],{"class":223},[200,104742,227],{"class":235},[200,104744,25426],{"class":210},[200,104746,2104],{"class":235},[200,104748,215],{"class":214},[200,104750,46647],{"class":210},[200,104752,104753,104755,104757],{"class":202,"line":5469},[200,104754,11730],{"class":206},[200,104756,46075],{"class":223},[200,104758,3130],{"class":235},[200,104760,104761,104763],{"class":202,"line":5474},[200,104762,46660],{"class":210},[200,104764,252],{"class":214},[200,104766,104767,104769,104771,104773,104775,104777,104779,104781,104783],{"class":202,"line":13237},[200,104768,46667],{"class":223},[200,104770,227],{"class":235},[200,104772,13490],{"class":210},[200,104774,215],{"class":214},[200,104776,46676],{"class":210},[200,104778,14678],{"class":206},[200,104780,22752],{"class":1364},[200,104782,549],{"class":214},[200,104784,280],{"class":214},[200,104786,104787,104789,104791,104793,104795,104797],{"class":202,"line":13263},[200,104788,46689],{"class":235},[200,104790,239],{"class":214},[200,104792,46451],{"class":210},[200,104794,215],{"class":214},[200,104796,46698],{"class":210},[200,104798,252],{"class":214},[200,104800,104801,104803,104805,104807],{"class":202,"line":13279},[200,104802,46705],{"class":235},[200,104804,239],{"class":214},[200,104806,46631],{"class":210},[200,104808,252],{"class":214},[200,104810,104811,104813,104815,104817,104819,104821,104823,104825,104827,104829,104831,104833],{"class":202,"line":13323},[200,104812,46716],{"class":235},[200,104814,239],{"class":214},[200,104816,46451],{"class":210},[200,104818,215],{"class":214},[200,104820,46725],{"class":210},[200,104822,215],{"class":214},[200,104824,6202],{"class":223},[200,104826,227],{"class":235},[200,104828,249],{"class":214},[200,104830,242],{"class":214},[200,104832,2104],{"class":235},[200,104834,252],{"class":214},[200,104836,104837,104839,104841,104843,104845],{"class":202,"line":13329},[200,104838,46744],{"class":214},[200,104840,13490],{"class":210},[200,104842,215],{"class":214},[200,104844,46751],{"class":210},[200,104846,252],{"class":214},[200,104848,104849,104851,104853],{"class":202,"line":13334},[200,104850,13388],{"class":214},[200,104852,2104],{"class":235},[200,104854,252],{"class":214},[200,104856,104857],{"class":202,"line":13360},[200,104858,46766],{"class":235},[200,104860,104861],{"class":202,"line":13365},[200,104862,4977],{"class":214},[200,104864,104865],{"class":202,"line":13385},[200,104866,528],{"emptyLinePlaceholder":20},[200,104868,104869],{"class":202,"line":13405},[200,104870,19641],{"class":401},[200,104872,104873],{"class":202,"line":13410},[200,104874,46783],{"class":401},[200,104876,104877],{"class":202,"line":13418},[200,104878,19651],{"class":401},[200,104880,104881,104883,104885,104887,104889,104891,104893,104895],{"class":202,"line":13436},[200,104882,2014],{"class":379},[200,104884,9869],{"class":210},[200,104886,239],{"class":214},[200,104888,42637],{"class":1364},[200,104890,509],{"class":214},[200,104892,452],{"class":206},[200,104894,18754],{"class":223},[200,104896,3130],{"class":235},[200,104898,104899,104901,104903,104905,104907,104909],{"class":202,"line":13466},[200,104900,46810],{"class":210},[200,104902,215],{"class":214},[200,104904,46815],{"class":210},[200,104906,14678],{"class":206},[200,104908,22752],{"class":1364},[200,104910,252],{"class":214},[200,104912,104913],{"class":202,"line":13472},[200,104914,4457],{"class":214},[200,104916,104917,104919,104921,104923,104925,104927],{"class":202,"line":13477},[200,104918,46830],{"class":235},[200,104920,239],{"class":214},[200,104922,242],{"class":214},[200,104924,18779],{"class":245},[200,104926,249],{"class":214},[200,104928,252],{"class":214},[200,104930,104931,104933,104935],{"class":202,"line":13482},[200,104932,46845],{"class":235},[200,104934,239],{"class":214},[200,104936,280],{"class":214},[200,104938,104939,104941,104943,104945,104947,104949],{"class":202,"line":13497},[200,104940,13421],{"class":235},[200,104942,239],{"class":214},[200,104944,3499],{"class":210},[200,104946,215],{"class":214},[200,104948,156],{"class":210},[200,104950,252],{"class":214},[200,104952,104953],{"class":202,"line":13508},[200,104954,4965],{"class":214},[200,104956,104957],{"class":202,"line":13513},[200,104958,12098],{"class":214},[200,104960,104961],{"class":202,"line":13518},[200,104962,3869],{"class":235},[200,104964,104965],{"class":202,"line":20247},[200,104966,528],{"emptyLinePlaceholder":20},[200,104968,104969,104971,104973,104975,104977,104979],{"class":202,"line":20269},[200,104970,2014],{"class":379},[200,104972,46886],{"class":210},[200,104974,509],{"class":214},[200,104976,9869],{"class":210},[200,104978,215],{"class":214},[200,104980,46895],{"class":210},[200,104982,104983],{"class":202,"line":20274},[200,104984,528],{"emptyLinePlaceholder":20},[200,104986,104987],{"class":202,"line":20279},[200,104988,19641],{"class":401},[200,104990,104991],{"class":202,"line":20284},[200,104992,46908],{"class":401},[200,104994,104995],{"class":202,"line":20305},[200,104996,19651],{"class":401},[200,104998,104999,105001,105003,105005,105007,105009,105011,105013,105015,105017,105019,105021,105023],{"class":202,"line":20312},[200,105000,2014],{"class":379},[200,105002,34533],{"class":210},[200,105004,239],{"class":214},[200,105006,42637],{"class":1364},[200,105008,509],{"class":214},[200,105010,452],{"class":206},[200,105012,4858],{"class":223},[200,105014,227],{"class":235},[200,105016,249],{"class":214},[200,105018,46935],{"class":245},[200,105020,249],{"class":214},[200,105022,549],{"class":214},[200,105024,280],{"class":214},[200,105026,105027,105029,105031],{"class":202,"line":20317},[200,105028,46946],{"class":235},[200,105030,239],{"class":214},[200,105032,280],{"class":214},[200,105034,105035,105037,105039,105041,105043,105045,105047,105049],{"class":202,"line":20333},[200,105036,46955],{"class":235},[200,105038,239],{"class":214},[200,105040,11154],{"class":214},[200,105042,46962],{"class":245},[200,105044,4868],{"class":214},[200,105046,46967],{"class":210},[200,105048,23045],{"class":214},[200,105050,252],{"class":214},[200,105052,105053],{"class":202,"line":46974},[200,105054,12098],{"class":214},[200,105056,105057,105059],{"class":202,"line":46979},[200,105058,2111],{"class":214},[200,105060,416],{"class":235},[200,105062,105063],{"class":202,"line":46986},[200,105064,528],{"emptyLinePlaceholder":20},[200,105066,105067],{"class":202,"line":46991},[200,105068,19641],{"class":401},[200,105070,105071],{"class":202,"line":46996},[200,105072,46999],{"class":401},[200,105074,105075],{"class":202,"line":47002},[200,105076,47005],{"class":401},[200,105078,105079],{"class":202,"line":47008},[200,105080,47011],{"class":401},[200,105082,105083],{"class":202,"line":47014},[200,105084,47017],{"class":401},[200,105086,105087],{"class":202,"line":47020},[200,105088,19651],{"class":401},[200,105090,105091,105093,105095,105097,105099,105101],{"class":202,"line":47025},[200,105092,3311],{"class":206},[200,105094,45996],{"class":223},[200,105096,227],{"class":235},[200,105098,25426],{"class":210},[200,105100,549],{"class":214},[200,105102,280],{"class":214},[200,105104,105105,105107],{"class":202,"line":47040},[200,105106,46034],{"class":210},[200,105108,252],{"class":214},[200,105110,105111,105113],{"class":202,"line":47047},[200,105112,47050],{"class":210},[200,105114,252],{"class":214},[200,105116,105117,105119],{"class":202,"line":47055},[200,105118,2111],{"class":214},[200,105120,416],{"class":235},[200,105122,105123,105125],{"class":202,"line":47062},[200,105124,5095],{"class":214},[200,105126,416],{"class":235},[200,105128,105129],{"class":202,"line":47069},[200,105130,1129],{"class":214},[35,105132,47074],{},[1929,105134,24146],{"id":24145},[35,105136,47079],{},[35,105138,46146,105139],{},[1599,105140,46149],{"repo":38413},[51,105142,47087],{"id":47086},[35,105144,105145],{},[1707,105146],{"alt":47092,"src":47093},[35,105148,105149],{},[1216,105150,105151,47101,105154,47105,105157,45894,105160,215],{},[39,105152,38397],{"href":45853,"rel":105153},[77],[39,105155,45889],{"href":18579,"rel":105156},[77],[39,105158,19427],{"href":45844,"rel":105159},[77],[39,105161,11636],{"href":45853,"rel":105162},[77],[35,105164,47114],{},[1929,105166,47118],{"id":47117},[35,105168,47121],{},[35,105170,47124,105171],{},[1216,105172,47127],{},[35,105174,47130],{},[35,105176,105177],{},[1707,105178],{"alt":47118,"src":47135},[35,105180,47138,105181,47144],{},[39,105182,47143],{"href":47141,"rel":105183},[77],[35,105185,47147],{},[1593,105187,105188,105192,105196,105200,105204],{},[71,105189,47152,105190,47156],{},[156,105191,47155],{},[71,105193,47152,105194,47162],{},[156,105195,47161],{},[71,105197,47152,105198,47167],{},[156,105199,2476],{},[71,105201,47152,105202,47173],{},[156,105203,47172],{},[71,105205,47152,105206,47179],{},[156,105207,47178],{},[35,105209,47182],{},[1929,105211,31458],{"id":31457},[35,105213,47187,105214,47193,105217,47199,105220],{},[39,105215,47192],{"href":47190,"rel":105216},[77],[39,105218,47198],{"href":47196,"rel":105219},[77],[1216,105221,47202],{},[35,105223,47205,105224,47209],{},[1216,105225,47208],{},[35,105227,47212],{},[1929,105229,47216],{"id":47215},[35,105231,47219,105232,47223,105234,215],{},[1216,105233,47222],{},[156,105235,47226],{},[192,105237,105238],{"className":21457,"code":47229,"language":12075,"meta":13,"style":13},[156,105239,105240,105248,105256,105264,105272],{"__ignoreMap":13},[200,105241,105242,105244,105246],{"class":202,"line":203},[200,105243,599],{"class":214},[200,105245,21468],{"class":235},[200,105247,647],{"class":214},[200,105249,105250,105252,105254],{"class":202,"line":14},[200,105251,656],{"class":214},[200,105253,47246],{"class":235},[200,105255,647],{"class":214},[200,105257,105258,105260,105262],{"class":202,"line":255},[200,105259,803],{"class":214},[200,105261,47255],{"class":235},[200,105263,21527],{"class":214},[200,105265,105266,105268,105270],{"class":202,"line":272},[200,105267,892],{"class":214},[200,105269,47246],{"class":235},[200,105271,647],{"class":214},[200,105273,105274,105276,105278],{"class":202,"line":283},[200,105275,680],{"class":214},[200,105277,21468],{"class":235},[200,105279,647],{"class":214},[1929,105281,47277],{"id":47276},[35,105283,47280],{},[35,105285,105286],{},[1707,105287],{"alt":47285,"src":47286},[38437,105289,47290],{"id":47289},[35,105291,47293,105292,47297,105294,47301],{},[156,105293,47296],{},[156,105295,47300],{},[192,105297,105298],{"className":1380,"code":47304,"language":1383,"meta":13,"style":13},[156,105299,105300,105318,105340,105358,105376,105392,105412,105416,105422],{"__ignoreMap":13},[200,105301,105302,105304,105306,105308,105310,105312,105314,105316],{"class":202,"line":203},[200,105303,1390],{"class":206},[200,105305,1393],{"class":206},[200,105307,38723],{"class":223},[200,105309,227],{"class":210},[200,105311,3726],{"class":379},[200,105313,3729],{"class":214},[200,105315,393],{"class":379},[200,105317,280],{"class":214},[200,105319,105320,105322,105324,105326,105328,105330,105332,105334,105336,105338],{"class":202,"line":14},[200,105321,3738],{"class":379},[200,105323,47331],{"class":210},[200,105325,509],{"class":214},[200,105327,452],{"class":206},[200,105329,47338],{"class":223},[200,105331,1549],{"class":235},[200,105333,215],{"class":214},[200,105335,769],{"class":223},[200,105337,227],{"class":235},[200,105339,230],{"class":214},[200,105341,105342,105344,105346,105348,105350,105352,105354,105356],{"class":202,"line":255},[200,105343,15861],{"class":235},[200,105345,239],{"class":214},[200,105347,47357],{"class":210},[200,105349,215],{"class":214},[200,105351,47161],{"class":210},[200,105353,215],{"class":214},[200,105355,3295],{"class":210},[200,105357,252],{"class":214},[200,105359,105360,105362,105364,105366,105368,105370,105372,105374],{"class":202,"line":272},[200,105361,47372],{"class":235},[200,105363,239],{"class":214},[200,105365,47357],{"class":210},[200,105367,215],{"class":214},[200,105369,47161],{"class":210},[200,105371,215],{"class":214},[200,105373,47385],{"class":210},[200,105375,252],{"class":214},[200,105377,105378,105380,105382,105384,105386,105388,105390],{"class":202,"line":283},[200,105379,11812],{"class":235},[200,105381,239],{"class":214},[200,105383,47357],{"class":210},[200,105385,215],{"class":214},[200,105387,47161],{"class":210},[200,105389,215],{"class":214},[200,105391,29957],{"class":210},[200,105393,105394,105396,105398,105400,105402,105404,105406,105408,105410],{"class":202,"line":300},[200,105395,5095],{"class":214},[200,105397,2104],{"class":235},[200,105399,215],{"class":214},[200,105401,1489],{"class":223},[200,105403,227],{"class":235},[200,105405,47418],{"class":210},[200,105407,215],{"class":214},[200,105409,47161],{"class":210},[200,105411,416],{"class":235},[200,105413,105414],{"class":202,"line":310},[200,105415,528],{"emptyLinePlaceholder":20},[200,105417,105418,105420],{"class":202,"line":336},[200,105419,3758],{"class":206},[200,105421,47435],{"class":210},[200,105423,105424,105426],{"class":202,"line":342},[200,105425,413],{"class":214},[200,105427,416],{"class":210},[35,105429,41033,105430,47447,105432,3092,105434,9502,105436,106,105438],{},[156,105431,47446],{},[156,105433,3295],{},[156,105435,47385],{},[156,105437,20112],{},[1216,105439,47456],{},[35,105441,47459,105442,47463],{},[156,105443,47462],{},[192,105445,105446],{"className":1380,"code":47466,"language":1383,"meta":13,"style":13},[156,105447,105448,105470,105488,105492,105522,105542,105556,105568,105572,105578],{"__ignoreMap":13},[200,105449,105450,105452,105454,105456,105458,105460,105462,105464,105466,105468],{"class":202,"line":203},[200,105451,1390],{"class":206},[200,105453,1393],{"class":206},[200,105455,38723],{"class":223},[200,105457,227],{"class":210},[200,105459,3726],{"class":379},[200,105461,1094],{"class":214},[200,105463,25426],{"class":386},[200,105465,2104],{"class":214},[200,105467,393],{"class":379},[200,105469,280],{"class":214},[200,105471,105472,105474,105476,105478,105480,105482,105484,105486],{"class":202,"line":14},[200,105473,3738],{"class":379},[200,105475,40809],{"class":210},[200,105477,509],{"class":214},[200,105479,452],{"class":206},[200,105481,40816],{"class":223},[200,105483,227],{"class":235},[200,105485,25426],{"class":210},[200,105487,416],{"class":235},[200,105489,105490],{"class":202,"line":255},[200,105491,528],{"emptyLinePlaceholder":20},[200,105493,105494,105496,105498,105500,105502,105504,105506,105508,105510,105512,105514,105516,105518,105520],{"class":202,"line":272},[200,105495,6177],{"class":206},[200,105497,47338],{"class":223},[200,105499,1549],{"class":235},[200,105501,215],{"class":214},[200,105503,47525],{"class":223},[200,105505,227],{"class":235},[200,105507,47418],{"class":210},[200,105509,215],{"class":214},[200,105511,47161],{"class":210},[200,105513,2104],{"class":235},[200,105515,215],{"class":214},[200,105517,47540],{"class":223},[200,105519,227],{"class":235},[200,105521,230],{"class":214},[200,105523,105524,105526,105528,105530,105532,105534,105536,105538,105540],{"class":202,"line":283},[200,105525,47372],{"class":235},[200,105527,239],{"class":214},[200,105529,47553],{"class":223},[200,105531,227],{"class":235},[200,105533,23914],{"class":210},[200,105535,215],{"class":214},[200,105537,20112],{"class":210},[200,105539,2104],{"class":235},[200,105541,252],{"class":214},[200,105543,105544,105546,105548,105550,105552,105554],{"class":202,"line":300},[200,105545,11812],{"class":235},[200,105547,239],{"class":214},[200,105549,40809],{"class":210},[200,105551,215],{"class":214},[200,105553,20112],{"class":210},[200,105555,252],{"class":214},[200,105557,105558,105560,105562,105564,105566],{"class":202,"line":310},[200,105559,5095],{"class":214},[200,105561,2104],{"class":235},[200,105563,215],{"class":214},[200,105565,47590],{"class":223},[200,105567,466],{"class":235},[200,105569,105570],{"class":202,"line":336},[200,105571,528],{"emptyLinePlaceholder":20},[200,105573,105574,105576],{"class":202,"line":342},[200,105575,3758],{"class":206},[200,105577,47603],{"class":210},[200,105579,105580,105582],{"class":202,"line":365},[200,105581,413],{"class":214},[200,105583,416],{"class":210},[35,105585,47612,105586,215],{},[6862,105587,47615],{},[51,105589,47619],{"id":47618},[35,105591,47622],{},[192,105593,105594],{"className":21457,"code":47625,"language":12075,"meta":13,"style":13},[156,105595,105596,105616,105624,105636,105642,105650,105654,105662,105670,105680,105684,105692,105700],{"__ignoreMap":13},[200,105597,105598,105600,105602,105604,105606,105608,105610,105612,105614],{"class":202,"line":203},[200,105599,599],{"class":214},[200,105601,14896],{"class":235},[200,105603,21825],{"class":379},[200,105605,14594],{"class":379},[200,105607,449],{"class":214},[200,105609,610],{"class":214},[200,105611,1383],{"class":245},[200,105613,610],{"class":214},[200,105615,647],{"class":214},[200,105617,105618,105620,105622],{"class":202,"line":14},[200,105619,47652],{"class":223},[200,105621,227],{"class":210},[200,105623,230],{"class":214},[200,105625,105626,105628,105630,105632,105634],{"class":202,"line":255},[200,105627,47661],{"class":235},[200,105629,239],{"class":214},[200,105631,242],{"class":214},[200,105633,39189],{"class":245},[200,105635,991],{"class":214},[200,105637,105638,105640],{"class":202,"line":272},[200,105639,413],{"class":214},[200,105641,416],{"class":210},[200,105643,105644,105646,105648],{"class":202,"line":283},[200,105645,680],{"class":214},[200,105647,14896],{"class":235},[200,105649,647],{"class":214},[200,105651,105652],{"class":202,"line":300},[200,105653,528],{"emptyLinePlaceholder":20},[200,105655,105656,105658,105660],{"class":202,"line":310},[200,105657,599],{"class":214},[200,105659,21468],{"class":235},[200,105661,647],{"class":214},[200,105663,105664,105666,105668],{"class":202,"line":336},[200,105665,656],{"class":214},[200,105667,47702],{"class":235},[200,105669,647],{"class":214},[200,105671,105672,105674,105676,105678],{"class":202,"line":342},[200,105673,803],{"class":214},[200,105675,47711],{"class":235},[200,105677,47714],{"class":379},[200,105679,647],{"class":214},[200,105681,105682],{"class":202,"line":365},[200,105683,47721],{"class":401},[200,105685,105686,105688,105690],{"class":202,"line":371},[200,105687,15078],{"class":214},[200,105689,47711],{"class":235},[200,105691,647],{"class":214},[200,105693,105694,105696,105698],{"class":202,"line":398},[200,105695,892],{"class":214},[200,105697,47702],{"class":235},[200,105699,647],{"class":214},[200,105701,105702,105704,105706],{"class":202,"line":405},[200,105703,680],{"class":214},[200,105705,21468],{"class":235},[200,105707,647],{"class":214},[35,105709,47748,105710,47751],{},[156,105711,47711],{},[35,105713,47754,105714,2186],{},[156,105715,47711],{},[192,105717,105718],{"className":21457,"code":47759,"language":12075,"meta":13,"style":13},[156,105719,105720,105740,105752,105766,105778,105784,105798,105812,105820,105826,105840,105854,105860,105864,105900,105910,105916,105924,105928,105936,105980],{"__ignoreMap":13},[200,105721,105722,105724,105726,105728,105730,105732,105734,105736,105738],{"class":202,"line":203},[200,105723,599],{"class":214},[200,105725,14896],{"class":235},[200,105727,21825],{"class":379},[200,105729,14594],{"class":379},[200,105731,449],{"class":214},[200,105733,610],{"class":214},[200,105735,1383],{"class":245},[200,105737,610],{"class":214},[200,105739,647],{"class":214},[200,105741,105742,105744,105746,105748,105750],{"class":202,"line":14},[200,105743,443],{"class":379},[200,105745,47788],{"class":210},[200,105747,449],{"class":214},[200,105749,350],{"class":210},[200,105751,230],{"class":214},[200,105753,105754,105756,105758,105760,105762,105764],{"class":202,"line":255},[200,105755,18976],{"class":235},[200,105757,239],{"class":214},[200,105759,242],{"class":214},[200,105761,3295],{"class":245},[200,105763,249],{"class":214},[200,105765,252],{"class":214},[200,105767,105768,105770,105772,105774,105776],{"class":202,"line":272},[200,105769,47813],{"class":235},[200,105771,239],{"class":214},[200,105773,242],{"class":214},[200,105775,2902],{"class":245},[200,105777,991],{"class":214},[200,105779,105780,105782],{"class":202,"line":283},[200,105781,26897],{"class":214},[200,105783,280],{"class":214},[200,105785,105786,105788,105790,105792,105794,105796],{"class":202,"line":300},[200,105787,18976],{"class":235},[200,105789,239],{"class":214},[200,105791,242],{"class":214},[200,105793,20112],{"class":245},[200,105795,249],{"class":214},[200,105797,252],{"class":214},[200,105799,105800,105802,105804,105806,105808,105810],{"class":202,"line":310},[200,105801,47813],{"class":235},[200,105803,239],{"class":214},[200,105805,242],{"class":214},[200,105807,40718],{"class":245},[200,105809,249],{"class":214},[200,105811,252],{"class":214},[200,105813,105814,105816,105818],{"class":202,"line":336},[200,105815,47860],{"class":235},[200,105817,239],{"class":214},[200,105819,22236],{"class":2438},[200,105821,105822,105824],{"class":202,"line":342},[200,105823,26897],{"class":214},[200,105825,280],{"class":214},[200,105827,105828,105830,105832,105834,105836,105838],{"class":202,"line":365},[200,105829,18976],{"class":235},[200,105831,239],{"class":214},[200,105833,242],{"class":214},[200,105835,47385],{"class":245},[200,105837,249],{"class":214},[200,105839,252],{"class":214},[200,105841,105842,105844,105846,105848,105850,105852],{"class":202,"line":371},[200,105843,47813],{"class":235},[200,105845,239],{"class":214},[200,105847,242],{"class":214},[200,105849,47895],{"class":245},[200,105851,249],{"class":214},[200,105853,252],{"class":214},[200,105855,105856,105858],{"class":202,"line":398},[200,105857,413],{"class":214},[200,105859,4511],{"class":210},[200,105861,105862],{"class":202,"line":405},[200,105863,528],{"emptyLinePlaceholder":20},[200,105865,105866,105868,105870,105872,105874,105876,105878,105880,105882,105884,105886,105888,105890,105892,105894,105896,105898],{"class":202,"line":410},[200,105867,443],{"class":379},[200,105869,318],{"class":214},[200,105871,5056],{"class":235},[200,105873,239],{"class":214},[200,105875,47331],{"class":210},[200,105877,549],{"class":214},[200,105879,47926],{"class":210},[200,105881,413],{"class":214},[200,105883,509],{"class":214},[200,105885,452],{"class":206},[200,105887,47935],{"class":223},[200,105889,227],{"class":210},[200,105891,249],{"class":214},[200,105893,47942],{"class":245},[200,105895,249],{"class":214},[200,105897,549],{"class":214},[200,105899,280],{"class":214},[200,105901,105902,105904,105906,105908],{"class":202,"line":860},[200,105903,47953],{"class":235},[200,105905,239],{"class":214},[200,105907,16169],{"class":2438},[200,105909,252],{"class":214},[200,105911,105912,105914],{"class":202,"line":889},[200,105913,413],{"class":214},[200,105915,416],{"class":210},[200,105917,105918,105920,105922],{"class":202,"line":899},[200,105919,680],{"class":214},[200,105921,14896],{"class":235},[200,105923,647],{"class":214},[200,105925,105926],{"class":202,"line":904},[200,105927,528],{"emptyLinePlaceholder":20},[200,105929,105930,105932,105934],{"class":202,"line":930},[200,105931,599],{"class":214},[200,105933,21468],{"class":235},[200,105935,647],{"class":214},[200,105937,105938,105940,105942,105944,105946,105948,105950,105952,105954,105956,105958,105960,105962,105964,105966,105968,105970,105972,105974,105976,105978],{"class":202,"line":3655},[200,105939,656],{"class":214},[200,105941,47992],{"class":235},[200,105943,2186],{"class":214},[200,105945,47997],{"class":379},[200,105947,449],{"class":214},[200,105949,610],{"class":214},[200,105951,47997],{"class":210},[200,105953,610],{"class":214},[200,105955,2186],{"class":214},[200,105957,48010],{"class":379},[200,105959,449],{"class":214},[200,105961,610],{"class":214},[200,105963,47161],{"class":210},[200,105965,610],{"class":214},[200,105967,2186],{"class":214},[200,105969,9900],{"class":379},[200,105971,449],{"class":214},[200,105973,610],{"class":214},[200,105975,48029],{"class":210},[200,105977,610],{"class":214},[200,105979,21527],{"class":214},[200,105981,105982,105984,105986],{"class":202,"line":3660},[200,105983,680],{"class":214},[200,105985,21468],{"class":235},[200,105987,647],{"class":214},[35,105989,48044,105990],{},[1216,105991,48047],{},[35,105993,48050],{},[192,105995,105996],{"className":21457,"code":48053,"language":12075,"meta":13,"style":13},[156,105997,105998,106006,106012,106024,106028,106040,106046,106058,106070,106082,106086,106094,106102],{"__ignoreMap":13},[200,105999,106000,106002,106004],{"class":202,"line":203},[200,106001,599],{"class":214},[200,106003,21468],{"class":235},[200,106005,647],{"class":214},[200,106007,106008,106010],{"class":202,"line":14},[200,106009,656],{"class":214},[200,106011,48070],{"class":235},[200,106013,106014,106016,106018,106020,106022],{"class":202,"line":255},[200,106015,21482],{"class":379},[200,106017,449],{"class":214},[200,106019,610],{"class":214},[200,106021,48081],{"class":245},[200,106023,616],{"class":214},[200,106025,106026],{"class":202,"line":272},[200,106027,11550],{"class":214},[200,106029,106030,106032,106034,106036,106038],{"class":202,"line":283},[200,106031,803],{"class":214},[200,106033,21468],{"class":235},[200,106035,23911],{"class":214},[200,106037,48098],{"class":379},[200,106039,647],{"class":214},[200,106041,106042,106044],{"class":202,"line":300},[200,106043,22071],{"class":214},[200,106045,48107],{"class":235},[200,106047,106048,106050,106052,106054,106056],{"class":202,"line":310},[200,106049,23928],{"class":379},[200,106051,449],{"class":214},[200,106053,610],{"class":214},[200,106055,48118],{"class":245},[200,106057,616],{"class":214},[200,106059,106060,106062,106064,106066,106068],{"class":202,"line":336},[200,106061,48125],{"class":379},[200,106063,449],{"class":214},[200,106065,610],{"class":214},[200,106067,48132],{"class":245},[200,106069,616],{"class":214},[200,106071,106072,106074,106076,106078,106080],{"class":202,"line":342},[200,106073,48139],{"class":379},[200,106075,449],{"class":214},[200,106077,610],{"class":214},[200,106079,48146],{"class":245},[200,106081,616],{"class":214},[200,106083,106084],{"class":202,"line":365},[200,106085,23957],{"class":214},[200,106087,106088,106090,106092],{"class":202,"line":371},[200,106089,15078],{"class":214},[200,106091,21468],{"class":235},[200,106093,647],{"class":214},[200,106095,106096,106098,106100],{"class":202,"line":398},[200,106097,892],{"class":214},[200,106099,48167],{"class":235},[200,106101,647],{"class":214},[200,106103,106104,106106,106108],{"class":202,"line":405},[200,106105,680],{"class":214},[200,106107,21468],{"class":235},[200,106109,647],{"class":214},[35,106111,48180],{},[192,106113,106114],{"className":21457,"code":48183,"language":12075,"meta":13,"style":13},[156,106115,106116,106136,106156,106176,106198,106202,106216,106244,106250,106254,106270,106274,106288,106296,106302,106306,106330,106348,106362,106376,106382,106386,106394,106398,106406,106450,106478,106500,106508,106512,106530,106534,106542,106550],{"__ignoreMap":13},[200,106117,106118,106120,106122,106124,106126,106128,106130,106132,106134],{"class":202,"line":203},[200,106119,599],{"class":214},[200,106121,14896],{"class":235},[200,106123,14594],{"class":379},[200,106125,449],{"class":214},[200,106127,610],{"class":214},[200,106129,1383],{"class":245},[200,106131,610],{"class":214},[200,106133,21825],{"class":379},[200,106135,647],{"class":214},[200,106137,106138,106140,106142,106144,106146,106148,106150,106152,106154],{"class":202,"line":14},[200,106139,972],{"class":206},[200,106141,321],{"class":206},[200,106143,318],{"class":214},[200,106145,48216],{"class":210},[200,106147,980],{"class":214},[200,106149,983],{"class":206},[200,106151,242],{"class":214},[200,106153,48225],{"class":245},[200,106155,991],{"class":214},[200,106157,106158,106160,106162,106164,106166,106168,106170,106172,106174],{"class":202,"line":255},[200,106159,972],{"class":206},[200,106161,321],{"class":206},[200,106163,318],{"class":214},[200,106165,48238],{"class":210},[200,106167,980],{"class":214},[200,106169,983],{"class":206},[200,106171,242],{"class":214},[200,106173,19813],{"class":245},[200,106175,991],{"class":214},[200,106177,106178,106180,106182,106184,106186,106188,106190,106192,106194,106196],{"class":202,"line":272},[200,106179,972],{"class":206},[200,106181,318],{"class":214},[200,106183,42642],{"class":210},[200,106185,549],{"class":214},[200,106187,22752],{"class":210},[200,106189,980],{"class":214},[200,106191,983],{"class":206},[200,106193,242],{"class":214},[200,106195,19813],{"class":245},[200,106197,991],{"class":214},[200,106199,106200],{"class":202,"line":283},[200,106201,528],{"emptyLinePlaceholder":20},[200,106203,106204,106206,106208,106210,106212,106214],{"class":202,"line":300},[200,106205,443],{"class":379},[200,106207,41681],{"class":210},[200,106209,449],{"class":214},[200,106211,42642],{"class":223},[200,106213,227],{"class":210},[200,106215,230],{"class":214},[200,106217,106218,106220,106222,106224,106226,106228,106230,106232,106234,106236,106238,106240,106242],{"class":202,"line":310},[200,106219,236],{"class":235},[200,106221,239],{"class":214},[200,106223,22752],{"class":223},[200,106225,227],{"class":210},[200,106227,2767],{"class":214},[200,106229,39109],{"class":235},[200,106231,239],{"class":214},[200,106233,242],{"class":214},[200,106235,48309],{"class":245},[200,106237,249],{"class":214},[200,106239,980],{"class":214},[200,106241,2104],{"class":210},[200,106243,252],{"class":214},[200,106245,106246,106248],{"class":202,"line":336},[200,106247,413],{"class":214},[200,106249,416],{"class":210},[200,106251,106252],{"class":202,"line":342},[200,106253,528],{"emptyLinePlaceholder":20},[200,106255,106256,106258,106260,106262,106264,106266,106268],{"class":202,"line":365},[200,106257,3786],{"class":379},[200,106259,48334],{"class":1364},[200,106261,509],{"class":214},[200,106263,48238],{"class":1364},[200,106265,48341],{"class":214},[200,106267,41805],{"class":210},[200,106269,647],{"class":214},[200,106271,106272],{"class":202,"line":371},[200,106273,528],{"emptyLinePlaceholder":20},[200,106275,106276,106278,106280,106282,106284,106286],{"class":202,"line":398},[200,106277,443],{"class":379},[200,106279,18960],{"class":210},[200,106281,449],{"class":214},[200,106283,41750],{"class":223},[200,106285,227],{"class":210},[200,106287,230],{"class":214},[200,106289,106290,106292,106294],{"class":202,"line":405},[200,106291,236],{"class":235},[200,106293,239],{"class":214},[200,106295,48372],{"class":214},[200,106297,106298,106300],{"class":202,"line":410},[200,106299,413],{"class":214},[200,106301,416],{"class":210},[200,106303,106304],{"class":202,"line":860},[200,106305,528],{"emptyLinePlaceholder":20},[200,106307,106308,106310,106312,106314,106316,106318,106320,106322,106324,106326,106328],{"class":202,"line":889},[200,106309,3726],{"class":379},[200,106311,5002],{"class":379},[200,106313,41783],{"class":223},[200,106315,227],{"class":214},[200,106317,25426],{"class":386},[200,106319,239],{"class":214},[200,106321,48216],{"class":1364},[200,106323,599],{"class":214},[200,106325,48403],{"class":1364},[200,106327,46469],{"class":214},[200,106329,280],{"class":214},[200,106331,106332,106334,106336,106338,106340,106342,106344,106346],{"class":202,"line":899},[200,106333,6177],{"class":206},[200,106335,18754],{"class":223},[200,106337,227],{"class":235},[200,106339,249],{"class":214},[200,106341,47942],{"class":245},[200,106343,249],{"class":214},[200,106345,549],{"class":214},[200,106347,280],{"class":214},[200,106349,106350,106352,106354,106356,106358,106360],{"class":202,"line":904},[200,106351,18772],{"class":235},[200,106353,239],{"class":214},[200,106355,242],{"class":214},[200,106357,18779],{"class":245},[200,106359,249],{"class":214},[200,106361,252],{"class":214},[200,106363,106364,106366,106368,106370,106372,106374],{"class":202,"line":930},[200,106365,18788],{"class":235},[200,106367,239],{"class":214},[200,106369,43463],{"class":210},[200,106371,215],{"class":214},[200,106373,12736],{"class":210},[200,106375,252],{"class":214},[200,106377,106378,106380],{"class":202,"line":3655},[200,106379,5095],{"class":214},[200,106381,416],{"class":235},[200,106383,106384],{"class":202,"line":3660},[200,106385,1129],{"class":214},[200,106387,106388,106390,106392],{"class":202,"line":4035},[200,106389,680],{"class":214},[200,106391,14896],{"class":235},[200,106393,647],{"class":214},[200,106395,106396],{"class":202,"line":4490},[200,106397,528],{"emptyLinePlaceholder":20},[200,106399,106400,106402,106404],{"class":202,"line":4496},[200,106401,599],{"class":214},[200,106403,21468],{"class":235},[200,106405,647],{"class":214},[200,106407,106408,106410,106412,106414,106416,106418,106420,106422,106424,106426,106428,106430,106432,106434,106436,106438,106440,106442,106444,106446,106448],{"class":202,"line":4502},[200,106409,656],{"class":214},[200,106411,48490],{"class":235},[200,106413,2186],{"class":214},[200,106415,48495],{"class":379},[200,106417,449],{"class":214},[200,106419,610],{"class":214},[200,106421,48495],{"class":210},[200,106423,610],{"class":214},[200,106425,2186],{"class":214},[200,106427,26875],{"class":379},[200,106429,449],{"class":214},[200,106431,610],{"class":214},[200,106433,26875],{"class":210},[200,106435,610],{"class":214},[200,106437,22417],{"class":214},[200,106439,916],{"class":379},[200,106441,449],{"class":214},[200,106443,610],{"class":214},[200,106445,48526],{"class":210},[200,106447,610],{"class":214},[200,106449,647],{"class":214},[200,106451,106452,106454,106456,106458,106460,106462,106464,106466,106468,106470,106472,106474,106476],{"class":202,"line":4508},[200,106453,803],{"class":214},[200,106455,48537],{"class":235},[200,106457,21516],{"class":379},[200,106459,449],{"class":214},[200,106461,610],{"class":214},[200,106463,40718],{"class":245},[200,106465,610],{"class":214},[200,106467,700],{"class":379},[200,106469,449],{"class":214},[200,106471,610],{"class":214},[200,106473,20112],{"class":245},[200,106475,610],{"class":214},[200,106477,647],{"class":214},[200,106479,106480,106482,106484,106486,106488,106490,106492,106494,106496,106498],{"class":202,"line":5469},[200,106481,22071],{"class":214},[200,106483,48566],{"class":235},[200,106485,42013],{"class":379},[200,106487,449],{"class":214},[200,106489,610],{"class":214},[200,106491,26875],{"class":210},[200,106493,215],{"class":214},[200,106495,20112],{"class":210},[200,106497,610],{"class":214},[200,106499,21527],{"class":214},[200,106501,106502,106504,106506],{"class":202,"line":5474},[200,106503,15078],{"class":214},[200,106505,48537],{"class":235},[200,106507,647],{"class":214},[200,106509,106510],{"class":202,"line":13237},[200,106511,528],{"emptyLinePlaceholder":20},[200,106513,106514,106516,106518,106520,106522,106524,106526,106528],{"class":202,"line":13263},[200,106515,803],{"class":214},[200,106517,48601],{"class":235},[200,106519,321],{"class":379},[200,106521,449],{"class":214},[200,106523,610],{"class":214},[200,106525,916],{"class":245},[200,106527,610],{"class":214},[200,106529,647],{"class":214},[200,106531,106532],{"class":202,"line":13279},[200,106533,48618],{"class":210},[200,106535,106536,106538,106540],{"class":202,"line":13323},[200,106537,15078],{"class":214},[200,106539,48601],{"class":235},[200,106541,647],{"class":214},[200,106543,106544,106546,106548],{"class":202,"line":13329},[200,106545,892],{"class":214},[200,106547,48490],{"class":235},[200,106549,647],{"class":214},[200,106551,106552,106554,106556],{"class":202,"line":13334},[200,106553,680],{"class":214},[200,106555,21468],{"class":235},[200,106557,647],{"class":214},[35,106559,48645,106560,48649,106562,48652,106564,48655],{},[1216,106561,48648],{},[156,106563,48526],{},[156,106565,33165],{},[35,106567,48658,106568,48662],{},[156,106569,48661],{},[35,106571,106572],{},[1216,106573,48667],{},[35,106575,48670],{},[7151,106577,106578,106580,106582,106586,106588,106592,106608,106610,106612,106614,106624,106804,106810,106817,106819,106823,106827,106981,106985,107415,107417,107421,107741,107743,107745,107749,107853,107859,107863,107869,108069,108071,108123,108125,108127,108129,108133,108135,108139,108155,108165,108241,108251,108265],{"icon":45851,"text":48673,"to":45853,"target":7156},[1929,106579,48677],{"id":48676},[35,106581,48680],{},[35,106583,46146,106584],{},[1599,106585,46149],{"repo":38413},[51,106587,48688],{"id":48687},[35,106589,106590],{},[1707,106591],{"alt":48693,"src":48694},[35,106593,106594],{},[1216,106595,106596,47101,106599,45890,106602,45894,106605,215],{},[39,106597,38397],{"href":45853,"rel":106598},[77],[39,106600,45889],{"href":18579,"rel":106601},[77],[39,106603,19427],{"href":45844,"rel":106604},[77],[39,106606,11636],{"href":45853,"rel":106607},[77],[35,106609,48713],{},[1929,106611,48717],{"id":48716},[35,106613,48720],{},[35,106615,48723,106616,48727,106618,48731,106620,48735,106622,48738],{},[156,106617,48726],{},[156,106619,48730],{},[156,106621,48734],{},[156,106623,3295],{},[192,106625,106626],{"className":1380,"code":48741,"language":1383,"meta":13,"style":13},[156,106627,106628,106650,106668,106672,106702,106722,106736,106778,106782,106798],{"__ignoreMap":13},[200,106629,106630,106632,106634,106636,106638,106640,106642,106644,106646,106648],{"class":202,"line":203},[200,106631,1390],{"class":206},[200,106633,1393],{"class":206},[200,106635,38723],{"class":223},[200,106637,227],{"class":210},[200,106639,3726],{"class":379},[200,106641,1094],{"class":214},[200,106643,25426],{"class":386},[200,106645,2104],{"class":214},[200,106647,393],{"class":379},[200,106649,280],{"class":214},[200,106651,106652,106654,106656,106658,106660,106662,106664,106666],{"class":202,"line":14},[200,106653,3738],{"class":379},[200,106655,40809],{"class":210},[200,106657,509],{"class":214},[200,106659,452],{"class":206},[200,106661,40816],{"class":223},[200,106663,227],{"class":235},[200,106665,25426],{"class":210},[200,106667,416],{"class":235},[200,106669,106670],{"class":202,"line":255},[200,106671,528],{"emptyLinePlaceholder":20},[200,106673,106674,106676,106678,106680,106682,106684,106686,106688,106690,106692,106694,106696,106698,106700],{"class":202,"line":272},[200,106675,6177],{"class":206},[200,106677,47338],{"class":223},[200,106679,1549],{"class":235},[200,106681,215],{"class":214},[200,106683,48800],{"class":223},[200,106685,227],{"class":235},[200,106687,47418],{"class":210},[200,106689,215],{"class":214},[200,106691,47161],{"class":210},[200,106693,2104],{"class":235},[200,106695,215],{"class":214},[200,106697,48815],{"class":223},[200,106699,227],{"class":235},[200,106701,230],{"class":214},[200,106703,106704,106706,106708,106710,106712,106714,106716,106718,106720],{"class":202,"line":283},[200,106705,47372],{"class":235},[200,106707,239],{"class":214},[200,106709,47553],{"class":223},[200,106711,227],{"class":235},[200,106713,23914],{"class":210},[200,106715,215],{"class":214},[200,106717,20112],{"class":210},[200,106719,2104],{"class":235},[200,106721,252],{"class":214},[200,106723,106724,106726,106728,106730,106732,106734],{"class":202,"line":300},[200,106725,11812],{"class":235},[200,106727,239],{"class":214},[200,106729,40809],{"class":210},[200,106731,215],{"class":214},[200,106733,20112],{"class":210},[200,106735,252],{"class":214},[200,106737,106738,106740,106742,106744,106746,106748,106750,106752,106754,106756,106758,106760,106762,106764,106766,106768,106770,106772,106774,106776],{"class":202,"line":310},[200,106739,5095],{"class":214},[200,106741,2104],{"class":235},[200,106743,215],{"class":214},[200,106745,48864],{"class":223},[200,106747,227],{"class":235},[200,106749,48869],{"class":223},[200,106751,227],{"class":235},[200,106753,47418],{"class":210},[200,106755,215],{"class":214},[200,106757,47161],{"class":210},[200,106759,215],{"class":214},[200,106761,3295],{"class":210},[200,106763,549],{"class":214},[200,106765,48886],{"class":210},[200,106767,215],{"class":214},[200,106769,3295],{"class":210},[200,106771,6131],{"class":235},[200,106773,215],{"class":214},[200,106775,47590],{"class":223},[200,106777,466],{"class":235},[200,106779,106780],{"class":202,"line":336},[200,106781,528],{"emptyLinePlaceholder":20},[200,106783,106784,106786,106788,106790,106792,106794,106796],{"class":202,"line":342},[200,106785,3758],{"class":206},[200,106787,40842],{"class":223},[200,106789,227],{"class":235},[200,106791,25426],{"class":210},[200,106793,549],{"class":214},[200,106795,48917],{"class":2060},[200,106797,416],{"class":235},[200,106799,106800,106802],{"class":202,"line":365},[200,106801,413],{"class":214},[200,106803,416],{"class":210},[35,106805,106806],{},[1216,106807,48930,106808,48934],{},[156,106809,48933],{},[35,106811,48937,106812,48943,106815],{},[39,106813,48942],{"href":48940,"rel":106814},[77],[1216,106816,48946],{},[1929,106818,48950],{"id":48949},[35,106820,48953,106821,48957],{},[1216,106822,48956],{},[35,106824,48960,106825,2186],{},[156,106826,48963],{},[192,106828,106829],{"className":1380,"code":48966,"language":1383,"meta":13,"style":13},[156,106830,106831,106853,106875,106893,106911,106929,106945,106965,106969,106975],{"__ignoreMap":13},[200,106832,106833,106835,106837,106839,106841,106843,106845,106847,106849,106851],{"class":202,"line":203},[200,106834,1390],{"class":206},[200,106836,1393],{"class":206},[200,106838,38723],{"class":223},[200,106840,227],{"class":210},[200,106842,3726],{"class":379},[200,106844,1094],{"class":214},[200,106846,25426],{"class":386},[200,106848,2104],{"class":214},[200,106850,393],{"class":379},[200,106852,280],{"class":214},[200,106854,106855,106857,106859,106861,106863,106865,106867,106869,106871,106873],{"class":202,"line":14},[200,106856,3738],{"class":379},[200,106858,48997],{"class":210},[200,106860,509],{"class":214},[200,106862,452],{"class":206},[200,106864,47338],{"class":223},[200,106866,1549],{"class":235},[200,106868,215],{"class":214},[200,106870,769],{"class":223},[200,106872,227],{"class":235},[200,106874,230],{"class":214},[200,106876,106877,106879,106881,106883,106885,106887,106889,106891],{"class":202,"line":255},[200,106878,15861],{"class":235},[200,106880,239],{"class":214},[200,106882,47357],{"class":210},[200,106884,215],{"class":214},[200,106886,47155],{"class":210},[200,106888,215],{"class":214},[200,106890,3295],{"class":210},[200,106892,252],{"class":214},[200,106894,106895,106897,106899,106901,106903,106905,106907,106909],{"class":202,"line":272},[200,106896,49036],{"class":235},[200,106898,239],{"class":214},[200,106900,47357],{"class":210},[200,106902,215],{"class":214},[200,106904,47155],{"class":210},[200,106906,215],{"class":214},[200,106908,46052],{"class":210},[200,106910,252],{"class":214},[200,106912,106913,106915,106917,106919,106921,106923,106925,106927],{"class":202,"line":283},[200,106914,49055],{"class":235},[200,106916,239],{"class":214},[200,106918,47357],{"class":210},[200,106920,215],{"class":214},[200,106922,47155],{"class":210},[200,106924,215],{"class":214},[200,106926,49068],{"class":210},[200,106928,252],{"class":214},[200,106930,106931,106933,106935,106937,106939,106941,106943],{"class":202,"line":300},[200,106932,49075],{"class":235},[200,106934,239],{"class":214},[200,106936,47357],{"class":210},[200,106938,215],{"class":214},[200,106940,47155],{"class":210},[200,106942,215],{"class":214},[200,106944,49088],{"class":210},[200,106946,106947,106949,106951,106953,106955,106957,106959,106961,106963],{"class":202,"line":310},[200,106948,5095],{"class":214},[200,106950,2104],{"class":235},[200,106952,215],{"class":214},[200,106954,1489],{"class":223},[200,106956,227],{"class":235},[200,106958,47418],{"class":210},[200,106960,215],{"class":214},[200,106962,47155],{"class":210},[200,106964,416],{"class":235},[200,106966,106967],{"class":202,"line":336},[200,106968,528],{"emptyLinePlaceholder":20},[200,106970,106971,106973],{"class":202,"line":342},[200,106972,3758],{"class":206},[200,106974,49119],{"class":210},[200,106976,106977,106979],{"class":202,"line":365},[200,106978,413],{"class":214},[200,106980,416],{"class":210},[35,106982,49128,106983,49131],{},[156,106984,47992],{},[192,106986,106987],{"className":21457,"code":49134,"language":12075,"meta":13,"style":13},[156,106988,106989,107009,107021,107035,107047,107053,107067,107081,107087,107101,107119,107127,107133,107147,107159,107165,107197,107207,107213,107221,107225,107233,107241,107251,107283,107307,107363,107367,107375,107383,107391,107399,107407],{"__ignoreMap":13},[200,106990,106991,106993,106995,106997,106999,107001,107003,107005,107007],{"class":202,"line":203},[200,106992,599],{"class":214},[200,106994,14896],{"class":235},[200,106996,14594],{"class":379},[200,106998,449],{"class":214},[200,107000,610],{"class":214},[200,107002,1383],{"class":245},[200,107004,610],{"class":214},[200,107006,21825],{"class":379},[200,107008,647],{"class":214},[200,107010,107011,107013,107015,107017,107019],{"class":202,"line":14},[200,107012,443],{"class":379},[200,107014,47788],{"class":210},[200,107016,449],{"class":214},[200,107018,350],{"class":210},[200,107020,230],{"class":214},[200,107022,107023,107025,107027,107029,107031,107033],{"class":202,"line":255},[200,107024,18976],{"class":235},[200,107026,239],{"class":214},[200,107028,242],{"class":214},[200,107030,3295],{"class":245},[200,107032,249],{"class":214},[200,107034,252],{"class":214},[200,107036,107037,107039,107041,107043,107045],{"class":202,"line":272},[200,107038,47813],{"class":235},[200,107040,239],{"class":214},[200,107042,242],{"class":214},[200,107044,2902],{"class":245},[200,107046,991],{"class":214},[200,107048,107049,107051],{"class":202,"line":283},[200,107050,26897],{"class":214},[200,107052,280],{"class":214},[200,107054,107055,107057,107059,107061,107063,107065],{"class":202,"line":300},[200,107056,18976],{"class":235},[200,107058,239],{"class":214},[200,107060,242],{"class":214},[200,107062,46052],{"class":245},[200,107064,249],{"class":214},[200,107066,252],{"class":214},[200,107068,107069,107071,107073,107075,107077,107079],{"class":202,"line":310},[200,107070,47813],{"class":235},[200,107072,239],{"class":214},[200,107074,242],{"class":214},[200,107076,49225],{"class":245},[200,107078,249],{"class":214},[200,107080,252],{"class":214},[200,107082,107083,107085],{"class":202,"line":336},[200,107084,26897],{"class":214},[200,107086,280],{"class":214},[200,107088,107089,107091,107093,107095,107097,107099],{"class":202,"line":342},[200,107090,18976],{"class":235},[200,107092,239],{"class":214},[200,107094,242],{"class":214},[200,107096,49068],{"class":245},[200,107098,249],{"class":214},[200,107100,252],{"class":214},[200,107102,107103,107105,107107,107109,107111,107113,107115,107117],{"class":202,"line":365},[200,107104,47813],{"class":235},[200,107106,239],{"class":214},[200,107108,242],{"class":214},[200,107110,49260],{"class":245},[200,107112,16056],{"class":210},[200,107114,49265],{"class":245},[200,107116,249],{"class":214},[200,107118,252],{"class":214},[200,107120,107121,107123,107125],{"class":202,"line":371},[200,107122,47860],{"class":235},[200,107124,239],{"class":214},[200,107126,22236],{"class":2438},[200,107128,107129,107131],{"class":202,"line":398},[200,107130,26897],{"class":214},[200,107132,280],{"class":214},[200,107134,107135,107137,107139,107141,107143,107145],{"class":202,"line":405},[200,107136,18976],{"class":235},[200,107138,239],{"class":214},[200,107140,242],{"class":214},[200,107142,39182],{"class":245},[200,107144,249],{"class":214},[200,107146,252],{"class":214},[200,107148,107149,107151,107153,107155,107157],{"class":202,"line":410},[200,107150,47813],{"class":235},[200,107152,239],{"class":214},[200,107154,242],{"class":214},[200,107156,49308],{"class":245},[200,107158,991],{"class":214},[200,107160,107161,107163],{"class":202,"line":860},[200,107162,413],{"class":214},[200,107164,4511],{"class":210},[200,107166,107167,107169,107171,107173,107175,107177,107179,107181,107183,107185,107187,107189,107191,107193,107195],{"class":202,"line":889},[200,107168,443],{"class":379},[200,107170,318],{"class":214},[200,107172,5056],{"class":235},[200,107174,239],{"class":214},[200,107176,49329],{"class":210},[200,107178,413],{"class":214},[200,107180,509],{"class":214},[200,107182,452],{"class":206},[200,107184,47935],{"class":223},[200,107186,227],{"class":210},[200,107188,249],{"class":214},[200,107190,49344],{"class":245},[200,107192,249],{"class":214},[200,107194,549],{"class":214},[200,107196,280],{"class":214},[200,107198,107199,107201,107203,107205],{"class":202,"line":899},[200,107200,47953],{"class":235},[200,107202,239],{"class":214},[200,107204,16169],{"class":2438},[200,107206,252],{"class":214},[200,107208,107209,107211],{"class":202,"line":904},[200,107210,413],{"class":214},[200,107212,416],{"class":210},[200,107214,107215,107217,107219],{"class":202,"line":930},[200,107216,680],{"class":214},[200,107218,14896],{"class":235},[200,107220,647],{"class":214},[200,107222,107223],{"class":202,"line":3655},[200,107224,528],{"emptyLinePlaceholder":20},[200,107226,107227,107229,107231],{"class":202,"line":3660},[200,107228,599],{"class":214},[200,107230,21468],{"class":235},[200,107232,647],{"class":214},[200,107234,107235,107237,107239],{"class":202,"line":4035},[200,107236,656],{"class":214},[200,107238,47702],{"class":235},[200,107240,647],{"class":214},[200,107242,107243,107245,107247,107249],{"class":202,"line":4490},[200,107244,803],{"class":214},[200,107246,47711],{"class":235},[200,107248,47714],{"class":379},[200,107250,647],{"class":214},[200,107252,107253,107255,107257,107259,107261,107263,107265,107267,107269,107271,107273,107275,107277,107279,107281],{"class":202,"line":4496},[200,107254,22071],{"class":214},[200,107256,47992],{"class":235},[200,107258,2186],{"class":214},[200,107260,47997],{"class":379},[200,107262,449],{"class":214},[200,107264,610],{"class":214},[200,107266,47997],{"class":210},[200,107268,610],{"class":214},[200,107270,2186],{"class":214},[200,107272,48010],{"class":379},[200,107274,449],{"class":214},[200,107276,610],{"class":214},[200,107278,47155],{"class":210},[200,107280,610],{"class":214},[200,107282,647],{"class":214},[200,107284,107285,107287,107289,107291,107293,107295,107297,107299,107301,107303,107305],{"class":202,"line":4502},[200,107286,49441],{"class":214},[200,107288,21468],{"class":235},[200,107290,23911],{"class":214},[200,107292,49448],{"class":379},[200,107294,449],{"class":214},[200,107296,610],{"class":214},[200,107298,2767],{"class":214},[200,107300,49457],{"class":210},[200,107302,413],{"class":214},[200,107304,610],{"class":214},[200,107306,647],{"class":214},[200,107308,107309,107311,107313,107315,107317,107319,107321,107323,107325,107327,107329,107331,107333,107335,107337,107339,107341,107343,107345,107347,107349,107351,107353,107355,107357,107359,107361],{"class":202,"line":4508},[200,107310,49468],{"class":214},[200,107312,49471],{"class":235},[200,107314,49474],{"class":379},[200,107316,449],{"class":214},[200,107318,610],{"class":214},[200,107320,49481],{"class":245},[200,107322,610],{"class":214},[200,107324,2186],{"class":214},[200,107326,49488],{"class":379},[200,107328,449],{"class":214},[200,107330,610],{"class":214},[200,107332,49495],{"class":210},[200,107334,215],{"class":214},[200,107336,39182],{"class":210},[200,107338,49502],{"class":214},[200,107340,249],{"class":214},[200,107342,39189],{"class":245},[200,107344,249],{"class":214},[200,107346,49511],{"class":214},[200,107348,249],{"class":214},[200,107350,49516],{"class":245},[200,107352,249],{"class":214},[200,107354,49521],{"class":214},[200,107356,249],{"class":214},[200,107358,49526],{"class":245},[200,107360,14706],{"class":214},[200,107362,647],{"class":214},[200,107364,107365],{"class":202,"line":5469},[200,107366,49535],{"class":210},[200,107368,107369,107371,107373],{"class":202,"line":5474},[200,107370,49540],{"class":214},[200,107372,49471],{"class":235},[200,107374,647],{"class":214},[200,107376,107377,107379,107381],{"class":202,"line":13237},[200,107378,49549],{"class":214},[200,107380,21468],{"class":235},[200,107382,647],{"class":214},[200,107384,107385,107387,107389],{"class":202,"line":13263},[200,107386,43803],{"class":214},[200,107388,47992],{"class":235},[200,107390,647],{"class":214},[200,107392,107393,107395,107397],{"class":202,"line":13279},[200,107394,15078],{"class":214},[200,107396,47711],{"class":235},[200,107398,647],{"class":214},[200,107400,107401,107403,107405],{"class":202,"line":13323},[200,107402,892],{"class":214},[200,107404,47702],{"class":235},[200,107406,647],{"class":214},[200,107408,107409,107411,107413],{"class":202,"line":13329},[200,107410,680],{"class":214},[200,107412,21468],{"class":235},[200,107414,647],{"class":214},[35,107416,49588],{},[35,107418,49591,107419,49595],{},[156,107420,49594],{},[192,107422,107423],{"className":21457,"code":49598,"language":12075,"meta":13,"style":13},[156,107424,107425,107445,107457,107471,107485,107499,107513,107521,107535,107539,107545,107559,107573,107587,107601,107609,107623,107627,107633,107641,107645,107653,107661,107669,107677,107697,107705,107713,107717,107725,107733],{"__ignoreMap":13},[200,107426,107427,107429,107431,107433,107435,107437,107439,107441,107443],{"class":202,"line":203},[200,107428,599],{"class":214},[200,107430,14896],{"class":235},[200,107432,14594],{"class":379},[200,107434,449],{"class":214},[200,107436,610],{"class":214},[200,107438,1383],{"class":245},[200,107440,610],{"class":214},[200,107442,21825],{"class":379},[200,107444,647],{"class":214},[200,107446,107447,107449,107451,107453,107455],{"class":202,"line":14},[200,107448,443],{"class":379},[200,107450,49627],{"class":210},[200,107452,449],{"class":214},[200,107454,350],{"class":210},[200,107456,230],{"class":214},[200,107458,107459,107461,107463,107465,107467,107469],{"class":202,"line":255},[200,107460,49638],{"class":235},[200,107462,239],{"class":214},[200,107464,242],{"class":214},[200,107466,47161],{"class":245},[200,107468,249],{"class":214},[200,107470,252],{"class":214},[200,107472,107473,107475,107477,107479,107481,107483],{"class":202,"line":272},[200,107474,47813],{"class":235},[200,107476,239],{"class":214},[200,107478,242],{"class":214},[200,107480,48081],{"class":245},[200,107482,249],{"class":214},[200,107484,252],{"class":214},[200,107486,107487,107489,107491,107493,107495,107497],{"class":202,"line":283},[200,107488,49667],{"class":235},[200,107490,239],{"class":214},[200,107492,242],{"class":214},[200,107494,49674],{"class":245},[200,107496,249],{"class":214},[200,107498,252],{"class":214},[200,107500,107501,107503,107505,107507,107509,107511],{"class":202,"line":300},[200,107502,49683],{"class":235},[200,107504,239],{"class":214},[200,107506,242],{"class":214},[200,107508,49690],{"class":245},[200,107510,249],{"class":214},[200,107512,252],{"class":214},[200,107514,107515,107517,107519],{"class":202,"line":310},[200,107516,49699],{"class":235},[200,107518,239],{"class":214},[200,107520,280],{"class":214},[200,107522,107523,107525,107527,107529,107531,107533],{"class":202,"line":336},[200,107524,49708],{"class":235},[200,107526,239],{"class":214},[200,107528,242],{"class":214},[200,107530,48081],{"class":245},[200,107532,249],{"class":214},[200,107534,252],{"class":214},[200,107536,107537],{"class":202,"line":342},[200,107538,4505],{"class":214},[200,107540,107541,107543],{"class":202,"line":365},[200,107542,26897],{"class":214},[200,107544,280],{"class":214},[200,107546,107547,107549,107551,107553,107555,107557],{"class":202,"line":371},[200,107548,49638],{"class":235},[200,107550,239],{"class":214},[200,107552,242],{"class":214},[200,107554,47155],{"class":245},[200,107556,249],{"class":214},[200,107558,252],{"class":214},[200,107560,107561,107563,107565,107567,107569,107571],{"class":202,"line":398},[200,107562,47813],{"class":235},[200,107564,239],{"class":214},[200,107566,242],{"class":214},[200,107568,49753],{"class":245},[200,107570,249],{"class":214},[200,107572,252],{"class":214},[200,107574,107575,107577,107579,107581,107583,107585],{"class":202,"line":405},[200,107576,49667],{"class":235},[200,107578,239],{"class":214},[200,107580,242],{"class":214},[200,107582,49768],{"class":245},[200,107584,249],{"class":214},[200,107586,252],{"class":214},[200,107588,107589,107591,107593,107595,107597,107599],{"class":202,"line":410},[200,107590,49683],{"class":235},[200,107592,239],{"class":214},[200,107594,242],{"class":214},[200,107596,49783],{"class":245},[200,107598,249],{"class":214},[200,107600,252],{"class":214},[200,107602,107603,107605,107607],{"class":202,"line":860},[200,107604,49699],{"class":235},[200,107606,239],{"class":214},[200,107608,280],{"class":214},[200,107610,107611,107613,107615,107617,107619,107621],{"class":202,"line":889},[200,107612,49708],{"class":235},[200,107614,239],{"class":214},[200,107616,242],{"class":214},[200,107618,49753],{"class":245},[200,107620,249],{"class":214},[200,107622,252],{"class":214},[200,107624,107625],{"class":202,"line":899},[200,107626,4505],{"class":214},[200,107628,107629,107631],{"class":202,"line":904},[200,107630,413],{"class":214},[200,107632,4511],{"class":210},[200,107634,107635,107637,107639],{"class":202,"line":930},[200,107636,680],{"class":214},[200,107638,14896],{"class":235},[200,107640,647],{"class":214},[200,107642,107643],{"class":202,"line":3655},[200,107644,528],{"emptyLinePlaceholder":20},[200,107646,107647,107649,107651],{"class":202,"line":3660},[200,107648,599],{"class":214},[200,107650,21468],{"class":235},[200,107652,647],{"class":214},[200,107654,107655,107657,107659],{"class":202,"line":4035},[200,107656,656],{"class":214},[200,107658,47246],{"class":235},[200,107660,647],{"class":214},[200,107662,107663,107665,107667],{"class":202,"line":4490},[200,107664,803],{"class":214},[200,107666,47711],{"class":235},[200,107668,647],{"class":214},[200,107670,107671,107673,107675],{"class":202,"line":4496},[200,107672,22071],{"class":214},[200,107674,49594],{"class":235},[200,107676,647],{"class":214},[200,107678,107679,107681,107683,107685,107687,107689,107691,107693,107695],{"class":202,"line":4502},[200,107680,49441],{"class":214},[200,107682,49870],{"class":235},[200,107684,2186],{"class":214},[200,107686,49875],{"class":379},[200,107688,449],{"class":214},[200,107690,610],{"class":214},[200,107692,49875],{"class":210},[200,107694,610],{"class":214},[200,107696,21527],{"class":214},[200,107698,107699,107701,107703],{"class":202,"line":4508},[200,107700,43803],{"class":214},[200,107702,49594],{"class":235},[200,107704,647],{"class":214},[200,107706,107707,107709,107711],{"class":202,"line":5469},[200,107708,15078],{"class":214},[200,107710,47711],{"class":235},[200,107712,647],{"class":214},[200,107714,107715],{"class":202,"line":5474},[200,107716,528],{"emptyLinePlaceholder":20},[200,107718,107719,107721,107723],{"class":202,"line":13237},[200,107720,803],{"class":214},[200,107722,47255],{"class":235},[200,107724,21527],{"class":214},[200,107726,107727,107729,107731],{"class":202,"line":13263},[200,107728,892],{"class":214},[200,107730,47246],{"class":235},[200,107732,647],{"class":214},[200,107734,107735,107737,107739],{"class":202,"line":13279},[200,107736,680],{"class":214},[200,107738,21468],{"class":235},[200,107740,647],{"class":214},[51,107742,49933],{"id":49932},[35,107744,49936],{},[35,107746,49939,107747,49943],{},[156,107748,49942],{},[192,107750,107751],{"className":1380,"code":49946,"language":1383,"meta":13,"style":13},[156,107752,107753,107769,107785,107789,107815,107819,107831,107847],{"__ignoreMap":13},[200,107754,107755,107757,107759,107761,107763,107765,107767],{"class":202,"line":203},[200,107756,1390],{"class":206},[200,107758,1393],{"class":206},[200,107760,49957],{"class":223},[200,107762,227],{"class":210},[200,107764,1549],{"class":214},[200,107766,393],{"class":379},[200,107768,280],{"class":214},[200,107770,107771,107773,107775,107777,107779,107781,107783],{"class":202,"line":14},[200,107772,3738],{"class":379},[200,107774,318],{"class":214},[200,107776,34533],{"class":210},[200,107778,980],{"class":214},[200,107780,509],{"class":214},[200,107782,49980],{"class":223},[200,107784,466],{"class":235},[200,107786,107787],{"class":202,"line":255},[200,107788,528],{"emptyLinePlaceholder":20},[200,107790,107791,107793,107795,107797,107799,107801,107803,107805,107807,107809,107811,107813],{"class":202,"line":272},[200,107792,3738],{"class":379},[200,107794,49993],{"class":210},[200,107796,509],{"class":214},[200,107798,34533],{"class":210},[200,107800,215],{"class":214},[200,107802,15840],{"class":210},[200,107804,6536],{"class":214},[200,107806,39182],{"class":210},[200,107808,3298],{"class":214},[200,107810,242],{"class":214},[200,107812,39189],{"class":245},[200,107814,991],{"class":214},[200,107816,107817],{"class":202,"line":283},[200,107818,528],{"emptyLinePlaceholder":20},[200,107820,107821,107823,107825,107827,107829],{"class":202,"line":300},[200,107822,20098],{"class":206},[200,107824,1094],{"class":235},[200,107826,11852],{"class":214},[200,107828,50028],{"class":210},[200,107830,416],{"class":235},[200,107832,107833,107835,107837,107839,107841,107843,107845],{"class":202,"line":310},[200,107834,3311],{"class":206},[200,107836,50037],{"class":223},[200,107838,227],{"class":235},[200,107840,249],{"class":214},[200,107842,2898],{"class":245},[200,107844,249],{"class":214},[200,107846,416],{"class":235},[200,107848,107849,107851],{"class":202,"line":336},[200,107850,413],{"class":214},[200,107852,416],{"class":210},[35,107854,107855],{},[1216,107856,50058,107857,215],{},[156,107858,47652],{},[35,107860,50063,107861,50067],{},[156,107862,50066],{},[35,107864,50070,107865,50074,107867,2186],{},[156,107866,50073],{},[156,107868,50077],{},[192,107870,107871],{"className":1380,"code":50080,"language":1383,"meta":13,"style":13},[156,107872,107873,107893,107913,107917,107947,107965,107969,108007,108017,108027,108041,108047,108051,108055,108065],{"__ignoreMap":13},[200,107874,107875,107877,107879,107881,107883,107885,107887,107889,107891],{"class":202,"line":203},[200,107876,972],{"class":206},[200,107878,321],{"class":206},[200,107880,318],{"class":214},[200,107882,50093],{"class":210},[200,107884,980],{"class":214},[200,107886,983],{"class":206},[200,107888,242],{"class":214},[200,107890,50102],{"class":245},[200,107892,991],{"class":214},[200,107894,107895,107897,107899,107901,107903,107905,107907,107909,107911],{"class":202,"line":14},[200,107896,972],{"class":206},[200,107898,321],{"class":206},[200,107900,318],{"class":214},[200,107902,39087],{"class":210},[200,107904,980],{"class":214},[200,107906,983],{"class":206},[200,107908,242],{"class":214},[200,107910,1929],{"class":245},[200,107912,991],{"class":214},[200,107914,107915],{"class":202,"line":255},[200,107916,528],{"emptyLinePlaceholder":20},[200,107918,107919,107921,107923,107925,107927,107929,107931,107933,107935,107937,107939,107941,107943,107945],{"class":202,"line":272},[200,107920,1390],{"class":206},[200,107922,380],{"class":379},[200,107924,5002],{"class":379},[200,107926,50139],{"class":223},[200,107928,227],{"class":214},[200,107930,25426],{"class":386},[200,107932,239],{"class":214},[200,107934,39087],{"class":1364},[200,107936,50150],{"class":214},[200,107938,5011],{"class":1364},[200,107940,599],{"class":214},[200,107942,39127],{"class":1364},[200,107944,674],{"class":214},[200,107946,280],{"class":214},[200,107948,107949,107951,107953,107955,107957,107959,107961,107963],{"class":202,"line":283},[200,107950,3738],{"class":379},[200,107952,39138],{"class":210},[200,107954,509],{"class":214},[200,107956,452],{"class":206},[200,107958,39474],{"class":223},[200,107960,227],{"class":235},[200,107962,25426],{"class":210},[200,107964,416],{"class":235},[200,107966,107967],{"class":202,"line":300},[200,107968,528],{"emptyLinePlaceholder":20},[200,107970,107971,107973,107975,107977,107979,107981,107983,107985,107987,107989,107991,107993,107995,107997,107999,108001,108003,108005],{"class":202,"line":310},[200,107972,20098],{"class":206},[200,107974,1094],{"class":235},[200,107976,11852],{"class":214},[200,107978,39544],{"class":210},[200,107980,215],{"class":214},[200,107982,26847],{"class":210},[200,107984,15013],{"class":214},[200,107986,39138],{"class":210},[200,107988,215],{"class":214},[200,107990,26847],{"class":210},[200,107992,215],{"class":214},[200,107994,39182],{"class":210},[200,107996,13398],{"class":214},[200,107998,242],{"class":214},[200,108000,39189],{"class":245},[200,108002,249],{"class":214},[200,108004,5282],{"class":235},[200,108006,230],{"class":214},[200,108008,108009,108011,108013,108015],{"class":202,"line":336},[200,108010,39200],{"class":206},[200,108012,39203],{"class":223},[200,108014,227],{"class":235},[200,108016,230],{"class":214},[200,108018,108019,108021,108023,108025],{"class":202,"line":342},[200,108020,39212],{"class":235},[200,108022,239],{"class":214},[200,108024,39575],{"class":2060},[200,108026,252],{"class":214},[200,108028,108029,108031,108033,108035,108037,108039],{"class":202,"line":365},[200,108030,39233],{"class":235},[200,108032,239],{"class":214},[200,108034,242],{"class":214},[200,108036,50251],{"class":245},[200,108038,249],{"class":214},[200,108040,252],{"class":214},[200,108042,108043,108045],{"class":202,"line":371},[200,108044,2111],{"class":214},[200,108046,416],{"class":235},[200,108048,108049],{"class":202,"line":398},[200,108050,4505],{"class":214},[200,108052,108053],{"class":202,"line":405},[200,108054,528],{"emptyLinePlaceholder":20},[200,108056,108057,108059,108061,108063],{"class":202,"line":410},[200,108058,3758],{"class":206},[200,108060,39138],{"class":210},[200,108062,14678],{"class":206},[200,108064,39277],{"class":1364},[200,108066,108067],{"class":202,"line":860},[200,108068,1129],{"class":214},[35,108070,50286],{},[192,108072,108073],{"className":1380,"code":50289,"language":1383,"meta":13,"style":13},[156,108074,108075,108097,108109,108113,108117],{"__ignoreMap":13},[200,108076,108077,108079,108081,108083,108085,108087,108089,108091,108093,108095],{"class":202,"line":203},[200,108078,1390],{"class":206},[200,108080,1393],{"class":206},[200,108082,38723],{"class":223},[200,108084,227],{"class":210},[200,108086,3726],{"class":379},[200,108088,1094],{"class":214},[200,108090,25426],{"class":386},[200,108092,2104],{"class":214},[200,108094,393],{"class":379},[200,108096,280],{"class":214},[200,108098,108099,108101,108103,108105,108107],{"class":202,"line":14},[200,108100,6177],{"class":206},[200,108102,50139],{"class":223},[200,108104,227],{"class":235},[200,108106,25426],{"class":210},[200,108108,416],{"class":235},[200,108110,108111],{"class":202,"line":255},[200,108112,528],{"emptyLinePlaceholder":20},[200,108114,108115],{"class":202,"line":272},[200,108116,50334],{"class":401},[200,108118,108119,108121],{"class":202,"line":283},[200,108120,413],{"class":214},[200,108122,416],{"class":210},[35,108124,50343],{},[1929,108126,50347],{"id":50346},[35,108128,50350],{},[35,108130,46146,108131],{},[1599,108132,46149],{"repo":38413},[51,108134,50358],{"id":50357},[35,108136,108137],{},[1707,108138],{"alt":50363,"src":50364},[35,108140,108141],{},[1216,108142,108143,47101,108146,47105,108149,45894,108152,215],{},[39,108144,38397],{"href":45853,"rel":108145},[77],[39,108147,45889],{"href":18579,"rel":108148},[77],[39,108150,19427],{"href":45844,"rel":108151},[77],[39,108153,11636],{"href":45853,"rel":108154},[77],[35,108156,50383,108157,50386,108159,50389,108161,50392,108163,50395],{},[156,108158,33157],{},[156,108160,33157],{},[156,108162,9909],{},[156,108164,48029],{},[192,108166,108167],{"className":1380,"code":50398,"language":1383,"meta":13,"style":13},[156,108168,108169,108201,108211,108223,108235],{"__ignoreMap":13},[200,108170,108171,108173,108175,108177,108179,108181,108183,108185,108187,108189,108191,108193,108195,108197,108199],{"class":202,"line":203},[200,108172,443],{"class":379},[200,108174,318],{"class":214},[200,108176,5056],{"class":210},[200,108178,549],{"class":214},[200,108180,47926],{"class":210},[200,108182,413],{"class":214},[200,108184,509],{"class":214},[200,108186,452],{"class":206},[200,108188,47935],{"class":223},[200,108190,227],{"class":210},[200,108192,249],{"class":214},[200,108194,47942],{"class":245},[200,108196,249],{"class":214},[200,108198,549],{"class":214},[200,108200,280],{"class":214},[200,108202,108203,108205,108207,108209],{"class":202,"line":14},[200,108204,47953],{"class":235},[200,108206,239],{"class":214},[200,108208,16169],{"class":2438},[200,108210,252],{"class":214},[200,108212,108213,108215,108217,108219,108221],{"class":202,"line":255},[200,108214,50447],{"class":235},[200,108216,239],{"class":214},[200,108218,2439],{"class":2438},[200,108220,549],{"class":214},[200,108222,50456],{"class":401},[200,108224,108225,108227,108229,108231,108233],{"class":202,"line":272},[200,108226,50461],{"class":223},[200,108228,239],{"class":214},[200,108230,3729],{"class":214},[200,108232,393],{"class":379},[200,108234,6433],{"class":210},[200,108236,108237,108239],{"class":202,"line":283},[200,108238,413],{"class":214},[200,108240,416],{"class":210},[35,108242,19439,108243,50480,108245,50483,108247,50486,108249,50489],{},[156,108244,48029],{},[156,108246,9900],{},[156,108248,47992],{},[1216,108250,45783],{},[11400,108252,132,108253,132,108259],{"flex":50492,"md":50493,"gap-x-2":13},[130,108254,136,108255,136,108257,132],{},[1707,108256],{"src":50498,"alt":50499,"loading":9909},[143,108258,50502],{},[130,108260,136,108261,136,108263,132],{},[1707,108262],{"src":50507,"alt":50508,"loading":9909},[143,108264,50511],{},[7151,108266,108267,108269,108274,108278,108280,108287,108289,108291,108299,108305,108375,108385,108493,108499,108505,108715,108721,108723,108757,108764,108774,108776,108994,108998,109002,109004,109006,109010,109162,109164,109166,109168,109172,109174,109176,109178,109182,109184,109191,109193,109201,109203,109207,109217,109219,109239,109241,109629,109639,109643,110263,110265,110267,110271,110287,110289,110291,110295,110297,110753,110759,110771,110779,110781,110881,110883,110885,110889,111321,111331,111337,111339,111343,111349,111353,111517,111519,111537,111541,111545,112759,112763,112953,112955,113081,113085,113197,113201,113211,113573,113585,113587,113589,113591,113593,113597,113613,113615,113623,113627,113629,113645,113647,113683,113697,113717,113719,113931,113935,114143,114145,114147,114149,114151,114201,114205,114211,114219,114403,114409,114411,114415,114419,114423,114441,114443,114447,114455,114533,114535,114539,114541,114583,114587,114589,114591,114771,114773,114775,114777,114779,114781,114785,114787,114791,114797,114799,114801,114803,114853,114859,114863,114883,114885,114947,114953,114955,114961,115036,115038,115040,115304,115306,115308,115594,115596,115600,115602,115604,115606,115610,115626,115628,115630,115632,115634,115638,115642,115648,115968,115970,115972,115974,115978,116144,116146,116150,116286,116288,116290,116292,116608,116612,116614,116618,116628,116636,116638,116762,116766,116773,116779,117045,117050,117052,117190,117194,117196,117198],{"icon":7153,"text":50514,"to":50515,"target":7156},[1929,108268,50519],{"id":50518},[35,108270,50522,108271,50527],{},[39,108272,50526],{"href":2467,"rel":108273},[77],[35,108275,50530,108276,50534],{},[156,108277,50533],{},[38437,108279,50538],{"id":50537},[35,108281,50541,108282,50549],{},[39,108283,108285],{"href":50544,"rel":108284},[77],[156,108286,50548],{},[35,108288,50552],{},[38437,108290,50556],{"id":50555},[35,108292,50559,108293,1094,108295,50569],{},[156,108294,50562],{},[1216,108296,50565,108297,50568],{},[156,108298,24477],{},[35,108300,50572,108301,50575,108303,2186],{},[156,108302,50073],{},[156,108304,33160],{},[192,108306,108307],{"className":1380,"code":50580,"language":1383,"meta":13,"style":13},[156,108308,108309,108331,108343,108347,108369],{"__ignoreMap":13},[200,108310,108311,108313,108315,108317,108319,108321,108323,108325,108327,108329],{"class":202,"line":203},[200,108312,1390],{"class":206},[200,108314,1393],{"class":206},[200,108316,38723],{"class":223},[200,108318,227],{"class":210},[200,108320,3726],{"class":379},[200,108322,1094],{"class":214},[200,108324,25426],{"class":386},[200,108326,2104],{"class":214},[200,108328,393],{"class":379},[200,108330,280],{"class":214},[200,108332,108333,108335,108337,108339,108341],{"class":202,"line":14},[200,108334,6177],{"class":206},[200,108336,50139],{"class":223},[200,108338,227],{"class":235},[200,108340,25426],{"class":210},[200,108342,416],{"class":235},[200,108344,108345],{"class":202,"line":255},[200,108346,528],{"emptyLinePlaceholder":20},[200,108348,108349,108351,108353,108355,108357,108359,108361,108363,108365,108367],{"class":202,"line":272},[200,108350,3738],{"class":379},[200,108352,5056],{"class":210},[200,108354,509],{"class":214},[200,108356,452],{"class":206},[200,108358,18754],{"class":223},[200,108360,227],{"class":235},[200,108362,249],{"class":214},[200,108364,50544],{"class":245},[200,108366,249],{"class":214},[200,108368,416],{"class":235},[200,108370,108371,108373],{"class":202,"line":283},[200,108372,413],{"class":214},[200,108374,416],{"class":210},[35,108376,50651,108377,3092,108379,3092,108381,9502,108383,50662],{},[156,108378,20112],{},[156,108380,50656],{},[156,108382,3786],{},[156,108384,50661],{},[192,108386,108387],{"className":1380,"code":50665,"language":1383,"meta":13,"style":13},[156,108388,108389,108403,108453],{"__ignoreMap":13},[200,108390,108391,108393,108395,108397,108399,108401],{"class":202,"line":203},[200,108392,443],{"class":379},[200,108394,50674],{"class":210},[200,108396,449],{"class":214},[200,108398,5056],{"class":210},[200,108400,215],{"class":214},[200,108402,50683],{"class":210},[200,108404,108405,108407,108409,108411,108413,108415,108417,108419,108421,108423,108425,108427,108429,108431,108433,108435,108437,108439,108441,108443,108445,108447,108449,108451],{"class":202,"line":14},[200,108406,50688],{"class":214},[200,108408,3541],{"class":223},[200,108410,227],{"class":210},[200,108412,3966],{"class":214},[200,108414,700],{"class":386},[200,108416,549],{"class":214},[200,108418,321],{"class":386},[200,108420,549],{"class":214},[200,108422,50705],{"class":386},[200,108424,549],{"class":214},[200,108426,50710],{"class":386},[200,108428,390],{"class":214},[200,108430,393],{"class":379},[200,108432,1094],{"class":210},[200,108434,2767],{"class":214},[200,108436,700],{"class":210},[200,108438,549],{"class":214},[200,108440,321],{"class":210},[200,108442,549],{"class":214},[200,108444,50705],{"class":210},[200,108446,549],{"class":214},[200,108448,50733],{"class":210},[200,108450,413],{"class":214},[200,108452,584],{"class":210},[200,108454,108455,108457,108459,108461,108463,108465,108467,108469,108471,108473,108475,108477,108479,108481,108483,108485,108487,108489,108491],{"class":202,"line":255},[200,108456,50688],{"class":214},[200,108458,3773],{"class":223},[200,108460,227],{"class":210},[200,108462,3966],{"class":214},[200,108464,321],{"class":386},[200,108466,390],{"class":214},[200,108468,393],{"class":379},[200,108470,50756],{"class":210},[200,108472,6558],{"class":214},[200,108474,242],{"class":214},[200,108476,50763],{"class":245},[200,108478,249],{"class":214},[200,108480,15013],{"class":214},[200,108482,50756],{"class":210},[200,108484,6558],{"class":214},[200,108486,242],{"class":214},[200,108488,50776],{"class":245},[200,108490,249],{"class":214},[200,108492,416],{"class":210},[35,108494,50783,108495,50787,108497,50790],{},[156,108496,50786],{},[156,108498,50656],{},[35,108500,50793,108501,50797,108503,2186],{},[156,108502,50796],{},[156,108504,2476],{},[192,108506,108507],{"className":1380,"code":50802,"language":1383,"meta":13,"style":13},[156,108508,108509,108533,108571,108603,108635,108689,108709],{"__ignoreMap":13},[200,108510,108511,108513,108515,108517,108519,108521,108523,108525,108527,108529,108531],{"class":202,"line":203},[200,108512,1390],{"class":206},[200,108514,14305],{"class":379},[200,108516,50674],{"class":210},[200,108518,449],{"class":214},[200,108520,50817],{"class":223},[200,108522,227],{"class":210},[200,108524,249],{"class":214},[200,108526,2476],{"class":245},[200,108528,249],{"class":214},[200,108530,549],{"class":214},[200,108532,280],{"class":214},[200,108534,108535,108537,108539,108541,108543,108545,108547,108549,108551,108553,108555,108557,108559,108561,108563,108565,108567,108569],{"class":202,"line":14},[200,108536,49638],{"class":235},[200,108538,239],{"class":214},[200,108540,50838],{"class":223},[200,108542,227],{"class":210},[200,108544,249],{"class":214},[200,108546,3295],{"class":245},[200,108548,249],{"class":214},[200,108550,2104],{"class":210},[200,108552,215],{"class":214},[200,108554,50853],{"class":223},[200,108556,227],{"class":210},[200,108558,2767],{"class":214},[200,108560,50860],{"class":235},[200,108562,239],{"class":214},[200,108564,2439],{"class":2438},[200,108566,980],{"class":214},[200,108568,2104],{"class":210},[200,108570,252],{"class":214},[200,108572,108573,108575,108577,108579,108581,108583,108585,108587,108589,108591,108593,108595,108597,108599,108601],{"class":202,"line":255},[200,108574,236],{"class":235},[200,108576,239],{"class":214},[200,108578,50879],{"class":223},[200,108580,227],{"class":210},[200,108582,249],{"class":214},[200,108584,20112],{"class":245},[200,108586,249],{"class":214},[200,108588,2104],{"class":210},[200,108590,215],{"class":214},[200,108592,50894],{"class":223},[200,108594,1549],{"class":210},[200,108596,215],{"class":214},[200,108598,50901],{"class":223},[200,108600,1549],{"class":210},[200,108602,252],{"class":214},[200,108604,108605,108607,108609,108611,108613,108615,108617,108619,108621,108623,108625,108627,108629,108631,108633],{"class":202,"line":272},[200,108606,50910],{"class":235},[200,108608,239],{"class":214},[200,108610,50879],{"class":223},[200,108612,227],{"class":210},[200,108614,249],{"class":214},[200,108616,50656],{"class":245},[200,108618,249],{"class":214},[200,108620,2104],{"class":210},[200,108622,215],{"class":214},[200,108624,50894],{"class":223},[200,108626,1549],{"class":210},[200,108628,215],{"class":214},[200,108630,50901],{"class":223},[200,108632,1549],{"class":210},[200,108634,252],{"class":214},[200,108636,108637,108639,108641,108643,108645,108647,108649,108651,108653,108655,108657,108659,108661,108663,108665,108667,108669,108671,108673,108675,108677,108679,108681,108683,108685,108687],{"class":202,"line":283},[200,108638,50943],{"class":235},[200,108640,239],{"class":214},[200,108642,50879],{"class":223},[200,108644,227],{"class":210},[200,108646,249],{"class":214},[200,108648,3786],{"class":245},[200,108650,249],{"class":214},[200,108652,549],{"class":214},[200,108654,318],{"class":214},[200,108656,50962],{"class":235},[200,108658,239],{"class":214},[200,108660,350],{"class":210},[200,108662,249],{"class":214},[200,108664,50763],{"class":245},[200,108666,249],{"class":214},[200,108668,549],{"class":214},[200,108670,242],{"class":214},[200,108672,50776],{"class":245},[200,108674,249],{"class":214},[200,108676,18849],{"class":210},[200,108678,413],{"class":214},[200,108680,2104],{"class":210},[200,108682,215],{"class":214},[200,108684,50894],{"class":223},[200,108686,1549],{"class":210},[200,108688,252],{"class":214},[200,108690,108691,108693,108695,108697,108699,108701,108703,108705,108707],{"class":202,"line":300},[200,108692,49667],{"class":235},[200,108694,239],{"class":214},[200,108696,50879],{"class":223},[200,108698,227],{"class":210},[200,108700,249],{"class":214},[200,108702,50661],{"class":245},[200,108704,249],{"class":214},[200,108706,2104],{"class":210},[200,108708,252],{"class":214},[200,108710,108711,108713],{"class":202,"line":310},[200,108712,413],{"class":214},[200,108714,416],{"class":210},[938,108716,108717],{},[35,108718,108719,51027],{},[200,108720,944],{},[35,108722,51030],{},[192,108724,108725],{"className":1380,"code":51033,"language":1383,"meta":13,"style":13},[156,108726,108727],{"__ignoreMap":13},[200,108728,108729,108731,108733,108735,108737,108739,108741,108743,108745,108747,108749,108751,108753,108755],{"class":202,"line":203},[200,108730,207],{"class":206},[200,108732,47338],{"class":223},[200,108734,1549],{"class":210},[200,108736,215],{"class":214},[200,108738,47525],{"class":223},[200,108740,51050],{"class":210},[200,108742,215],{"class":214},[200,108744,51055],{"class":210},[200,108746,215],{"class":214},[200,108748,47540],{"class":223},[200,108750,51062],{"class":210},[200,108752,215],{"class":214},[200,108754,47590],{"class":223},[200,108756,466],{"class":210},[35,108758,51071,108759,51075,108761,51081],{},[6862,108760,51074],{},[39,108762,51080],{"href":51078,"rel":108763},[77],[35,108765,51084,108766,17897,108768,51090,108770,51094,108772,51098],{},[156,108767,51087],{},[156,108769,27725],{},[156,108771,51093],{},[6862,108773,51097],{},[35,108775,51101],{},[192,108777,108778],{"className":1380,"code":51104,"language":1383,"meta":13,"style":13},[156,108779,108780,108804,108826,108850,108854,108886,108926,108990],{"__ignoreMap":13},[200,108781,108782,108784,108786,108788,108790,108792,108794,108796,108798,108800,108802],{"class":202,"line":203},[200,108783,443],{"class":379},[200,108785,51113],{"class":210},[200,108787,449],{"class":214},[200,108789,6460],{"class":210},[200,108791,215],{"class":214},[200,108793,27119],{"class":223},[200,108795,51124],{"class":210},[200,108797,3948],{"class":2060},[200,108799,32210],{"class":210},[200,108801,215],{"class":214},[200,108803,13230],{"class":210},[200,108805,108806,108808,108810,108812,108814,108816,108818,108820,108822,108824],{"class":202,"line":14},[200,108807,443],{"class":379},[200,108809,51139],{"class":210},[200,108811,449],{"class":214},[200,108813,51144],{"class":210},[200,108815,215],{"class":214},[200,108817,51149],{"class":223},[200,108819,227],{"class":210},[200,108821,51154],{"class":2060},[200,108823,11960],{"class":214},[200,108825,51159],{"class":210},[200,108827,108828,108830,108832,108834,108836,108838,108840,108842,108844,108846,108848],{"class":202,"line":255},[200,108829,443],{"class":379},[200,108831,51166],{"class":210},[200,108833,449],{"class":214},[200,108835,51144],{"class":210},[200,108837,215],{"class":214},[200,108839,51175],{"class":223},[200,108841,51178],{"class":210},[200,108843,215],{"class":214},[200,108845,51183],{"class":210},[200,108847,2898],{"class":214},[200,108849,51188],{"class":210},[200,108851,108852],{"class":202,"line":272},[200,108853,528],{"emptyLinePlaceholder":20},[200,108855,108856,108858,108860,108862,108864,108866,108868,108870,108872,108874,108876,108878,108880,108882,108884],{"class":202,"line":283},[200,108857,1089],{"class":206},[200,108859,1094],{"class":210},[200,108861,12337],{"class":379},[200,108863,51203],{"class":210},[200,108865,449],{"class":214},[200,108867,51208],{"class":2060},[200,108869,51211],{"class":214},[200,108871,51203],{"class":210},[200,108873,599],{"class":214},[200,108875,51218],{"class":210},[200,108877,51211],{"class":214},[200,108879,51223],{"class":210},[200,108881,51226],{"class":214},[200,108883,5282],{"class":210},[200,108885,230],{"class":214},[200,108887,108888,108890,108892,108894,108896,108898,108900,108902,108904,108906,108908,108910,108912,108914,108916,108918,108920,108922,108924],{"class":202,"line":300},[200,108889,3738],{"class":379},[200,108891,51237],{"class":210},[200,108893,509],{"class":214},[200,108895,51242],{"class":210},[200,108897,215],{"class":214},[200,108899,3943],{"class":223},[200,108901,227],{"class":235},[200,108903,51251],{"class":210},[200,108905,29668],{"class":214},[200,108907,51256],{"class":210},[200,108909,549],{"class":214},[200,108911,1094],{"class":235},[200,108913,51251],{"class":210},[200,108915,13215],{"class":214},[200,108917,3953],{"class":2060},[200,108919,5282],{"class":235},[200,108921,29200],{"class":214},[200,108923,51256],{"class":210},[200,108925,416],{"class":235},[200,108927,108928,108930,108932,108934,108936,108938,108940,108942,108944,108946,108948,108950,108952,108954,108956,108958,108960,108962,108964,108966,108968,108970,108972,108974,108976,108978,108980,108982,108984,108986,108988],{"class":202,"line":310},[200,108929,6177],{"class":206},[200,108931,47338],{"class":223},[200,108933,1549],{"class":235},[200,108935,215],{"class":214},[200,108937,47525],{"class":223},[200,108939,227],{"class":235},[200,108941,47418],{"class":210},[200,108943,215],{"class":214},[200,108945,2476],{"class":210},[200,108947,2104],{"class":235},[200,108949,215],{"class":214},[200,108951,47540],{"class":223},[200,108953,227],{"class":235},[200,108955,47540],{"class":210},[200,108957,2104],{"class":235},[200,108959,215],{"class":214},[200,108961,51311],{"class":223},[200,108963,227],{"class":235},[200,108965,2767],{"class":214},[200,108967,43458],{"class":235},[200,108969,239],{"class":214},[200,108971,47357],{"class":210},[200,108973,215],{"class":214},[200,108975,2476],{"class":210},[200,108977,215],{"class":214},[200,108979,50656],{"class":210},[200,108981,980],{"class":214},[200,108983,2104],{"class":235},[200,108985,215],{"class":214},[200,108987,47590],{"class":223},[200,108989,466],{"class":235},[200,108991,108992],{"class":202,"line":336},[200,108993,1129],{"class":214},[35,108995,51346,108996,51349],{},[156,108997,51311],{},[35,108999,109000],{},[1216,109001,51354],{},[35,109003,51357],{},[38437,109005,51361],{"id":51360},[35,109007,51364,109008,51368],{},[156,109009,51367],{},[192,109011,109012],{"className":1380,"code":51371,"language":1383,"meta":13,"style":13},[156,109013,109014,109032,109054,109072,109090,109108,109126,109146,109150,109156],{"__ignoreMap":13},[200,109015,109016,109018,109020,109022,109024,109026,109028,109030],{"class":202,"line":203},[200,109017,1390],{"class":206},[200,109019,1393],{"class":206},[200,109021,38723],{"class":223},[200,109023,227],{"class":210},[200,109025,3726],{"class":379},[200,109027,3729],{"class":214},[200,109029,393],{"class":379},[200,109031,280],{"class":214},[200,109033,109034,109036,109038,109040,109042,109044,109046,109048,109050,109052],{"class":202,"line":14},[200,109035,3738],{"class":379},[200,109037,51242],{"class":210},[200,109039,509],{"class":214},[200,109041,452],{"class":206},[200,109043,47338],{"class":223},[200,109045,1549],{"class":235},[200,109047,215],{"class":214},[200,109049,769],{"class":223},[200,109051,227],{"class":235},[200,109053,230],{"class":214},[200,109055,109056,109058,109060,109062,109064,109066,109068,109070],{"class":202,"line":255},[200,109057,15861],{"class":235},[200,109059,239],{"class":214},[200,109061,47357],{"class":210},[200,109063,215],{"class":214},[200,109065,2476],{"class":210},[200,109067,215],{"class":214},[200,109069,3295],{"class":210},[200,109071,252],{"class":214},[200,109073,109074,109076,109078,109080,109082,109084,109086,109088],{"class":202,"line":272},[200,109075,11812],{"class":235},[200,109077,239],{"class":214},[200,109079,47357],{"class":210},[200,109081,215],{"class":214},[200,109083,2476],{"class":210},[200,109085,215],{"class":214},[200,109087,20112],{"class":210},[200,109089,252],{"class":214},[200,109091,109092,109094,109096,109098,109100,109102,109104,109106],{"class":202,"line":283},[200,109093,286],{"class":235},[200,109095,239],{"class":214},[200,109097,47357],{"class":210},[200,109099,215],{"class":214},[200,109101,2476],{"class":210},[200,109103,215],{"class":214},[200,109105,3786],{"class":210},[200,109107,252],{"class":214},[200,109109,109110,109112,109114,109116,109118,109120,109122,109124],{"class":202,"line":300},[200,109111,51472],{"class":235},[200,109113,239],{"class":214},[200,109115,47357],{"class":210},[200,109117,215],{"class":214},[200,109119,2476],{"class":210},[200,109121,215],{"class":214},[200,109123,50661],{"class":210},[200,109125,252],{"class":214},[200,109127,109128,109130,109132,109134,109136,109138,109140,109142,109144],{"class":202,"line":310},[200,109129,5095],{"class":214},[200,109131,2104],{"class":235},[200,109133,215],{"class":214},[200,109135,1489],{"class":223},[200,109137,227],{"class":235},[200,109139,47418],{"class":210},[200,109141,215],{"class":214},[200,109143,2476],{"class":210},[200,109145,416],{"class":235},[200,109147,109148],{"class":202,"line":336},[200,109149,528],{"emptyLinePlaceholder":20},[200,109151,109152,109154],{"class":202,"line":342},[200,109153,3758],{"class":206},[200,109155,51517],{"class":210},[200,109157,109158,109160],{"class":202,"line":365},[200,109159,413],{"class":214},[200,109161,416],{"class":210},[35,109163,51526],{},[1929,109165,24146],{"id":51529},[35,109167,51532],{},[35,109169,109170],{},[1707,109171],{"alt":50519,"src":51537},[35,109173,51540],{},[35,109175,51543],{},[51,109177,51547],{"id":51546},[35,109179,109180],{},[1216,109181,51552],{},[35,109183,51555],{},[35,109185,51558,109186,51564,109189],{},[39,109187,51563],{"href":51561,"rel":109188},[77],[1216,109190,51567],{},[35,109192,51570],{},[68,109194,109195,109197,109199],{},[71,109196,51575],{},[71,109198,51578],{},[71,109200,51581],{},[35,109202,51584],{},[35,109204,51587,109205],{},[1216,109206,51590],{},[35,109208,51593,109209,51597,109211,51601,109213,51604,109215],{},[156,109210,51596],{},[156,109212,51600],{},[156,109214,51596],{},[1216,109216,51607],{},[35,109218,51610],{},[1593,109220,109221,109223,109225,109231,109237],{},[71,109222,51615],{},[71,109224,51618],{},[71,109226,51621,109227,5569,109229,215],{},[156,109228,49068],{},[156,109230,51626],{},[71,109232,51629,109233,51633,109235,215],{},[156,109234,51632],{},[156,109236,51636],{},[71,109238,51639],{},[35,109240,51642],{},[192,109242,109243],{"className":1380,"code":51645,"language":1383,"meta":13,"style":13},[156,109244,109245,109269,109307,109339,109371,109403,109423,109449,109511,109557,109623],{"__ignoreMap":13},[200,109246,109247,109249,109251,109253,109255,109257,109259,109261,109263,109265,109267],{"class":202,"line":203},[200,109248,1390],{"class":206},[200,109250,14305],{"class":379},[200,109252,49329],{"class":210},[200,109254,449],{"class":214},[200,109256,50817],{"class":223},[200,109258,227],{"class":210},[200,109260,249],{"class":214},[200,109262,47155],{"class":245},[200,109264,249],{"class":214},[200,109266,549],{"class":214},[200,109268,280],{"class":214},[200,109270,109271,109273,109275,109277,109279,109281,109283,109285,109287,109289,109291,109293,109295,109297,109299,109301,109303,109305],{"class":202,"line":14},[200,109272,49638],{"class":235},[200,109274,239],{"class":214},[200,109276,50838],{"class":223},[200,109278,227],{"class":210},[200,109280,249],{"class":214},[200,109282,3295],{"class":245},[200,109284,249],{"class":214},[200,109286,2104],{"class":210},[200,109288,215],{"class":214},[200,109290,50853],{"class":223},[200,109292,227],{"class":210},[200,109294,2767],{"class":214},[200,109296,50860],{"class":235},[200,109298,239],{"class":214},[200,109300,2439],{"class":2438},[200,109302,980],{"class":214},[200,109304,2104],{"class":210},[200,109306,252],{"class":214},[200,109308,109309,109311,109313,109315,109317,109319,109321,109323,109325,109327,109329,109331,109333,109335,109337],{"class":202,"line":255},[200,109310,51714],{"class":235},[200,109312,239],{"class":214},[200,109314,50838],{"class":223},[200,109316,227],{"class":210},[200,109318,249],{"class":214},[200,109320,51725],{"class":245},[200,109322,249],{"class":214},[200,109324,2104],{"class":210},[200,109326,215],{"class":214},[200,109328,50894],{"class":223},[200,109330,1549],{"class":210},[200,109332,215],{"class":214},[200,109334,50901],{"class":223},[200,109336,1549],{"class":210},[200,109338,252],{"class":214},[200,109340,109341,109343,109345,109347,109349,109351,109353,109355,109357,109359,109361,109363,109365,109367,109369],{"class":202,"line":272},[200,109342,51748],{"class":235},[200,109344,239],{"class":214},[200,109346,50879],{"class":223},[200,109348,227],{"class":210},[200,109350,249],{"class":214},[200,109352,51759],{"class":245},[200,109354,249],{"class":214},[200,109356,2104],{"class":210},[200,109358,215],{"class":214},[200,109360,50894],{"class":223},[200,109362,1549],{"class":210},[200,109364,215],{"class":214},[200,109366,50901],{"class":223},[200,109368,1549],{"class":210},[200,109370,252],{"class":214},[200,109372,109373,109375,109377,109379,109381,109383,109385,109387,109389,109391,109393,109395,109397,109399,109401],{"class":202,"line":283},[200,109374,51782],{"class":235},[200,109376,239],{"class":214},[200,109378,50879],{"class":223},[200,109380,227],{"class":210},[200,109382,249],{"class":214},[200,109384,51626],{"class":245},[200,109386,249],{"class":214},[200,109388,2104],{"class":210},[200,109390,215],{"class":214},[200,109392,50894],{"class":223},[200,109394,1549],{"class":210},[200,109396,215],{"class":214},[200,109398,50901],{"class":223},[200,109400,1549],{"class":210},[200,109402,252],{"class":214},[200,109404,109405,109407,109409,109411,109413,109415,109417,109419,109421],{"class":202,"line":300},[200,109406,236],{"class":235},[200,109408,239],{"class":214},[200,109410,50879],{"class":223},[200,109412,227],{"class":210},[200,109414,249],{"class":214},[200,109416,20112],{"class":245},[200,109418,249],{"class":214},[200,109420,2104],{"class":210},[200,109422,252],{"class":214},[200,109424,109425,109427,109429,109431,109433,109435,109437,109439,109441,109443,109445,109447],{"class":202,"line":310},[200,109426,51835],{"class":235},[200,109428,239],{"class":214},[200,109430,50879],{"class":223},[200,109432,227],{"class":210},[200,109434,249],{"class":214},[200,109436,51846],{"class":245},[200,109438,249],{"class":214},[200,109440,2104],{"class":210},[200,109442,215],{"class":214},[200,109444,50894],{"class":223},[200,109446,1549],{"class":210},[200,109448,252],{"class":214},[200,109450,109451,109453,109455,109457,109459,109461,109463,109465,109467,109469,109471,109473,109475,109477,109479,109481,109483,109485,109487,109489,109491,109493,109495,109497,109499,109501,109503,109505,109507,109509],{"class":202,"line":336},[200,109452,51863],{"class":235},[200,109454,239],{"class":214},[200,109456,50879],{"class":223},[200,109458,227],{"class":210},[200,109460,249],{"class":214},[200,109462,51874],{"class":245},[200,109464,249],{"class":214},[200,109466,549],{"class":214},[200,109468,318],{"class":214},[200,109470,50962],{"class":235},[200,109472,239],{"class":214},[200,109474,350],{"class":210},[200,109476,249],{"class":214},[200,109478,39189],{"class":245},[200,109480,249],{"class":214},[200,109482,549],{"class":214},[200,109484,242],{"class":214},[200,109486,51899],{"class":245},[200,109488,249],{"class":214},[200,109490,18849],{"class":210},[200,109492,413],{"class":214},[200,109494,2104],{"class":210},[200,109496,215],{"class":214},[200,109498,51912],{"class":223},[200,109500,227],{"class":210},[200,109502,249],{"class":214},[200,109504,51899],{"class":245},[200,109506,249],{"class":214},[200,109508,2104],{"class":210},[200,109510,252],{"class":214},[200,109512,109513,109515,109517,109519,109521,109523,109525,109527,109529,109531,109533,109535,109537,109539,109541,109543,109545,109547,109549,109551,109553,109555],{"class":202,"line":342},[200,109514,51929],{"class":235},[200,109516,239],{"class":214},[200,109518,50879],{"class":223},[200,109520,227],{"class":210},[200,109522,249],{"class":214},[200,109524,51632],{"class":245},[200,109526,249],{"class":214},[200,109528,2104],{"class":210},[200,109530,215],{"class":214},[200,109532,50894],{"class":223},[200,109534,1549],{"class":210},[200,109536,215],{"class":214},[200,109538,51954],{"class":223},[200,109540,227],{"class":210},[200,109542,1549],{"class":214},[200,109544,393],{"class":379},[200,109546,51963],{"class":223},[200,109548,3333],{"class":214},[200,109550,51968],{"class":245},[200,109552,3333],{"class":214},[200,109554,2104],{"class":210},[200,109556,252],{"class":214},[200,109558,109559,109561,109563,109565,109567,109569,109571,109573,109575,109577,109579,109581,109583,109585,109587,109589,109591,109593,109595,109597,109599,109601,109603,109605,109607,109609,109611,109613,109615,109617,109619,109621],{"class":202,"line":365},[200,109560,51979],{"class":235},[200,109562,239],{"class":214},[200,109564,50879],{"class":223},[200,109566,227],{"class":210},[200,109568,249],{"class":214},[200,109570,51636],{"class":245},[200,109572,249],{"class":214},[200,109574,2104],{"class":210},[200,109576,215],{"class":214},[200,109578,50894],{"class":223},[200,109580,1549],{"class":210},[200,109582,215],{"class":214},[200,109584,51954],{"class":223},[200,109586,227],{"class":210},[200,109588,1549],{"class":214},[200,109590,393],{"class":379},[200,109592,51963],{"class":223},[200,109594,3333],{"class":214},[200,109596,51968],{"class":245},[200,109598,3333],{"class":214},[200,109600,2104],{"class":210},[200,109602,215],{"class":214},[200,109604,52024],{"class":223},[200,109606,227],{"class":210},[200,109608,1549],{"class":214},[200,109610,393],{"class":379},[200,109612,51963],{"class":223},[200,109614,3333],{"class":214},[200,109616,51968],{"class":245},[200,109618,3333],{"class":214},[200,109620,2104],{"class":210},[200,109622,252],{"class":214},[200,109624,109625,109627],{"class":202,"line":371},[200,109626,413],{"class":214},[200,109628,416],{"class":210},[35,109630,52051,109631,1655,109633,52056,109635,52059,109637,52062],{},[156,109632,51954],{},[156,109634,52024],{},[156,109636,37035],{},[156,109638,37060],{},[35,109640,52065,109641,52069],{},[156,109642,52068],{},[192,109644,109645],{"className":1380,"code":52072,"language":1383,"meta":13,"style":13},[156,109646,109647,109663,109671,109681,109685,109703,109721,109725,109739,109743,109757,109761,109825,109829,109833,109837,109841,109845,109871,109905,109911,109925,109939,109953,109967,109981,109999,110007,110021,110035,110049,110063,110067,110085,110089,110093,110097,110101,110105,110119,110127,110141,110155,110169,110183,110197,110219,110223,110229,110233,110253,110257],{"__ignoreMap":13},[200,109648,109649,109651,109653,109655,109657,109659,109661],{"class":202,"line":203},[200,109650,1390],{"class":206},[200,109652,1393],{"class":206},[200,109654,45956],{"class":210},[200,109656,215],{"class":214},[200,109658,45961],{"class":223},[200,109660,227],{"class":210},[200,109662,230],{"class":214},[200,109664,109665,109667,109669],{"class":202,"line":14},[200,109666,45970],{"class":235},[200,109668,239],{"class":214},[200,109670,280],{"class":214},[200,109672,109673,109675,109677,109679],{"class":202,"line":255},[200,109674,45979],{"class":235},[200,109676,239],{"class":214},[200,109678,2439],{"class":2438},[200,109680,252],{"class":214},[200,109682,109683],{"class":202,"line":272},[200,109684,368],{"class":214},[200,109686,109687,109689,109691,109693,109695,109697,109699,109701],{"class":202,"line":283},[200,109688,3234],{"class":379},[200,109690,45996],{"class":235},[200,109692,227],{"class":214},[200,109694,25426],{"class":386},[200,109696,549],{"class":214},[200,109698,3561],{"class":386},[200,109700,2104],{"class":214},[200,109702,280],{"class":214},[200,109704,109705,109707,109709,109711,109713,109715,109717,109719],{"class":202,"line":300},[200,109706,2014],{"class":379},[200,109708,318],{"class":214},[200,109710,34533],{"class":235},[200,109712,239],{"class":214},[200,109714,52143],{"class":210},[200,109716,980],{"class":214},[200,109718,509],{"class":214},[200,109720,4215],{"class":210},[200,109722,109723],{"class":202,"line":310},[200,109724,528],{"emptyLinePlaceholder":20},[200,109726,109727,109729,109731,109733,109735,109737],{"class":202,"line":336},[200,109728,2014],{"class":379},[200,109730,52160],{"class":210},[200,109732,509],{"class":214},[200,109734,52143],{"class":210},[200,109736,215],{"class":214},[200,109738,52169],{"class":210},[200,109740,109741],{"class":202,"line":342},[200,109742,528],{"emptyLinePlaceholder":20},[200,109744,109745,109747,109749,109751,109753,109755],{"class":202,"line":365},[200,109746,52178],{"class":379},[200,109748,34533],{"class":210},[200,109750,239],{"class":214},[200,109752,34635],{"class":1364},[200,109754,39992],{"class":214},[200,109756,52189],{"class":1364},[200,109758,109759],{"class":202,"line":371},[200,109760,528],{"emptyLinePlaceholder":20},[200,109762,109763,109765,109767,109769,109771,109773,109775,109777,109779,109781,109783,109785,109787,109789,109791,109793,109795,109797,109799,109801,109803,109805,109807,109809,109811,109813,109815,109817,109819,109821,109823],{"class":202,"line":398},[200,109764,52198],{"class":210},[200,109766,509],{"class":214},[200,109768,452],{"class":206},[200,109770,47338],{"class":223},[200,109772,1549],{"class":235},[200,109774,215],{"class":214},[200,109776,769],{"class":223},[200,109778,1549],{"class":235},[200,109780,215],{"class":214},[200,109782,1489],{"class":223},[200,109784,227],{"class":235},[200,109786,47418],{"class":210},[200,109788,215],{"class":214},[200,109790,47155],{"class":210},[200,109792,2104],{"class":235},[200,109794,215],{"class":214},[200,109796,48864],{"class":223},[200,109798,227],{"class":235},[200,109800,48869],{"class":223},[200,109802,227],{"class":235},[200,109804,47418],{"class":210},[200,109806,215],{"class":214},[200,109808,47155],{"class":210},[200,109810,215],{"class":214},[200,109812,46052],{"class":210},[200,109814,549],{"class":214},[200,109816,52160],{"class":210},[200,109818,6131],{"class":235},[200,109820,215],{"class":214},[200,109822,42718],{"class":223},[200,109824,466],{"class":235},[200,109826,109827],{"class":202,"line":405},[200,109828,528],{"emptyLinePlaceholder":20},[200,109830,109831],{"class":202,"line":410},[200,109832,19641],{"class":401},[200,109834,109835],{"class":202,"line":860},[200,109836,52271],{"class":401},[200,109838,109839],{"class":202,"line":889},[200,109840,52276],{"class":401},[200,109842,109843],{"class":202,"line":899},[200,109844,19651],{"class":401},[200,109846,109847,109849,109851,109853,109855,109857,109859,109861,109863,109865,109867,109869],{"class":202,"line":904},[200,109848,5267],{"class":206},[200,109850,1094],{"class":235},[200,109852,11852],{"class":214},[200,109854,26847],{"class":210},[200,109856,15013],{"class":214},[200,109858,52295],{"class":223},[200,109860,227],{"class":235},[200,109862,26847],{"class":210},[200,109864,549],{"class":214},[200,109866,52143],{"class":210},[200,109868,6478],{"class":235},[200,109870,230],{"class":214},[200,109872,109873,109875,109877,109879,109881,109883,109885,109887,109889,109891,109893,109895,109897,109899,109901,109903],{"class":202,"line":930},[200,109874,46034],{"class":210},[200,109876,509],{"class":214},[200,109878,452],{"class":206},[200,109880,47338],{"class":223},[200,109882,1549],{"class":235},[200,109884,215],{"class":214},[200,109886,47525],{"class":223},[200,109888,227],{"class":235},[200,109890,47418],{"class":210},[200,109892,215],{"class":214},[200,109894,47155],{"class":210},[200,109896,2104],{"class":235},[200,109898,215],{"class":214},[200,109900,47540],{"class":223},[200,109902,227],{"class":235},[200,109904,230],{"class":214},[200,109906,109907,109909],{"class":202,"line":3655},[200,109908,46043],{"class":210},[200,109910,252],{"class":214},[200,109912,109913,109915,109917,109919,109921,109923],{"class":202,"line":3660},[200,109914,52352],{"class":235},[200,109916,239],{"class":214},[200,109918,52143],{"class":210},[200,109920,215],{"class":214},[200,109922,51626],{"class":210},[200,109924,252],{"class":214},[200,109926,109927,109929,109931,109933,109935,109937],{"class":202,"line":4035},[200,109928,52367],{"class":235},[200,109930,239],{"class":214},[200,109932,52143],{"class":210},[200,109934,215],{"class":214},[200,109936,51759],{"class":210},[200,109938,252],{"class":214},[200,109940,109941,109943,109945,109947,109949,109951],{"class":202,"line":4490},[200,109942,11737],{"class":235},[200,109944,239],{"class":214},[200,109946,52143],{"class":210},[200,109948,215],{"class":214},[200,109950,20112],{"class":210},[200,109952,252],{"class":214},[200,109954,109955,109957,109959,109961,109963,109965],{"class":202,"line":4496},[200,109956,52396],{"class":235},[200,109958,239],{"class":214},[200,109960,52143],{"class":210},[200,109962,215],{"class":214},[200,109964,51846],{"class":210},[200,109966,252],{"class":214},[200,109968,109969,109971,109973,109975,109977,109979],{"class":202,"line":4502},[200,109970,2647],{"class":214},[200,109972,2104],{"class":235},[200,109974,215],{"class":214},[200,109976,52417],{"class":223},[200,109978,227],{"class":235},[200,109980,230],{"class":214},[200,109982,109983,109985,109987,109989,109991,109993,109995,109997],{"class":202,"line":4508},[200,109984,52426],{"class":235},[200,109986,239],{"class":214},[200,109988,47357],{"class":210},[200,109990,215],{"class":214},[200,109992,47155],{"class":210},[200,109994,215],{"class":214},[200,109996,46052],{"class":210},[200,109998,252],{"class":214},[200,110000,110001,110003,110005],{"class":202,"line":5469},[200,110002,52445],{"class":235},[200,110004,239],{"class":214},[200,110006,280],{"class":214},[200,110008,110009,110011,110013,110015,110017,110019],{"class":202,"line":5474},[200,110010,52454],{"class":235},[200,110012,239],{"class":214},[200,110014,52143],{"class":210},[200,110016,215],{"class":214},[200,110018,51626],{"class":210},[200,110020,252],{"class":214},[200,110022,110023,110025,110027,110029,110031,110033],{"class":202,"line":13237},[200,110024,52469],{"class":235},[200,110026,239],{"class":214},[200,110028,52143],{"class":210},[200,110030,215],{"class":214},[200,110032,51759],{"class":210},[200,110034,252],{"class":214},[200,110036,110037,110039,110041,110043,110045,110047],{"class":202,"line":13263},[200,110038,52484],{"class":235},[200,110040,239],{"class":214},[200,110042,52143],{"class":210},[200,110044,215],{"class":214},[200,110046,20112],{"class":210},[200,110048,252],{"class":214},[200,110050,110051,110053,110055,110057,110059,110061],{"class":202,"line":13279},[200,110052,52499],{"class":235},[200,110054,239],{"class":214},[200,110056,52143],{"class":210},[200,110058,215],{"class":214},[200,110060,51846],{"class":210},[200,110062,252],{"class":214},[200,110064,110065],{"class":202,"line":13323},[200,110066,4965],{"class":214},[200,110068,110069,110071,110073,110075,110077,110079,110081,110083],{"class":202,"line":13329},[200,110070,2647],{"class":214},[200,110072,2104],{"class":235},[200,110074,215],{"class":214},[200,110076,52524],{"class":223},[200,110078,1549],{"class":235},[200,110080,215],{"class":214},[200,110082,42718],{"class":223},[200,110084,466],{"class":235},[200,110086,110087],{"class":202,"line":13334},[200,110088,4977],{"class":214},[200,110090,110091],{"class":202,"line":13360},[200,110092,528],{"emptyLinePlaceholder":20},[200,110094,110095],{"class":202,"line":13365},[200,110096,19641],{"class":401},[200,110098,110099],{"class":202,"line":13385},[200,110100,52549],{"class":401},[200,110102,110103],{"class":202,"line":13405},[200,110104,19651],{"class":401},[200,110106,110107,110109,110111,110113,110115,110117],{"class":202,"line":13410},[200,110108,19231],{"class":206},[200,110110,46021],{"class":223},[200,110112,227],{"class":235},[200,110114,25426],{"class":210},[200,110116,549],{"class":214},[200,110118,280],{"class":214},[200,110120,110121,110123,110125],{"class":202,"line":13418},[200,110122,46034],{"class":235},[200,110124,239],{"class":214},[200,110126,280],{"class":214},[200,110128,110129,110131,110133,110135,110137,110139],{"class":202,"line":13436},[200,110130,12065],{"class":235},[200,110132,239],{"class":214},[200,110134,34533],{"class":210},[200,110136,215],{"class":214},[200,110138,3295],{"class":210},[200,110140,52590],{"class":214},[200,110142,110143,110145,110147,110149,110151,110153],{"class":202,"line":13466},[200,110144,52352],{"class":235},[200,110146,239],{"class":214},[200,110148,52143],{"class":210},[200,110150,215],{"class":214},[200,110152,51626],{"class":210},[200,110154,252],{"class":214},[200,110156,110157,110159,110161,110163,110165,110167],{"class":202,"line":13472},[200,110158,52367],{"class":235},[200,110160,239],{"class":214},[200,110162,52143],{"class":210},[200,110164,215],{"class":214},[200,110166,51759],{"class":210},[200,110168,252],{"class":214},[200,110170,110171,110173,110175,110177,110179,110181],{"class":202,"line":13477},[200,110172,11737],{"class":235},[200,110174,239],{"class":214},[200,110176,52143],{"class":210},[200,110178,215],{"class":214},[200,110180,20112],{"class":210},[200,110182,252],{"class":214},[200,110184,110185,110187,110189,110191,110193,110195],{"class":202,"line":13482},[200,110186,52396],{"class":235},[200,110188,239],{"class":214},[200,110190,52143],{"class":210},[200,110192,215],{"class":214},[200,110194,51846],{"class":210},[200,110196,252],{"class":214},[200,110198,110199,110201,110203,110205,110207,110209,110211,110213,110215,110217],{"class":202,"line":13497},[200,110200,52651],{"class":235},[200,110202,239],{"class":214},[200,110204,34533],{"class":210},[200,110206,215],{"class":214},[200,110208,39182],{"class":210},[200,110210,15013],{"class":214},[200,110212,242],{"class":214},[200,110214,51899],{"class":245},[200,110216,249],{"class":214},[200,110218,252],{"class":214},[200,110220,110221],{"class":202,"line":13508},[200,110222,12098],{"class":214},[200,110224,110225,110227],{"class":202,"line":13513},[200,110226,2111],{"class":214},[200,110228,416],{"class":235},[200,110230,110231],{"class":202,"line":13518},[200,110232,528],{"emptyLinePlaceholder":20},[200,110234,110235,110237,110239,110241,110243,110245,110247,110249,110251],{"class":202,"line":20247},[200,110236,3311],{"class":206},[200,110238,46075],{"class":223},[200,110240,227],{"class":235},[200,110242,25426],{"class":210},[200,110244,549],{"class":214},[200,110246,242],{"class":214},[200,110248,52700],{"class":245},[200,110250,249],{"class":214},[200,110252,416],{"class":235},[200,110254,110255],{"class":202,"line":20269},[200,110256,368],{"class":214},[200,110258,110259,110261],{"class":202,"line":20274},[200,110260,413],{"class":214},[200,110262,416],{"class":210},[35,110264,52717],{},[51,110266,52721],{"id":52720},[35,110268,110269],{},[1707,110270],{"alt":52726,"src":52727},[35,110272,110273],{},[1216,110274,110275,47101,110278,47105,110281,45894,110284,215],{},[39,110276,38397],{"href":45853,"rel":110277},[77],[39,110279,45889],{"href":18579,"rel":110280},[77],[39,110282,19427],{"href":45844,"rel":110283},[77],[39,110285,11636],{"href":45853,"rel":110286},[77],[35,110288,52746],{},[1929,110290,52750],{"id":52749},[35,110292,110293],{},[1216,110294,52755],{},[35,110296,52758],{},[192,110298,110299],{"className":1380,"code":52761,"language":1383,"meta":13,"style":13},[156,110300,110301,110325,110363,110395,110421,110447,110523,110591,110611,110637,110663,110705,110747],{"__ignoreMap":13},[200,110302,110303,110305,110307,110309,110311,110313,110315,110317,110319,110321,110323],{"class":202,"line":203},[200,110304,1390],{"class":206},[200,110306,14305],{"class":379},[200,110308,52772],{"class":210},[200,110310,449],{"class":214},[200,110312,50817],{"class":223},[200,110314,227],{"class":210},[200,110316,249],{"class":214},[200,110318,51600],{"class":245},[200,110320,249],{"class":214},[200,110322,549],{"class":214},[200,110324,280],{"class":214},[200,110326,110327,110329,110331,110333,110335,110337,110339,110341,110343,110345,110347,110349,110351,110353,110355,110357,110359,110361],{"class":202,"line":14},[200,110328,49638],{"class":235},[200,110330,239],{"class":214},[200,110332,50838],{"class":223},[200,110334,227],{"class":210},[200,110336,249],{"class":214},[200,110338,3295],{"class":245},[200,110340,249],{"class":214},[200,110342,2104],{"class":210},[200,110344,215],{"class":214},[200,110346,50853],{"class":223},[200,110348,227],{"class":210},[200,110350,2767],{"class":214},[200,110352,50860],{"class":235},[200,110354,239],{"class":214},[200,110356,2439],{"class":2438},[200,110358,980],{"class":214},[200,110360,2104],{"class":210},[200,110362,252],{"class":214},[200,110364,110365,110367,110369,110371,110373,110375,110377,110379,110381,110383,110385,110387,110389,110391,110393],{"class":202,"line":255},[200,110366,52831],{"class":235},[200,110368,239],{"class":214},[200,110370,50879],{"class":223},[200,110372,227],{"class":210},[200,110374,249],{"class":214},[200,110376,52842],{"class":245},[200,110378,249],{"class":214},[200,110380,2104],{"class":210},[200,110382,215],{"class":214},[200,110384,50894],{"class":223},[200,110386,1549],{"class":210},[200,110388,215],{"class":214},[200,110390,50901],{"class":223},[200,110392,1549],{"class":210},[200,110394,252],{"class":214},[200,110396,110397,110399,110401,110403,110405,110407,110409,110411,110413,110415,110417,110419],{"class":202,"line":272},[200,110398,52865],{"class":235},[200,110400,239],{"class":214},[200,110402,50879],{"class":223},[200,110404,227],{"class":210},[200,110406,249],{"class":214},[200,110408,47385],{"class":245},[200,110410,249],{"class":214},[200,110412,2104],{"class":210},[200,110414,215],{"class":214},[200,110416,50894],{"class":223},[200,110418,1549],{"class":210},[200,110420,252],{"class":214},[200,110422,110423,110425,110427,110429,110431,110433,110435,110437,110439,110441,110443,110445],{"class":202,"line":283},[200,110424,23703],{"class":235},[200,110426,239],{"class":214},[200,110428,50879],{"class":223},[200,110430,227],{"class":210},[200,110432,249],{"class":214},[200,110434,3581],{"class":245},[200,110436,249],{"class":214},[200,110438,2104],{"class":210},[200,110440,215],{"class":214},[200,110442,50894],{"class":223},[200,110444,1549],{"class":210},[200,110446,252],{"class":214},[200,110448,110449,110451,110453,110455,110457,110459,110461,110463,110465,110467,110469,110471,110473,110475,110477,110479,110481,110483,110485,110487,110489,110491,110493,110495,110497,110499,110501,110503,110505,110507,110509,110511,110513,110515,110517,110519,110521],{"class":202,"line":300},[200,110450,52918],{"class":235},[200,110452,239],{"class":214},[200,110454,50879],{"class":223},[200,110456,227],{"class":210},[200,110458,249],{"class":214},[200,110460,39512],{"class":245},[200,110462,249],{"class":214},[200,110464,549],{"class":214},[200,110466,318],{"class":214},[200,110468,50962],{"class":235},[200,110470,239],{"class":214},[200,110472,350],{"class":210},[200,110474,249],{"class":214},[200,110476,52945],{"class":245},[200,110478,249],{"class":214},[200,110480,549],{"class":214},[200,110482,242],{"class":214},[200,110484,52954],{"class":245},[200,110486,249],{"class":214},[200,110488,549],{"class":214},[200,110490,242],{"class":214},[200,110492,52963],{"class":245},[200,110494,249],{"class":214},[200,110496,18849],{"class":210},[200,110498,413],{"class":214},[200,110500,2104],{"class":210},[200,110502,215],{"class":214},[200,110504,50894],{"class":223},[200,110506,1549],{"class":210},[200,110508,215],{"class":214},[200,110510,51912],{"class":223},[200,110512,227],{"class":210},[200,110514,249],{"class":214},[200,110516,52945],{"class":245},[200,110518,249],{"class":214},[200,110520,2104],{"class":210},[200,110522,252],{"class":214},[200,110524,110525,110527,110529,110531,110533,110535,110537,110539,110541,110543,110545,110547,110549,110551,110553,110555,110557,110559,110561,110563,110565,110567,110569,110571,110573,110575,110577,110579,110581,110583,110585,110587,110589],{"class":202,"line":310},[200,110526,52998],{"class":235},[200,110528,239],{"class":214},[200,110530,50879],{"class":223},[200,110532,227],{"class":210},[200,110534,249],{"class":214},[200,110536,53009],{"class":245},[200,110538,249],{"class":214},[200,110540,549],{"class":214},[200,110542,318],{"class":214},[200,110544,50962],{"class":235},[200,110546,239],{"class":214},[200,110548,350],{"class":210},[200,110550,249],{"class":214},[200,110552,53026],{"class":245},[200,110554,249],{"class":214},[200,110556,549],{"class":214},[200,110558,242],{"class":214},[200,110560,53035],{"class":245},[200,110562,249],{"class":214},[200,110564,18849],{"class":210},[200,110566,413],{"class":214},[200,110568,2104],{"class":210},[200,110570,215],{"class":214},[200,110572,50894],{"class":223},[200,110574,1549],{"class":210},[200,110576,215],{"class":214},[200,110578,51912],{"class":223},[200,110580,227],{"class":210},[200,110582,249],{"class":214},[200,110584,53026],{"class":245},[200,110586,249],{"class":214},[200,110588,2104],{"class":210},[200,110590,252],{"class":214},[200,110592,110593,110595,110597,110599,110601,110603,110605,110607,110609],{"class":202,"line":336},[200,110594,53070],{"class":235},[200,110596,239],{"class":214},[200,110598,50879],{"class":223},[200,110600,227],{"class":210},[200,110602,249],{"class":214},[200,110604,53081],{"class":245},[200,110606,249],{"class":214},[200,110608,2104],{"class":210},[200,110610,252],{"class":214},[200,110612,110613,110615,110617,110619,110621,110623,110625,110627,110629,110631,110633,110635],{"class":202,"line":342},[200,110614,53092],{"class":235},[200,110616,239],{"class":214},[200,110618,50879],{"class":223},[200,110620,227],{"class":210},[200,110622,249],{"class":214},[200,110624,53103],{"class":245},[200,110626,249],{"class":214},[200,110628,2104],{"class":210},[200,110630,215],{"class":214},[200,110632,50894],{"class":223},[200,110634,1549],{"class":210},[200,110636,252],{"class":214},[200,110638,110639,110641,110643,110645,110647,110649,110651,110653,110655,110657,110659,110661],{"class":202,"line":365},[200,110640,258],{"class":235},[200,110642,239],{"class":214},[200,110644,50879],{"class":223},[200,110646,227],{"class":210},[200,110648,249],{"class":214},[200,110650,3597],{"class":245},[200,110652,249],{"class":214},[200,110654,2104],{"class":210},[200,110656,215],{"class":214},[200,110658,50894],{"class":223},[200,110660,1549],{"class":210},[200,110662,252],{"class":214},[200,110664,110665,110667,110669,110671,110673,110675,110677,110679,110681,110683,110685,110687,110689,110691,110693,110695,110697,110699,110701,110703],{"class":202,"line":371},[200,110666,53146],{"class":235},[200,110668,239],{"class":214},[200,110670,50838],{"class":223},[200,110672,227],{"class":210},[200,110674,249],{"class":214},[200,110676,51596],{"class":245},[200,110678,249],{"class":214},[200,110680,2104],{"class":210},[200,110682,215],{"class":214},[200,110684,50894],{"class":223},[200,110686,1549],{"class":210},[200,110688,215],{"class":214},[200,110690,53171],{"class":223},[200,110692,227],{"class":210},[200,110694,1549],{"class":214},[200,110696,393],{"class":379},[200,110698,48997],{"class":210},[200,110700,215],{"class":214},[200,110702,53184],{"class":210},[200,110704,252],{"class":214},[200,110706,110707,110709,110711,110713,110715,110717,110719,110721,110723,110725,110727,110729,110731,110733,110735,110737,110739,110741,110743,110745],{"class":202,"line":398},[200,110708,53191],{"class":235},[200,110710,239],{"class":214},[200,110712,50838],{"class":223},[200,110714,227],{"class":210},[200,110716,249],{"class":214},[200,110718,53202],{"class":245},[200,110720,249],{"class":214},[200,110722,2104],{"class":210},[200,110724,215],{"class":214},[200,110726,50894],{"class":223},[200,110728,1549],{"class":210},[200,110730,215],{"class":214},[200,110732,53171],{"class":223},[200,110734,227],{"class":210},[200,110736,1549],{"class":214},[200,110738,393],{"class":379},[200,110740,47331],{"class":210},[200,110742,215],{"class":214},[200,110744,53184],{"class":210},[200,110746,252],{"class":214},[200,110748,110749,110751],{"class":202,"line":405},[200,110750,413],{"class":214},[200,110752,416],{"class":210},[35,110754,53239,110755,53242,110757,53246],{},[156,110756,52842],{},[156,110758,53245],{},[35,110760,53249,110761,53252,110763,53255,110765,53259,110767,53262,110769,215],{},[156,110762,47155],{},[156,110764,51600],{},[156,110766,53258],{},[156,110768,3295],{},[156,110770,47155],{},[35,110772,53239,110773,53270,110775,53262,110777,53275],{},[156,110774,53269],{},[156,110776,3295],{},[156,110778,47161],{},[35,110780,53278],{},[192,110782,110783],{"className":1380,"code":53281,"language":1383,"meta":13,"style":13},[156,110784,110785,110813,110825,110831,110835,110863,110875],{"__ignoreMap":13},[200,110786,110787,110789,110791,110793,110795,110797,110799,110801,110803,110805,110807,110809,110811],{"class":202,"line":203},[200,110788,1390],{"class":206},[200,110790,14305],{"class":379},[200,110792,53292],{"class":210},[200,110794,449],{"class":214},[200,110796,53297],{"class":223},[200,110798,53300],{"class":210},[200,110800,549],{"class":214},[200,110802,383],{"class":214},[200,110804,53307],{"class":386},[200,110806,390],{"class":214},[200,110808,393],{"class":379},[200,110810,1094],{"class":210},[200,110812,230],{"class":214},[200,110814,110815,110817,110819,110821,110823],{"class":202,"line":14},[200,110816,53320],{"class":235},[200,110818,239],{"class":214},[200,110820,53307],{"class":223},[200,110822,53327],{"class":210},[200,110824,252],{"class":214},[200,110826,110827,110829],{"class":202,"line":255},[200,110828,413],{"class":214},[200,110830,584],{"class":210},[200,110832,110833],{"class":202,"line":272},[200,110834,528],{"emptyLinePlaceholder":20},[200,110836,110837,110839,110841,110843,110845,110847,110849,110851,110853,110855,110857,110859,110861],{"class":202,"line":283},[200,110838,1390],{"class":206},[200,110840,14305],{"class":379},[200,110842,53348],{"class":210},[200,110844,449],{"class":214},[200,110846,53297],{"class":223},[200,110848,53355],{"class":210},[200,110850,549],{"class":214},[200,110852,383],{"class":214},[200,110854,53307],{"class":386},[200,110856,390],{"class":214},[200,110858,393],{"class":379},[200,110860,1094],{"class":210},[200,110862,230],{"class":214},[200,110864,110865,110867,110869,110871,110873],{"class":202,"line":300},[200,110866,53320],{"class":235},[200,110868,239],{"class":214},[200,110870,53307],{"class":223},[200,110872,53327],{"class":210},[200,110874,252],{"class":214},[200,110876,110877,110879],{"class":202,"line":310},[200,110878,413],{"class":214},[200,110880,584],{"class":210},[1929,110882,53391],{"id":53390},[35,110884,53394],{},[35,110886,53397,110887,53401],{},[156,110888,53400],{},[192,110890,110891],{"className":1380,"code":53404,"language":1383,"meta":13,"style":13},[156,110892,110893,110927,110931,110953,110975,110979,111003,111015,111043,111055,111073,111085,111107,111119,111123,111153,111165,111185,111199,111213,111227,111241,111255,111269,111283,111295,111299,111315],{"__ignoreMap":13},[200,110894,110895,110897,110899,110901,110903,110905,110907,110909,110911,110913,110915,110917,110919,110921,110923,110925],{"class":202,"line":203},[200,110896,972],{"class":206},[200,110898,318],{"class":214},[200,110900,20155],{"class":210},[200,110902,549],{"class":214},[200,110904,42642],{"class":210},[200,110906,549],{"class":214},[200,110908,22752],{"class":210},[200,110910,549],{"class":214},[200,110912,50962],{"class":210},[200,110914,14678],{"class":206},[200,110916,53431],{"class":210},[200,110918,980],{"class":214},[200,110920,983],{"class":206},[200,110922,242],{"class":214},[200,110924,19813],{"class":245},[200,110926,991],{"class":214},[200,110928,110929],{"class":202,"line":14},[200,110930,528],{"emptyLinePlaceholder":20},[200,110932,110933,110935,110937,110939,110941,110943,110945,110947,110949,110951],{"class":202,"line":255},[200,110934,1390],{"class":206},[200,110936,1393],{"class":206},[200,110938,38723],{"class":223},[200,110940,227],{"class":210},[200,110942,3726],{"class":379},[200,110944,1094],{"class":214},[200,110946,25426],{"class":386},[200,110948,2104],{"class":214},[200,110950,393],{"class":379},[200,110952,280],{"class":214},[200,110954,110955,110957,110959,110961,110963,110965,110967,110969,110971,110973],{"class":202,"line":272},[200,110956,3738],{"class":379},[200,110958,318],{"class":214},[200,110960,34533],{"class":210},[200,110962,980],{"class":214},[200,110964,509],{"class":214},[200,110966,452],{"class":206},[200,110968,38997],{"class":223},[200,110970,227],{"class":235},[200,110972,25426],{"class":210},[200,110974,416],{"class":235},[200,110976,110977],{"class":202,"line":283},[200,110978,528],{"emptyLinePlaceholder":20},[200,110980,110981,110983,110985,110987,110989,110991,110993,110995,110997,110999,111001],{"class":202,"line":300},[200,110982,3738],{"class":379},[200,110984,40809],{"class":210},[200,110986,509],{"class":214},[200,110988,452],{"class":206},[200,110990,40951],{"class":223},[200,110992,227],{"class":235},[200,110994,25426],{"class":210},[200,110996,549],{"class":214},[200,110998,42642],{"class":223},[200,111000,227],{"class":235},[200,111002,230],{"class":214},[200,111004,111005,111007,111009,111011,111013],{"class":202,"line":310},[200,111006,21482],{"class":235},[200,111008,239],{"class":214},[200,111010,22752],{"class":223},[200,111012,1549],{"class":235},[200,111014,252],{"class":214},[200,111016,111017,111019,111021,111023,111025,111027,111029,111031,111033,111035,111037,111039,111041],{"class":202,"line":336},[200,111018,53534],{"class":235},[200,111020,239],{"class":214},[200,111022,53431],{"class":223},[200,111024,25224],{"class":235},[200,111026,249],{"class":214},[200,111028,53026],{"class":245},[200,111030,249],{"class":214},[200,111032,549],{"class":214},[200,111034,242],{"class":214},[200,111036,53035],{"class":245},[200,111038,249],{"class":214},[200,111040,32210],{"class":235},[200,111042,252],{"class":214},[200,111044,111045,111047,111049,111051,111053],{"class":202,"line":342},[200,111046,53563],{"class":235},[200,111048,239],{"class":214},[200,111050,20155],{"class":223},[200,111052,1549],{"class":235},[200,111054,252],{"class":214},[200,111056,111057,111059,111061,111063,111065,111067,111069,111071],{"class":202,"line":365},[200,111058,53576],{"class":235},[200,111060,239],{"class":214},[200,111062,22752],{"class":223},[200,111064,1549],{"class":235},[200,111066,215],{"class":214},[200,111068,53587],{"class":223},[200,111070,1549],{"class":235},[200,111072,252],{"class":214},[200,111074,111075,111077,111079,111081,111083],{"class":202,"line":371},[200,111076,53596],{"class":235},[200,111078,239],{"class":214},[200,111080,22752],{"class":223},[200,111082,1549],{"class":235},[200,111084,252],{"class":214},[200,111086,111087,111089,111091,111093,111095,111097,111099,111101,111103,111105],{"class":202,"line":398},[200,111088,3151],{"class":235},[200,111090,239],{"class":214},[200,111092,22752],{"class":223},[200,111094,1549],{"class":235},[200,111096,215],{"class":214},[200,111098,4138],{"class":223},[200,111100,227],{"class":235},[200,111102,53623],{"class":2060},[200,111104,2104],{"class":235},[200,111106,252],{"class":214},[200,111108,111109,111111,111113,111115,111117],{"class":202,"line":405},[200,111110,5095],{"class":214},[200,111112,2104],{"class":235},[200,111114,215],{"class":214},[200,111116,40994],{"class":210},[200,111118,416],{"class":235},[200,111120,111121],{"class":202,"line":410},[200,111122,528],{"emptyLinePlaceholder":20},[200,111124,111125,111127,111129,111131,111133,111135,111137,111139,111141,111143,111145,111147,111149,111151],{"class":202,"line":860},[200,111126,6177],{"class":206},[200,111128,47338],{"class":223},[200,111130,1549],{"class":235},[200,111132,215],{"class":214},[200,111134,47525],{"class":223},[200,111136,227],{"class":235},[200,111138,47418],{"class":210},[200,111140,215],{"class":214},[200,111142,51600],{"class":210},[200,111144,2104],{"class":235},[200,111146,215],{"class":214},[200,111148,47540],{"class":223},[200,111150,227],{"class":235},[200,111152,230],{"class":214},[200,111154,111155,111157,111159,111161,111163],{"class":202,"line":889},[200,111156,53678],{"class":235},[200,111158,239],{"class":214},[200,111160,53683],{"class":223},[200,111162,1549],{"class":235},[200,111164,252],{"class":214},[200,111166,111167,111169,111171,111173,111175,111177,111179,111181,111183],{"class":202,"line":899},[200,111168,47372],{"class":235},[200,111170,239],{"class":214},[200,111172,47553],{"class":223},[200,111174,227],{"class":235},[200,111176,23914],{"class":210},[200,111178,215],{"class":214},[200,111180,3581],{"class":210},[200,111182,2104],{"class":235},[200,111184,252],{"class":214},[200,111186,111187,111189,111191,111193,111195,111197],{"class":202,"line":904},[200,111188,21482],{"class":235},[200,111190,239],{"class":214},[200,111192,40809],{"class":210},[200,111194,215],{"class":214},[200,111196,3581],{"class":210},[200,111198,252],{"class":214},[200,111200,111201,111203,111205,111207,111209,111211],{"class":202,"line":930},[200,111202,53563],{"class":235},[200,111204,239],{"class":214},[200,111206,40809],{"class":210},[200,111208,215],{"class":214},[200,111210,53269],{"class":210},[200,111212,252],{"class":214},[200,111214,111215,111217,111219,111221,111223,111225],{"class":202,"line":3655},[200,111216,53534],{"class":235},[200,111218,239],{"class":214},[200,111220,40809],{"class":210},[200,111222,215],{"class":214},[200,111224,53748],{"class":210},[200,111226,252],{"class":214},[200,111228,111229,111231,111233,111235,111237,111239],{"class":202,"line":3660},[200,111230,53576],{"class":235},[200,111232,239],{"class":214},[200,111234,40809],{"class":210},[200,111236,215],{"class":214},[200,111238,53763],{"class":210},[200,111240,252],{"class":214},[200,111242,111243,111245,111247,111249,111251,111253],{"class":202,"line":4035},[200,111244,53596],{"class":235},[200,111246,239],{"class":214},[200,111248,40809],{"class":210},[200,111250,215],{"class":214},[200,111252,53778],{"class":210},[200,111254,252],{"class":214},[200,111256,111257,111259,111261,111263,111265,111267],{"class":202,"line":4490},[200,111258,3151],{"class":235},[200,111260,239],{"class":214},[200,111262,40809],{"class":210},[200,111264,215],{"class":214},[200,111266,3597],{"class":210},[200,111268,252],{"class":214},[200,111270,111271,111273,111275,111277,111279,111281],{"class":202,"line":4496},[200,111272,53799],{"class":235},[200,111274,239],{"class":214},[200,111276,34533],{"class":210},[200,111278,215],{"class":214},[200,111280,3295],{"class":210},[200,111282,252],{"class":214},[200,111284,111285,111287,111289,111291,111293],{"class":202,"line":4502},[200,111286,5095],{"class":214},[200,111288,2104],{"class":235},[200,111290,215],{"class":214},[200,111292,47590],{"class":223},[200,111294,466],{"class":235},[200,111296,111297],{"class":202,"line":4508},[200,111298,528],{"emptyLinePlaceholder":20},[200,111300,111301,111303,111305,111307,111309,111311,111313],{"class":202,"line":5469},[200,111302,3758],{"class":206},[200,111304,40842],{"class":223},[200,111306,227],{"class":235},[200,111308,25426],{"class":210},[200,111310,549],{"class":214},[200,111312,40851],{"class":2060},[200,111314,416],{"class":235},[200,111316,111317,111319],{"class":202,"line":5474},[200,111318,413],{"class":214},[200,111320,416],{"class":210},[35,111322,53850,111323,53854,111325,53857,111327,51597,111329,53862],{},[6862,111324,53853],{},[156,111326,38959],{},[156,111328,53258],{},[156,111330,51600],{},[35,111332,41033,111333,53868,111335,215],{},[156,111334,53867],{},[156,111336,53871],{},[1929,111338,53875],{"id":53874},[35,111340,111341],{},[1216,111342,53880],{},[35,111344,53883,111345,53887,111347],{},[1216,111346,53886],{},[1216,111348,53890],{},[35,111350,53893,111351,2186],{},[156,111352,53896],{},[192,111354,111355],{"className":1380,"code":53899,"language":1383,"meta":13,"style":13},[156,111356,111357,111375,111389,111397,111413,111457,111501,111505,111511],{"__ignoreMap":13},[200,111358,111359,111361,111363,111365,111367,111369,111371,111373],{"class":202,"line":203},[200,111360,1390],{"class":206},[200,111362,1393],{"class":206},[200,111364,38723],{"class":223},[200,111366,227],{"class":210},[200,111368,3726],{"class":379},[200,111370,3729],{"class":214},[200,111372,393],{"class":379},[200,111374,280],{"class":214},[200,111376,111377,111379,111381,111383,111385,111387],{"class":202,"line":14},[200,111378,3738],{"class":379},[200,111380,53926],{"class":210},[200,111382,509],{"class":214},[200,111384,452],{"class":206},[200,111386,47338],{"class":223},[200,111388,466],{"class":235},[200,111390,111391,111393,111395],{"class":202,"line":255},[200,111392,3770],{"class":214},[200,111394,769],{"class":223},[200,111396,466],{"class":235},[200,111398,111399,111401,111403,111405,111407,111409,111411],{"class":202,"line":272},[200,111400,3770],{"class":214},[200,111402,1489],{"class":223},[200,111404,227],{"class":235},[200,111406,47418],{"class":210},[200,111408,215],{"class":214},[200,111410,51600],{"class":210},[200,111412,416],{"class":235},[200,111414,111415,111417,111419,111421,111423,111425,111427,111429,111431,111433,111435,111437,111439,111441,111443,111445,111447,111449,111451,111453,111455],{"class":202,"line":283},[200,111416,3770],{"class":214},[200,111418,53965],{"class":223},[200,111420,227],{"class":235},[200,111422,47418],{"class":210},[200,111424,215],{"class":214},[200,111426,47155],{"class":210},[200,111428,549],{"class":214},[200,111430,53978],{"class":223},[200,111432,227],{"class":235},[200,111434,47418],{"class":210},[200,111436,215],{"class":214},[200,111438,51600],{"class":210},[200,111440,215],{"class":214},[200,111442,53258],{"class":210},[200,111444,549],{"class":214},[200,111446,47357],{"class":210},[200,111448,215],{"class":214},[200,111450,47155],{"class":210},[200,111452,215],{"class":214},[200,111454,3295],{"class":210},[200,111456,584],{"class":235},[200,111458,111459,111461,111463,111465,111467,111469,111471,111473,111475,111477,111479,111481,111483,111485,111487,111489,111491,111493,111495,111497,111499],{"class":202,"line":300},[200,111460,3770],{"class":214},[200,111462,53965],{"class":223},[200,111464,227],{"class":235},[200,111466,47418],{"class":210},[200,111468,215],{"class":214},[200,111470,47161],{"class":210},[200,111472,549],{"class":214},[200,111474,53978],{"class":223},[200,111476,227],{"class":235},[200,111478,47418],{"class":210},[200,111480,215],{"class":214},[200,111482,51600],{"class":210},[200,111484,215],{"class":214},[200,111486,53269],{"class":210},[200,111488,549],{"class":214},[200,111490,47357],{"class":210},[200,111492,215],{"class":214},[200,111494,47161],{"class":210},[200,111496,215],{"class":214},[200,111498,3295],{"class":210},[200,111500,584],{"class":235},[200,111502,111503],{"class":202,"line":310},[200,111504,528],{"emptyLinePlaceholder":20},[200,111506,111507,111509],{"class":202,"line":336},[200,111508,3758],{"class":206},[200,111510,54059],{"class":210},[200,111512,111513,111515],{"class":202,"line":342},[200,111514,413],{"class":214},[200,111516,416],{"class":210},[35,111518,54068],{},[35,111520,54071,111521,54075,111523,54078,111525,53252,111527,54083,111529,54086,111531,54089,111533,13741,111535,215],{},[156,111522,54074],{},[156,111524,51600],{},[156,111526,47155],{},[156,111528,47161],{},[156,111530,53965],{},[156,111532,51600],{},[156,111534,47155],{},[156,111536,47161],{},[35,111538,111539],{},[1707,111540],{"alt":54098,"src":54099},[35,111542,54102,111543,54105],{},[156,111544,47992],{},[192,111546,111547],{"className":21457,"code":54108,"language":12075,"meta":13,"style":13},[156,111548,111549,111569,111581,111595,111609,111615,111629,111643,111653,111659,111673,111687,111697,111703,111717,111731,111741,111747,111761,111775,111781,111795,111809,111815,111829,111843,111849,111863,111877,111883,111897,111911,111917,111921,111961,111971,111981,111995,112001,112009,112013,112021,112027,112041,112055,112069,112073,112097,112103,112119,112131,112143,112161,112173,112177,112193,112197,112215,112223,112241,112249,112273,112279,112291,112303,112321,112333,112337,112353,112357,112375,112383,112391,112415,112443,112449,112461,112473,112477,112481,112489,112497,112525,112531,112543,112555,112559,112563,112571,112579,112587,112611,112629,112635,112647,112659,112671,112675,112691,112699,112707,112731,112735,112743,112751],{"__ignoreMap":13},[200,111550,111551,111553,111555,111557,111559,111561,111563,111565,111567],{"class":202,"line":203},[200,111552,599],{"class":214},[200,111554,14896],{"class":235},[200,111556,14594],{"class":379},[200,111558,449],{"class":214},[200,111560,610],{"class":214},[200,111562,1383],{"class":245},[200,111564,610],{"class":214},[200,111566,21825],{"class":379},[200,111568,647],{"class":214},[200,111570,111571,111573,111575,111577,111579],{"class":202,"line":14},[200,111572,443],{"class":379},[200,111574,47788],{"class":210},[200,111576,449],{"class":214},[200,111578,350],{"class":210},[200,111580,230],{"class":214},[200,111582,111583,111585,111587,111589,111591,111593],{"class":202,"line":255},[200,111584,18976],{"class":235},[200,111586,239],{"class":214},[200,111588,242],{"class":214},[200,111590,3295],{"class":245},[200,111592,249],{"class":214},[200,111594,252],{"class":214},[200,111596,111597,111599,111601,111603,111605,111607],{"class":202,"line":272},[200,111598,47813],{"class":235},[200,111600,239],{"class":214},[200,111602,242],{"class":214},[200,111604,2902],{"class":245},[200,111606,249],{"class":214},[200,111608,252],{"class":214},[200,111610,111611,111613],{"class":202,"line":283},[200,111612,26897],{"class":214},[200,111614,280],{"class":214},[200,111616,111617,111619,111621,111623,111625,111627],{"class":202,"line":300},[200,111618,18976],{"class":235},[200,111620,239],{"class":214},[200,111622,242],{"class":214},[200,111624,3581],{"class":245},[200,111626,249],{"class":214},[200,111628,252],{"class":214},[200,111630,111631,111633,111635,111637,111639,111641],{"class":202,"line":310},[200,111632,47813],{"class":235},[200,111634,239],{"class":214},[200,111636,242],{"class":214},[200,111638,54201],{"class":245},[200,111640,249],{"class":214},[200,111642,252],{"class":214},[200,111644,111645,111647,111649,111651],{"class":202,"line":336},[200,111646,47860],{"class":235},[200,111648,239],{"class":214},[200,111650,2439],{"class":2438},[200,111652,252],{"class":214},[200,111654,111655,111657],{"class":202,"line":342},[200,111656,26897],{"class":214},[200,111658,280],{"class":214},[200,111660,111661,111663,111665,111667,111669,111671],{"class":202,"line":365},[200,111662,18976],{"class":235},[200,111664,239],{"class":214},[200,111666,242],{"class":214},[200,111668,39512],{"class":245},[200,111670,249],{"class":214},[200,111672,252],{"class":214},[200,111674,111675,111677,111679,111681,111683,111685],{"class":202,"line":371},[200,111676,47813],{"class":235},[200,111678,239],{"class":214},[200,111680,242],{"class":214},[200,111682,54246],{"class":245},[200,111684,249],{"class":214},[200,111686,252],{"class":214},[200,111688,111689,111691,111693,111695],{"class":202,"line":398},[200,111690,47860],{"class":235},[200,111692,239],{"class":214},[200,111694,2439],{"class":2438},[200,111696,252],{"class":214},[200,111698,111699,111701],{"class":202,"line":405},[200,111700,26897],{"class":214},[200,111702,280],{"class":214},[200,111704,111705,111707,111709,111711,111713,111715],{"class":202,"line":410},[200,111706,18976],{"class":235},[200,111708,239],{"class":214},[200,111710,242],{"class":214},[200,111712,53748],{"class":245},[200,111714,249],{"class":214},[200,111716,252],{"class":214},[200,111718,111719,111721,111723,111725,111727,111729],{"class":202,"line":860},[200,111720,47813],{"class":235},[200,111722,239],{"class":214},[200,111724,242],{"class":214},[200,111726,54291],{"class":245},[200,111728,249],{"class":214},[200,111730,252],{"class":214},[200,111732,111733,111735,111737,111739],{"class":202,"line":889},[200,111734,47860],{"class":235},[200,111736,239],{"class":214},[200,111738,2439],{"class":2438},[200,111740,252],{"class":214},[200,111742,111743,111745],{"class":202,"line":899},[200,111744,26897],{"class":214},[200,111746,280],{"class":214},[200,111748,111749,111751,111753,111755,111757,111759],{"class":202,"line":904},[200,111750,18976],{"class":235},[200,111752,239],{"class":214},[200,111754,242],{"class":214},[200,111756,53763],{"class":245},[200,111758,249],{"class":214},[200,111760,252],{"class":214},[200,111762,111763,111765,111767,111769,111771,111773],{"class":202,"line":930},[200,111764,47813],{"class":235},[200,111766,239],{"class":214},[200,111768,242],{"class":214},[200,111770,54336],{"class":245},[200,111772,249],{"class":214},[200,111774,252],{"class":214},[200,111776,111777,111779],{"class":202,"line":3655},[200,111778,26897],{"class":214},[200,111780,280],{"class":214},[200,111782,111783,111785,111787,111789,111791,111793],{"class":202,"line":3660},[200,111784,18976],{"class":235},[200,111786,239],{"class":214},[200,111788,242],{"class":214},[200,111790,53778],{"class":245},[200,111792,249],{"class":214},[200,111794,252],{"class":214},[200,111796,111797,111799,111801,111803,111805,111807],{"class":202,"line":4035},[200,111798,47813],{"class":235},[200,111800,239],{"class":214},[200,111802,242],{"class":214},[200,111804,54371],{"class":245},[200,111806,249],{"class":214},[200,111808,252],{"class":214},[200,111810,111811,111813],{"class":202,"line":4490},[200,111812,26897],{"class":214},[200,111814,280],{"class":214},[200,111816,111817,111819,111821,111823,111825,111827],{"class":202,"line":4496},[200,111818,18976],{"class":235},[200,111820,239],{"class":214},[200,111822,242],{"class":214},[200,111824,3597],{"class":245},[200,111826,249],{"class":214},[200,111828,252],{"class":214},[200,111830,111831,111833,111835,111837,111839,111841],{"class":202,"line":4502},[200,111832,47813],{"class":235},[200,111834,239],{"class":214},[200,111836,242],{"class":214},[200,111838,54406],{"class":245},[200,111840,249],{"class":214},[200,111842,252],{"class":214},[200,111844,111845,111847],{"class":202,"line":4508},[200,111846,26897],{"class":214},[200,111848,280],{"class":214},[200,111850,111851,111853,111855,111857,111859,111861],{"class":202,"line":5469},[200,111852,18976],{"class":235},[200,111854,239],{"class":214},[200,111856,242],{"class":214},[200,111858,54427],{"class":245},[200,111860,249],{"class":214},[200,111862,252],{"class":214},[200,111864,111865,111867,111869,111871,111873,111875],{"class":202,"line":5474},[200,111866,47813],{"class":235},[200,111868,239],{"class":214},[200,111870,242],{"class":214},[200,111872,54442],{"class":245},[200,111874,249],{"class":214},[200,111876,252],{"class":214},[200,111878,111879,111881],{"class":202,"line":13237},[200,111880,26897],{"class":214},[200,111882,280],{"class":214},[200,111884,111885,111887,111889,111891,111893,111895],{"class":202,"line":13263},[200,111886,18976],{"class":235},[200,111888,239],{"class":214},[200,111890,242],{"class":214},[200,111892,54463],{"class":245},[200,111894,249],{"class":214},[200,111896,252],{"class":214},[200,111898,111899,111901,111903,111905,111907,111909],{"class":202,"line":13279},[200,111900,47813],{"class":235},[200,111902,239],{"class":214},[200,111904,242],{"class":214},[200,111906,54478],{"class":245},[200,111908,249],{"class":214},[200,111910,252],{"class":214},[200,111912,111913,111915],{"class":202,"line":13323},[200,111914,413],{"class":214},[200,111916,4511],{"class":210},[200,111918,111919],{"class":202,"line":13329},[200,111920,528],{"emptyLinePlaceholder":20},[200,111922,111923,111925,111927,111929,111931,111933,111935,111937,111939,111941,111943,111945,111947,111949,111951,111953,111955,111957,111959],{"class":202,"line":13334},[200,111924,443],{"class":379},[200,111926,318],{"class":214},[200,111928,5056],{"class":235},[200,111930,239],{"class":214},[200,111932,53926],{"class":210},[200,111934,549],{"class":214},[200,111936,54509],{"class":210},[200,111938,549],{"class":214},[200,111940,47926],{"class":210},[200,111942,413],{"class":214},[200,111944,509],{"class":214},[200,111946,452],{"class":206},[200,111948,47935],{"class":223},[200,111950,227],{"class":210},[200,111952,249],{"class":214},[200,111954,54528],{"class":245},[200,111956,249],{"class":214},[200,111958,549],{"class":214},[200,111960,280],{"class":214},[200,111962,111963,111965,111967,111969],{"class":202,"line":13360},[200,111964,47953],{"class":235},[200,111966,239],{"class":214},[200,111968,16169],{"class":2438},[200,111970,252],{"class":214},[200,111972,111973,111975,111977,111979],{"class":202,"line":13365},[200,111974,50447],{"class":235},[200,111976,239],{"class":214},[200,111978,2439],{"class":2438},[200,111980,252],{"class":214},[200,111982,111983,111985,111987,111989,111991,111993],{"class":202,"line":13385},[200,111984,50461],{"class":223},[200,111986,239],{"class":214},[200,111988,3729],{"class":214},[200,111990,393],{"class":379},[200,111992,27681],{"class":210},[200,111994,252],{"class":214},[200,111996,111997,111999],{"class":202,"line":13405},[200,111998,413],{"class":214},[200,112000,416],{"class":210},[200,112002,112003,112005,112007],{"class":202,"line":13410},[200,112004,680],{"class":214},[200,112006,14896],{"class":235},[200,112008,647],{"class":214},[200,112010,112011],{"class":202,"line":13418},[200,112012,528],{"emptyLinePlaceholder":20},[200,112014,112015,112017,112019],{"class":202,"line":13436},[200,112016,599],{"class":214},[200,112018,21468],{"class":235},[200,112020,647],{"class":214},[200,112022,112023,112025],{"class":202,"line":13466},[200,112024,656],{"class":214},[200,112026,54601],{"class":235},[200,112028,112029,112031,112033,112035,112037,112039],{"class":202,"line":13472},[200,112030,23865],{"class":214},[200,112032,47997],{"class":379},[200,112034,449],{"class":214},[200,112036,610],{"class":214},[200,112038,47997],{"class":210},[200,112040,616],{"class":214},[200,112042,112043,112045,112047,112049,112051,112053],{"class":202,"line":13477},[200,112044,23865],{"class":214},[200,112046,48010],{"class":379},[200,112048,449],{"class":214},[200,112050,610],{"class":214},[200,112052,51600],{"class":210},[200,112054,616],{"class":214},[200,112056,112057,112059,112061,112063,112065,112067],{"class":202,"line":13482},[200,112058,23865],{"class":214},[200,112060,9900],{"class":379},[200,112062,449],{"class":214},[200,112064,610],{"class":214},[200,112066,48029],{"class":210},[200,112068,616],{"class":214},[200,112070,112071],{"class":202,"line":13497},[200,112072,11550],{"class":214},[200,112074,112075,112077,112079,112081,112083,112085,112087,112089,112091,112093,112095],{"class":202,"line":13508},[200,112076,803],{"class":214},[200,112078,21468],{"class":235},[200,112080,23911],{"class":214},[200,112082,54658],{"class":379},[200,112084,449],{"class":214},[200,112086,610],{"class":214},[200,112088,2767],{"class":214},[200,112090,49457],{"class":210},[200,112092,413],{"class":214},[200,112094,610],{"class":214},[200,112096,647],{"class":214},[200,112098,112099,112101],{"class":202,"line":13513},[200,112100,22071],{"class":214},[200,112102,48107],{"class":235},[200,112104,112105,112107,112109,112111,112113,112115,112117],{"class":202,"line":13518},[200,112106,54683],{"class":206},[200,112108,449],{"class":214},[200,112110,610],{"class":214},[200,112112,49495],{"class":210},[200,112114,215],{"class":214},[200,112116,53763],{"class":210},[200,112118,616],{"class":214},[200,112120,112121,112123,112125,112127,112129],{"class":202,"line":20247},[200,112122,54700],{"class":379},[200,112124,449],{"class":214},[200,112126,610],{"class":214},[200,112128,14620],{"class":245},[200,112130,616],{"class":214},[200,112132,112133,112135,112137,112139,112141],{"class":202,"line":20269},[200,112134,52426],{"class":379},[200,112136,449],{"class":214},[200,112138,610],{"class":214},[200,112140,7156],{"class":245},[200,112142,616],{"class":214},[200,112144,112145,112147,112149,112151,112153,112155,112157,112159],{"class":202,"line":20274},[200,112146,54725],{"class":214},[200,112148,54728],{"class":379},[200,112150,449],{"class":214},[200,112152,610],{"class":214},[200,112154,49495],{"class":210},[200,112156,215],{"class":214},[200,112158,53763],{"class":210},[200,112160,616],{"class":214},[200,112162,112163,112165,112167,112169,112171],{"class":202,"line":20279},[200,112164,54745],{"class":379},[200,112166,449],{"class":214},[200,112168,610],{"class":214},[200,112170,54752],{"class":245},[200,112172,616],{"class":214},[200,112174,112175],{"class":202,"line":20284},[200,112176,54759],{"class":214},[200,112178,112179,112181,112183,112185,112187,112189,112191],{"class":202,"line":20305},[200,112180,49441],{"class":214},[200,112182,200],{"class":235},[200,112184,674],{"class":214},[200,112186,54770],{"class":210},[200,112188,680],{"class":214},[200,112190,200],{"class":235},[200,112192,647],{"class":214},[200,112194,112195],{"class":202,"line":20312},[200,112196,528],{"emptyLinePlaceholder":20},[200,112198,112199,112201,112203,112205,112207,112209,112211,112213],{"class":202,"line":20317},[200,112200,49441],{"class":214},[200,112202,200],{"class":235},[200,112204,54789],{"class":379},[200,112206,449],{"class":214},[200,112208,610],{"class":214},[200,112210,54796],{"class":245},[200,112212,610],{"class":214},[200,112214,21527],{"class":214},[200,112216,112217,112219,112221],{"class":202,"line":20333},[200,112218,43803],{"class":214},[200,112220,48601],{"class":235},[200,112222,647],{"class":214},[200,112224,112225,112227,112229,112231,112233,112235,112237,112239],{"class":202,"line":46974},[200,112226,22071],{"class":214},[200,112228,200],{"class":235},[200,112230,54817],{"class":379},[200,112232,674],{"class":214},[200,112234,54822],{"class":210},[200,112236,680],{"class":214},[200,112238,200],{"class":235},[200,112240,647],{"class":214},[200,112242,112243,112245,112247],{"class":202,"line":46979},[200,112244,15078],{"class":214},[200,112246,21468],{"class":235},[200,112248,647],{"class":214},[200,112250,112251,112253,112255,112257,112259,112261,112263,112265,112267,112269,112271],{"class":202,"line":46986},[200,112252,803],{"class":214},[200,112254,21468],{"class":235},[200,112256,23911],{"class":214},[200,112258,54847],{"class":379},[200,112260,449],{"class":214},[200,112262,610],{"class":214},[200,112264,2767],{"class":214},[200,112266,49457],{"class":210},[200,112268,413],{"class":214},[200,112270,610],{"class":214},[200,112272,647],{"class":214},[200,112274,112275,112277],{"class":202,"line":46991},[200,112276,22071],{"class":214},[200,112278,48107],{"class":235},[200,112280,112281,112283,112285,112287,112289],{"class":202,"line":46996},[200,112282,54700],{"class":379},[200,112284,449],{"class":214},[200,112286,610],{"class":214},[200,112288,14620],{"class":245},[200,112290,616],{"class":214},[200,112292,112293,112295,112297,112299,112301],{"class":202,"line":47002},[200,112294,52426],{"class":379},[200,112296,449],{"class":214},[200,112298,610],{"class":214},[200,112300,7156],{"class":245},[200,112302,616],{"class":214},[200,112304,112305,112307,112309,112311,112313,112315,112317,112319],{"class":202,"line":47008},[200,112306,54725],{"class":214},[200,112308,54728],{"class":379},[200,112310,449],{"class":214},[200,112312,610],{"class":214},[200,112314,49495],{"class":210},[200,112316,215],{"class":214},[200,112318,53778],{"class":210},[200,112320,616],{"class":214},[200,112322,112323,112325,112327,112329,112331],{"class":202,"line":47014},[200,112324,54745],{"class":379},[200,112326,449],{"class":214},[200,112328,610],{"class":214},[200,112330,54752],{"class":245},[200,112332,616],{"class":214},[200,112334,112335],{"class":202,"line":47020},[200,112336,54759],{"class":214},[200,112338,112339,112341,112343,112345,112347,112349,112351],{"class":202,"line":47025},[200,112340,49441],{"class":214},[200,112342,200],{"class":235},[200,112344,674],{"class":214},[200,112346,54936],{"class":210},[200,112348,680],{"class":214},[200,112350,200],{"class":235},[200,112352,647],{"class":214},[200,112354,112355],{"class":202,"line":47040},[200,112356,528],{"emptyLinePlaceholder":20},[200,112358,112359,112361,112363,112365,112367,112369,112371,112373],{"class":202,"line":47047},[200,112360,49441],{"class":214},[200,112362,200],{"class":235},[200,112364,54789],{"class":379},[200,112366,449],{"class":214},[200,112368,610],{"class":214},[200,112370,54796],{"class":245},[200,112372,610],{"class":214},[200,112374,21527],{"class":214},[200,112376,112377,112379,112381],{"class":202,"line":47055},[200,112378,43803],{"class":214},[200,112380,48601],{"class":235},[200,112382,647],{"class":214},[200,112384,112385,112387,112389],{"class":202,"line":47062},[200,112386,15078],{"class":214},[200,112388,21468],{"class":235},[200,112390,647],{"class":214},[200,112392,112393,112395,112397,112399,112401,112403,112405,112407,112409,112411,112413],{"class":202,"line":47069},[200,112394,803],{"class":214},[200,112396,21468],{"class":235},[200,112398,23911],{"class":214},[200,112400,54991],{"class":379},[200,112402,449],{"class":214},[200,112404,610],{"class":214},[200,112406,2767],{"class":214},[200,112408,49457],{"class":210},[200,112410,413],{"class":214},[200,112412,610],{"class":214},[200,112414,647],{"class":214},[200,112416,112417,112419,112421,112423,112425,112427,112429,112431,112433,112435,112437,112439,112441],{"class":202,"line":55008},[200,112418,22071],{"class":214},[200,112420,21468],{"class":235},[200,112422,34453],{"class":206},[200,112424,449],{"class":214},[200,112426,610],{"class":214},[200,112428,49495],{"class":210},[200,112430,215],{"class":214},[200,112432,55025],{"class":210},[200,112434,6558],{"class":214},[200,112436,242],{"class":214},[200,112438,53026],{"class":245},[200,112440,14706],{"class":214},[200,112442,647],{"class":214},[200,112444,112445,112447],{"class":202,"line":55038},[200,112446,49441],{"class":214},[200,112448,55043],{"class":235},[200,112450,112451,112453,112455,112457,112459],{"class":202,"line":55046},[200,112452,55049],{"class":379},[200,112454,449],{"class":214},[200,112456,610],{"class":214},[200,112458,55056],{"class":245},[200,112460,616],{"class":214},[200,112462,112463,112465,112467,112469,112471],{"class":202,"line":55061},[200,112464,55064],{"class":379},[200,112466,449],{"class":214},[200,112468,610],{"class":214},[200,112470,49481],{"class":245},[200,112472,616],{"class":214},[200,112474,112475],{"class":202,"line":55075},[200,112476,55078],{"class":214},[200,112478,112479],{"class":202,"line":55081},[200,112480,55084],{"class":210},[200,112482,112483,112485,112487],{"class":202,"line":55087},[200,112484,49549],{"class":214},[200,112486,49471],{"class":235},[200,112488,647],{"class":214},[200,112490,112491,112493,112495],{"class":202,"line":55096},[200,112492,43803],{"class":214},[200,112494,21468],{"class":235},[200,112496,647],{"class":214},[200,112498,112499,112501,112503,112505,112507,112509,112511,112513,112515,112517,112519,112521,112523],{"class":202,"line":55105},[200,112500,22071],{"class":214},[200,112502,21468],{"class":235},[200,112504,55112],{"class":206},[200,112506,449],{"class":214},[200,112508,610],{"class":214},[200,112510,49495],{"class":210},[200,112512,215],{"class":214},[200,112514,55025],{"class":210},[200,112516,6558],{"class":214},[200,112518,242],{"class":214},[200,112520,53035],{"class":245},[200,112522,14706],{"class":214},[200,112524,647],{"class":214},[200,112526,112527,112529],{"class":202,"line":55135},[200,112528,49441],{"class":214},[200,112530,55043],{"class":235},[200,112532,112533,112535,112537,112539,112541],{"class":202,"line":55142},[200,112534,55049],{"class":379},[200,112536,449],{"class":214},[200,112538,610],{"class":214},[200,112540,55151],{"class":245},[200,112542,616],{"class":214},[200,112544,112545,112547,112549,112551,112553],{"class":202,"line":55156},[200,112546,55064],{"class":379},[200,112548,449],{"class":214},[200,112550,610],{"class":214},[200,112552,49481],{"class":245},[200,112554,616],{"class":214},[200,112556,112557],{"class":202,"line":55169},[200,112558,55078],{"class":214},[200,112560,112561],{"class":202,"line":55174},[200,112562,55177],{"class":210},[200,112564,112565,112567,112569],{"class":202,"line":55180},[200,112566,49549],{"class":214},[200,112568,49471],{"class":235},[200,112570,647],{"class":214},[200,112572,112573,112575,112577],{"class":202,"line":55189},[200,112574,43803],{"class":214},[200,112576,21468],{"class":235},[200,112578,647],{"class":214},[200,112580,112581,112583,112585],{"class":202,"line":55198},[200,112582,15078],{"class":214},[200,112584,21468],{"class":235},[200,112586,647],{"class":214},[200,112588,112589,112591,112593,112595,112597,112599,112601,112603,112605,112607,112609],{"class":202,"line":55207},[200,112590,803],{"class":214},[200,112592,21468],{"class":235},[200,112594,23911],{"class":214},[200,112596,55216],{"class":379},[200,112598,449],{"class":214},[200,112600,610],{"class":214},[200,112602,2767],{"class":214},[200,112604,49457],{"class":210},[200,112606,413],{"class":214},[200,112608,610],{"class":214},[200,112610,647],{"class":214},[200,112612,112613,112615,112617,112619,112621,112623,112625,112627],{"class":202,"line":55233},[200,112614,22071],{"class":214},[200,112616,11400],{"class":235},[200,112618,54789],{"class":379},[200,112620,449],{"class":214},[200,112622,610],{"class":214},[200,112624,55246],{"class":245},[200,112626,610],{"class":214},[200,112628,647],{"class":214},[200,112630,112631,112633],{"class":202,"line":55253},[200,112632,49441],{"class":214},[200,112634,11463],{"class":235},[200,112636,112637,112639,112641,112643,112645],{"class":202,"line":55260},[200,112638,55263],{"class":379},[200,112640,449],{"class":214},[200,112642,610],{"class":214},[200,112644,55270],{"class":245},[200,112646,616],{"class":214},[200,112648,112649,112651,112653,112655,112657],{"class":202,"line":55275},[200,112650,55278],{"class":379},[200,112652,449],{"class":214},[200,112654,610],{"class":214},[200,112656,55285],{"class":245},[200,112658,616],{"class":214},[200,112660,112661,112663,112665,112667,112669],{"class":202,"line":55290},[200,112662,55293],{"class":379},[200,112664,449],{"class":214},[200,112666,610],{"class":214},[200,112668,55300],{"class":245},[200,112670,616],{"class":214},[200,112672,112673],{"class":202,"line":55305},[200,112674,55078],{"class":214},[200,112676,112677,112679,112681,112683,112685,112687,112689],{"class":202,"line":55310},[200,112678,49441],{"class":214},[200,112680,200],{"class":235},[200,112682,674],{"class":214},[200,112684,55319],{"class":210},[200,112686,680],{"class":214},[200,112688,200],{"class":235},[200,112690,647],{"class":214},[200,112692,112693,112695,112697],{"class":202,"line":55328},[200,112694,43803],{"class":214},[200,112696,11400],{"class":235},[200,112698,647],{"class":214},[200,112700,112701,112703,112705],{"class":202,"line":55337},[200,112702,15078],{"class":214},[200,112704,21468],{"class":235},[200,112706,647],{"class":214},[200,112708,112709,112711,112713,112715,112717,112719,112721,112723,112725,112727,112729],{"class":202,"line":55346},[200,112710,803],{"class":214},[200,112712,21468],{"class":235},[200,112714,23911],{"class":214},[200,112716,55355],{"class":379},[200,112718,449],{"class":214},[200,112720,610],{"class":214},[200,112722,2767],{"class":214},[200,112724,49457],{"class":210},[200,112726,413],{"class":214},[200,112728,610],{"class":214},[200,112730,647],{"class":214},[200,112732,112733],{"class":202,"line":55372},[200,112734,55375],{"class":210},[200,112736,112737,112739,112741],{"class":202,"line":55378},[200,112738,15078],{"class":214},[200,112740,21468],{"class":235},[200,112742,647],{"class":214},[200,112744,112745,112747,112749],{"class":202,"line":55387},[200,112746,892],{"class":214},[200,112748,47992],{"class":235},[200,112750,647],{"class":214},[200,112752,112753,112755,112757],{"class":202,"line":55396},[200,112754,680],{"class":214},[200,112756,21468],{"class":235},[200,112758,647],{"class":214},[35,112760,112761,55408],{},[6862,112762,55407],{},[192,112764,112765],{"className":28358,"code":55411,"language":28360,"meta":13,"style":13},[156,112766,112767,112771,112785,112803,112821,112839,112851,112865,112883,112899,112903,112915,112929,112945,112949],{"__ignoreMap":13},[200,112768,112769],{"class":202,"line":203},[200,112770,230],{"class":214},[200,112772,112773,112775,112777,112779,112781,112783],{"class":202,"line":14},[200,112774,28489],{"class":214},[200,112776,3295],{"class":379},[200,112778,610],{"class":214},[200,112780,239],{"class":214},[200,112782,3953],{"class":2060},[200,112784,252],{"class":214},[200,112786,112787,112789,112791,112793,112795,112797,112799,112801],{"class":202,"line":255},[200,112788,28489],{"class":214},[200,112790,3581],{"class":379},[200,112792,610],{"class":214},[200,112794,239],{"class":214},[200,112796,28499],{"class":214},[200,112798,55446],{"class":245},[200,112800,610],{"class":214},[200,112802,252],{"class":214},[200,112804,112805,112807,112809,112811,112813,112815,112817,112819],{"class":202,"line":272},[200,112806,28489],{"class":214},[200,112808,39512],{"class":379},[200,112810,610],{"class":214},[200,112812,239],{"class":214},[200,112814,28499],{"class":214},[200,112816,52945],{"class":245},[200,112818,610],{"class":214},[200,112820,252],{"class":214},[200,112822,112823,112825,112827,112829,112831,112833,112835,112837],{"class":202,"line":283},[200,112824,28489],{"class":214},[200,112826,53763],{"class":379},[200,112828,610],{"class":214},[200,112830,239],{"class":214},[200,112832,28499],{"class":214},[200,112834,29434],{"class":245},[200,112836,610],{"class":214},[200,112838,252],{"class":214},[200,112840,112841,112843,112845,112847,112849],{"class":202,"line":300},[200,112842,28489],{"class":214},[200,112844,26847],{"class":379},[200,112846,610],{"class":214},[200,112848,239],{"class":214},[200,112850,280],{"class":214},[200,112852,112853,112855,112857,112859,112861,112863],{"class":202,"line":310},[200,112854,28529],{"class":214},[200,112856,3295],{"class":1364},[200,112858,610],{"class":214},[200,112860,239],{"class":214},[200,112862,3953],{"class":2060},[200,112864,252],{"class":214},[200,112866,112867,112869,112871,112873,112875,112877,112879,112881],{"class":202,"line":336},[200,112868,28529],{"class":214},[200,112870,20112],{"class":1364},[200,112872,610],{"class":214},[200,112874,239],{"class":214},[200,112876,28499],{"class":214},[200,112878,34550],{"class":245},[200,112880,610],{"class":214},[200,112882,252],{"class":214},[200,112884,112885,112887,112889,112891,112893,112895,112897],{"class":202,"line":342},[200,112886,28529],{"class":214},[200,112888,55537],{"class":1364},[200,112890,610],{"class":214},[200,112892,239],{"class":214},[200,112894,28499],{"class":214},[200,112896,55546],{"class":245},[200,112898,616],{"class":214},[200,112900,112901],{"class":202,"line":365},[200,112902,368],{"class":214},[200,112904,112905,112907,112909,112911,112913],{"class":202,"line":371},[200,112906,28489],{"class":214},[200,112908,54427],{"class":379},[200,112910,610],{"class":214},[200,112912,239],{"class":214},[200,112914,280],{"class":214},[200,112916,112917,112919,112921,112923,112925,112927],{"class":202,"line":398},[200,112918,28529],{"class":214},[200,112920,3295],{"class":1364},[200,112922,610],{"class":214},[200,112924,239],{"class":214},[200,112926,3953],{"class":2060},[200,112928,252],{"class":214},[200,112930,112931,112933,112935,112937,112939,112941,112943],{"class":202,"line":405},[200,112932,28529],{"class":214},[200,112934,20112],{"class":1364},[200,112936,610],{"class":214},[200,112938,239],{"class":214},[200,112940,28499],{"class":214},[200,112942,55593],{"class":245},[200,112944,616],{"class":214},[200,112946,112947],{"class":202,"line":410},[200,112948,4505],{"class":214},[200,112950,112951],{"class":202,"line":860},[200,112952,1129],{"class":214},[35,112954,55606],{},[192,112956,112957],{"className":28358,"code":55609,"language":28360,"meta":13,"style":13},[156,112958,112959,112963,112975,112989,113007,113025,113041,113045,113057,113061,113073,113077],{"__ignoreMap":13},[200,112960,112961],{"class":202,"line":203},[200,112962,230],{"class":214},[200,112964,112965,112967,112969,112971,112973],{"class":202,"line":14},[200,112966,28489],{"class":214},[200,112968,21468],{"class":379},[200,112970,610],{"class":214},[200,112972,239],{"class":214},[200,112974,280],{"class":214},[200,112976,112977,112979,112981,112983,112985,112987],{"class":202,"line":255},[200,112978,28529],{"class":214},[200,112980,3295],{"class":1364},[200,112982,610],{"class":214},[200,112984,239],{"class":214},[200,112986,3953],{"class":2060},[200,112988,252],{"class":214},[200,112990,112991,112993,112995,112997,112999,113001,113003,113005],{"class":202,"line":272},[200,112992,28529],{"class":214},[200,112994,3581],{"class":1364},[200,112996,610],{"class":214},[200,112998,239],{"class":214},[200,113000,28499],{"class":214},[200,113002,55446],{"class":245},[200,113004,610],{"class":214},[200,113006,252],{"class":214},[200,113008,113009,113011,113013,113015,113017,113019,113021,113023],{"class":202,"line":283},[200,113010,28529],{"class":214},[200,113012,39512],{"class":1364},[200,113014,610],{"class":214},[200,113016,239],{"class":214},[200,113018,28499],{"class":214},[200,113020,52945],{"class":245},[200,113022,610],{"class":214},[200,113024,252],{"class":214},[200,113026,113027,113029,113031,113033,113035,113037,113039],{"class":202,"line":300},[200,113028,28529],{"class":214},[200,113030,53763],{"class":1364},[200,113032,610],{"class":214},[200,113034,239],{"class":214},[200,113036,28499],{"class":214},[200,113038,29434],{"class":245},[200,113040,616],{"class":214},[200,113042,113043],{"class":202,"line":310},[200,113044,368],{"class":214},[200,113046,113047,113049,113051,113053,113055],{"class":202,"line":336},[200,113048,28489],{"class":214},[200,113050,26847],{"class":379},[200,113052,610],{"class":214},[200,113054,239],{"class":214},[200,113056,280],{"class":214},[200,113058,113059],{"class":202,"line":342},[200,113060,368],{"class":214},[200,113062,113063,113065,113067,113069,113071],{"class":202,"line":365},[200,113064,28489],{"class":214},[200,113066,54427],{"class":379},[200,113068,610],{"class":214},[200,113070,239],{"class":214},[200,113072,280],{"class":214},[200,113074,113075],{"class":202,"line":371},[200,113076,4505],{"class":214},[200,113078,113079],{"class":202,"line":398},[200,113080,1129],{"class":214},[35,113082,55736,113083],{},[1216,113084,55739],{},[192,113086,113087],{"className":28358,"code":55742,"language":28360,"meta":13,"style":13},[156,113088,113089,113093,113105,113123,113139,113143,113155,113173,113189,113193],{"__ignoreMap":13},[200,113090,113091],{"class":202,"line":203},[200,113092,230],{"class":214},[200,113094,113095,113097,113099,113101,113103],{"class":202,"line":14},[200,113096,28489],{"class":214},[200,113098,21468],{"class":379},[200,113100,610],{"class":214},[200,113102,239],{"class":214},[200,113104,280],{"class":214},[200,113106,113107,113109,113111,113113,113115,113117,113119,113121],{"class":202,"line":255},[200,113108,28529],{"class":214},[200,113110,3295],{"class":1364},[200,113112,610],{"class":214},[200,113114,239],{"class":214},[200,113116,28499],{"class":214},[200,113118,55446],{"class":245},[200,113120,610],{"class":214},[200,113122,252],{"class":214},[200,113124,113125,113127,113129,113131,113133,113135,113137],{"class":202,"line":272},[200,113126,28529],{"class":214},[200,113128,39512],{"class":1364},[200,113130,610],{"class":214},[200,113132,239],{"class":214},[200,113134,28499],{"class":214},[200,113136,29434],{"class":245},[200,113138,616],{"class":214},[200,113140,113141],{"class":202,"line":283},[200,113142,368],{"class":214},[200,113144,113145,113147,113149,113151,113153],{"class":202,"line":300},[200,113146,28489],{"class":214},[200,113148,26847],{"class":379},[200,113150,610],{"class":214},[200,113152,239],{"class":214},[200,113154,280],{"class":214},[200,113156,113157,113159,113161,113163,113165,113167,113169,113171],{"class":202,"line":310},[200,113158,28529],{"class":214},[200,113160,3295],{"class":1364},[200,113162,610],{"class":214},[200,113164,239],{"class":214},[200,113166,28499],{"class":214},[200,113168,34550],{"class":245},[200,113170,610],{"class":214},[200,113172,252],{"class":214},[200,113174,113175,113177,113179,113181,113183,113185,113187],{"class":202,"line":336},[200,113176,28529],{"class":214},[200,113178,51626],{"class":1364},[200,113180,610],{"class":214},[200,113182,239],{"class":214},[200,113184,28499],{"class":214},[200,113186,55843],{"class":245},[200,113188,616],{"class":214},[200,113190,113191],{"class":202,"line":342},[200,113192,4505],{"class":214},[200,113194,113195],{"class":202,"line":365},[200,113196,1129],{"class":214},[35,113198,55856,113199],{},[1216,113200,55859],{},[35,113202,55862,113203,1655,113206,55871,113209,55876],{},[39,113204,55865],{"href":55865,"rel":113205},[77],[39,113207,55869],{"href":55869,"rel":113208},[77],[55873,113210,55875],{},[192,113212,113213],{"className":1380,"code":55879,"language":1383,"meta":13,"style":13},[156,113214,113215,113237,113255,113273,113291,113309,113327,113345,113363,113371,113389,113407,113425,113429,113437,113481,113485],{"__ignoreMap":13},[200,113216,113217,113219,113221,113223,113225,113227,113229,113231,113233,113235],{"class":202,"line":203},[200,113218,443],{"class":379},[200,113220,52772],{"class":210},[200,113222,449],{"class":214},[200,113224,452],{"class":206},[200,113226,47338],{"class":223},[200,113228,1549],{"class":210},[200,113230,215],{"class":214},[200,113232,769],{"class":223},[200,113234,227],{"class":210},[200,113236,230],{"class":214},[200,113238,113239,113241,113243,113245,113247,113249,113251,113253],{"class":202,"line":14},[200,113240,49638],{"class":235},[200,113242,239],{"class":214},[200,113244,47357],{"class":210},[200,113246,215],{"class":214},[200,113248,51600],{"class":210},[200,113250,215],{"class":214},[200,113252,3295],{"class":210},[200,113254,252],{"class":214},[200,113256,113257,113259,113261,113263,113265,113267,113269,113271],{"class":202,"line":255},[200,113258,23703],{"class":235},[200,113260,239],{"class":214},[200,113262,47357],{"class":210},[200,113264,215],{"class":214},[200,113266,51600],{"class":210},[200,113268,215],{"class":214},[200,113270,3581],{"class":210},[200,113272,252],{"class":214},[200,113274,113275,113277,113279,113281,113283,113285,113287,113289],{"class":202,"line":272},[200,113276,258],{"class":235},[200,113278,239],{"class":214},[200,113280,47357],{"class":210},[200,113282,215],{"class":214},[200,113284,51600],{"class":210},[200,113286,215],{"class":214},[200,113288,3597],{"class":210},[200,113290,252],{"class":214},[200,113292,113293,113295,113297,113299,113301,113303,113305,113307],{"class":202,"line":283},[200,113294,52918],{"class":235},[200,113296,239],{"class":214},[200,113298,47357],{"class":210},[200,113300,215],{"class":214},[200,113302,51600],{"class":210},[200,113304,215],{"class":214},[200,113306,39512],{"class":210},[200,113308,252],{"class":214},[200,113310,113311,113313,113315,113317,113319,113321,113323,113325],{"class":202,"line":300},[200,113312,52998],{"class":235},[200,113314,239],{"class":214},[200,113316,47357],{"class":210},[200,113318,215],{"class":214},[200,113320,51600],{"class":210},[200,113322,215],{"class":214},[200,113324,53748],{"class":210},[200,113326,252],{"class":214},[200,113328,113329,113331,113333,113335,113337,113339,113341,113343],{"class":202,"line":310},[200,113330,53070],{"class":235},[200,113332,239],{"class":214},[200,113334,47357],{"class":210},[200,113336,215],{"class":214},[200,113338,51600],{"class":210},[200,113340,215],{"class":214},[200,113342,53763],{"class":210},[200,113344,252],{"class":214},[200,113346,113347,113349,113351,113353,113355,113357,113359,113361],{"class":202,"line":336},[200,113348,53092],{"class":235},[200,113350,239],{"class":214},[200,113352,47357],{"class":210},[200,113354,215],{"class":214},[200,113356,51600],{"class":210},[200,113358,215],{"class":214},[200,113360,53778],{"class":210},[200,113362,252],{"class":214},[200,113364,113365,113367,113369],{"class":202,"line":342},[200,113366,56034],{"class":235},[200,113368,239],{"class":214},[200,113370,280],{"class":214},[200,113372,113373,113375,113377,113379,113381,113383,113385,113387],{"class":202,"line":365},[200,113374,11812],{"class":235},[200,113376,239],{"class":214},[200,113378,47357],{"class":210},[200,113380,215],{"class":214},[200,113382,47155],{"class":210},[200,113384,215],{"class":214},[200,113386,20112],{"class":210},[200,113388,252],{"class":214},[200,113390,113391,113393,113395,113397,113399,113401,113403,113405],{"class":202,"line":371},[200,113392,56061],{"class":235},[200,113394,239],{"class":214},[200,113396,47357],{"class":210},[200,113398,215],{"class":214},[200,113400,47155],{"class":210},[200,113402,215],{"class":214},[200,113404,51626],{"class":210},[200,113406,252],{"class":214},[200,113408,113409,113411,113413,113415,113417,113419,113421,113423],{"class":202,"line":398},[200,113410,56080],{"class":235},[200,113412,239],{"class":214},[200,113414,47357],{"class":210},[200,113416,215],{"class":214},[200,113418,47155],{"class":210},[200,113420,215],{"class":214},[200,113422,55537],{"class":210},[200,113424,252],{"class":214},[200,113426,113427],{"class":202,"line":405},[200,113428,368],{"class":214},[200,113430,113431,113433,113435],{"class":202,"line":410},[200,113432,56103],{"class":235},[200,113434,239],{"class":214},[200,113436,280],{"class":214},[200,113438,113439,113441,113443,113445,113447,113449,113451,113453,113455,113457,113459,113461,113463,113465,113467,113469,113471,113473,113475,113477,113479],{"class":202,"line":860},[200,113440,11812],{"class":235},[200,113442,239],{"class":214},[200,113444,51963],{"class":223},[200,113446,599],{"class":214},[200,113448,328],{"class":1364},[200,113450,674],{"class":214},[200,113452,3319],{"class":214},[200,113454,47418],{"class":210},[200,113456,215],{"class":214},[200,113458,47161],{"class":210},[200,113460,215],{"class":214},[200,113462,20112],{"class":210},[200,113464,23045],{"class":214},[200,113466,215],{"class":214},[200,113468,44022],{"class":223},[200,113470,227],{"class":210},[200,113472,249],{"class":214},[200,113474,56146],{"class":245},[200,113476,249],{"class":214},[200,113478,2104],{"class":210},[200,113480,252],{"class":214},[200,113482,113483],{"class":202,"line":889},[200,113484,368],{"class":214},[200,113486,113487,113489,113491,113493,113495,113497,113499,113501,113503,113505,113507,113509,113511,113513,113515,113517,113519,113521,113523,113525,113527,113529,113531,113533,113535,113537,113539,113541,113543,113545,113547,113549,113551,113553,113555,113557,113559,113561,113563,113565,113567,113569,113571],{"class":202,"line":899},[200,113488,413],{"class":214},[200,113490,2104],{"class":210},[200,113492,215],{"class":214},[200,113494,1489],{"class":223},[200,113496,51050],{"class":210},[200,113498,215],{"class":214},[200,113500,56173],{"class":210},[200,113502,215],{"class":214},[200,113504,53965],{"class":223},[200,113506,51050],{"class":210},[200,113508,215],{"class":214},[200,113510,47155],{"class":210},[200,113512,549],{"class":214},[200,113514,53978],{"class":223},[200,113516,51050],{"class":210},[200,113518,215],{"class":214},[200,113520,51600],{"class":210},[200,113522,215],{"class":214},[200,113524,53258],{"class":210},[200,113526,549],{"class":214},[200,113528,47357],{"class":210},[200,113530,215],{"class":214},[200,113532,47155],{"class":210},[200,113534,215],{"class":214},[200,113536,56210],{"class":210},[200,113538,215],{"class":214},[200,113540,53965],{"class":223},[200,113542,51050],{"class":210},[200,113544,215],{"class":214},[200,113546,47161],{"class":210},[200,113548,549],{"class":214},[200,113550,53978],{"class":223},[200,113552,51050],{"class":210},[200,113554,215],{"class":214},[200,113556,51600],{"class":210},[200,113558,215],{"class":214},[200,113560,53269],{"class":210},[200,113562,549],{"class":214},[200,113564,47357],{"class":210},[200,113566,215],{"class":214},[200,113568,47161],{"class":210},[200,113570,215],{"class":214},[200,113572,56247],{"class":210},[35,113574,113575,56253,113577,5569,113579,56258,113581,1655,113583,56263],{},[156,113576,56252],{},[156,113578,20112],{},[156,113580,56146],{},[156,113582,26847],{},[156,113584,54427],{},[1929,113586,24146],{"id":56266},[35,113588,56269],{},[35,113590,56272],{},[51,113592,56276],{"id":56275},[35,113594,113595],{},[1707,113596],{"alt":56281,"src":56282},[35,113598,113599],{},[1216,113600,113601,47101,113604,45890,113607,45894,113610,215],{},[39,113602,38397],{"href":45853,"rel":113603},[77],[39,113605,45889],{"href":18579,"rel":113606},[77],[39,113608,19427],{"href":45844,"rel":113609},[77],[39,113611,11636],{"href":45853,"rel":113612},[77],[35,113614,56301],{},[1593,113616,113617,113619,113621],{},[71,113618,56306],{},[71,113620,56309],{},[71,113622,56312],{},[35,113624,56315,113625],{},[1216,113626,56318],{},[1929,113628,56322],{"id":56321},[35,113630,56325,113631,56329,113633,56332,113635,56335,113637,56338,113639,56341,113641,56344,113643,56347],{},[156,113632,56328],{},[156,113634,47385],{},[156,113636,52842],{},[156,113638,47385],{},[156,113640,52842],{},[156,113642,47385],{},[156,113644,52842],{},[35,113646,56350],{},[192,113648,113649],{"className":1380,"code":56353,"language":1383,"meta":13,"style":13},[156,113650,113651,113663],{"__ignoreMap":13},[200,113652,113653,113655,113657,113659,113661],{"class":202,"line":203},[200,113654,443],{"class":379},[200,113656,56362],{"class":210},[200,113658,449],{"class":214},[200,113660,56367],{"class":223},[200,113662,466],{"class":210},[200,113664,113665,113667,113669,113671,113673,113675,113677,113679,113681],{"class":202,"line":14},[200,113666,443],{"class":379},[200,113668,56376],{"class":210},[200,113670,449],{"class":214},[200,113672,56381],{"class":210},[200,113674,215],{"class":214},[200,113676,20107],{"class":210},[200,113678,215],{"class":214},[200,113680,56390],{"class":210},[200,113682,56393],{"class":401},[35,113684,10674,113685,56398,113687,56401,113689,56404,113691,56407,113693,39879,113695,2186],{},[156,113686,47385],{},[156,113688,47385],{},[156,113690,52842],{},[156,113692,47385],{},[156,113694,47385],{},[156,113696,52842],{},[192,113698,113699],{"className":1380,"code":56414,"language":1383,"meta":13,"style":13},[156,113700,113701],{"__ignoreMap":13},[200,113702,113703,113705,113707,113709,113711,113713,113715],{"class":202,"line":203},[200,113704,47385],{"class":210},[200,113706,215],{"class":214},[200,113708,3943],{"class":223},[200,113710,227],{"class":210},[200,113712,56429],{"class":214},[200,113714,56432],{"class":2060},[200,113716,416],{"class":210},[35,113718,56437],{},[192,113720,113721],{"className":21457,"code":56440,"language":12075,"meta":13,"style":13},[156,113722,113723,113743,113755,113773,113807,113813,113853,113863,113869,113877,113881,113889,113895,113907,113911,113915,113923],{"__ignoreMap":13},[200,113724,113725,113727,113729,113731,113733,113735,113737,113739,113741],{"class":202,"line":203},[200,113726,599],{"class":214},[200,113728,14896],{"class":235},[200,113730,14594],{"class":379},[200,113732,449],{"class":214},[200,113734,610],{"class":214},[200,113736,1383],{"class":245},[200,113738,610],{"class":214},[200,113740,21825],{"class":379},[200,113742,647],{"class":214},[200,113744,113745,113747,113749,113751,113753],{"class":202,"line":14},[200,113746,443],{"class":379},[200,113748,56362],{"class":210},[200,113750,449],{"class":214},[200,113752,56367],{"class":223},[200,113754,466],{"class":210},[200,113756,113757,113759,113761,113763,113765,113767,113769,113771],{"class":202,"line":255},[200,113758,443],{"class":379},[200,113760,56481],{"class":210},[200,113762,449],{"class":214},[200,113764,56486],{"class":223},[200,113766,227],{"class":210},[200,113768,1549],{"class":214},[200,113770,393],{"class":379},[200,113772,280],{"class":214},[200,113774,113775,113777,113779,113781,113783,113785,113787,113789,113791,113793,113795,113797,113799,113801,113803,113805],{"class":202,"line":272},[200,113776,3758],{"class":206},[200,113778,1094],{"class":235},[200,113780,56503],{"class":210},[200,113782,215],{"class":214},[200,113784,20107],{"class":210},[200,113786,215],{"class":214},[200,113788,47385],{"class":210},[200,113790,14678],{"class":206},[200,113792,22752],{"class":1364},[200,113794,2104],{"class":235},[200,113796,215],{"class":214},[200,113798,3943],{"class":223},[200,113800,227],{"class":235},[200,113802,56429],{"class":214},[200,113804,56432],{"class":2060},[200,113806,416],{"class":235},[200,113808,113809,113811],{"class":202,"line":283},[200,113810,413],{"class":214},[200,113812,416],{"class":210},[200,113814,113815,113817,113819,113821,113823,113825,113827,113829,113831,113833,113835,113837,113839,113841,113843,113845,113847,113849,113851],{"class":202,"line":300},[200,113816,443],{"class":379},[200,113818,318],{"class":214},[200,113820,5056],{"class":235},[200,113822,239],{"class":214},[200,113824,56548],{"class":210},[200,113826,413],{"class":214},[200,113828,509],{"class":214},[200,113830,452],{"class":206},[200,113832,47935],{"class":223},[200,113834,227],{"class":210},[200,113836,3333],{"class":214},[200,113838,56563],{"class":245},[200,113840,4868],{"class":214},[200,113842,52842],{"class":210},[200,113844,215],{"class":214},[200,113846,15840],{"class":210},[200,113848,23045],{"class":214},[200,113850,549],{"class":214},[200,113852,280],{"class":214},[200,113854,113855,113857,113859,113861],{"class":202,"line":310},[200,113856,47953],{"class":235},[200,113858,239],{"class":214},[200,113860,16169],{"class":2438},[200,113862,252],{"class":214},[200,113864,113865,113867],{"class":202,"line":336},[200,113866,413],{"class":214},[200,113868,416],{"class":210},[200,113870,113871,113873,113875],{"class":202,"line":342},[200,113872,680],{"class":214},[200,113874,14896],{"class":235},[200,113876,647],{"class":214},[200,113878,113879],{"class":202,"line":365},[200,113880,528],{"emptyLinePlaceholder":20},[200,113882,113883,113885,113887],{"class":202,"line":371},[200,113884,599],{"class":214},[200,113886,21468],{"class":235},[200,113888,647],{"class":214},[200,113890,113891,113893],{"class":202,"line":398},[200,113892,656],{"class":214},[200,113894,56620],{"class":235},[200,113896,113897,113899,113901,113903,113905],{"class":202,"line":405},[200,113898,56625],{"class":379},[200,113900,449],{"class":214},[200,113902,610],{"class":214},[200,113904,21468],{"class":245},[200,113906,616],{"class":214},[200,113908,113909],{"class":202,"line":410},[200,113910,11550],{"class":214},[200,113912,113913],{"class":202,"line":860},[200,113914,56642],{"class":210},[200,113916,113917,113919,113921],{"class":202,"line":889},[200,113918,892],{"class":214},[200,113920,56649],{"class":235},[200,113922,647],{"class":214},[200,113924,113925,113927,113929],{"class":202,"line":899},[200,113926,680],{"class":214},[200,113928,21468],{"class":235},[200,113930,647],{"class":214},[35,113932,56662,113933,2186],{},[156,113934,48864],{},[192,113936,113937],{"className":1380,"code":56667,"language":1383,"meta":13,"style":13},[156,113938,113939,113961,113965,113987,114011,114033,114045,114049,114063,114067,114127,114131,114137],{"__ignoreMap":13},[200,113940,113941,113943,113945,113947,113949,113951,113953,113955,113957,113959],{"class":202,"line":203},[200,113942,972],{"class":206},[200,113944,318],{"class":214},[200,113946,42642],{"class":210},[200,113948,549],{"class":214},[200,113950,22752],{"class":210},[200,113952,980],{"class":214},[200,113954,983],{"class":206},[200,113956,242],{"class":214},[200,113958,19813],{"class":245},[200,113960,991],{"class":214},[200,113962,113963],{"class":202,"line":14},[200,113964,528],{"emptyLinePlaceholder":20},[200,113966,113967,113969,113971,113973,113975,113977,113979,113981,113983,113985],{"class":202,"line":255},[200,113968,1390],{"class":206},[200,113970,1393],{"class":206},[200,113972,38723],{"class":223},[200,113974,227],{"class":210},[200,113976,3726],{"class":379},[200,113978,1094],{"class":214},[200,113980,25426],{"class":386},[200,113982,2104],{"class":214},[200,113984,393],{"class":379},[200,113986,280],{"class":214},[200,113988,113989,113991,113993,113995,113997,113999,114001,114003,114005,114007,114009],{"class":202,"line":272},[200,113990,3738],{"class":379},[200,113992,48886],{"class":210},[200,113994,509],{"class":214},[200,113996,452],{"class":206},[200,113998,56730],{"class":223},[200,114000,227],{"class":235},[200,114002,25426],{"class":210},[200,114004,549],{"class":214},[200,114006,42642],{"class":223},[200,114008,227],{"class":235},[200,114010,230],{"class":214},[200,114012,114013,114015,114017,114019,114021,114023,114025,114027,114029,114031],{"class":202,"line":283},[200,114014,53678],{"class":235},[200,114016,239],{"class":214},[200,114018,22752],{"class":223},[200,114020,1549],{"class":235},[200,114022,215],{"class":214},[200,114024,56757],{"class":223},[200,114026,227],{"class":235},[200,114028,56432],{"class":2060},[200,114030,2104],{"class":235},[200,114032,252],{"class":214},[200,114034,114035,114037,114039,114041,114043],{"class":202,"line":300},[200,114036,5095],{"class":214},[200,114038,2104],{"class":235},[200,114040,215],{"class":214},[200,114042,40994],{"class":210},[200,114044,416],{"class":235},[200,114046,114047],{"class":202,"line":310},[200,114048,528],{"emptyLinePlaceholder":20},[200,114050,114051,114053,114055,114057,114059,114061],{"class":202,"line":336},[200,114052,3738],{"class":379},[200,114054,318],{"class":214},[200,114056,56790],{"class":210},[200,114058,980],{"class":214},[200,114060,509],{"class":214},[200,114062,56797],{"class":210},[200,114064,114065],{"class":202,"line":342},[200,114066,528],{"emptyLinePlaceholder":20},[200,114068,114069,114071,114073,114075,114077,114079,114081,114083,114085,114087,114089,114091,114093,114095,114097,114099,114101,114103,114105,114107,114109,114111,114113,114115,114117,114119,114121,114123,114125],{"class":202,"line":365},[200,114070,3738],{"class":379},[200,114072,56808],{"class":210},[200,114074,509],{"class":214},[200,114076,452],{"class":206},[200,114078,47338],{"class":223},[200,114080,1549],{"class":235},[200,114082,215],{"class":214},[200,114084,769],{"class":223},[200,114086,1549],{"class":235},[200,114088,215],{"class":214},[200,114090,1489],{"class":223},[200,114092,227],{"class":235},[200,114094,47418],{"class":210},[200,114096,215],{"class":214},[200,114098,51600],{"class":210},[200,114100,2104],{"class":235},[200,114102,215],{"class":214},[200,114104,48864],{"class":223},[200,114106,227],{"class":235},[200,114108,48869],{"class":223},[200,114110,227],{"class":235},[200,114112,47418],{"class":210},[200,114114,215],{"class":214},[200,114116,51600],{"class":210},[200,114118,215],{"class":214},[200,114120,52842],{"class":210},[200,114122,549],{"class":214},[200,114124,56790],{"class":210},[200,114126,584],{"class":235},[200,114128,114129],{"class":202,"line":371},[200,114130,528],{"emptyLinePlaceholder":20},[200,114132,114133,114135],{"class":202,"line":398},[200,114134,3758],{"class":206},[200,114136,56873],{"class":210},[200,114138,114139,114141],{"class":202,"line":405},[200,114140,413],{"class":214},[200,114142,416],{"class":210},[35,114144,56882],{},[1929,114146,56886],{"id":56885},[35,114148,56889],{},[35,114150,56892],{},[192,114152,114153],{"className":590,"code":56895,"language":592,"meta":13,"style":13},[156,114154,114155,114171],{"__ignoreMap":13},[200,114156,114157,114159,114161,114163,114165,114167,114169],{"class":202,"line":203},[200,114158,599],{"class":214},[200,114160,35],{"class":235},[200,114162,674],{"class":214},[200,114164,56908],{"class":210},[200,114166,680],{"class":214},[200,114168,35],{"class":235},[200,114170,647],{"class":214},[200,114172,114173,114175,114177,114179,114181,114183,114185,114187,114189,114191,114193,114195,114197,114199],{"class":202,"line":14},[200,114174,599],{"class":214},[200,114176,14896],{"class":235},[200,114178,674],{"class":214},[200,114180,56925],{"class":210},[200,114182,215],{"class":214},[200,114184,1117],{"class":223},[200,114186,227],{"class":210},[200,114188,249],{"class":214},[200,114190,56936],{"class":245},[200,114192,249],{"class":214},[200,114194,5282],{"class":210},[200,114196,680],{"class":214},[200,114198,14896],{"class":235},[200,114200,647],{"class":214},[35,114202,56949,114203,56953],{},[156,114204,56952],{},[35,114206,56956,114207,56959,114209,56963],{},[156,114208,9689],{},[6862,114210,56962],{},[35,114212,56966,114213,56969,114215,56972,114217,56976],{},[156,114214,9689],{},[156,114216,53400],{},[156,114218,56975],{},[192,114220,114221],{"className":1380,"code":56979,"language":1383,"meta":13,"style":13},[156,114222,114223,114249,114261,114277,114291,114305,114319,114333,114347,114361,114377,114391],{"__ignoreMap":13},[200,114224,114225,114227,114229,114231,114233,114235,114237,114239,114241,114243,114245,114247],{"class":202,"line":203},[200,114226,207],{"class":206},[200,114228,47338],{"class":223},[200,114230,1549],{"class":210},[200,114232,215],{"class":214},[200,114234,47525],{"class":223},[200,114236,51050],{"class":210},[200,114238,215],{"class":214},[200,114240,56173],{"class":210},[200,114242,215],{"class":214},[200,114244,47540],{"class":223},[200,114246,227],{"class":210},[200,114248,230],{"class":214},[200,114250,114251,114253,114255,114257,114259],{"class":202,"line":14},[200,114252,52831],{"class":235},[200,114254,239],{"class":214},[200,114256,53683],{"class":223},[200,114258,1549],{"class":210},[200,114260,252],{"class":214},[200,114262,114263,114265,114267,114269,114271,114273,114275],{"class":202,"line":255},[200,114264,52865],{"class":235},[200,114266,239],{"class":214},[200,114268,47553],{"class":223},[200,114270,57030],{"class":210},[200,114272,215],{"class":214},[200,114274,57035],{"class":210},[200,114276,252],{"class":214},[200,114278,114279,114281,114283,114285,114287,114289],{"class":202,"line":272},[200,114280,23703],{"class":235},[200,114282,239],{"class":214},[200,114284,40809],{"class":210},[200,114286,215],{"class":214},[200,114288,3581],{"class":210},[200,114290,252],{"class":214},[200,114292,114293,114295,114297,114299,114301,114303],{"class":202,"line":283},[200,114294,53191],{"class":235},[200,114296,239],{"class":214},[200,114298,40809],{"class":210},[200,114300,215],{"class":214},[200,114302,53269],{"class":210},[200,114304,252],{"class":214},[200,114306,114307,114309,114311,114313,114315,114317],{"class":202,"line":300},[200,114308,52998],{"class":235},[200,114310,239],{"class":214},[200,114312,40809],{"class":210},[200,114314,215],{"class":214},[200,114316,53748],{"class":210},[200,114318,252],{"class":214},[200,114320,114321,114323,114325,114327,114329,114331],{"class":202,"line":310},[200,114322,53070],{"class":235},[200,114324,239],{"class":214},[200,114326,40809],{"class":210},[200,114328,215],{"class":214},[200,114330,53763],{"class":210},[200,114332,252],{"class":214},[200,114334,114335,114337,114339,114341,114343,114345],{"class":202,"line":336},[200,114336,53092],{"class":235},[200,114338,239],{"class":214},[200,114340,40809],{"class":210},[200,114342,215],{"class":214},[200,114344,53778],{"class":210},[200,114346,252],{"class":214},[200,114348,114349,114351,114353,114355,114357,114359],{"class":202,"line":342},[200,114350,258],{"class":235},[200,114352,239],{"class":214},[200,114354,40809],{"class":210},[200,114356,215],{"class":214},[200,114358,3597],{"class":210},[200,114360,252],{"class":214},[200,114362,114363,114365,114367,114369,114371,114373,114375],{"class":202,"line":365},[200,114364,57126],{"class":235},[200,114366,239],{"class":214},[200,114368,57131],{"class":223},[200,114370,57030],{"class":210},[200,114372,215],{"class":214},[200,114374,57138],{"class":210},[200,114376,252],{"class":214},[200,114378,114379,114381,114383,114385,114387,114389],{"class":202,"line":371},[200,114380,53146],{"class":235},[200,114382,239],{"class":214},[200,114384,34533],{"class":210},[200,114386,215],{"class":214},[200,114388,3295],{"class":210},[200,114390,252],{"class":214},[200,114392,114393,114395,114397,114399,114401],{"class":202,"line":398},[200,114394,413],{"class":214},[200,114396,2104],{"class":210},[200,114398,215],{"class":214},[200,114400,47590],{"class":223},[200,114402,466],{"class":210},[35,114404,15736,114405,57172,114407,57175],{},[156,114406,57171],{},[156,114408,9689],{},[1929,114410,57179],{"id":57178},[35,114412,114413],{},[1216,114414,57184],{},[35,114416,114417],{},[1216,114418,57189],{},[35,114420,57192,114421,57196],{},[1216,114422,57195],{},[1593,114424,114425,114427,114429,114431,114433,114435,114437,114439],{},[71,114426,57201],{},[71,114428,34008],{},[71,114430,57206],{},[71,114432,57209],{},[71,114434,34013],{},[71,114436,57214],{},[71,114438,57217],{},[71,114440,57220],{},[35,114442,57223],{},[35,114444,57226,114445],{},[1216,114446,57229],{},[35,114448,57232,114449,106,114451],{},[156,114450,57235],{},[1216,114452,114453,57241],{},[156,114454,57240],{},[192,114456,114457],{"className":1380,"code":57244,"language":1383,"meta":13,"style":13},[156,114458,114459,114473,114513,114527],{"__ignoreMap":13},[200,114460,114461,114463,114465,114467,114469,114471],{"class":202,"line":203},[200,114462,57235],{"class":223},[200,114464,227],{"class":210},[200,114466,3726],{"class":379},[200,114468,3729],{"class":214},[200,114470,393],{"class":379},[200,114472,280],{"class":214},[200,114474,114475,114477,114479,114481,114483,114485,114487,114489,114491,114493,114495,114497,114499,114501,114503,114505,114507,114509,114511],{"class":202,"line":14},[200,114476,3738],{"class":379},[200,114478,57267],{"class":210},[200,114480,509],{"class":214},[200,114482,452],{"class":206},[200,114484,27446],{"class":214},[200,114486,227],{"class":235},[200,114488,249],{"class":214},[200,114490,57280],{"class":245},[200,114492,249],{"class":214},[200,114494,2104],{"class":235},[200,114496,215],{"class":214},[200,114498,10944],{"class":223},[200,114500,227],{"class":235},[200,114502,57293],{"class":386},[200,114504,393],{"class":379},[200,114506,57298],{"class":210},[200,114508,215],{"class":214},[200,114510,51912],{"class":210},[200,114512,416],{"class":235},[200,114514,114515,114517,114519,114521,114523,114525],{"class":202,"line":255},[200,114516,3738],{"class":379},[200,114518,57311],{"class":210},[200,114520,509],{"class":214},[200,114522,13007],{"class":214},[200,114524,57267],{"class":223},[200,114526,466],{"class":235},[200,114528,114529,114531],{"class":202,"line":272},[200,114530,413],{"class":214},[200,114532,416],{"class":210},[35,114534,57328],{},[35,114536,114537],{},[1707,114538],{"alt":57333,"src":57334},[35,114540,57337],{},[192,114542,114543],{"className":21457,"code":57340,"language":12075,"meta":13,"style":13},[156,114544,114545,114565,114575],{"__ignoreMap":13},[200,114546,114547,114549,114551,114553,114555,114557,114559,114561,114563],{"class":202,"line":203},[200,114548,599],{"class":214},[200,114550,14896],{"class":235},[200,114552,14594],{"class":379},[200,114554,449],{"class":214},[200,114556,610],{"class":214},[200,114558,1383],{"class":245},[200,114560,610],{"class":214},[200,114562,21825],{"class":379},[200,114564,647],{"class":214},[200,114566,114567,114569,114571,114573],{"class":202,"line":14},[200,114568,972],{"class":206},[200,114570,242],{"class":214},[200,114572,57371],{"class":245},[200,114574,991],{"class":214},[200,114576,114577,114579,114581],{"class":202,"line":255},[200,114578,680],{"class":214},[200,114580,14896],{"class":235},[200,114582,647],{"class":214},[35,114584,114585],{},[1707,114586],{"alt":57386,"src":57387},[35,114588,57390],{},[35,114590,57393],{},[192,114592,114593],{"className":21457,"code":57396,"language":12075,"meta":13,"style":13},[156,114594,114595,114615,114637,114651,114691,114705,114733,114751,114757,114763],{"__ignoreMap":13},[200,114596,114597,114599,114601,114603,114605,114607,114609,114611,114613],{"class":202,"line":203},[200,114598,599],{"class":214},[200,114600,14896],{"class":235},[200,114602,14594],{"class":379},[200,114604,449],{"class":214},[200,114606,610],{"class":214},[200,114608,1383],{"class":245},[200,114610,610],{"class":214},[200,114612,21825],{"class":379},[200,114614,647],{"class":214},[200,114616,114617,114619,114621,114623,114625,114627,114629,114631,114633,114635],{"class":202,"line":14},[200,114618,443],{"class":379},[200,114620,22710],{"class":210},[200,114622,449],{"class":214},[200,114624,18698],{"class":223},[200,114626,599],{"class":214},[200,114628,328],{"class":1364},[200,114630,674],{"class":214},[200,114632,227],{"class":210},[200,114634,22700],{"class":214},[200,114636,416],{"class":210},[200,114638,114639,114641,114643,114645,114647,114649],{"class":202,"line":255},[200,114640,57235],{"class":223},[200,114642,227],{"class":210},[200,114644,3726],{"class":379},[200,114646,3729],{"class":214},[200,114648,393],{"class":379},[200,114650,280],{"class":214},[200,114652,114653,114655,114657,114659,114661,114663,114665,114667,114669,114671,114673,114675,114677,114679,114681,114683,114685,114687,114689],{"class":202,"line":272},[200,114654,3738],{"class":379},[200,114656,57267],{"class":210},[200,114658,509],{"class":214},[200,114660,452],{"class":206},[200,114662,27446],{"class":214},[200,114664,227],{"class":235},[200,114666,249],{"class":214},[200,114668,57280],{"class":245},[200,114670,249],{"class":214},[200,114672,2104],{"class":235},[200,114674,215],{"class":214},[200,114676,10944],{"class":223},[200,114678,227],{"class":235},[200,114680,57293],{"class":386},[200,114682,393],{"class":379},[200,114684,57298],{"class":210},[200,114686,215],{"class":214},[200,114688,51912],{"class":210},[200,114690,416],{"class":235},[200,114692,114693,114695,114697,114699,114701,114703],{"class":202,"line":283},[200,114694,3738],{"class":379},[200,114696,57311],{"class":210},[200,114698,509],{"class":214},[200,114700,13007],{"class":214},[200,114702,57267],{"class":223},[200,114704,466],{"class":235},[200,114706,114707,114709,114711,114713,114715,114717,114719,114721,114723,114725,114727,114729,114731],{"class":202,"line":300},[200,114708,57513],{"class":210},[200,114710,215],{"class":214},[200,114712,57518],{"class":210},[200,114714,215],{"class":214},[200,114716,25410],{"class":223},[200,114718,227],{"class":235},[200,114720,249],{"class":214},[200,114722,57529],{"class":245},[200,114724,249],{"class":214},[200,114726,549],{"class":214},[200,114728,3729],{"class":214},[200,114730,393],{"class":379},[200,114732,280],{"class":214},[200,114734,114735,114737,114739,114741,114743,114745,114747,114749],{"class":202,"line":310},[200,114736,3151],{"class":210},[200,114738,215],{"class":214},[200,114740,15840],{"class":210},[200,114742,509],{"class":214},[200,114744,57552],{"class":210},[200,114746,215],{"class":214},[200,114748,15840],{"class":223},[200,114750,466],{"class":235},[200,114752,114753,114755],{"class":202,"line":336},[200,114754,5095],{"class":214},[200,114756,416],{"class":235},[200,114758,114759,114761],{"class":202,"line":342},[200,114760,413],{"class":214},[200,114762,416],{"class":210},[200,114764,114765,114767,114769],{"class":202,"line":365},[200,114766,680],{"class":214},[200,114768,14896],{"class":235},[200,114770,647],{"class":214},[35,114772,57581],{},[1929,114774,57585],{"id":57584},[35,114776,57588],{},[35,114778,57591],{},[51,114780,57595],{"id":57594},[35,114782,114783],{},[1216,114784,57600],{},[35,114786,57603],{},[35,114788,57606,114789],{},[1216,114790,57609],{},[35,114792,57612,114793,57615,114795,215],{},[156,114794,3597],{},[156,114796,56975],{},[1929,114798,57621],{"id":57620},[35,114800,57624],{},[35,114802,57627],{},[192,114804,114805],{"className":590,"code":57630,"language":592,"meta":13,"style":13},[156,114806,114807,114823],{"__ignoreMap":13},[200,114808,114809,114811,114813,114815,114817,114819,114821],{"class":202,"line":203},[200,114810,599],{"class":214},[200,114812,35],{"class":235},[200,114814,674],{"class":214},[200,114816,57643],{"class":210},[200,114818,680],{"class":214},[200,114820,35],{"class":235},[200,114822,647],{"class":214},[200,114824,114825,114827,114829,114831,114833,114835,114837,114839,114841,114843,114845,114847,114849,114851],{"class":202,"line":14},[200,114826,599],{"class":214},[200,114828,14896],{"class":235},[200,114830,674],{"class":214},[200,114832,56925],{"class":210},[200,114834,215],{"class":214},[200,114836,1117],{"class":223},[200,114838,227],{"class":210},[200,114840,249],{"class":214},[200,114842,57670],{"class":245},[200,114844,249],{"class":214},[200,114846,5282],{"class":210},[200,114848,680],{"class":214},[200,114850,14896],{"class":235},[200,114852,647],{"class":214},[35,114854,57683,114855,57686,114857,57690],{},[156,114856,56952],{},[156,114858,57689],{},[35,114860,57693,114861,57697],{},[156,114862,57696],{},[192,114864,114865],{"className":590,"code":57700,"language":592,"meta":13,"style":13},[156,114866,114867],{"__ignoreMap":13},[200,114868,114869,114871,114873,114875,114877,114879,114881],{"class":202,"line":203},[200,114870,599],{"class":214},[200,114872,35],{"class":235},[200,114874,674],{"class":214},[200,114876,57643],{"class":210},[200,114878,680],{"class":214},[200,114880,35],{"class":235},[200,114882,647],{"class":214},[35,114884,57721],{},[192,114886,114887],{"className":1380,"code":57724,"language":1383,"meta":13,"style":13},[156,114888,114889,114915,114941],{"__ignoreMap":13},[200,114890,114891,114893,114895,114897,114899,114901,114903,114905,114907,114909,114911,114913],{"class":202,"line":203},[200,114892,207],{"class":206},[200,114894,47338],{"class":223},[200,114896,1549],{"class":210},[200,114898,215],{"class":214},[200,114900,47525],{"class":223},[200,114902,51050],{"class":210},[200,114904,215],{"class":214},[200,114906,56173],{"class":210},[200,114908,215],{"class":214},[200,114910,47540],{"class":223},[200,114912,227],{"class":210},[200,114914,230],{"class":214},[200,114916,114917,114919,114921,114923,114925,114927,114929,114931,114933,114935,114937,114939],{"class":202,"line":14},[200,114918,258],{"class":235},[200,114920,239],{"class":214},[200,114922,40809],{"class":210},[200,114924,215],{"class":214},[200,114926,3833],{"class":210},[200,114928,27725],{"class":214},[200,114930,57769],{"class":223},[200,114932,57030],{"class":210},[200,114934,215],{"class":214},[200,114936,57776],{"class":210},[200,114938,239],{"class":214},[200,114940,11911],{"class":214},[200,114942,114943,114945],{"class":202,"line":255},[200,114944,413],{"class":214},[200,114946,416],{"class":210},[35,114948,15736,114949,57792,114951,57795],{},[156,114950,57791],{},[156,114952,57696],{},[1929,114954,57799],{"id":57798},[35,114956,57802,114957,57805,114959,57808],{},[156,114958,56975],{},[156,114960,3597],{},[192,114962,114963],{"className":57811,"code":57812,"language":1383,"meta":13,"style":13},[156,114964,114965,114989,115009,115030],{"__ignoreMap":13},[200,114966,114967,114969,114971,114973,114975,114977,114979,114981,114983,114985,114987],{"class":202,"line":203},[200,114968,1390],{"class":206},[200,114970,14305],{"class":379},[200,114972,52772],{"class":210},[200,114974,449],{"class":214},[200,114976,50817],{"class":223},[200,114978,227],{"class":210},[200,114980,249],{"class":214},[200,114982,51600],{"class":245},[200,114984,249],{"class":214},[200,114986,549],{"class":214},[200,114988,280],{"class":214},[200,114990,114991,114993,114995,114997,114999,115001,115003,115005,115007],{"class":202,"line":14},[200,114992,258],{"class":235},[200,114994,239],{"class":214},[200,114996,50879],{"class":223},[200,114998,227],{"class":210},[200,115000,249],{"class":214},[200,115002,3597],{"class":245},[200,115004,249],{"class":214},[200,115006,2104],{"class":210},[200,115008,252],{"class":214},[200,115010,115012,115014,115016,115018,115020,115022,115024,115026,115028],{"class":115011,"line":255},[202,57862,37151],[200,115013,57126],{"class":235},[200,115015,239],{"class":214},[200,115017,50879],{"class":223},[200,115019,227],{"class":210},[200,115021,249],{"class":214},[200,115023,57875],{"class":245},[200,115025,249],{"class":214},[200,115027,2104],{"class":210},[200,115029,549],{"class":214},[200,115031,115032,115034],{"class":202,"line":272},[200,115033,413],{"class":214},[200,115035,416],{"class":210},[1929,115037,57891],{"id":57890},[35,115039,57894],{},[192,115041,115042],{"className":21457,"code":57897,"language":12075,"meta":13,"style":13},[156,115043,115044,115064,115074,115078,115092,115132,115158,115172,115178,115218,115246,115252,115258,115266,115270,115278,115296],{"__ignoreMap":13},[200,115045,115046,115048,115050,115052,115054,115056,115058,115060,115062],{"class":202,"line":203},[200,115047,599],{"class":214},[200,115049,14896],{"class":235},[200,115051,14594],{"class":379},[200,115053,449],{"class":214},[200,115055,610],{"class":214},[200,115057,1383],{"class":245},[200,115059,610],{"class":214},[200,115061,21825],{"class":379},[200,115063,647],{"class":214},[200,115065,115066,115068,115070,115072],{"class":202,"line":14},[200,115067,972],{"class":206},[200,115069,242],{"class":214},[200,115071,57928],{"class":245},[200,115073,991],{"class":214},[200,115075,115076],{"class":202,"line":255},[200,115077,528],{"emptyLinePlaceholder":20},[200,115079,115080,115082,115084,115086,115088,115090],{"class":202,"line":272},[200,115081,57240],{"class":223},[200,115083,227],{"class":210},[200,115085,3726],{"class":379},[200,115087,3729],{"class":214},[200,115089,393],{"class":379},[200,115091,280],{"class":214},[200,115093,115094,115096,115098,115100,115102,115104,115106,115108,115110,115112,115114,115116,115118,115120,115122,115124,115126,115128,115130],{"class":202,"line":283},[200,115095,3738],{"class":379},[200,115097,57955],{"class":210},[200,115099,509],{"class":214},[200,115101,452],{"class":206},[200,115103,27446],{"class":214},[200,115105,227],{"class":235},[200,115107,249],{"class":214},[200,115109,57968],{"class":245},[200,115111,249],{"class":214},[200,115113,2104],{"class":235},[200,115115,215],{"class":214},[200,115117,10944],{"class":223},[200,115119,227],{"class":235},[200,115121,57293],{"class":386},[200,115123,393],{"class":379},[200,115125,57298],{"class":210},[200,115127,215],{"class":214},[200,115129,51912],{"class":210},[200,115131,416],{"class":235},[200,115133,115134,115136,115138,115140,115142,115144,115146,115148,115150,115152,115154,115156],{"class":202,"line":300},[200,115135,57995],{"class":210},[200,115137,215],{"class":214},[200,115139,15840],{"class":210},[200,115141,509],{"class":214},[200,115143,13007],{"class":214},[200,115145,57955],{"class":223},[200,115147,227],{"class":235},[200,115149,249],{"class":214},[200,115151,58012],{"class":245},[200,115153,249],{"class":214},[200,115155,549],{"class":214},[200,115157,280],{"class":214},[200,115159,115160,115162,115164,115166,115168,115170],{"class":202,"line":310},[200,115161,58023],{"class":235},[200,115163,239],{"class":214},[200,115165,242],{"class":214},[200,115167,58030],{"class":245},[200,115169,249],{"class":214},[200,115171,252],{"class":214},[200,115173,115174,115176],{"class":202,"line":336},[200,115175,5095],{"class":214},[200,115177,416],{"class":235},[200,115179,115180,115182,115184,115186,115188,115190,115192,115194,115196,115198,115200,115202,115204,115206,115208,115210,115212,115214,115216],{"class":202,"line":342},[200,115181,57995],{"class":210},[200,115183,215],{"class":214},[200,115185,15840],{"class":210},[200,115187,6536],{"class":214},[200,115189,25410],{"class":223},[200,115191,227],{"class":235},[200,115193,249],{"class":214},[200,115195,58059],{"class":245},[200,115197,249],{"class":214},[200,115199,549],{"class":214},[200,115201,1094],{"class":214},[200,115203,19215],{"class":386},[200,115205,549],{"class":214},[200,115207,58072],{"class":386},[200,115209,549],{"class":214},[200,115211,58077],{"class":386},[200,115213,2104],{"class":214},[200,115215,393],{"class":379},[200,115217,280],{"class":214},[200,115219,115220,115222,115224,115226,115228,115230,115232,115234,115236,115238,115240,115242,115244],{"class":202,"line":365},[200,115221,27151],{"class":210},[200,115223,215],{"class":214},[200,115225,3597],{"class":210},[200,115227,509],{"class":214},[200,115229,58096],{"class":210},[200,115231,215],{"class":214},[200,115233,15840],{"class":210},[200,115235,6536],{"class":214},[200,115237,58105],{"class":210},[200,115239,215],{"class":214},[200,115241,15970],{"class":210},[200,115243,3906],{"class":214},[200,115245,18888],{"class":214},[200,115247,115248,115250],{"class":202,"line":371},[200,115249,5095],{"class":214},[200,115251,416],{"class":235},[200,115253,115254,115256],{"class":202,"line":398},[200,115255,413],{"class":214},[200,115257,416],{"class":210},[200,115259,115260,115262,115264],{"class":202,"line":405},[200,115261,680],{"class":214},[200,115263,14896],{"class":235},[200,115265,647],{"class":214},[200,115267,115268],{"class":202,"line":410},[200,115269,528],{"emptyLinePlaceholder":20},[200,115271,115272,115274,115276],{"class":202,"line":860},[200,115273,599],{"class":214},[200,115275,21468],{"class":235},[200,115277,647],{"class":214},[200,115279,115280,115282,115284,115286,115288,115290,115292,115294],{"class":202,"line":889},[200,115281,656],{"class":214},[200,115283,11400],{"class":235},[200,115285,3239],{"class":379},[200,115287,449],{"class":214},[200,115289,610],{"class":214},[200,115291,58160],{"class":245},[200,115293,610],{"class":214},[200,115295,21527],{"class":214},[200,115297,115298,115300,115302],{"class":202,"line":899},[200,115299,680],{"class":214},[200,115301,21468],{"class":235},[200,115303,647],{"class":214},[35,115305,58175],{},[35,115307,58178],{},[192,115309,115310],{"className":21457,"code":58181,"language":12075,"meta":13,"style":13},[156,115311,115312,115332,115344,115348,115362,115402,115430,115464,115470,115476,115484,115488,115496,115502,115514,115526,115556,115560,115578,115586],{"__ignoreMap":13},[200,115313,115314,115316,115318,115320,115322,115324,115326,115328,115330],{"class":202,"line":203},[200,115315,599],{"class":214},[200,115317,14896],{"class":235},[200,115319,14594],{"class":379},[200,115321,449],{"class":214},[200,115323,610],{"class":214},[200,115325,1383],{"class":245},[200,115327,610],{"class":214},[200,115329,21825],{"class":379},[200,115331,647],{"class":214},[200,115333,115334,115336,115338,115340,115342],{"class":202,"line":14},[200,115335,443],{"class":379},[200,115337,58210],{"class":210},[200,115339,449],{"class":214},[200,115341,18698],{"class":223},[200,115343,466],{"class":210},[200,115345,115346],{"class":202,"line":255},[200,115347,528],{"emptyLinePlaceholder":20},[200,115349,115350,115352,115354,115356,115358,115360],{"class":202,"line":272},[200,115351,57240],{"class":223},[200,115353,227],{"class":210},[200,115355,3726],{"class":379},[200,115357,3729],{"class":214},[200,115359,393],{"class":379},[200,115361,280],{"class":214},[200,115363,115364,115366,115368,115370,115372,115374,115376,115378,115380,115382,115384,115386,115388,115390,115392,115394,115396,115398,115400],{"class":202,"line":283},[200,115365,57995],{"class":210},[200,115367,215],{"class":214},[200,115369,15840],{"class":210},[200,115371,6536],{"class":214},[200,115373,25410],{"class":223},[200,115375,227],{"class":235},[200,115377,249],{"class":214},[200,115379,58059],{"class":245},[200,115381,249],{"class":214},[200,115383,549],{"class":214},[200,115385,1094],{"class":214},[200,115387,19215],{"class":386},[200,115389,549],{"class":214},[200,115391,58072],{"class":386},[200,115393,549],{"class":214},[200,115395,58077],{"class":386},[200,115397,2104],{"class":214},[200,115399,393],{"class":379},[200,115401,280],{"class":214},[200,115403,115404,115406,115408,115410,115412,115414,115416,115418,115420,115422,115424,115426,115428],{"class":202,"line":300},[200,115405,27151],{"class":210},[200,115407,215],{"class":214},[200,115409,3597],{"class":210},[200,115411,509],{"class":214},[200,115413,58096],{"class":210},[200,115415,215],{"class":214},[200,115417,15840],{"class":210},[200,115419,6536],{"class":214},[200,115421,58105],{"class":210},[200,115423,215],{"class":214},[200,115425,15970],{"class":210},[200,115427,3906],{"class":214},[200,115429,18888],{"class":214},[200,115431,115432,115434,115436,115438,115440,115442,115444,115446,115448,115450,115452,115454,115456,115458,115460,115462],{"class":202,"line":310},[200,115433,58307],{"class":210},[200,115435,215],{"class":214},[200,115437,15840],{"class":210},[200,115439,215],{"class":214},[200,115441,58316],{"class":223},[200,115443,227],{"class":235},[200,115445,249],{"class":214},[200,115447,3597],{"class":245},[200,115449,249],{"class":214},[200,115451,549],{"class":214},[200,115453,318],{"class":214},[200,115455,58331],{"class":235},[200,115457,239],{"class":214},[200,115459,2439],{"class":2438},[200,115461,980],{"class":214},[200,115463,416],{"class":235},[200,115465,115466,115468],{"class":202,"line":336},[200,115467,5095],{"class":214},[200,115469,416],{"class":235},[200,115471,115472,115474],{"class":202,"line":342},[200,115473,413],{"class":214},[200,115475,416],{"class":210},[200,115477,115478,115480,115482],{"class":202,"line":365},[200,115479,680],{"class":214},[200,115481,14896],{"class":235},[200,115483,647],{"class":214},[200,115485,115486],{"class":202,"line":371},[200,115487,528],{"emptyLinePlaceholder":20},[200,115489,115490,115492,115494],{"class":202,"line":398},[200,115491,599],{"class":214},[200,115493,21468],{"class":235},[200,115495,647],{"class":214},[200,115497,115498,115500],{"class":202,"line":405},[200,115499,656],{"class":214},[200,115501,58378],{"class":235},[200,115503,115504,115506,115508,115510,115512],{"class":202,"line":410},[200,115505,58383],{"class":379},[200,115507,449],{"class":214},[200,115509,610],{"class":214},[200,115511,54406],{"class":245},[200,115513,616],{"class":214},[200,115515,115516,115518,115520,115522,115524],{"class":202,"line":860},[200,115517,11812],{"class":379},[200,115519,449],{"class":214},[200,115521,610],{"class":214},[200,115523,3597],{"class":245},[200,115525,616],{"class":214},[200,115527,115528,115530,115532,115534,115536,115538,115540,115542,115544,115546,115548,115550,115552,115554],{"class":202,"line":889},[200,115529,23865],{"class":214},[200,115531,58410],{"class":379},[200,115533,449],{"class":214},[200,115535,58415],{"class":214},[200,115537,26875],{"class":210},[200,115539,215],{"class":214},[200,115541,3597],{"class":210},[200,115543,6536],{"class":214},[200,115545,51183],{"class":210},[200,115547,14985],{"class":214},[200,115549,51208],{"class":2060},[200,115551,413],{"class":214},[200,115553,58434],{"class":245},[200,115555,58437],{"class":214},[200,115557,115558],{"class":202,"line":899},[200,115559,11550],{"class":214},[200,115561,115562,115564,115566,115568,115570,115572,115574,115576],{"class":202,"line":904},[200,115563,803],{"class":214},[200,115565,11400],{"class":235},[200,115567,3239],{"class":379},[200,115569,449],{"class":214},[200,115571,610],{"class":214},[200,115573,58160],{"class":245},[200,115575,610],{"class":214},[200,115577,21527],{"class":214},[200,115579,115580,115582,115584],{"class":202,"line":930},[200,115581,892],{"class":214},[200,115583,48537],{"class":235},[200,115585,647],{"class":214},[200,115587,115588,115590,115592],{"class":202,"line":3655},[200,115589,680],{"class":214},[200,115591,21468],{"class":235},[200,115593,647],{"class":214},[35,115595,58478],{},[35,115597,115598],{},[1707,115599],{"alt":58483,"src":58484},[1929,115601,24146],{"id":58487},[35,115603,58490],{},[51,115605,58494],{"id":58493},[35,115607,115608],{},[1707,115609],{"alt":58499,"src":58500},[35,115611,115612],{},[1216,115613,115614,47101,115617,47105,115620,45894,115623,215],{},[39,115615,38397],{"href":45853,"rel":115616},[77],[39,115618,45889],{"href":18579,"rel":115619},[77],[39,115621,19427],{"href":45844,"rel":115622},[77],[39,115624,11636],{"href":45853,"rel":115625},[77],[35,115627,58519],{},[35,115629,58522],{},[35,115631,58525],{},[1929,115633,58529],{"id":58528},[35,115635,58532,115636],{},[1216,115637,58535],{},[35,115639,115640],{},[1707,115641],{"alt":58540,"src":58541},[35,115643,58544,115644,58547,115646,215],{},[156,115645,51600],{},[156,115647,58550],{},[192,115649,115650],{"className":1380,"code":58553,"language":1383,"meta":13,"style":13},[156,115651,115652,115676,115714,115740,115746,115750,115774,115812,115844,115850,115854,115878,115920,115962],{"__ignoreMap":13},[200,115653,115654,115656,115658,115660,115662,115664,115666,115668,115670,115672,115674],{"class":202,"line":203},[200,115655,1390],{"class":206},[200,115657,14305],{"class":379},[200,115659,52772],{"class":210},[200,115661,449],{"class":214},[200,115663,50817],{"class":223},[200,115665,227],{"class":210},[200,115667,249],{"class":214},[200,115669,51600],{"class":245},[200,115671,249],{"class":214},[200,115673,549],{"class":214},[200,115675,280],{"class":214},[200,115677,115678,115680,115682,115684,115686,115688,115690,115692,115694,115696,115698,115700,115702,115704,115706,115708,115710,115712],{"class":202,"line":14},[200,115679,49638],{"class":235},[200,115681,239],{"class":214},[200,115683,50838],{"class":223},[200,115685,227],{"class":210},[200,115687,249],{"class":214},[200,115689,3295],{"class":245},[200,115691,249],{"class":214},[200,115693,2104],{"class":210},[200,115695,215],{"class":214},[200,115697,50853],{"class":223},[200,115699,227],{"class":210},[200,115701,2767],{"class":214},[200,115703,50860],{"class":235},[200,115705,239],{"class":214},[200,115707,2439],{"class":2438},[200,115709,980],{"class":214},[200,115711,2104],{"class":210},[200,115713,252],{"class":214},[200,115715,115716,115718,115720,115722,115724,115726,115728,115730,115732,115734,115736,115738],{"class":202,"line":255},[200,115717,23703],{"class":235},[200,115719,239],{"class":214},[200,115721,50879],{"class":223},[200,115723,227],{"class":210},[200,115725,249],{"class":214},[200,115727,3581],{"class":245},[200,115729,249],{"class":214},[200,115731,2104],{"class":210},[200,115733,215],{"class":214},[200,115735,50894],{"class":223},[200,115737,1549],{"class":210},[200,115739,252],{"class":214},[200,115741,115742,115744],{"class":202,"line":272},[200,115743,413],{"class":214},[200,115745,416],{"class":210},[200,115747,115748],{"class":202,"line":283},[200,115749,528],{"emptyLinePlaceholder":20},[200,115751,115752,115754,115756,115758,115760,115762,115764,115766,115768,115770,115772],{"class":202,"line":300},[200,115753,1390],{"class":206},[200,115755,14305],{"class":379},[200,115757,50674],{"class":210},[200,115759,449],{"class":214},[200,115761,50817],{"class":223},[200,115763,227],{"class":210},[200,115765,249],{"class":214},[200,115767,2476],{"class":245},[200,115769,249],{"class":214},[200,115771,549],{"class":214},[200,115773,280],{"class":214},[200,115775,115776,115778,115780,115782,115784,115786,115788,115790,115792,115794,115796,115798,115800,115802,115804,115806,115808,115810],{"class":202,"line":310},[200,115777,49638],{"class":235},[200,115779,239],{"class":214},[200,115781,50838],{"class":223},[200,115783,227],{"class":210},[200,115785,249],{"class":214},[200,115787,3295],{"class":245},[200,115789,249],{"class":214},[200,115791,2104],{"class":210},[200,115793,215],{"class":214},[200,115795,50853],{"class":223},[200,115797,227],{"class":210},[200,115799,2767],{"class":214},[200,115801,50860],{"class":235},[200,115803,239],{"class":214},[200,115805,2439],{"class":2438},[200,115807,980],{"class":214},[200,115809,2104],{"class":210},[200,115811,252],{"class":214},[200,115813,115814,115816,115818,115820,115822,115824,115826,115828,115830,115832,115834,115836,115838,115840,115842],{"class":202,"line":336},[200,115815,236],{"class":235},[200,115817,239],{"class":214},[200,115819,50879],{"class":223},[200,115821,227],{"class":210},[200,115823,249],{"class":214},[200,115825,20112],{"class":245},[200,115827,249],{"class":214},[200,115829,2104],{"class":210},[200,115831,215],{"class":214},[200,115833,50894],{"class":223},[200,115835,1549],{"class":210},[200,115837,215],{"class":214},[200,115839,50901],{"class":223},[200,115841,1549],{"class":210},[200,115843,252],{"class":214},[200,115845,115846,115848],{"class":202,"line":342},[200,115847,413],{"class":214},[200,115849,416],{"class":210},[200,115851,115852],{"class":202,"line":365},[200,115853,528],{"emptyLinePlaceholder":20},[200,115855,115856,115858,115860,115862,115864,115866,115868,115870,115872,115874,115876],{"class":202,"line":371},[200,115857,1390],{"class":206},[200,115859,14305],{"class":379},[200,115861,58766],{"class":210},[200,115863,449],{"class":214},[200,115865,50817],{"class":223},[200,115867,227],{"class":210},[200,115869,249],{"class":214},[200,115871,58777],{"class":245},[200,115873,249],{"class":214},[200,115875,549],{"class":214},[200,115877,280],{"class":214},[200,115879,115880,115882,115884,115886,115888,115890,115892,115894,115896,115898,115900,115902,115904,115906,115908,115910,115912,115914,115916,115918],{"class":202,"line":398},[200,115881,58788],{"class":235},[200,115883,239],{"class":214},[200,115885,50838],{"class":223},[200,115887,227],{"class":210},[200,115889,249],{"class":214},[200,115891,58799],{"class":245},[200,115893,249],{"class":214},[200,115895,2104],{"class":210},[200,115897,215],{"class":214},[200,115899,50894],{"class":223},[200,115901,1549],{"class":210},[200,115903,215],{"class":214},[200,115905,53171],{"class":223},[200,115907,227],{"class":210},[200,115909,1549],{"class":214},[200,115911,393],{"class":379},[200,115913,51242],{"class":210},[200,115915,215],{"class":214},[200,115917,53184],{"class":210},[200,115919,252],{"class":214},[200,115921,115922,115924,115926,115928,115930,115932,115934,115936,115938,115940,115942,115944,115946,115948,115950,115952,115954,115956,115958,115960],{"class":202,"line":405},[200,115923,58832],{"class":235},[200,115925,239],{"class":214},[200,115927,50838],{"class":223},[200,115929,227],{"class":210},[200,115931,249],{"class":214},[200,115933,58843],{"class":245},[200,115935,249],{"class":214},[200,115937,2104],{"class":210},[200,115939,215],{"class":214},[200,115941,50894],{"class":223},[200,115943,1549],{"class":210},[200,115945,215],{"class":214},[200,115947,53171],{"class":223},[200,115949,227],{"class":210},[200,115951,1549],{"class":214},[200,115953,393],{"class":379},[200,115955,53926],{"class":210},[200,115957,215],{"class":214},[200,115959,53184],{"class":210},[200,115961,252],{"class":214},[200,115963,115964,115966],{"class":202,"line":410},[200,115965,413],{"class":214},[200,115967,416],{"class":210},[1929,115969,58881],{"id":58880},[35,115971,58884],{},[38437,115973,58888],{"id":58887},[35,115975,58891,115976,58895],{},[156,115977,58894],{},[192,115979,115980],{"className":21457,"code":58898,"language":12075,"meta":13,"style":13},[156,115981,115982,116002,116034,116044,116058,116064,116072,116076,116084,116090,116104,116108,116120,116132,116136],{"__ignoreMap":13},[200,115983,115984,115986,115988,115990,115992,115994,115996,115998,116000],{"class":202,"line":203},[200,115985,599],{"class":214},[200,115987,14896],{"class":235},[200,115989,14594],{"class":379},[200,115991,449],{"class":214},[200,115993,610],{"class":214},[200,115995,1383],{"class":245},[200,115997,610],{"class":214},[200,115999,21825],{"class":379},[200,116001,647],{"class":214},[200,116003,116004,116006,116008,116010,116012,116014,116016,116018,116020,116022,116024,116026,116028,116030,116032],{"class":202,"line":14},[200,116005,443],{"class":379},[200,116007,318],{"class":214},[200,116009,5056],{"class":235},[200,116011,239],{"class":214},[200,116013,50674],{"class":210},[200,116015,413],{"class":214},[200,116017,509],{"class":214},[200,116019,452],{"class":206},[200,116021,47935],{"class":223},[200,116023,227],{"class":210},[200,116025,249],{"class":214},[200,116027,58947],{"class":245},[200,116029,249],{"class":214},[200,116031,549],{"class":214},[200,116033,280],{"class":214},[200,116035,116036,116038,116040,116042],{"class":202,"line":255},[200,116037,47953],{"class":235},[200,116039,239],{"class":214},[200,116041,16169],{"class":2438},[200,116043,252],{"class":214},[200,116045,116046,116048,116050,116052,116054,116056],{"class":202,"line":272},[200,116047,50461],{"class":223},[200,116049,239],{"class":214},[200,116051,3729],{"class":214},[200,116053,393],{"class":379},[200,116055,27681],{"class":210},[200,116057,252],{"class":214},[200,116059,116060,116062],{"class":202,"line":283},[200,116061,413],{"class":214},[200,116063,416],{"class":210},[200,116065,116066,116068,116070],{"class":202,"line":300},[200,116067,680],{"class":214},[200,116069,14896],{"class":235},[200,116071,647],{"class":214},[200,116073,116074],{"class":202,"line":310},[200,116075,528],{"emptyLinePlaceholder":20},[200,116077,116078,116080,116082],{"class":202,"line":336},[200,116079,599],{"class":214},[200,116081,21468],{"class":235},[200,116083,647],{"class":214},[200,116085,116086,116088],{"class":202,"line":342},[200,116087,656],{"class":214},[200,116089,59010],{"class":235},[200,116091,116092,116094,116096,116098,116100,116102],{"class":202,"line":365},[200,116093,23865],{"class":214},[200,116095,29900],{"class":379},[200,116097,449],{"class":214},[200,116099,610],{"class":214},[200,116101,2476],{"class":210},[200,116103,616],{"class":214},[200,116105,116106],{"class":202,"line":371},[200,116107,59029],{"class":379},[200,116109,116110,116112,116114,116116,116118],{"class":202,"line":398},[200,116111,59034],{"class":379},[200,116113,449],{"class":214},[200,116115,610],{"class":214},[200,116117,3295],{"class":245},[200,116119,616],{"class":214},[200,116121,116122,116124,116126,116128,116130],{"class":202,"line":405},[200,116123,59047],{"class":379},[200,116125,449],{"class":214},[200,116127,610],{"class":214},[200,116129,20112],{"class":245},[200,116131,616],{"class":214},[200,116133,116134],{"class":202,"line":410},[200,116135,23365],{"class":214},[200,116137,116138,116140,116142],{"class":202,"line":860},[200,116139,680],{"class":214},[200,116141,21468],{"class":235},[200,116143,647],{"class":214},[38437,116145,59071],{"id":59070},[35,116147,59074,116148,59078],{},[156,116149,59077],{},[192,116151,116152],{"className":1380,"code":59081,"language":1383,"meta":13,"style":13},[156,116153,116154,116184,116188,116204,116232,116280],{"__ignoreMap":13},[200,116155,116156,116158,116160,116162,116164,116166,116168,116170,116172,116174,116176,116178,116180,116182],{"class":202,"line":203},[200,116157,972],{"class":206},[200,116159,318],{"class":214},[200,116161,59092],{"class":210},[200,116163,549],{"class":214},[200,116165,20155],{"class":210},[200,116167,549],{"class":214},[200,116169,42642],{"class":210},[200,116171,549],{"class":214},[200,116173,22752],{"class":210},[200,116175,980],{"class":214},[200,116177,983],{"class":206},[200,116179,242],{"class":214},[200,116181,19813],{"class":245},[200,116183,991],{"class":214},[200,116185,116186],{"class":202,"line":14},[200,116187,528],{"emptyLinePlaceholder":20},[200,116189,116190,116192,116194,116196,116198,116200,116202],{"class":202,"line":255},[200,116191,1390],{"class":206},[200,116193,14305],{"class":379},[200,116195,59127],{"class":210},[200,116197,449],{"class":214},[200,116199,42642],{"class":223},[200,116201,227],{"class":210},[200,116203,230],{"class":214},[200,116205,116206,116208,116210,116212,116214,116216,116218,116220,116222,116224,116226,116228,116230],{"class":202,"line":272},[200,116207,23703],{"class":235},[200,116209,239],{"class":214},[200,116211,22752],{"class":223},[200,116213,227],{"class":210},[200,116215,2767],{"class":214},[200,116217,39109],{"class":235},[200,116219,239],{"class":214},[200,116221,242],{"class":214},[200,116223,48309],{"class":245},[200,116225,249],{"class":214},[200,116227,980],{"class":214},[200,116229,2104],{"class":210},[200,116231,252],{"class":214},[200,116233,116234,116236,116238,116240,116242,116244,116246,116248,116250,116252,116254,116256,116258,116260,116262,116264,116266,116268,116270,116272,116274,116276,116278],{"class":202,"line":283},[200,116235,59168],{"class":235},[200,116237,239],{"class":214},[200,116239,59092],{"class":223},[200,116241,227],{"class":210},[200,116243,59177],{"class":223},[200,116245,59180],{"class":210},[200,116247,215],{"class":214},[200,116249,4138],{"class":223},[200,116251,227],{"class":210},[200,116253,59189],{"class":2060},[200,116255,549],{"class":214},[200,116257,318],{"class":214},[200,116259,39109],{"class":235},[200,116261,239],{"class":214},[200,116263,242],{"class":214},[200,116265,59202],{"class":245},[200,116267,249],{"class":214},[200,116269,980],{"class":214},[200,116271,2104],{"class":210},[200,116273,215],{"class":214},[200,116275,53587],{"class":223},[200,116277,1549],{"class":210},[200,116279,252],{"class":214},[200,116281,116282,116284],{"class":202,"line":300},[200,116283,413],{"class":214},[200,116285,416],{"class":210},[35,116287,59225],{},[38437,116289,59229],{"id":59228},[35,116291,59232],{},[192,116293,116294],{"className":1380,"code":59235,"language":1383,"meta":13,"style":13},[156,116295,116296,116314,116318,116340,116362,116366,116392,116396,116436,116450,116464,116482,116488,116492,116506,116582,116586,116602],{"__ignoreMap":13},[200,116297,116298,116300,116302,116304,116306,116308,116310,116312],{"class":202,"line":203},[200,116299,972],{"class":206},[200,116301,318],{"class":214},[200,116303,59246],{"class":210},[200,116305,980],{"class":214},[200,116307,983],{"class":206},[200,116309,242],{"class":214},[200,116311,42350],{"class":245},[200,116313,991],{"class":214},[200,116315,116316],{"class":202,"line":14},[200,116317,528],{"emptyLinePlaceholder":20},[200,116319,116320,116322,116324,116326,116328,116330,116332,116334,116336,116338],{"class":202,"line":255},[200,116321,1390],{"class":206},[200,116323,1393],{"class":206},[200,116325,38723],{"class":223},[200,116327,227],{"class":210},[200,116329,3726],{"class":379},[200,116331,1094],{"class":214},[200,116333,25426],{"class":386},[200,116335,2104],{"class":214},[200,116337,393],{"class":379},[200,116339,280],{"class":214},[200,116341,116342,116344,116346,116348,116350,116352,116354,116356,116358,116360],{"class":202,"line":272},[200,116343,3738],{"class":379},[200,116345,318],{"class":214},[200,116347,34533],{"class":210},[200,116349,980],{"class":214},[200,116351,509],{"class":214},[200,116353,452],{"class":206},[200,116355,38997],{"class":223},[200,116357,227],{"class":235},[200,116359,25426],{"class":210},[200,116361,416],{"class":235},[200,116363,116364],{"class":202,"line":283},[200,116365,528],{"emptyLinePlaceholder":20},[200,116367,116368,116370,116372,116374,116376,116378,116380,116382,116384,116386,116388,116390],{"class":202,"line":300},[200,116369,3738],{"class":379},[200,116371,40809],{"class":210},[200,116373,509],{"class":214},[200,116375,452],{"class":206},[200,116377,40951],{"class":223},[200,116379,227],{"class":235},[200,116381,25426],{"class":210},[200,116383,549],{"class":214},[200,116385,59246],{"class":210},[200,116387,215],{"class":214},[200,116389,40994],{"class":210},[200,116391,416],{"class":235},[200,116393,116394],{"class":202,"line":310},[200,116395,528],{"emptyLinePlaceholder":20},[200,116397,116398,116400,116402,116404,116406,116408,116410,116412,116414,116416,116418,116420,116422,116424,116426,116428,116430,116432,116434],{"class":202,"line":336},[200,116399,3738],{"class":379},[200,116401,350],{"class":214},[200,116403,21468],{"class":210},[200,116405,360],{"class":214},[200,116407,509],{"class":214},[200,116409,452],{"class":206},[200,116411,47338],{"class":223},[200,116413,1549],{"class":235},[200,116415,215],{"class":214},[200,116417,47525],{"class":223},[200,116419,227],{"class":235},[200,116421,47418],{"class":210},[200,116423,215],{"class":214},[200,116425,51600],{"class":210},[200,116427,2104],{"class":235},[200,116429,215],{"class":214},[200,116431,47540],{"class":223},[200,116433,227],{"class":235},[200,116435,230],{"class":214},[200,116437,116438,116440,116442,116444,116446,116448],{"class":202,"line":342},[200,116439,21482],{"class":235},[200,116441,239],{"class":214},[200,116443,40809],{"class":210},[200,116445,215],{"class":214},[200,116447,3581],{"class":210},[200,116449,252],{"class":214},[200,116451,116452,116454,116456,116458,116460,116462],{"class":202,"line":365},[200,116453,5095],{"class":214},[200,116455,2104],{"class":235},[200,116457,215],{"class":214},[200,116459,52524],{"class":223},[200,116461,227],{"class":235},[200,116463,230],{"class":214},[200,116465,116466,116468,116470,116472,116474,116476,116478,116480],{"class":202,"line":371},[200,116467,15861],{"class":235},[200,116469,239],{"class":214},[200,116471,47357],{"class":210},[200,116473,215],{"class":214},[200,116475,51600],{"class":210},[200,116477,215],{"class":214},[200,116479,3295],{"class":210},[200,116481,252],{"class":214},[200,116483,116484,116486],{"class":202,"line":398},[200,116485,5095],{"class":214},[200,116487,416],{"class":235},[200,116489,116490],{"class":202,"line":405},[200,116491,528],{"emptyLinePlaceholder":20},[200,116493,116494,116496,116498,116500,116502,116504],{"class":202,"line":410},[200,116495,20098],{"class":206},[200,116497,1094],{"class":235},[200,116499,23914],{"class":210},[200,116501,215],{"class":214},[200,116503,59077],{"class":210},[200,116505,416],{"class":235},[200,116507,116508,116510,116512,116514,116516,116518,116520,116522,116524,116526,116528,116530,116532,116534,116536,116538,116540,116542,116544,116546,116548,116550,116552,116554,116556,116558,116560,116562,116564,116566,116568,116570,116572,116574,116576,116578,116580],{"class":202,"line":860},[200,116509,19231],{"class":206},[200,116511,47338],{"class":223},[200,116513,1549],{"class":235},[200,116515,215],{"class":214},[200,116517,47525],{"class":223},[200,116519,227],{"class":235},[200,116521,47418],{"class":210},[200,116523,215],{"class":214},[200,116525,58777],{"class":210},[200,116527,2104],{"class":235},[200,116529,215],{"class":214},[200,116531,47540],{"class":223},[200,116533,227],{"class":235},[200,116535,23914],{"class":210},[200,116537,215],{"class":214},[200,116539,59077],{"class":210},[200,116541,215],{"class":214},[200,116543,3541],{"class":223},[200,116545,227],{"class":235},[200,116547,3295],{"class":386},[200,116549,393],{"class":379},[200,116551,1094],{"class":235},[200,116553,2767],{"class":214},[200,116555,59499],{"class":235},[200,116557,239],{"class":214},[200,116559,3239],{"class":210},[200,116561,549],{"class":214},[200,116563,59508],{"class":235},[200,116565,239],{"class":214},[200,116567,56808],{"class":210},[200,116569,215],{"class":214},[200,116571,3295],{"class":210},[200,116573,980],{"class":214},[200,116575,59521],{"class":235},[200,116577,215],{"class":214},[200,116579,47590],{"class":223},[200,116581,466],{"class":235},[200,116583,116584],{"class":202,"line":889},[200,116585,528],{"emptyLinePlaceholder":20},[200,116587,116588,116590,116592,116594,116596,116598,116600],{"class":202,"line":899},[200,116589,3758],{"class":206},[200,116591,40842],{"class":223},[200,116593,227],{"class":235},[200,116595,25426],{"class":210},[200,116597,549],{"class":214},[200,116599,40851],{"class":2060},[200,116601,416],{"class":235},[200,116603,116604,116606],{"class":202,"line":904},[200,116605,413],{"class":214},[200,116607,416],{"class":210},[35,116609,116610],{},[1216,116611,59558],{},[1929,116613,59562],{"id":59561},[35,116615,116616],{},[1216,116617,59567],{},[35,116619,59570,116620,59573,116622,53252,116624,59578,116626,215],{},[156,116621,51600],{},[156,116623,47155],{},[156,116625,47161],{},[39,116627,59582],{"href":59581},[35,116629,59585,116630,59588,116632,59591,116634,59594],{},[156,116631,2476],{},[156,116633,58777],{},[156,116635,2476],{},[35,116637,59597],{},[192,116639,116640],{"className":28358,"code":59600,"language":28360,"meta":13,"style":13},[156,116641,116642,116646,116660,116678,116690,116722,116754,116758],{"__ignoreMap":13},[200,116643,116644],{"class":202,"line":203},[200,116645,230],{"class":214},[200,116647,116648,116650,116652,116654,116656,116658],{"class":202,"line":14},[200,116649,28489],{"class":214},[200,116651,3295],{"class":379},[200,116653,610],{"class":214},[200,116655,239],{"class":214},[200,116657,3953],{"class":2060},[200,116659,252],{"class":214},[200,116661,116662,116664,116666,116668,116670,116672,116674,116676],{"class":202,"line":255},[200,116663,28489],{"class":214},[200,116665,3581],{"class":379},[200,116667,610],{"class":214},[200,116669,239],{"class":214},[200,116671,28499],{"class":214},[200,116673,59635],{"class":245},[200,116675,610],{"class":214},[200,116677,252],{"class":214},[200,116679,116680,116682,116684,116686,116688],{"class":202,"line":272},[200,116681,28489],{"class":214},[200,116683,2476],{"class":379},[200,116685,610],{"class":214},[200,116687,239],{"class":214},[200,116689,1410],{"class":214},[200,116691,116692,116694,116696,116698,116700,116702,116704,116706,116708,116710,116712,116714,116716,116718,116720],{"class":202,"line":283},[200,116693,59656],{"class":214},[200,116695,28499],{"class":214},[200,116697,3295],{"class":1364},[200,116699,610],{"class":214},[200,116701,239],{"class":214},[200,116703,3953],{"class":2060},[200,116705,549],{"class":214},[200,116707,28499],{"class":214},[200,116709,20112],{"class":1364},[200,116711,610],{"class":214},[200,116713,239],{"class":214},[200,116715,28499],{"class":214},[200,116717,59681],{"class":245},[200,116719,610],{"class":214},[200,116721,333],{"class":214},[200,116723,116724,116726,116728,116730,116732,116734,116736,116738,116740,116742,116744,116746,116748,116750,116752],{"class":202,"line":300},[200,116725,59656],{"class":214},[200,116727,28499],{"class":214},[200,116729,3295],{"class":1364},[200,116731,610],{"class":214},[200,116733,239],{"class":214},[200,116735,59700],{"class":2060},[200,116737,549],{"class":214},[200,116739,28499],{"class":214},[200,116741,20112],{"class":1364},[200,116743,610],{"class":214},[200,116745,239],{"class":214},[200,116747,28499],{"class":214},[200,116749,59715],{"class":245},[200,116751,610],{"class":214},[200,116753,34638],{"class":214},[200,116755,116756],{"class":202,"line":310},[200,116757,13683],{"class":214},[200,116759,116760],{"class":202,"line":336},[200,116761,1129],{"class":214},[35,116763,59730,116764,59734],{},[156,116765,59733],{},[35,116767,59737,116768,59745],{},[39,116769,116771],{"href":59740,"rel":116770},[77],[156,116772,59744],{},[35,116774,59748,116775,59751,116777,59754],{},[156,116776,58777],{},[156,116778,58777],{},[192,116780,116781],{"className":1380,"code":59757,"language":1383,"meta":13,"style":13},[156,116782,116783,116787,116819,116823,116835,116841,116845,116849,116877,116891,116905,116919,116927,116941,116955,116969,116977,116983,116987,116991,117023,117027,117039],{"__ignoreMap":13},[200,116784,116785],{"class":202,"line":203},[200,116786,59764],{"class":401},[200,116788,116789,116791,116793,116795,116797,116799,116801,116803,116805,116807,116809,116811,116813,116815,116817],{"class":202,"line":14},[200,116790,1390],{"class":206},[200,116792,14305],{"class":379},[200,116794,59773],{"class":210},[200,116796,449],{"class":214},[200,116798,53297],{"class":223},[200,116800,59780],{"class":210},[200,116802,549],{"class":214},[200,116804,383],{"class":214},[200,116806,59787],{"class":386},[200,116808,549],{"class":214},[200,116810,53307],{"class":386},[200,116812,390],{"class":214},[200,116814,393],{"class":379},[200,116816,1094],{"class":210},[200,116818,230],{"class":214},[200,116820,116821],{"class":202,"line":255},[200,116822,59804],{"class":401},[200,116824,116825,116827,116829,116831,116833],{"class":202,"line":272},[200,116826,1405],{"class":235},[200,116828,239],{"class":214},[200,116830,53307],{"class":223},[200,116832,59815],{"class":210},[200,116834,252],{"class":214},[200,116836,116837,116839],{"class":202,"line":283},[200,116838,413],{"class":214},[200,116840,584],{"class":210},[200,116842,116843],{"class":202,"line":300},[200,116844,528],{"emptyLinePlaceholder":20},[200,116846,116847],{"class":202,"line":310},[200,116848,59832],{"class":401},[200,116850,116851,116853,116855,116857,116859,116861,116863,116865,116867,116869,116871,116873,116875],{"class":202,"line":336},[200,116852,1390],{"class":206},[200,116854,14305],{"class":379},[200,116856,59841],{"class":210},[200,116858,449],{"class":214},[200,116860,53297],{"class":223},[200,116862,59848],{"class":210},[200,116864,549],{"class":214},[200,116866,383],{"class":214},[200,116868,59787],{"class":386},[200,116870,390],{"class":214},[200,116872,393],{"class":379},[200,116874,1094],{"class":210},[200,116876,230],{"class":214},[200,116878,116879,116881,116883,116885,116887,116889],{"class":202,"line":342},[200,116880,59867],{"class":235},[200,116882,239],{"class":214},[200,116884,59787],{"class":223},[200,116886,51178],{"class":210},[200,116888,549],{"class":214},[200,116890,280],{"class":214},[200,116892,116893,116895,116897,116899,116901,116903],{"class":202,"line":365},[200,116894,59882],{"class":235},[200,116896,239],{"class":214},[200,116898,59887],{"class":210},[200,116900,215],{"class":214},[200,116902,59892],{"class":210},[200,116904,252],{"class":214},[200,116906,116907,116909,116911,116913,116915,116917],{"class":202,"line":371},[200,116908,59899],{"class":235},[200,116910,239],{"class":214},[200,116912,59904],{"class":210},[200,116914,215],{"class":214},[200,116916,59909],{"class":210},[200,116918,252],{"class":214},[200,116920,116921,116923,116925],{"class":202,"line":398},[200,116922,5095],{"class":214},[200,116924,2104],{"class":210},[200,116926,252],{"class":214},[200,116928,116929,116931,116933,116935,116937,116939],{"class":202,"line":405},[200,116930,15700],{"class":235},[200,116932,239],{"class":214},[200,116934,59787],{"class":223},[200,116936,59780],{"class":210},[200,116938,549],{"class":214},[200,116940,280],{"class":214},[200,116942,116943,116945,116947,116949,116951,116953],{"class":202,"line":410},[200,116944,59882],{"class":235},[200,116946,239],{"class":214},[200,116948,59887],{"class":210},[200,116950,215],{"class":214},[200,116952,59946],{"class":210},[200,116954,252],{"class":214},[200,116956,116957,116959,116961,116963,116965,116967],{"class":202,"line":860},[200,116958,59899],{"class":235},[200,116960,239],{"class":214},[200,116962,59957],{"class":210},[200,116964,215],{"class":214},[200,116966,59909],{"class":210},[200,116968,252],{"class":214},[200,116970,116971,116973,116975],{"class":202,"line":889},[200,116972,5095],{"class":214},[200,116974,2104],{"class":210},[200,116976,252],{"class":214},[200,116978,116979,116981],{"class":202,"line":899},[200,116980,413],{"class":214},[200,116982,584],{"class":210},[200,116984,116985],{"class":202,"line":904},[200,116986,528],{"emptyLinePlaceholder":20},[200,116988,116989],{"class":202,"line":930},[200,116990,59986],{"class":401},[200,116992,116993,116995,116997,116999,117001,117003,117005,117007,117009,117011,117013,117015,117017,117019,117021],{"class":202,"line":3655},[200,116994,1390],{"class":206},[200,116996,14305],{"class":379},[200,116998,59995],{"class":210},[200,117000,449],{"class":214},[200,117002,53297],{"class":223},[200,117004,51178],{"class":210},[200,117006,549],{"class":214},[200,117008,383],{"class":214},[200,117010,59787],{"class":386},[200,117012,549],{"class":214},[200,117014,53307],{"class":386},[200,117016,390],{"class":214},[200,117018,393],{"class":379},[200,117020,1094],{"class":210},[200,117022,230],{"class":214},[200,117024,117025],{"class":202,"line":3660},[200,117026,60024],{"class":401},[200,117028,117029,117031,117033,117035,117037],{"class":202,"line":4035},[200,117030,53320],{"class":235},[200,117032,239],{"class":214},[200,117034,53307],{"class":223},[200,117036,59815],{"class":210},[200,117038,252],{"class":214},[200,117040,117041,117043],{"class":202,"line":4490},[200,117042,413],{"class":214},[200,117044,584],{"class":210},[35,117046,60045,117047,60051],{},[39,117048,60050],{"href":60048,"rel":117049},[77],[35,117051,60054],{},[192,117053,117054],{"className":1380,"code":60057,"language":1383,"meta":13,"style":13},[156,117055,117056,117074,117104,117112,117122,117132,117140,117148,117156,117160,117164,117168,117174,117178,117184],{"__ignoreMap":13},[200,117057,117058,117060,117062,117064,117066,117068,117070,117072],{"class":202,"line":203},[200,117059,1390],{"class":206},[200,117061,1393],{"class":206},[200,117063,38723],{"class":223},[200,117065,227],{"class":210},[200,117067,3726],{"class":379},[200,117069,3729],{"class":214},[200,117071,393],{"class":379},[200,117073,280],{"class":214},[200,117075,117076,117078,117080,117082,117084,117086,117088,117090,117092,117094,117096,117098,117100,117102],{"class":202,"line":14},[200,117077,3738],{"class":379},[200,117079,53926],{"class":210},[200,117081,509],{"class":214},[200,117083,452],{"class":206},[200,117085,47338],{"class":223},[200,117087,1549],{"class":235},[200,117089,215],{"class":214},[200,117091,3524],{"class":210},[200,117093,215],{"class":214},[200,117095,51600],{"class":210},[200,117097,215],{"class":214},[200,117099,40103],{"class":223},[200,117101,227],{"class":235},[200,117103,230],{"class":214},[200,117105,117106,117108,117110],{"class":202,"line":255},[200,117107,60112],{"class":235},[200,117109,239],{"class":214},[200,117111,280],{"class":214},[200,117113,117114,117116,117118,117120],{"class":202,"line":272},[200,117115,60121],{"class":235},[200,117117,239],{"class":214},[200,117119,2439],{"class":2438},[200,117121,252],{"class":214},[200,117123,117124,117126,117128,117130],{"class":202,"line":283},[200,117125,60132],{"class":235},[200,117127,239],{"class":214},[200,117129,2439],{"class":2438},[200,117131,252],{"class":214},[200,117133,117134,117136,117138],{"class":202,"line":300},[200,117135,60143],{"class":235},[200,117137,239],{"class":214},[200,117139,280],{"class":214},[200,117141,117142,117144,117146],{"class":202,"line":310},[200,117143,32485],{"class":235},[200,117145,239],{"class":214},[200,117147,280],{"class":214},[200,117149,117150,117152,117154],{"class":202,"line":336},[200,117151,60160],{"class":235},[200,117153,239],{"class":214},[200,117155,22236],{"class":2438},[200,117157,117158],{"class":202,"line":342},[200,117159,4965],{"class":214},[200,117161,117162],{"class":202,"line":365},[200,117163,12098],{"class":214},[200,117165,117166],{"class":202,"line":371},[200,117167,339],{"class":214},[200,117169,117170,117172],{"class":202,"line":398},[200,117171,5095],{"class":214},[200,117173,416],{"class":235},[200,117175,117176],{"class":202,"line":405},[200,117177,528],{"emptyLinePlaceholder":20},[200,117179,117180,117182],{"class":202,"line":410},[200,117181,3758],{"class":206},[200,117183,54059],{"class":210},[200,117185,117186,117188],{"class":202,"line":860},[200,117187,413],{"class":214},[200,117189,416],{"class":210},[35,117191,60201,117192,60205],{},[156,117193,60204],{},[1929,117195,24146],{"id":60208},[35,117197,60211],{},[35,117199,60214],{},[1170,117201,60217],{},{"title":13,"searchDepth":14,"depth":14,"links":117203},[117204,117205],{"id":45870,"depth":14,"text":45871},{"id":46152,"depth":14,"text":46153},{},{"title":45820,"description":60223},{"loc":60225,"images":117209},[117210,117211,117212,117213,117214,117215,117216,117217,117218,117219,117220,117221,117222,117223,117224,117225,117226,117227,117228,117229,117230,117231,117232,117233],{"loc":45877},{"loc":45909},{"loc":46159},{"loc":46205},{"loc":46282},{"loc":46298},{"loc":46314},{"loc":46361},{"loc":47093},{"loc":47135},{"loc":47286},{"loc":48694},{"loc":50364},{"loc":50498},{"loc":50507},{"loc":51537},{"loc":52727},{"loc":54099},{"loc":56282},{"loc":57334},{"loc":57387},{"loc":58484},{"loc":58500},{"loc":58541},{"id":60257,"title":60258,"body":117235,"contentId":20859,"date":60689,"description":60690,"extension":18,"meta":117526,"navigation":20,"path":60692,"rawbody":60693,"robots":23,"schemaOrg":23,"seo":117527,"sitemap":117528,"stem":60703,"__hash__":60704},{"type":7,"value":117236,"toc":117519},[117237,117243,117248,117252],[35,117238,60263,117239,60267,117241,215],{},[6862,117240,60266],{},[6862,117242,60270],{},[35,117244,60273,117245,60278],{},[39,117246,60277],{"href":7155,"rel":117247},[77],[35,117249,117250],{},[1707,117251],{"alt":60283,"src":60284},[7151,117253,117254,117256,117262,117267,117273,117275,117385,117394,117396,117400,117405,117409,117413,117415,117417,117419,117421,117425,117429,117433,117437,117441,117445,117449,117453,117455,117459,117461,117463,117477,117479,117481,117483,117485,117487,117489],{"w-full":13,"icon":7153,"text":60287,"to":60288,"target":7156},[51,117255,20877],{"id":20876},[938,117257,117258],{},[35,117259,117260,60297],{},[200,117261,944],{},[35,117263,60300,117264,60306],{},[39,117265,60305],{"href":60303,"rel":117266},[77],[35,117268,60309,117269,1655,117271,60316],{},[6862,117270,60312],{},[6862,117272,60315],{},[35,117274,60319],{},[1593,117276,117277,117309,117345],{},[71,117278,60324,117279],{},[1593,117280,117281,117285,117289,117293,117297,117301,117305],{},[71,117282,117283,60332],{},[6862,117284,60331],{},[71,117286,117287,60338],{},[6862,117288,60337],{},[71,117290,117291,60344],{},[6862,117292,60343],{},[71,117294,117295,60350],{},[6862,117296,60349],{},[71,117298,117299,60356],{},[6862,117300,60355],{},[71,117302,117303,60362],{},[6862,117304,60361],{},[71,117306,117307,60368],{},[6862,117308,60367],{},[71,117310,60371,117311],{},[1593,117312,117313,117317,117321,117325,117329,117333,117337,117341],{},[71,117314,117315,60379],{},[6862,117316,60378],{},[71,117318,117319,60385],{},[6862,117320,60384],{},[71,117322,117323,60391],{},[6862,117324,60390],{},[71,117326,117327,60397],{},[6862,117328,60396],{},[71,117330,117331,60403],{},[6862,117332,60402],{},[71,117334,117335,60409],{},[6862,117336,60408],{},[71,117338,117339,60415],{},[6862,117340,60414],{},[71,117342,117343,60421],{},[6862,117344,60420],{},[71,117346,60424,117347],{},[1593,117348,117349,117353,117357,117361,117365,117369,117373,117377,117381],{},[71,117350,117351,60432],{},[6862,117352,60431],{},[71,117354,117355,60438],{},[6862,117356,60437],{},[71,117358,117359,60444],{},[6862,117360,60443],{},[71,117362,117363,60450],{},[6862,117364,60449],{},[71,117366,117367,60456],{},[6862,117368,60455],{},[71,117370,117371,60462],{},[6862,117372,60461],{},[71,117374,117375,60468],{},[6862,117376,60467],{},[71,117378,117379,60474],{},[6862,117380,60473],{},[71,117382,117383,60480],{},[6862,117384,60479],{},[938,117386,117387],{},[35,117388,117389,60487,117391,215],{},[200,117390,944],{},[39,117392,60492],{"href":60490,"rel":117393},[77],[35,117395,60495],{},[35,117397,60498,117398,60502],{},[6862,117399,60501],{},[35,117401,60505,117402,60509],{},[39,117403,21034],{"href":7155,"rel":117404},[77],[35,117406,60512,117407,60516],{},[6862,117408,60515],{},[35,117410,117411],{},[1707,117412],{"alt":60521,"src":60522},[1929,117414,60526],{"id":60525},[35,117416,60529],{},[1929,117418,60533],{"id":60532},[35,117420,60536],{},[35,117422,60539,117423,60543],{},[6862,117424,60542],{},[35,117426,117427],{},[1707,117428],{"alt":60548,"src":60549},[35,117430,60552,117431,60556],{},[6862,117432,60555],{},[35,117434,117435],{},[1707,117436],{"alt":60561,"src":60562},[35,117438,60565,117439,60568],{},[6862,117440,60312],{},[35,117442,60571,117443,60575],{},[6862,117444,60574],{},[35,117446,117447],{},[1707,117448],{"alt":60580,"src":60581},[35,117450,60584,117451,60588],{},[6862,117452,60587],{},[35,117454,60591],{},[35,117456,117457],{},[1707,117458],{"alt":60596,"src":60597},[51,117460,60601],{"id":60600},[35,117462,60604],{},[1593,117464,117465,117469,117473],{},[71,117466,117467,60612],{},[6862,117468,60611],{},[71,117470,117471,60618],{},[6862,117472,60617],{},[71,117474,117475,60624],{},[6862,117476,60623],{},[35,117478,60627],{},[35,117480,60630],{},[35,117482,60633],{},[35,117484,60636],{},[35,117486,60639],{},[35,117488,60642],{},[7918,117490,117491,117496],{"id":46184},[35,117492,60647,117493,215],{},[39,117494,21034],{"href":7155,"rel":117495},[77],[7151,117497,117498,117502,117504],{"w-full":13,"icon":7153,"text":60287,"to":60288,"target":7156},[35,117499,117500],{},[1216,117501,60657],{},[35,117503,60660],{},[60662,117505,117506,117508,117510,117515,117517],{},[51,117507,19349],{"id":19348},[35,117509,60668],{},[35,117511,60671,117512,60675],{},[39,117513,60277],{"href":7155,"rel":117514},[77],[35,117516,60678],{},[35,117518,60681],{},{"title":13,"searchDepth":14,"depth":14,"links":117520},[117521,117525],{"id":20876,"depth":14,"text":20877,"children":117522},[117523,117524],{"id":60525,"depth":255,"text":60526},{"id":60532,"depth":255,"text":60533},{"id":60600,"depth":14,"text":60601},{},{"title":60258,"description":60690},{"loc":60692,"images":117529},[117530,117531,117532,117533,117534,117535],{"loc":60284},{"loc":60522},{"loc":60549},{"loc":60562},{"loc":60581},{"loc":60597},{"id":60706,"title":60707,"body":117537,"contentId":46184,"date":60888,"description":60889,"extension":18,"meta":117656,"navigation":20,"path":60891,"rawbody":60892,"robots":23,"schemaOrg":23,"seo":117657,"sitemap":117658,"stem":60902,"__hash__":60903},{"type":7,"value":117538,"toc":117649},[117539,117541,117543,117547,117551,117553,117555,117557,117562,117567,117569,117571,117573,117578,117580,117585,117587,117589,117594,117598,117600,117602,117604,117608,117610,117614,117616,117620,117622,117626,117628,117630,117634,117636,117638,117640,117645],[35,117540,60712],{},[35,117542,60715],{},[35,117544,117545],{},[1707,117546],{"alt":60720,"src":60721},[35,117548,117549],{},[1216,117550,60726],{},[35,117552,60729],{},[51,117554,60733],{"id":60732},[35,117556,60736],{},[35,117558,60739,117559,60745],{},[39,117560,60744],{"href":60742,"rel":117561},[77],[35,117563,60748,117564,60754],{},[39,117565,60753],{"href":60751,"rel":117566},[77],[35,117568,60757],{},[51,117570,60761],{"id":60760},[35,117572,60764],{},[35,117574,60767,117575,60772],{},[39,117576,60771],{"href":34910,"rel":117577},[77],[35,117579,60775],{},[35,117581,60778,117582,60783],{},[39,117583,60782],{"href":7920,"rel":117584},[77],[51,117586,60787],{"id":60786},[35,117588,60790],{},[35,117590,60793,117591,60798],{},[39,117592,16112],{"href":60796,"rel":117593},[77],[35,117595,60801,117596,60805],{},[1216,117597,60804],{},[51,117599,60809],{"id":60808},[35,117601,60812],{},[35,117603,60815],{},[35,117605,117606],{},[1707,117607],{"alt":60820,"src":60821},[35,117609,60824],{},[35,117611,117612],{},[1707,117613],{"alt":60829,"src":60830},[35,117615,60833],{},[35,117617,117618],{},[1707,117619],{"alt":60838,"src":60839},[35,117621,60842],{},[35,117623,117624],{},[1707,117625],{"alt":60847,"src":60848},[35,117627,60851],{},[35,117629,60854],{},[35,117631,117632],{},[1707,117633],{"alt":60859,"src":60860},[51,117635,19349],{"id":19348},[35,117637,60865],{},[35,117639,60868],{},[35,117641,60871,117642,215],{},[39,117643,60875],{"href":36274,"rel":117644},[77],[35,117646,117647],{},[1216,117648,60880],{},{"title":13,"searchDepth":14,"depth":14,"links":117650},[117651,117652,117653,117654,117655],{"id":60732,"depth":14,"text":60733},{"id":60760,"depth":14,"text":60761},{"id":60786,"depth":14,"text":60787},{"id":60808,"depth":14,"text":60809},{"id":19348,"depth":14,"text":19349},{},{"title":60707,"description":60889},{"loc":60891,"images":117659},[117660,117661,117662,117663,117664,117665],{"loc":60721},{"loc":60821},{"loc":60830},{"loc":60839},{"loc":60848},{"loc":60860},{"id":60905,"title":60906,"body":117667,"contentId":63440,"date":63441,"description":63442,"extension":18,"meta":120007,"navigation":20,"path":63444,"rawbody":63445,"robots":23,"schemaOrg":23,"seo":120008,"sitemap":120009,"stem":63451,"__hash__":63452},{"type":7,"value":117668,"toc":119997},[117669,117673,117677,117679,117681,117683,117685,117689,117697,117701,118017,118023,118025,118035,118037,118041,118175,118177,118179,118181,118185,118467,118481,118483,118485,118487,118773,118779,118783,119007,119009,119011,119013,119017,119019,119021,119493,119499,119503,119509,119523,119529,119531,119535,119539,119541,119955,119961,119965,119969,119971,119975,119977,119979,119981,119991,119993,119995],[35,117670,60911,117671,60915],{},[6862,117672,60914],{},[35,117674,60918,117675,60923],{},[39,117676,60922],{"href":60921},[35,117678,60926],{},[51,117680,60930],{"id":60929},[35,117682,60933],{},[51,117684,60937],{"id":60936},[35,117686,60940,117687,60944],{},[156,117688,60943],{},[1593,117690,117691,117693,117695],{},[71,117692,60949],{},[71,117694,60952],{},[71,117696,60955],{},[35,117698,60958,117699,60961],{},[156,117700,28166],{},[192,117702,117703],{"className":25070,"code":60964,"language":25072,"meta":13,"style":13},[156,117704,117705,117723,117741,117759,117763,117777,117787,117791,117817,117821,117833,117859,117863,117893,117917,117921,117935,117945,117951,117955,117977,118011],{"__ignoreMap":13},[200,117706,117707,117709,117711,117713,117715,117717,117719,117721],{"class":202,"line":203},[200,117708,972],{"class":206},[200,117710,318],{"class":214},[200,117712,60975],{"class":210},[200,117714,980],{"class":214},[200,117716,983],{"class":206},[200,117718,242],{"class":214},[200,117720,60984],{"class":245},[200,117722,991],{"class":214},[200,117724,117725,117727,117729,117731,117733,117735,117737,117739],{"class":202,"line":14},[200,117726,972],{"class":206},[200,117728,318],{"class":214},[200,117730,60995],{"class":210},[200,117732,980],{"class":214},[200,117734,983],{"class":206},[200,117736,242],{"class":214},[200,117738,61004],{"class":245},[200,117740,991],{"class":214},[200,117742,117743,117745,117747,117749,117751,117753,117755,117757],{"class":202,"line":255},[200,117744,972],{"class":206},[200,117746,318],{"class":214},[200,117748,12472],{"class":210},[200,117750,980],{"class":214},[200,117752,983],{"class":206},[200,117754,242],{"class":214},[200,117756,12481],{"class":245},[200,117758,991],{"class":214},[200,117760,117761],{"class":202,"line":272},[200,117762,528],{"emptyLinePlaceholder":20},[200,117764,117765,117767,117769,117771,117773,117775],{"class":202,"line":283},[200,117766,443],{"class":379},[200,117768,61035],{"class":210},[200,117770,449],{"class":214},[200,117772,242],{"class":214},[200,117774,61042],{"class":245},[200,117776,991],{"class":214},[200,117778,117779,117781,117783,117785],{"class":202,"line":300},[200,117780,443],{"class":379},[200,117782,61051],{"class":210},[200,117784,449],{"class":214},[200,117786,61056],{"class":2060},[200,117788,117789],{"class":202,"line":310},[200,117790,528],{"emptyLinePlaceholder":20},[200,117792,117793,117795,117797,117799,117801,117803,117805,117807,117809,117811,117813,117815],{"class":202,"line":336},[200,117794,443],{"class":379},[200,117796,19846],{"class":210},[200,117798,449],{"class":214},[200,117800,60995],{"class":223},[200,117802,227],{"class":210},[200,117804,227],{"class":214},[200,117806,61077],{"class":386},[200,117808,549],{"class":214},[200,117810,10954],{"class":386},[200,117812,2104],{"class":214},[200,117814,393],{"class":379},[200,117816,280],{"class":214},[200,117818,117819],{"class":202,"line":342},[200,117820,61092],{"class":401},[200,117822,117823,117825,117827,117829,117831],{"class":202,"line":365},[200,117824,61097],{"class":210},[200,117826,215],{"class":214},[200,117828,39221],{"class":210},[200,117830,509],{"class":214},[200,117832,61106],{"class":2060},[200,117834,117835,117837,117839,117841,117843,117845,117847,117849,117851,117853,117855,117857],{"class":202,"line":371},[200,117836,61097],{"class":210},[200,117838,215],{"class":214},[200,117840,61115],{"class":223},[200,117842,227],{"class":235},[200,117844,249],{"class":214},[200,117846,61122],{"class":245},[200,117848,249],{"class":214},[200,117850,549],{"class":214},[200,117852,242],{"class":214},[200,117854,61131],{"class":245},[200,117856,249],{"class":214},[200,117858,416],{"class":235},[200,117860,117861],{"class":202,"line":398},[200,117862,528],{"emptyLinePlaceholder":20},[200,117864,117865,117867,117869,117871,117873,117875,117877,117879,117881,117883,117885,117887,117889,117891],{"class":202,"line":405},[200,117866,3738],{"class":379},[200,117868,61146],{"class":210},[200,117870,509],{"class":214},[200,117872,12472],{"class":223},[200,117874,227],{"class":235},[200,117876,15568],{"class":210},[200,117878,215],{"class":214},[200,117880,61159],{"class":223},[200,117882,1549],{"class":235},[200,117884,549],{"class":214},[200,117886,242],{"class":214},[200,117888,61168],{"class":245},[200,117890,249],{"class":214},[200,117892,416],{"class":235},[200,117894,117895,117897,117899,117901,117903,117905,117907,117909,117911,117913,117915],{"class":202,"line":410},[200,117896,3738],{"class":379},[200,117898,10290],{"class":210},[200,117900,509],{"class":214},[200,117902,60975],{"class":223},[200,117904,227],{"class":235},[200,117906,61187],{"class":210},[200,117908,549],{"class":214},[200,117910,242],{"class":214},[200,117912,14750],{"class":245},[200,117914,249],{"class":214},[200,117916,416],{"class":235},[200,117918,117919],{"class":202,"line":860},[200,117920,528],{"emptyLinePlaceholder":20},[200,117922,117923,117925,117927,117929,117931,117933],{"class":202,"line":889},[200,117924,61097],{"class":210},[200,117926,215],{"class":214},[200,117928,61210],{"class":223},[200,117930,227],{"class":235},[200,117932,592],{"class":210},[200,117934,416],{"class":235},[200,117936,117937,117939,117941,117943],{"class":202,"line":899},[200,117938,61097],{"class":210},[200,117940,215],{"class":214},[200,117942,28570],{"class":223},[200,117944,466],{"class":235},[200,117946,117947,117949],{"class":202,"line":904},[200,117948,413],{"class":214},[200,117950,416],{"class":210},[200,117952,117953],{"class":202,"line":930},[200,117954,528],{"emptyLinePlaceholder":20},[200,117956,117957,117959,117961,117963,117965,117967,117969,117971,117973,117975],{"class":202,"line":3655},[200,117958,3123],{"class":210},[200,117960,215],{"class":214},[200,117962,61245],{"class":223},[200,117964,61248],{"class":210},[200,117966,549],{"class":214},[200,117968,61253],{"class":210},[200,117970,549],{"class":214},[200,117972,3729],{"class":214},[200,117974,393],{"class":379},[200,117976,280],{"class":214},[200,117978,117979,117981,117983,117985,117987,117989,117991,117993,117995,117997,117999,118001,118003,118005,118007,118009],{"class":202,"line":3660},[200,117980,1112],{"class":210},[200,117982,215],{"class":214},[200,117984,1117],{"class":223},[200,117986,227],{"class":235},[200,117988,3333],{"class":214},[200,117990,61276],{"class":245},[200,117992,4868],{"class":214},[200,117994,61281],{"class":210},[200,117996,413],{"class":214},[200,117998,239],{"class":245},[200,118000,4868],{"class":214},[200,118002,61290],{"class":210},[200,118004,413],{"class":214},[200,118006,2898],{"class":245},[200,118008,3333],{"class":214},[200,118010,416],{"class":235},[200,118012,118013,118015],{"class":202,"line":4035},[200,118014,413],{"class":214},[200,118016,416],{"class":210},[35,118018,61307,118019,61310,118021,215],{},[156,118020,61168],{},[156,118022,61313],{},[35,118024,61316],{},[192,118026,118027],{"className":1354,"code":61319,"language":1357,"meta":13,"style":13},[156,118028,118029],{"__ignoreMap":13},[200,118030,118031,118033],{"class":202,"line":203},[200,118032,10040],{"class":1364},[200,118034,61328],{"class":245},[51,118036,61332],{"id":61331},[35,118038,60940,118039,61337],{},[156,118040,61168],{},[192,118042,118043],{"className":590,"code":61340,"language":592,"meta":13,"style":13},[156,118044,118045,118055,118073,118081,118099,118127,118143,118151,118159,118167],{"__ignoreMap":13},[200,118046,118047,118049,118051,118053],{"class":202,"line":203},[200,118048,61347],{"class":214},[200,118050,61350],{"class":235},[200,118052,10290],{"class":379},[200,118054,647],{"class":214},[200,118056,118057,118059,118061,118063,118065,118067,118069,118071],{"class":202,"line":14},[200,118058,599],{"class":214},[200,118060,592],{"class":235},[200,118062,14594],{"class":379},[200,118064,449],{"class":214},[200,118066,610],{"class":214},[200,118068,14601],{"class":245},[200,118070,610],{"class":214},[200,118072,647],{"class":214},[200,118074,118075,118077,118079],{"class":202,"line":255},[200,118076,656],{"class":214},[200,118078,14578],{"class":235},[200,118080,647],{"class":214},[200,118082,118083,118085,118087,118089,118091,118093,118095,118097],{"class":202,"line":272},[200,118084,803],{"class":214},[200,118086,2030],{"class":235},[200,118088,14743],{"class":379},[200,118090,449],{"class":214},[200,118092,610],{"class":214},[200,118094,61395],{"class":245},[200,118096,610],{"class":214},[200,118098,21527],{"class":214},[200,118100,118101,118103,118105,118107,118109,118111,118113,118115,118117,118119,118121,118123,118125],{"class":202,"line":283},[200,118102,803],{"class":214},[200,118104,2030],{"class":235},[200,118106,700],{"class":379},[200,118108,449],{"class":214},[200,118110,610],{"class":214},[200,118112,14798],{"class":245},[200,118114,610],{"class":214},[200,118116,6053],{"class":379},[200,118118,449],{"class":214},[200,118120,610],{"class":214},[200,118122,61424],{"class":245},[200,118124,610],{"class":214},[200,118126,21527],{"class":214},[200,118128,118129,118131,118133,118135,118137,118139,118141],{"class":202,"line":300},[200,118130,803],{"class":214},[200,118132,3581],{"class":235},[200,118134,674],{"class":214},[200,118136,61439],{"class":210},[200,118138,680],{"class":214},[200,118140,3581],{"class":235},[200,118142,647],{"class":214},[200,118144,118145,118147,118149],{"class":202,"line":310},[200,118146,892],{"class":214},[200,118148,14578],{"class":235},[200,118150,647],{"class":214},[200,118152,118153,118155,118157],{"class":202,"line":336},[200,118154,656],{"class":214},[200,118156,23914],{"class":235},[200,118158,647],{"class":214},[200,118160,118161,118163,118165],{"class":202,"line":342},[200,118162,892],{"class":214},[200,118164,23914],{"class":235},[200,118166,647],{"class":214},[200,118168,118169,118171,118173],{"class":202,"line":365},[200,118170,680],{"class":214},[200,118172,592],{"class":235},[200,118174,647],{"class":214},[35,118176,61480],{},[51,118178,61484],{"id":61483},[35,118180,61487],{},[35,118182,61490,118183,61494],{},[156,118184,61493],{},[192,118186,118187],{"className":25070,"code":61497,"language":25072,"meta":13,"style":13},[156,118188,118189,118193,118197,118223,118227,118251,118277,118303,118307,118311,118315,118321,118325,118337,118363,118367,118397,118421,118425,118439,118449,118453,118459,118463],{"__ignoreMap":13},[200,118190,118191],{"class":202,"line":203},[200,118192,32897],{"class":401},[200,118194,118195],{"class":202,"line":14},[200,118196,528],{"emptyLinePlaceholder":20},[200,118198,118199,118201,118203,118205,118207,118209,118211,118213,118215,118217,118219,118221],{"class":202,"line":255},[200,118200,443],{"class":379},[200,118202,19846],{"class":210},[200,118204,449],{"class":214},[200,118206,60995],{"class":223},[200,118208,227],{"class":210},[200,118210,227],{"class":214},[200,118212,61077],{"class":386},[200,118214,549],{"class":214},[200,118216,10954],{"class":386},[200,118218,2104],{"class":214},[200,118220,393],{"class":379},[200,118222,280],{"class":214},[200,118224,118225],{"class":202,"line":272},[200,118226,61538],{"class":401},[200,118228,118229,118231,118233,118235,118237,118239,118241,118243,118245,118247,118249],{"class":202,"line":283},[200,118230,20098],{"class":206},[200,118232,1094],{"class":235},[200,118234,61077],{"class":210},[200,118236,215],{"class":214},[200,118238,2035],{"class":210},[200,118240,3298],{"class":214},[200,118242,242],{"class":214},[200,118244,61493],{"class":245},[200,118246,249],{"class":214},[200,118248,5282],{"class":235},[200,118250,230],{"class":214},[200,118252,118253,118255,118257,118259,118261,118263,118265,118267,118269,118271,118273,118275],{"class":202,"line":300},[200,118254,61567],{"class":210},[200,118256,215],{"class":214},[200,118258,61115],{"class":223},[200,118260,227],{"class":235},[200,118262,249],{"class":214},[200,118264,61122],{"class":245},[200,118266,249],{"class":214},[200,118268,549],{"class":214},[200,118270,242],{"class":214},[200,118272,61586],{"class":245},[200,118274,249],{"class":214},[200,118276,416],{"class":235},[200,118278,118279,118281,118283,118285,118287,118289,118291,118293,118295,118297,118299,118301],{"class":202,"line":310},[200,118280,61567],{"class":210},[200,118282,215],{"class":214},[200,118284,61115],{"class":223},[200,118286,227],{"class":235},[200,118288,249],{"class":214},[200,118290,61605],{"class":245},[200,118292,249],{"class":214},[200,118294,549],{"class":214},[200,118296,242],{"class":214},[200,118298,61614],{"class":245},[200,118300,249],{"class":214},[200,118302,416],{"class":235},[200,118304,118305],{"class":202,"line":336},[200,118306,528],{"emptyLinePlaceholder":20},[200,118308,118309],{"class":202,"line":342},[200,118310,61627],{"class":401},[200,118312,118313],{"class":202,"line":365},[200,118314,4505],{"class":214},[200,118316,118317,118319],{"class":202,"line":371},[200,118318,45720],{"class":206},[200,118320,280],{"class":214},[200,118322,118323],{"class":202,"line":398},[200,118324,61642],{"class":401},[200,118326,118327,118329,118331,118333,118335],{"class":202,"line":405},[200,118328,61567],{"class":210},[200,118330,215],{"class":214},[200,118332,39221],{"class":210},[200,118334,509],{"class":214},[200,118336,61106],{"class":2060},[200,118338,118339,118341,118343,118345,118347,118349,118351,118353,118355,118357,118359,118361],{"class":202,"line":410},[200,118340,61567],{"class":210},[200,118342,215],{"class":214},[200,118344,61115],{"class":223},[200,118346,227],{"class":235},[200,118348,249],{"class":214},[200,118350,61122],{"class":245},[200,118352,249],{"class":214},[200,118354,549],{"class":214},[200,118356,242],{"class":214},[200,118358,61131],{"class":245},[200,118360,249],{"class":214},[200,118362,416],{"class":235},[200,118364,118365],{"class":202,"line":860},[200,118366,528],{"emptyLinePlaceholder":20},[200,118368,118369,118371,118373,118375,118377,118379,118381,118383,118385,118387,118389,118391,118393,118395],{"class":202,"line":889},[200,118370,2014],{"class":379},[200,118372,61146],{"class":210},[200,118374,509],{"class":214},[200,118376,12472],{"class":223},[200,118378,227],{"class":235},[200,118380,15568],{"class":210},[200,118382,215],{"class":214},[200,118384,61159],{"class":223},[200,118386,1549],{"class":235},[200,118388,549],{"class":214},[200,118390,242],{"class":214},[200,118392,61168],{"class":245},[200,118394,249],{"class":214},[200,118396,416],{"class":235},[200,118398,118399,118401,118403,118405,118407,118409,118411,118413,118415,118417,118419],{"class":202,"line":899},[200,118400,2014],{"class":379},[200,118402,10290],{"class":210},[200,118404,509],{"class":214},[200,118406,60975],{"class":223},[200,118408,227],{"class":235},[200,118410,61187],{"class":210},[200,118412,549],{"class":214},[200,118414,242],{"class":214},[200,118416,14750],{"class":245},[200,118418,249],{"class":214},[200,118420,416],{"class":235},[200,118422,118423],{"class":202,"line":904},[200,118424,528],{"emptyLinePlaceholder":20},[200,118426,118427,118429,118431,118433,118435,118437],{"class":202,"line":930},[200,118428,61567],{"class":210},[200,118430,215],{"class":214},[200,118432,61210],{"class":223},[200,118434,227],{"class":235},[200,118436,592],{"class":210},[200,118438,416],{"class":235},[200,118440,118441,118443,118445,118447],{"class":202,"line":3655},[200,118442,61567],{"class":210},[200,118444,215],{"class":214},[200,118446,28570],{"class":223},[200,118448,466],{"class":235},[200,118450,118451],{"class":202,"line":3660},[200,118452,4505],{"class":214},[200,118454,118455,118457],{"class":202,"line":4035},[200,118456,413],{"class":214},[200,118458,416],{"class":210},[200,118460,118461],{"class":202,"line":4490},[200,118462,528],{"emptyLinePlaceholder":20},[200,118464,118465],{"class":202,"line":4496},[200,118466,32897],{"class":401},[35,118468,61787,118469,61790,118471,1655,118473,61797,118475,61801,118477,61805,118479,61808],{},[156,118470,61493],{},[156,118472,61793],{},[156,118474,61796],{},[156,118476,61800],{},[6862,118478,61804],{},[156,118480,61800],{},[51,118482,61812],{"id":61811},[35,118484,61815],{},[35,118486,61818],{},[192,118488,118489],{"className":25070,"code":61821,"language":25072,"meta":13,"style":13},[156,118490,118491,118495,118499,118525,118529,118553,118557,118561,118565,118569,118595,118605,118609,118637,118645,118651,118655,118679,118683,118687,118691,118703,118729,118739,118745,118749,118755,118759,118763,118767],{"__ignoreMap":13},[200,118492,118493],{"class":202,"line":203},[200,118494,32897],{"class":401},[200,118496,118497],{"class":202,"line":14},[200,118498,528],{"emptyLinePlaceholder":20},[200,118500,118501,118503,118505,118507,118509,118511,118513,118515,118517,118519,118521,118523],{"class":202,"line":255},[200,118502,443],{"class":379},[200,118504,19846],{"class":210},[200,118506,449],{"class":214},[200,118508,60995],{"class":223},[200,118510,227],{"class":210},[200,118512,227],{"class":214},[200,118514,61077],{"class":386},[200,118516,549],{"class":214},[200,118518,10954],{"class":386},[200,118520,2104],{"class":214},[200,118522,393],{"class":379},[200,118524,280],{"class":214},[200,118526,118527],{"class":202,"line":272},[200,118528,61538],{"class":401},[200,118530,118531,118533,118535,118537,118539,118541,118543,118545,118547,118549,118551],{"class":202,"line":283},[200,118532,20098],{"class":206},[200,118534,1094],{"class":235},[200,118536,61077],{"class":210},[200,118538,215],{"class":214},[200,118540,2035],{"class":210},[200,118542,3298],{"class":214},[200,118544,242],{"class":214},[200,118546,61493],{"class":245},[200,118548,249],{"class":214},[200,118550,5282],{"class":235},[200,118552,230],{"class":214},[200,118554,118555],{"class":202,"line":300},[200,118556,13678],{"class":401},[200,118558,118559],{"class":202,"line":310},[200,118560,528],{"emptyLinePlaceholder":20},[200,118562,118563],{"class":202,"line":336},[200,118564,61898],{"class":401},[200,118566,118567],{"class":202,"line":342},[200,118568,4505],{"class":214},[200,118570,118571,118573,118575,118577,118579,118581,118583,118585,118587,118589,118591,118593],{"class":202,"line":365},[200,118572,45720],{"class":206},[200,118574,45723],{"class":206},[200,118576,1094],{"class":235},[200,118578,61077],{"class":210},[200,118580,215],{"class":214},[200,118582,2035],{"class":210},[200,118584,3298],{"class":214},[200,118586,242],{"class":214},[200,118588,61923],{"class":245},[200,118590,249],{"class":214},[200,118592,5282],{"class":235},[200,118594,230],{"class":214},[200,118596,118597,118599,118601,118603],{"class":202,"line":371},[200,118598,52178],{"class":379},[200,118600,40809],{"class":210},[200,118602,509],{"class":214},[200,118604,18888],{"class":214},[200,118606,118607],{"class":202,"line":398},[200,118608,528],{"emptyLinePlaceholder":20},[200,118610,118611,118613,118615,118617,118619,118621,118623,118625,118627,118629,118631,118633,118635],{"class":202,"line":405},[200,118612,61948],{"class":210},[200,118614,215],{"class":214},[200,118616,25410],{"class":223},[200,118618,227],{"class":235},[200,118620,249],{"class":214},[200,118622,12736],{"class":245},[200,118624,249],{"class":214},[200,118626,549],{"class":214},[200,118628,1094],{"class":214},[200,118630,3613],{"class":386},[200,118632,2104],{"class":214},[200,118634,393],{"class":379},[200,118636,280],{"class":214},[200,118638,118639,118641,118643],{"class":202,"line":410},[200,118640,41853],{"class":210},[200,118642,61979],{"class":214},[200,118644,61982],{"class":210},[200,118646,118647,118649],{"class":202,"line":860},[200,118648,2111],{"class":214},[200,118650,416],{"class":235},[200,118652,118653],{"class":202,"line":889},[200,118654,528],{"emptyLinePlaceholder":20},[200,118656,118657,118659,118661,118663,118665,118667,118669,118671,118673,118675,118677],{"class":202,"line":899},[200,118658,61948],{"class":210},[200,118660,215],{"class":214},[200,118662,25410],{"class":223},[200,118664,227],{"class":235},[200,118666,249],{"class":214},[200,118668,28570],{"class":245},[200,118670,249],{"class":214},[200,118672,549],{"class":214},[200,118674,3729],{"class":214},[200,118676,393],{"class":379},[200,118678,280],{"class":214},[200,118680,118681],{"class":202,"line":904},[200,118682,62021],{"class":401},[200,118684,118685],{"class":202,"line":930},[200,118686,528],{"emptyLinePlaceholder":20},[200,118688,118689],{"class":202,"line":3655},[200,118690,62030],{"class":401},[200,118692,118693,118695,118697,118699,118701],{"class":202,"line":3660},[200,118694,62035],{"class":210},[200,118696,215],{"class":214},[200,118698,39221],{"class":210},[200,118700,509],{"class":214},[200,118702,62044],{"class":2060},[200,118704,118705,118707,118709,118711,118713,118715,118717,118719,118721,118723,118725,118727],{"class":202,"line":4035},[200,118706,62035],{"class":210},[200,118708,215],{"class":214},[200,118710,61115],{"class":223},[200,118712,227],{"class":235},[200,118714,249],{"class":214},[200,118716,62059],{"class":245},[200,118718,249],{"class":214},[200,118720,549],{"class":214},[200,118722,242],{"class":214},[200,118724,2898],{"class":245},[200,118726,249],{"class":214},[200,118728,416],{"class":235},[200,118730,118731,118733,118735,118737],{"class":202,"line":4490},[200,118732,62035],{"class":210},[200,118734,215],{"class":214},[200,118736,28570],{"class":223},[200,118738,466],{"class":235},[200,118740,118741,118743],{"class":202,"line":4496},[200,118742,2111],{"class":214},[200,118744,416],{"class":235},[200,118746,118747],{"class":202,"line":4502},[200,118748,4505],{"class":214},[200,118750,118751,118753],{"class":202,"line":4508},[200,118752,45720],{"class":206},[200,118754,280],{"class":214},[200,118756,118757],{"class":202,"line":5469},[200,118758,61642],{"class":401},[200,118760,118761],{"class":202,"line":5474},[200,118762,13678],{"class":401},[200,118764,118765],{"class":202,"line":13237},[200,118766,4505],{"class":214},[200,118768,118769,118771],{"class":202,"line":13263},[200,118770,413],{"class":214},[200,118772,416],{"class":210},[35,118774,62118,118775,62121,118777,62124],{},[156,118776,61923],{},[156,118778,61168],{},[35,118780,62127,118781,62130],{},[156,118782,61168],{},[192,118784,118785],{"className":590,"code":62133,"language":592,"meta":13,"style":13},[156,118786,118787,118797,118815,118823,118841,118869,118885,118893,118901,118929,118957,118983,118991,118999],{"__ignoreMap":13},[200,118788,118789,118791,118793,118795],{"class":202,"line":203},[200,118790,61347],{"class":214},[200,118792,61350],{"class":235},[200,118794,10290],{"class":379},[200,118796,647],{"class":214},[200,118798,118799,118801,118803,118805,118807,118809,118811,118813],{"class":202,"line":14},[200,118800,599],{"class":214},[200,118802,592],{"class":235},[200,118804,14594],{"class":379},[200,118806,449],{"class":214},[200,118808,610],{"class":214},[200,118810,14601],{"class":245},[200,118812,610],{"class":214},[200,118814,647],{"class":214},[200,118816,118817,118819,118821],{"class":202,"line":255},[200,118818,656],{"class":214},[200,118820,14578],{"class":235},[200,118822,647],{"class":214},[200,118824,118825,118827,118829,118831,118833,118835,118837,118839],{"class":202,"line":272},[200,118826,803],{"class":214},[200,118828,2030],{"class":235},[200,118830,14743],{"class":379},[200,118832,449],{"class":214},[200,118834,610],{"class":214},[200,118836,61395],{"class":245},[200,118838,610],{"class":214},[200,118840,21527],{"class":214},[200,118842,118843,118845,118847,118849,118851,118853,118855,118857,118859,118861,118863,118865,118867],{"class":202,"line":283},[200,118844,803],{"class":214},[200,118846,2030],{"class":235},[200,118848,700],{"class":379},[200,118850,449],{"class":214},[200,118852,610],{"class":214},[200,118854,14798],{"class":245},[200,118856,610],{"class":214},[200,118858,6053],{"class":379},[200,118860,449],{"class":214},[200,118862,610],{"class":214},[200,118864,61424],{"class":245},[200,118866,610],{"class":214},[200,118868,21527],{"class":214},[200,118870,118871,118873,118875,118877,118879,118881,118883],{"class":202,"line":300},[200,118872,803],{"class":214},[200,118874,3581],{"class":235},[200,118876,674],{"class":214},[200,118878,61439],{"class":210},[200,118880,680],{"class":214},[200,118882,3581],{"class":235},[200,118884,647],{"class":214},[200,118886,118887,118889,118891],{"class":202,"line":310},[200,118888,892],{"class":214},[200,118890,14578],{"class":235},[200,118892,647],{"class":214},[200,118894,118895,118897,118899],{"class":202,"line":336},[200,118896,656],{"class":214},[200,118898,23914],{"class":235},[200,118900,647],{"class":214},[200,118902,118903,118905,118907,118909,118911,118913,118915,118917,118919,118921,118923,118925,118927],{"class":202,"line":342},[200,118904,803],{"class":214},[200,118906,602],{"class":235},[200,118908,40681],{"class":379},[200,118910,449],{"class":214},[200,118912,610],{"class":214},[200,118914,61923],{"class":245},[200,118916,610],{"class":214},[200,118918,6589],{"class":379},[200,118920,449],{"class":214},[200,118922,610],{"class":214},[200,118924,3793],{"class":245},[200,118926,610],{"class":214},[200,118928,647],{"class":214},[200,118930,118931,118933,118935,118937,118939,118941,118943,118945,118947,118949,118951,118953,118955],{"class":202,"line":365},[200,118932,22071],{"class":214},[200,118934,355],{"class":235},[200,118936,321],{"class":379},[200,118938,449],{"class":214},[200,118940,610],{"class":214},[200,118942,697],{"class":245},[200,118944,610],{"class":214},[200,118946,700],{"class":379},[200,118948,449],{"class":214},[200,118950,610],{"class":214},[200,118952,29303],{"class":245},[200,118954,610],{"class":214},[200,118956,21527],{"class":214},[200,118958,118959,118961,118963,118965,118967,118969,118971,118973,118975,118977,118979,118981],{"class":202,"line":371},[200,118960,22071],{"class":214},[200,118962,909],{"class":235},[200,118964,321],{"class":379},[200,118966,449],{"class":214},[200,118968,610],{"class":214},[200,118970,916],{"class":245},[200,118972,610],{"class":214},[200,118974,674],{"class":214},[200,118976,62326],{"class":210},[200,118978,680],{"class":214},[200,118980,909],{"class":235},[200,118982,647],{"class":214},[200,118984,118985,118987,118989],{"class":202,"line":398},[200,118986,15078],{"class":214},[200,118988,602],{"class":235},[200,118990,647],{"class":214},[200,118992,118993,118995,118997],{"class":202,"line":405},[200,118994,892],{"class":214},[200,118996,23914],{"class":235},[200,118998,647],{"class":214},[200,119000,119001,119003,119005],{"class":202,"line":410},[200,119002,680],{"class":214},[200,119004,592],{"class":235},[200,119006,647],{"class":214},[51,119008,62360],{"id":62359},[35,119010,62363],{},[35,119012,62366],{},[35,119014,119015],{},[1707,119016],{"alt":62371,"src":62372},[35,119018,62375],{},[35,119020,62378],{},[192,119022,119023],{"className":25070,"code":62381,"language":25072,"meta":13,"style":13},[156,119024,119025,119029,119033,119037,119051,119055,119081,119085,119109,119135,119161,119165,119179,119183,119207,119221,119227,119231,119235,119239,119265,119275,119279,119307,119315,119321,119325,119349,119353,119369,119395,119399,119403,119415,119441,119451,119457,119461,119467,119471,119475,119479,119485,119489],{"__ignoreMap":13},[200,119026,119027],{"class":202,"line":203},[200,119028,32897],{"class":401},[200,119030,119031],{"class":202,"line":14},[200,119032,528],{"emptyLinePlaceholder":20},[200,119034,119035],{"class":202,"line":255},[200,119036,62396],{"class":401},[200,119038,119039,119041,119043,119045,119047,119049],{"class":202,"line":272},[200,119040,443],{"class":379},[200,119042,62403],{"class":210},[200,119044,449],{"class":214},[200,119046,13007],{"class":214},[200,119048,15421],{"class":223},[200,119050,466],{"class":210},[200,119052,119053],{"class":202,"line":283},[200,119054,528],{"emptyLinePlaceholder":20},[200,119056,119057,119059,119061,119063,119065,119067,119069,119071,119073,119075,119077,119079],{"class":202,"line":300},[200,119058,443],{"class":379},[200,119060,19846],{"class":210},[200,119062,449],{"class":214},[200,119064,60995],{"class":223},[200,119066,227],{"class":210},[200,119068,227],{"class":214},[200,119070,61077],{"class":386},[200,119072,549],{"class":214},[200,119074,10954],{"class":386},[200,119076,2104],{"class":214},[200,119078,393],{"class":379},[200,119080,280],{"class":214},[200,119082,119083],{"class":202,"line":310},[200,119084,61538],{"class":401},[200,119086,119087,119089,119091,119093,119095,119097,119099,119101,119103,119105,119107],{"class":202,"line":336},[200,119088,20098],{"class":206},[200,119090,1094],{"class":235},[200,119092,61077],{"class":210},[200,119094,215],{"class":214},[200,119096,2035],{"class":210},[200,119098,3298],{"class":214},[200,119100,242],{"class":214},[200,119102,61493],{"class":245},[200,119104,249],{"class":214},[200,119106,5282],{"class":235},[200,119108,230],{"class":214},[200,119110,119111,119113,119115,119117,119119,119121,119123,119125,119127,119129,119131,119133],{"class":202,"line":342},[200,119112,61567],{"class":210},[200,119114,215],{"class":214},[200,119116,61115],{"class":223},[200,119118,227],{"class":235},[200,119120,249],{"class":214},[200,119122,61122],{"class":245},[200,119124,249],{"class":214},[200,119126,549],{"class":214},[200,119128,242],{"class":214},[200,119130,61586],{"class":245},[200,119132,249],{"class":214},[200,119134,416],{"class":235},[200,119136,119137,119139,119141,119143,119145,119147,119149,119151,119153,119155,119157,119159],{"class":202,"line":365},[200,119138,61567],{"class":210},[200,119140,215],{"class":214},[200,119142,61115],{"class":223},[200,119144,227],{"class":235},[200,119146,249],{"class":214},[200,119148,61605],{"class":245},[200,119150,249],{"class":214},[200,119152,549],{"class":214},[200,119154,242],{"class":214},[200,119156,61614],{"class":245},[200,119158,249],{"class":214},[200,119160,416],{"class":235},[200,119162,119163],{"class":202,"line":371},[200,119164,528],{"emptyLinePlaceholder":20},[200,119166,119167,119169,119171,119173,119175,119177],{"class":202,"line":398},[200,119168,62530],{"class":210},[200,119170,215],{"class":214},[200,119172,62535],{"class":223},[200,119174,227],{"class":235},[200,119176,10949],{"class":210},[200,119178,416],{"class":235},[200,119180,119181],{"class":202,"line":405},[200,119182,528],{"emptyLinePlaceholder":20},[200,119184,119185,119187,119189,119191,119193,119195,119197,119199,119201,119203,119205],{"class":202,"line":410},[200,119186,61948],{"class":210},[200,119188,215],{"class":214},[200,119190,25410],{"class":223},[200,119192,227],{"class":235},[200,119194,249],{"class":214},[200,119196,22609],{"class":245},[200,119198,249],{"class":214},[200,119200,549],{"class":214},[200,119202,3729],{"class":214},[200,119204,393],{"class":379},[200,119206,280],{"class":214},[200,119208,119209,119211,119213,119215,119217,119219],{"class":202,"line":860},[200,119210,62574],{"class":210},[200,119212,215],{"class":214},[200,119214,62579],{"class":223},[200,119216,227],{"class":235},[200,119218,10949],{"class":210},[200,119220,416],{"class":235},[200,119222,119223,119225],{"class":202,"line":889},[200,119224,2111],{"class":214},[200,119226,416],{"class":235},[200,119228,119229],{"class":202,"line":899},[200,119230,528],{"emptyLinePlaceholder":20},[200,119232,119233],{"class":202,"line":904},[200,119234,61898],{"class":401},[200,119236,119237],{"class":202,"line":930},[200,119238,4505],{"class":214},[200,119240,119241,119243,119245,119247,119249,119251,119253,119255,119257,119259,119261,119263],{"class":202,"line":3655},[200,119242,45720],{"class":206},[200,119244,45723],{"class":206},[200,119246,1094],{"class":235},[200,119248,61077],{"class":210},[200,119250,215],{"class":214},[200,119252,2035],{"class":210},[200,119254,3298],{"class":214},[200,119256,242],{"class":214},[200,119258,61923],{"class":245},[200,119260,249],{"class":214},[200,119262,5282],{"class":235},[200,119264,230],{"class":214},[200,119266,119267,119269,119271,119273],{"class":202,"line":3660},[200,119268,52178],{"class":379},[200,119270,40809],{"class":210},[200,119272,509],{"class":214},[200,119274,18888],{"class":214},[200,119276,119277],{"class":202,"line":4035},[200,119278,528],{"emptyLinePlaceholder":20},[200,119280,119281,119283,119285,119287,119289,119291,119293,119295,119297,119299,119301,119303,119305],{"class":202,"line":4490},[200,119282,61948],{"class":210},[200,119284,215],{"class":214},[200,119286,25410],{"class":223},[200,119288,227],{"class":235},[200,119290,249],{"class":214},[200,119292,12736],{"class":245},[200,119294,249],{"class":214},[200,119296,549],{"class":214},[200,119298,1094],{"class":214},[200,119300,3613],{"class":386},[200,119302,2104],{"class":214},[200,119304,393],{"class":379},[200,119306,280],{"class":214},[200,119308,119309,119311,119313],{"class":202,"line":4496},[200,119310,41853],{"class":210},[200,119312,61979],{"class":214},[200,119314,61982],{"class":210},[200,119316,119317,119319],{"class":202,"line":4502},[200,119318,2111],{"class":214},[200,119320,416],{"class":235},[200,119322,119323],{"class":202,"line":4508},[200,119324,528],{"emptyLinePlaceholder":20},[200,119326,119327,119329,119331,119333,119335,119337,119339,119341,119343,119345,119347],{"class":202,"line":5469},[200,119328,61948],{"class":210},[200,119330,215],{"class":214},[200,119332,25410],{"class":223},[200,119334,227],{"class":235},[200,119336,249],{"class":214},[200,119338,28570],{"class":245},[200,119340,249],{"class":214},[200,119342,549],{"class":214},[200,119344,3729],{"class":214},[200,119346,393],{"class":379},[200,119348,280],{"class":214},[200,119350,119351],{"class":202,"line":5474},[200,119352,62718],{"class":401},[200,119354,119355,119357,119359,119361,119363,119365,119367],{"class":202,"line":13237},[200,119356,62723],{"class":206},[200,119358,1094],{"class":235},[200,119360,443],{"class":379},[200,119362,62730],{"class":210},[200,119364,6457],{"class":214},[200,119366,62735],{"class":210},[200,119368,416],{"class":235},[200,119370,119371,119373,119375,119377,119379,119381,119383,119385,119387,119389,119391,119393],{"class":202,"line":13263},[200,119372,62742],{"class":210},[200,119374,215],{"class":214},[200,119376,61210],{"class":223},[200,119378,227],{"class":235},[200,119380,3333],{"class":214},[200,119382,62753],{"class":245},[200,119384,4868],{"class":214},[200,119386,23914],{"class":210},[200,119388,413],{"class":214},[200,119390,62762],{"class":210},[200,119392,3333],{"class":214},[200,119394,416],{"class":235},[200,119396,119397],{"class":202,"line":13279},[200,119398,528],{"emptyLinePlaceholder":20},[200,119400,119401],{"class":202,"line":13323},[200,119402,62030],{"class":401},[200,119404,119405,119407,119409,119411,119413],{"class":202,"line":13329},[200,119406,62035],{"class":210},[200,119408,215],{"class":214},[200,119410,39221],{"class":210},[200,119412,509],{"class":214},[200,119414,62044],{"class":2060},[200,119416,119417,119419,119421,119423,119425,119427,119429,119431,119433,119435,119437,119439],{"class":202,"line":13334},[200,119418,62035],{"class":210},[200,119420,215],{"class":214},[200,119422,61115],{"class":223},[200,119424,227],{"class":235},[200,119426,249],{"class":214},[200,119428,62059],{"class":245},[200,119430,249],{"class":214},[200,119432,549],{"class":214},[200,119434,242],{"class":214},[200,119436,2898],{"class":245},[200,119438,249],{"class":214},[200,119440,416],{"class":235},[200,119442,119443,119445,119447,119449],{"class":202,"line":13360},[200,119444,62035],{"class":210},[200,119446,215],{"class":214},[200,119448,28570],{"class":223},[200,119450,466],{"class":235},[200,119452,119453,119455],{"class":202,"line":13365},[200,119454,2111],{"class":214},[200,119456,416],{"class":235},[200,119458,119459],{"class":202,"line":13385},[200,119460,4505],{"class":214},[200,119462,119463,119465],{"class":202,"line":13405},[200,119464,45720],{"class":206},[200,119466,280],{"class":214},[200,119468,119469],{"class":202,"line":13410},[200,119470,61642],{"class":401},[200,119472,119473],{"class":202,"line":13418},[200,119474,13678],{"class":401},[200,119476,119477],{"class":202,"line":13436},[200,119478,4505],{"class":214},[200,119480,119481,119483],{"class":202,"line":13466},[200,119482,413],{"class":214},[200,119484,416],{"class":210},[200,119486,119487],{"class":202,"line":13472},[200,119488,528],{"emptyLinePlaceholder":20},[200,119490,119491],{"class":202,"line":13477},[200,119492,32897],{"class":401},[35,119494,62867,119495,62870,119497,62874],{},[156,119496,61493],{},[156,119498,62873],{},[35,119500,62877,119501,62880],{},[156,119502,61210],{},[35,119504,62883,119505,62886,119507,62889],{},[156,119506,61923],{},[156,119508,62873],{},[35,119510,62892,119511,62895,119513,62898,119515,62901,119517,62904,119519,62907,119521,215],{},[156,119512,12736],{},[156,119514,6098],{},[156,119516,12736],{},[156,119518,6098],{},[156,119520,12736],{},[156,119522,6098],{},[35,119524,62912,119525,62916,119527,215],{},[156,119526,62915],{},[156,119528,62919],{},[51,119530,62923],{"id":62922},[35,119532,62926,119533,215],{},[156,119534,61493],{},[35,119536,62931,119537,62935],{},[156,119538,62934],{},[35,119540,62938],{},[192,119542,119543],{"className":590,"code":62941,"language":592,"meta":13,"style":13},[156,119544,119545,119555,119573,119581,119599,119627,119643,119651,119655,119679,119683,119709,119713,119717,119737,119741,119767,119785,119789,119805,119809,119817,119825,119833,119845,119849,119877,119905,119931,119939,119947],{"__ignoreMap":13},[200,119546,119547,119549,119551,119553],{"class":202,"line":203},[200,119548,61347],{"class":214},[200,119550,61350],{"class":235},[200,119552,10290],{"class":379},[200,119554,647],{"class":214},[200,119556,119557,119559,119561,119563,119565,119567,119569,119571],{"class":202,"line":14},[200,119558,599],{"class":214},[200,119560,592],{"class":235},[200,119562,14594],{"class":379},[200,119564,449],{"class":214},[200,119566,610],{"class":214},[200,119568,14601],{"class":245},[200,119570,610],{"class":214},[200,119572,647],{"class":214},[200,119574,119575,119577,119579],{"class":202,"line":255},[200,119576,656],{"class":214},[200,119578,14578],{"class":235},[200,119580,647],{"class":214},[200,119582,119583,119585,119587,119589,119591,119593,119595,119597],{"class":202,"line":272},[200,119584,803],{"class":214},[200,119586,2030],{"class":235},[200,119588,14743],{"class":379},[200,119590,449],{"class":214},[200,119592,610],{"class":214},[200,119594,61395],{"class":245},[200,119596,610],{"class":214},[200,119598,21527],{"class":214},[200,119600,119601,119603,119605,119607,119609,119611,119613,119615,119617,119619,119621,119623,119625],{"class":202,"line":283},[200,119602,803],{"class":214},[200,119604,2030],{"class":235},[200,119606,700],{"class":379},[200,119608,449],{"class":214},[200,119610,610],{"class":214},[200,119612,14798],{"class":245},[200,119614,610],{"class":214},[200,119616,6053],{"class":379},[200,119618,449],{"class":214},[200,119620,610],{"class":214},[200,119622,61424],{"class":245},[200,119624,610],{"class":214},[200,119626,21527],{"class":214},[200,119628,119629,119631,119633,119635,119637,119639,119641],{"class":202,"line":300},[200,119630,803],{"class":214},[200,119632,3581],{"class":235},[200,119634,674],{"class":214},[200,119636,61439],{"class":210},[200,119638,680],{"class":214},[200,119640,3581],{"class":235},[200,119642,647],{"class":214},[200,119644,119645,119647,119649],{"class":202,"line":310},[200,119646,803],{"class":214},[200,119648,14896],{"class":235},[200,119650,647],{"class":214},[200,119652,119653],{"class":202,"line":336},[200,119654,63054],{"class":401},[200,119656,119657,119659,119661,119663,119665,119667,119669,119671,119673,119675,119677],{"class":202,"line":342},[200,119658,20136],{"class":379},[200,119660,63061],{"class":210},[200,119662,449],{"class":214},[200,119664,13007],{"class":214},[200,119666,63068],{"class":223},[200,119668,227],{"class":210},[200,119670,610],{"class":214},[200,119672,61493],{"class":245},[200,119674,610],{"class":214},[200,119676,2104],{"class":210},[200,119678,5944],{"class":214},[200,119680,119681],{"class":202,"line":365},[200,119682,528],{"emptyLinePlaceholder":20},[200,119684,119685,119687,119689,119691,119693,119695,119697,119699,119701,119703,119705,119707],{"class":202,"line":371},[200,119686,20136],{"class":379},[200,119688,63091],{"class":210},[200,119690,449],{"class":214},[200,119692,211],{"class":210},[200,119694,215],{"class":214},[200,119696,63100],{"class":223},[200,119698,227],{"class":210},[200,119700,610],{"class":214},[200,119702,1593],{"class":245},[200,119704,610],{"class":214},[200,119706,2104],{"class":210},[200,119708,5944],{"class":214},[200,119710,119711],{"class":202,"line":398},[200,119712,528],{"emptyLinePlaceholder":20},[200,119714,119715],{"class":202,"line":405},[200,119716,63121],{"class":401},[200,119718,119719,119721,119723,119725,119727,119729,119731,119733,119735],{"class":202,"line":410},[200,119720,63126],{"class":210},[200,119722,215],{"class":214},[200,119724,29138],{"class":223},[200,119726,509],{"class":214},[200,119728,1094],{"class":214},[200,119730,25426],{"class":386},[200,119732,2104],{"class":214},[200,119734,393],{"class":379},[200,119736,280],{"class":214},[200,119738,119739],{"class":202,"line":860},[200,119740,63147],{"class":401},[200,119742,119743,119745,119747,119749,119751,119753,119755,119757,119759,119761,119763,119765],{"class":202,"line":889},[200,119744,12999],{"class":379},[200,119746,63154],{"class":210},[200,119748,509],{"class":214},[200,119750,211],{"class":210},[200,119752,215],{"class":214},[200,119754,15947],{"class":223},[200,119756,227],{"class":235},[200,119758,610],{"class":214},[200,119760,71],{"class":245},[200,119762,610],{"class":214},[200,119764,2104],{"class":235},[200,119766,5944],{"class":214},[200,119768,119769,119771,119773,119775,119777,119779,119781,119783],{"class":202,"line":899},[200,119770,63179],{"class":210},[200,119772,215],{"class":214},[200,119774,63184],{"class":210},[200,119776,509],{"class":214},[200,119778,43463],{"class":210},[200,119780,215],{"class":214},[200,119782,12736],{"class":210},[200,119784,5944],{"class":214},[200,119786,119787],{"class":202,"line":904},[200,119788,528],{"emptyLinePlaceholder":20},[200,119790,119791,119793,119795,119797,119799,119801,119803],{"class":202,"line":930},[200,119792,63203],{"class":210},[200,119794,215],{"class":214},[200,119796,16017],{"class":223},[200,119798,227],{"class":235},[200,119800,71],{"class":210},[200,119802,2104],{"class":235},[200,119804,5944],{"class":214},[200,119806,119807],{"class":202,"line":3655},[200,119808,63220],{"class":214},[200,119810,119811,119813,119815],{"class":202,"line":3660},[200,119812,15078],{"class":214},[200,119814,14896],{"class":235},[200,119816,647],{"class":214},[200,119818,119819,119821,119823],{"class":202,"line":4035},[200,119820,892],{"class":214},[200,119822,14578],{"class":235},[200,119824,647],{"class":214},[200,119826,119827,119829,119831],{"class":202,"line":4490},[200,119828,656],{"class":214},[200,119830,23914],{"class":235},[200,119832,647],{"class":214},[200,119834,119835,119837,119839,119841,119843],{"class":202,"line":4496},[200,119836,803],{"class":214},[200,119838,1593],{"class":235},[200,119840,10206],{"class":214},[200,119842,1593],{"class":235},[200,119844,647],{"class":214},[200,119846,119847],{"class":202,"line":4502},[200,119848,528],{"emptyLinePlaceholder":20},[200,119850,119851,119853,119855,119857,119859,119861,119863,119865,119867,119869,119871,119873,119875],{"class":202,"line":4508},[200,119852,803],{"class":214},[200,119854,602],{"class":235},[200,119856,40681],{"class":379},[200,119858,449],{"class":214},[200,119860,610],{"class":214},[200,119862,61923],{"class":245},[200,119864,610],{"class":214},[200,119866,6589],{"class":379},[200,119868,449],{"class":214},[200,119870,610],{"class":214},[200,119872,3793],{"class":245},[200,119874,610],{"class":214},[200,119876,647],{"class":214},[200,119878,119879,119881,119883,119885,119887,119889,119891,119893,119895,119897,119899,119901,119903],{"class":202,"line":5469},[200,119880,22071],{"class":214},[200,119882,355],{"class":235},[200,119884,321],{"class":379},[200,119886,449],{"class":214},[200,119888,610],{"class":214},[200,119890,697],{"class":245},[200,119892,610],{"class":214},[200,119894,700],{"class":379},[200,119896,449],{"class":214},[200,119898,610],{"class":214},[200,119900,29303],{"class":245},[200,119902,610],{"class":214},[200,119904,21527],{"class":214},[200,119906,119907,119909,119911,119913,119915,119917,119919,119921,119923,119925,119927,119929],{"class":202,"line":5474},[200,119908,22071],{"class":214},[200,119910,909],{"class":235},[200,119912,321],{"class":379},[200,119914,449],{"class":214},[200,119916,610],{"class":214},[200,119918,916],{"class":245},[200,119920,610],{"class":214},[200,119922,674],{"class":214},[200,119924,62326],{"class":210},[200,119926,680],{"class":214},[200,119928,909],{"class":235},[200,119930,647],{"class":214},[200,119932,119933,119935,119937],{"class":202,"line":13237},[200,119934,15078],{"class":214},[200,119936,602],{"class":235},[200,119938,647],{"class":214},[200,119940,119941,119943,119945],{"class":202,"line":13263},[200,119942,892],{"class":214},[200,119944,23914],{"class":235},[200,119946,647],{"class":214},[200,119948,119949,119951,119953],{"class":202,"line":13279},[200,119950,680],{"class":214},[200,119952,592],{"class":235},[200,119954,647],{"class":214},[35,119956,63369,119957,63372,119959,63375],{},[156,119958,61493],{},[156,119960,62934],{},[35,119962,63378,119963,63381],{},[156,119964,29138],{},[35,119966,63384,119967,63387],{},[156,119968,71],{},[35,119970,63390],{},[35,119972,119973],{},[1707,119974],{"alt":63395,"src":63396},[51,119976,63400],{"id":63399},[35,119978,63403],{},[35,119980,63406],{},[1593,119982,119983,119987],{},[71,119984,63411,119985,63414],{},[156,119986,62934],{},[71,119988,63417,119989,63420],{},[156,119990,61793],{},[35,119992,63423],{},[35,119994,63426],{},[1170,119996,63429],{},{"title":13,"searchDepth":14,"depth":14,"links":119998},[119999,120000,120001,120002,120003,120004,120005,120006],{"id":60929,"depth":14,"text":60930},{"id":60936,"depth":14,"text":60937},{"id":61331,"depth":14,"text":61332},{"id":61483,"depth":14,"text":61484},{"id":61811,"depth":14,"text":61812},{"id":62359,"depth":14,"text":62360},{"id":62922,"depth":14,"text":62923},{"id":63399,"depth":14,"text":63400},{},{"title":60906,"description":63442},{"loc":63444,"images":120010},[120011,120012],{"loc":62372},{"loc":63396},{"id":63454,"title":63455,"body":120014,"contentId":63732,"date":63733,"description":63734,"extension":18,"meta":120194,"navigation":20,"path":63736,"rawbody":63737,"robots":23,"schemaOrg":23,"seo":120195,"sitemap":120196,"stem":63744,"__hash__":63745},{"type":7,"value":120015,"toc":120185},[120016,120022,120026,120032,120034,120038,120046,120050,120052,120054,120070,120072,120076,120078,120082,120086,120090,120094,120098,120102,120106,120110,120112,120120,120128,120132,120134,120138,120142,120146,120150,120156,120160,120164],[35,120017,63460,120018,63464,120020,63468],{},[6862,120019,63463],{},[6862,120021,63467],{},[35,120023,120024],{},[1216,120025,63473],{},[35,120027,63476,120028,63479,120030,63483],{},[6862,120029,33388],{},[6862,120031,63482],{},[51,120033,63487],{"id":63486},[35,120035,63490,120036,63494],{},[6862,120037,63493],{},[35,120039,63497,120040,63501,120042,63505,120044],{},[6862,120041,63500],{},[6862,120043,63504],{},[1599,120045,215],{"repo":63508},[35,120047,120048],{},[1216,120049,63513],{},[51,120051,63517],{"id":63516},[35,120053,63520],{},[35,120055,63523,120056,63531,120061,63535,120063,63538,120065,215],{},[6862,120057,120058],{},[39,120059,63530],{"href":63528,"rel":120060},[77],[6862,120062,63534],{},[6862,120064,17998],{},[6862,120066,120067],{},[39,120068,63545],{"href":63543,"rel":120069},[77],[35,120071,63548],{},[35,120073,120074],{},[1216,120075,63553],{},[51,120077,63557],{"id":63556},[35,120079,63560,120080,63564],{},[6862,120081,63563],{},[35,120083,120084],{},[1707,120085],{"alt":63563,"src":63569},[35,120087,120088,63574],{},[6862,120089,63500],{},[35,120091,120092],{},[1707,120093],{"alt":63500,"src":63579},[35,120095,63582,120096,63585],{},[6862,120097,33385],{},[35,120099,63588,120100,63592],{},[6862,120101,63591],{},[35,120103,63595,120104,63599],{},[6862,120105,63598],{},[35,120107,120108],{},[1216,120109,63604],{},[51,120111,63608],{"id":63607},[35,120113,63611,120114,63616,120117,63621],{},[39,120115,60753],{"href":63614,"rel":120116},[77],[39,120118,29841],{"href":63619,"rel":120119},[77],[35,120121,63624,120122,63628,120124,63632,120126,63636],{},[6862,120123,63627],{},[6862,120125,63631],{},[6862,120127,63635],{},[35,120129,63639,120130,215],{},[1216,120131,63642],{},[1929,120133,63646],{"id":63645},[35,120135,21121,120136,63652],{},[6862,120137,63651],{},[35,120139,120140],{},[1707,120141],{"alt":63657,"src":63658},[35,120143,63661,120144,63664],{},[156,120145,62934],{},[35,120147,63667,120148,63670],{},[6862,120149,63591],{},[35,120151,63673,120152,13741,120154,63680],{},[156,120153,63676],{},[156,120155,63679],{},[35,120157,120158],{},[1216,120159,63685],{},[35,120161,63688,120162,215],{},[6862,120163,63691],{},[7918,120165,120166,120170,120172,120176,120178],{"text":63694,"href":63695},[35,120167,120168],{},[1216,120169,63700],{},[51,120171,19349],{"id":19348},[35,120173,63705,120174],{},[1216,120175,63708],{},[35,120177,63711],{},[35,120179,120180],{},[1216,120181,63716,120182,63722],{},[39,120183,63721],{"href":63719,"rel":120184},[77],{"title":13,"searchDepth":14,"depth":14,"links":120186},[120187,120188,120189,120190,120193],{"id":63486,"depth":14,"text":63487},{"id":63516,"depth":14,"text":63517},{"id":63556,"depth":14,"text":63557},{"id":63607,"depth":14,"text":63608,"children":120191},[120192],{"id":63645,"depth":255,"text":63646},{"id":19348,"depth":14,"text":19349},{},{"title":63455,"description":63734},{"loc":63736,"images":120197},[120198,120199,120200],{"loc":63569},{"loc":63579},{"loc":63658},{"id":63747,"title":63748,"body":120202,"contentId":30564,"date":63963,"description":63964,"extension":18,"meta":120339,"navigation":20,"path":63966,"rawbody":63967,"robots":23,"schemaOrg":23,"seo":120340,"sitemap":120341,"stem":63970,"__hash__":63971},{"type":7,"value":120203,"toc":120331},[120204,120206,120208,120215,120223,120229,120231,120235,120237,120241,120243,120248,120250,120254,120258,120260,120262,120264,120266,120268,120272,120274,120276,120278,120280,120282,120287,120291,120295,120297,120299,120304,120308,120310,120312,120318,120323,120327,120329],[35,120205,63753],{},[51,120207,63757],{"id":63756},[35,120209,63760,120210],{},[1599,120211,63764,120212,215],{"repo":63763},[39,120213,33385],{"href":63767,"rel":120214},[77],[35,120216,63771,120217],{},[1599,120218,63775,120219],{"repo":63774},[1599,120220,63779,120221],{"repo":63778},[1216,120222,63782],{},[35,120224,63785,120225,63789,120227,63793],{},[6862,120226,63788],{},[6862,120228,63792],{},[51,120230,63797],{"id":63796},[35,120232,120233],{},[1216,120234,63802],{},[35,120236,63805],{},[35,120238,63808,120239],{},[1216,120240,63811],{},[35,120242,63814],{},[35,120244,63817,120245,63823],{},[39,120246,63822],{"href":63820,"rel":120247},[77],[35,120249,63826],{},[35,120251,63829,120252,215],{},[6862,120253,63832],{},[35,120255,120256],{},[1216,120257,63837],{},[51,120259,63841],{"id":63840},[35,120261,63844],{},[35,120263,63847],{},[35,120265,63850],{},[35,120267,63853],{},[35,120269,63856,120270,215],{},[6862,120271,63859],{},[51,120273,63863],{"id":63862},[35,120275,63866],{},[35,120277,63869],{},[35,120279,63872],{},[35,120281,63875],{},[35,120283,63878,120284,63883],{},[39,120285,63882],{"href":31082,"rel":120286},[77],[35,120288,63886,120289,63890],{},[6862,120290,63889],{},[35,120292,63893,120293],{},[1216,120294,63896],{},[51,120296,63900],{"id":63899},[35,120298,63903],{},[35,120300,63906,120301,63912],{},[39,120302,63911],{"href":63909,"rel":120303},[77],[938,120305,120306],{},[35,120307,63917],{},[35,120309,63920],{},[51,120311,63924],{"id":63923},[35,120313,63927,120314,63931,120316,63935],{},[6862,120315,63930],{},[6862,120317,63934],{},[35,120319,63938,120320,63942],{},[39,120321,18636],{"href":34319,"rel":120322},[77],[35,120324,63945,120325],{},[1216,120326,63948],{},[35,120328,63951],{},[35,120330,63954],{},{"title":13,"searchDepth":14,"depth":14,"links":120332},[120333,120334,120335,120336,120337,120338],{"id":63756,"depth":14,"text":63757},{"id":63796,"depth":14,"text":63797},{"id":63840,"depth":14,"text":63841},{"id":63862,"depth":14,"text":63863},{"id":63899,"depth":14,"text":63900},{"id":63923,"depth":14,"text":63924},{},{"title":63748,"description":63964},{"loc":63966},{"id":63973,"title":63974,"body":120343,"contentId":30570,"date":66776,"description":66777,"extension":18,"meta":122852,"navigation":20,"path":66779,"rawbody":66780,"robots":23,"schemaOrg":23,"seo":122853,"sitemap":122854,"stem":66783,"__hash__":66784},{"type":7,"value":120344,"toc":122845},[120345,120359,120363,120369,120378,120386,120388,120428,120430,120434,120448,120452,120468,120472,120484,120489,120491,120493,120507,120509,120519,120911,120917,120923,120927,121117,121123,121125,121129,121141,121153,121669,121677,121685,121693,121703,122843],[35,120346,63979,120347,35453,120349,63989,120354,215],{},[6862,120348,63982],{},[39,120350,120352],{"href":63985,"rel":120351},[77],[6862,120353,36573],{},[39,120355,120357],{"href":63992,"rel":120356},[77],[6862,120358,38535],{},[35,120360,63998,120361],{},[1599,120362,215],{"repo":64001},[35,120364,120365,64006,120367,64010],{},[6862,120366,36573],{},[6862,120368,64009],{},[35,120370,120371,64015,120373,64022],{},[6862,120372,38535],{},[39,120374,120376],{"href":38548,"rel":120375},[77],[6862,120377,64021],{},[35,120379,64025,120380,64028,120382,64031,120384,64035],{},[6862,120381,63982],{},[6862,120383,38550],{},[6862,120385,64034],{},[35,120387,64038],{},[1593,120389,120390,120401,120410,120421],{},[71,120391,120392,64050,120397,64053,120399,215],{},[39,120393,120395],{"href":64045,"rel":120394},[77],[6862,120396,64049],{},[6862,120398,64021],{},[6862,120400,64056],{},[71,120402,120403,64066,120408,64069],{},[39,120404,120406],{"href":64061,"rel":120405},[77],[6862,120407,64065],{},[6862,120409,52842],{},[71,120411,120412,64079,120417,35453,120419,215],{},[39,120413,120415],{"href":64074,"rel":120414},[77],[6862,120416,64078],{},[6862,120418,64082],{},[6862,120420,64085],{},[71,120422,120423,64095],{},[39,120424,120426],{"href":64090,"rel":120425},[77],[6862,120427,64094],{},[51,120429,64099],{"id":64098},[35,120431,64102,120432,2186],{},[6862,120433,64105],{},[192,120435,120436],{"className":2166,"code":64108,"language":2168,"meta":13,"style":13},[156,120437,120438],{"__ignoreMap":13},[200,120439,120440,120442,120444,120446],{"class":202,"line":203},[200,120441,30995],{"class":1364},[200,120443,64117],{"class":245},[200,120445,64120],{"class":245},[200,120447,64123],{"class":245},[35,120449,64126,120450,64130],{},[6862,120451,64129],{},[192,120453,120454],{"className":2166,"code":64133,"language":2168,"meta":13,"style":13},[156,120455,120456,120462],{"__ignoreMap":13},[200,120457,120458,120460],{"class":202,"line":203},[200,120459,36044],{"class":223},[200,120461,64123],{"class":245},[200,120463,120464,120466],{"class":202,"line":14},[200,120465,36084],{"class":1364},[200,120467,35925],{"class":245},[35,120469,64150,120470,64154],{},[6862,120471,64153],{},[192,120473,120474],{"className":2166,"code":64157,"language":2168,"meta":13,"style":13},[156,120475,120476],{"__ignoreMap":13},[200,120477,120478,120480,120482],{"class":202,"line":203},[200,120479,36084],{"class":1364},[200,120481,32462],{"class":245},[200,120483,13704],{"class":245},[35,120485,64170,120486,64175],{},[39,120487,64173],{"href":64173,"rel":120488},[77],[51,120490,64179],{"id":64178},[35,120492,64182],{},[192,120494,120495],{"className":2166,"code":64185,"language":2168,"meta":13,"style":13},[156,120496,120497],{"__ignoreMap":13},[200,120498,120499,120501,120503,120505],{"class":202,"line":203},[200,120500,36084],{"class":1364},[200,120502,1368],{"class":245},[200,120504,64196],{"class":245},[200,120506,64199],{"class":245},[1929,120508,64203],{"id":64202},[35,120510,64206,120511,64209,120513,2873,120515,64216,120517,64220],{},[6862,120512,56503],{},[156,120514,64212],{},[156,120516,64215],{},[6862,120518,64219],{},[192,120520,120521],{"className":64223,"code":64224,"filename":64225,"language":64226,"meta":13,"style":13},[156,120522,120523,120551,120569,120573,120589,120603,120609,120617,120625,120641,120649,120665,120693,120741,120767,120775,120783,120787,120791,120811,120839,120843,120857,120863,120869,120875,120879,120893,120899,120905],{"__ignoreMap":13},[200,120524,120525,120527,120529,120531,120533,120535,120537,120539,120541,120543,120545,120547,120549],{"class":202,"line":203},[200,120526,972],{"class":206},[200,120528,318],{"class":214},[200,120530,64237],{"class":210},[200,120532,549],{"class":214},[200,120534,64242],{"class":210},[200,120536,549],{"class":214},[200,120538,64247],{"class":210},[200,120540,980],{"class":214},[200,120542,983],{"class":206},[200,120544,242],{"class":214},[200,120546,64256],{"class":245},[200,120548,249],{"class":214},[200,120550,64261],{"class":401},[200,120552,120553,120555,120557,120559,120561,120563,120565,120567],{"class":202,"line":14},[200,120554,972],{"class":206},[200,120556,318],{"class":214},[200,120558,64270],{"class":210},[200,120560,980],{"class":214},[200,120562,983],{"class":206},[200,120564,242],{"class":214},[200,120566,64279],{"class":245},[200,120568,991],{"class":214},[200,120570,120571],{"class":202,"line":255},[200,120572,528],{"emptyLinePlaceholder":20},[200,120574,120575,120577,120579,120581,120583,120585,120587],{"class":202,"line":272},[200,120576,1390],{"class":206},[200,120578,1393],{"class":206},[200,120580,64294],{"class":223},[200,120582,227],{"class":210},[200,120584,1549],{"class":214},[200,120586,393],{"class":379},[200,120588,280],{"class":214},[200,120590,120591,120593,120595,120597,120599,120601],{"class":202,"line":283},[200,120592,3738],{"class":379},[200,120594,64309],{"class":210},[200,120596,509],{"class":214},[200,120598,3729],{"class":214},[200,120600,393],{"class":379},[200,120602,280],{"class":214},[200,120604,120605,120607],{"class":202,"line":300},[200,120606,3311],{"class":206},[200,120608,64324],{"class":235},[200,120610,120611,120613,120615],{"class":202,"line":310},[200,120612,22071],{"class":214},[200,120614,11400],{"class":235},[200,120616,647],{"class":214},[200,120618,120619,120621,120623],{"class":202,"line":336},[200,120620,49441],{"class":214},[200,120622,64339],{"class":1364},[200,120624,647],{"class":214},[200,120626,120627,120629,120631,120633,120635,120637,120639],{"class":202,"line":342},[200,120628,49468],{"class":214},[200,120630,3581],{"class":235},[200,120632,674],{"class":214},[200,120634,64352],{"class":210},[200,120636,680],{"class":214},[200,120638,3581],{"class":235},[200,120640,647],{"class":214},[200,120642,120643,120645,120647],{"class":202,"line":365},[200,120644,49549],{"class":214},[200,120646,64339],{"class":1364},[200,120648,647],{"class":214},[200,120650,120651,120653,120655,120657,120659,120661,120663],{"class":202,"line":371},[200,120652,49441],{"class":214},[200,120654,51],{"class":235},[200,120656,674],{"class":214},[200,120658,64377],{"class":210},[200,120660,680],{"class":214},[200,120662,51],{"class":235},[200,120664,647],{"class":214},[200,120666,120667,120669,120671,120673,120675,120677,120679,120681,120683,120685,120687,120689,120691],{"class":202,"line":398},[200,120668,49441],{"class":214},[200,120670,602],{"class":235},[200,120672,40681],{"class":379},[200,120674,449],{"class":214},[200,120676,610],{"class":214},[200,120678,64398],{"class":245},[200,120680,610],{"class":214},[200,120682,6589],{"class":379},[200,120684,449],{"class":214},[200,120686,610],{"class":214},[200,120688,18779],{"class":245},[200,120690,610],{"class":214},[200,120692,647],{"class":214},[200,120694,120695,120697,120699,120701,120703,120705,120707,120709,120711,120713,120715,120717,120719,120721,120723,120725,120727,120729,120731,120733,120735,120737,120739],{"class":202,"line":405},[200,120696,49468],{"class":214},[200,120698,355],{"class":235},[200,120700,321],{"class":379},[200,120702,449],{"class":214},[200,120704,610],{"class":214},[200,120706,2035],{"class":245},[200,120708,610],{"class":214},[200,120710,700],{"class":379},[200,120712,449],{"class":214},[200,120714,610],{"class":214},[200,120716,2035],{"class":245},[200,120718,610],{"class":214},[200,120720,40711],{"class":379},[200,120722,449],{"class":214},[200,120724,610],{"class":214},[200,120726,64447],{"class":245},[200,120728,610],{"class":214},[200,120730,64452],{"class":379},[200,120732,449],{"class":214},[200,120734,610],{"class":214},[200,120736,64459],{"class":245},[200,120738,610],{"class":214},[200,120740,21527],{"class":214},[200,120742,120743,120745,120747,120749,120751,120753,120755,120757,120759,120761,120763,120765],{"class":202,"line":410},[200,120744,49468],{"class":214},[200,120746,909],{"class":235},[200,120748,321],{"class":379},[200,120750,449],{"class":214},[200,120752,610],{"class":214},[200,120754,916],{"class":245},[200,120756,610],{"class":214},[200,120758,674],{"class":214},[200,120760,64484],{"class":210},[200,120762,680],{"class":214},[200,120764,909],{"class":235},[200,120766,647],{"class":214},[200,120768,120769,120771,120773],{"class":202,"line":860},[200,120770,49549],{"class":214},[200,120772,602],{"class":235},[200,120774,647],{"class":214},[200,120776,120777,120779,120781],{"class":202,"line":889},[200,120778,43803],{"class":214},[200,120780,11400],{"class":235},[200,120782,647],{"class":214},[200,120784,120785],{"class":202,"line":899},[200,120786,3869],{"class":235},[200,120788,120789],{"class":202,"line":904},[200,120790,4505],{"class":214},[200,120792,120793,120795,120797,120799,120801,120803,120805,120807,120809],{"class":202,"line":930},[200,120794,3738],{"class":379},[200,120796,14334],{"class":210},[200,120798,509],{"class":214},[200,120800,64247],{"class":223},[200,120802,227],{"class":235},[200,120804,599],{"class":214},[200,120806,64531],{"class":1364},[200,120808,22099],{"class":214},[200,120810,416],{"class":235},[200,120812,120813,120815,120817,120819,120821,120823,120825,120827,120829,120831,120833,120835,120837],{"class":202,"line":3655},[200,120814,3738],{"class":379},[200,120816,318],{"class":214},[200,120818,40809],{"class":210},[200,120820,549],{"class":214},[200,120822,64548],{"class":210},[200,120824,980],{"class":214},[200,120826,509],{"class":214},[200,120828,64242],{"class":210},[200,120830,215],{"class":214},[200,120832,14496],{"class":223},[200,120834,227],{"class":235},[200,120836,26785],{"class":210},[200,120838,416],{"class":235},[200,120840,120841],{"class":202,"line":3660},[200,120842,528],{"emptyLinePlaceholder":20},[200,120844,120845,120847,120849,120851,120853,120855],{"class":202,"line":4035},[200,120846,3738],{"class":379},[200,120848,3272],{"class":210},[200,120850,509],{"class":214},[200,120852,64270],{"class":223},[200,120854,227],{"class":235},[200,120856,230],{"class":214},[200,120858,120859,120861],{"class":202,"line":4490},[200,120860,18788],{"class":210},[200,120862,252],{"class":214},[200,120864,120865,120867],{"class":202,"line":4496},[200,120866,64593],{"class":210},[200,120868,252],{"class":214},[200,120870,120871,120873],{"class":202,"line":4502},[200,120872,5095],{"class":214},[200,120874,416],{"class":235},[200,120876,120877],{"class":202,"line":4508},[200,120878,528],{"emptyLinePlaceholder":20},[200,120880,120881,120883,120885,120887,120889,120891],{"class":202,"line":5469},[200,120882,3758],{"class":206},[200,120884,38723],{"class":223},[200,120886,227],{"class":235},[200,120888,1549],{"class":214},[200,120890,393],{"class":379},[200,120892,280],{"class":214},[200,120894,120895,120897],{"class":202,"line":5474},[200,120896,3311],{"class":206},[200,120898,64626],{"class":210},[200,120900,120901,120903],{"class":202,"line":13237},[200,120902,5095],{"class":214},[200,120904,416],{"class":235},[200,120906,120907,120909],{"class":202,"line":13263},[200,120908,413],{"class":214},[200,120910,416],{"class":210},[35,120912,64641,120913,64645,120915,215],{},[6862,120914,64644],{},[156,120916,64398],{},[35,120918,64650,120919,64654,120921,64658],{},[6862,120920,64653],{},[6862,120922,64657],{},[35,120924,15736,120925,64664],{},[156,120926,64663],{},[192,120928,120929],{"className":1380,"code":64667,"filename":64668,"language":1383,"meta":13,"style":13},[156,120930,120931,120939,120947,120957,120961,120965,120985,121003,121007,121017,121021,121025,121029,121033,121037,121059,121063,121067,121071,121075,121079,121083,121087,121091,121099,121103,121107,121113],{"__ignoreMap":13},[200,120932,120933,120935,120937],{"class":202,"line":203},[200,120934,64675],{"class":379},[200,120936,64678],{"class":1364},[200,120938,280],{"class":214},[200,120940,120941,120943,120945],{"class":202,"line":14},[200,120942,64685],{"class":235},[200,120944,239],{"class":214},[200,120946,23708],{"class":1364},[200,120948,120949,120951,120953,120955],{"class":202,"line":255},[200,120950,64694],{"class":235},[200,120952,239],{"class":214},[200,120954,22752],{"class":1364},[200,120956,64701],{"class":210},[200,120958,120959],{"class":202,"line":272},[200,120960,1129],{"class":214},[200,120962,120963],{"class":202,"line":283},[200,120964,528],{"emptyLinePlaceholder":20},[200,120966,120967,120969,120971,120973,120975,120977,120979,120981,120983],{"class":202,"line":300},[200,120968,1390],{"class":206},[200,120970,5002],{"class":379},[200,120972,64270],{"class":223},[200,120974,227],{"class":214},[200,120976,23874],{"class":386},[200,120978,239],{"class":214},[200,120980,64678],{"class":1364},[200,120982,2104],{"class":214},[200,120984,280],{"class":214},[200,120986,120987,120989,120991,120993,120995,120997,120999,121001],{"class":202,"line":310},[200,120988,3738],{"class":379},[200,120990,318],{"class":214},[200,120992,64548],{"class":210},[200,120994,549],{"class":214},[200,120996,40809],{"class":210},[200,120998,980],{"class":214},[200,121000,509],{"class":214},[200,121002,64748],{"class":210},[200,121004,121005],{"class":202,"line":336},[200,121006,528],{"emptyLinePlaceholder":20},[200,121008,121009,121011,121013,121015],{"class":202,"line":342},[200,121010,3758],{"class":206},[200,121012,64759],{"class":401},[200,121014,3333],{"class":214},[200,121016,64764],{"class":245},[200,121018,121019],{"class":202,"line":365},[200,121020,64769],{"class":245},[200,121022,121023],{"class":202,"line":371},[200,121024,64774],{"class":245},[200,121026,121027],{"class":202,"line":398},[200,121028,64779],{"class":245},[200,121030,121031],{"class":202,"line":405},[200,121032,64784],{"class":245},[200,121034,121035],{"class":202,"line":410},[200,121036,64789],{"class":245},[200,121038,121039,121041,121043,121045,121047,121049,121051,121053,121055,121057],{"class":202,"line":860},[200,121040,64794],{"class":214},[200,121042,14578],{"class":210},[200,121044,215],{"class":214},[200,121046,6202],{"class":223},[200,121048,227],{"class":210},[200,121050,249],{"class":214},[200,121052,6098],{"class":210},[200,121054,249],{"class":214},[200,121056,2104],{"class":210},[200,121058,1129],{"class":214},[200,121060,121061],{"class":202,"line":889},[200,121062,64817],{"class":245},[200,121064,121065],{"class":202,"line":899},[200,121066,64822],{"class":245},[200,121068,121069],{"class":202,"line":904},[200,121070,64827],{"class":245},[200,121072,121073],{"class":202,"line":930},[200,121074,64832],{"class":245},[200,121076,121077],{"class":202,"line":3655},[200,121078,64837],{"class":245},[200,121080,121081],{"class":202,"line":3660},[200,121082,64842],{"class":245},[200,121084,121085],{"class":202,"line":4035},[200,121086,64847],{"class":245},[200,121088,121089],{"class":202,"line":4490},[200,121090,64852],{"class":245},[200,121092,121093,121095,121097],{"class":202,"line":4496},[200,121094,64857],{"class":214},[200,121096,23914],{"class":210},[200,121098,1129],{"class":214},[200,121100,121101],{"class":202,"line":4502},[200,121102,64866],{"class":245},[200,121104,121105],{"class":202,"line":4508},[200,121106,64871],{"class":245},[200,121108,121109,121111],{"class":202,"line":5469},[200,121110,64876],{"class":245},[200,121112,12772],{"class":214},[200,121114,121115],{"class":202,"line":5474},[200,121116,1129],{"class":214},[35,121118,64885,121119,64889,121121,215],{},[6862,121120,64888],{},[6862,121122,64256],{},[1929,121124,64895],{"id":64894},[35,121126,64898,121127,64901],{},[156,121128,19813],{},[192,121130,121131],{"className":2166,"code":64904,"language":2168,"meta":13,"style":13},[156,121132,121133],{"__ignoreMap":13},[200,121134,121135,121137,121139],{"class":202,"line":203},[200,121136,36084],{"class":1364},[200,121138,1368],{"class":245},[200,121140,64915],{"class":245},[35,121142,64918,121143,64921,121145,64924,121147,64927,121149,64931,121151,215],{},[156,121144,64398],{},[6862,121146,64644],{},[6862,121148,64021],{},[156,121150,64930],{},[156,121152,64215],{},[192,121154,121155],{"className":64223,"code":64936,"filename":64937,"language":64226,"meta":13,"style":13},[156,121156,121157,121183,121201,121221,121239,121243,121265,121293,121315,121327,121331,121347,121367,121401,121405,121439,121443,121457,121463,121471,121479,121495,121503,121519,121525,121537,121547,121551,121555,121563,121567,121571,121575,121595,121627,121631,121641,121651,121657,121663],{"__ignoreMap":13},[200,121158,121159,121161,121163,121165,121167,121169,121171,121173,121175,121177,121179,121181],{"class":202,"line":203},[200,121160,972],{"class":206},[200,121162,318],{"class":214},[200,121164,64237],{"class":210},[200,121166,549],{"class":214},[200,121168,64242],{"class":210},[200,121170,549],{"class":214},[200,121172,64247],{"class":210},[200,121174,980],{"class":214},[200,121176,983],{"class":206},[200,121178,242],{"class":214},[200,121180,64256],{"class":245},[200,121182,991],{"class":214},[200,121184,121185,121187,121189,121191,121193,121195,121197,121199],{"class":202,"line":14},[200,121186,972],{"class":206},[200,121188,318],{"class":214},[200,121190,56790],{"class":210},[200,121192,980],{"class":214},[200,121194,983],{"class":206},[200,121196,242],{"class":214},[200,121198,64065],{"class":245},[200,121200,991],{"class":214},[200,121202,121203,121205,121207,121209,121211,121213,121215,121217,121219],{"class":202,"line":255},[200,121204,972],{"class":206},[200,121206,318],{"class":214},[200,121208,3180],{"class":210},[200,121210,980],{"class":214},[200,121212,983],{"class":206},[200,121214,242],{"class":214},[200,121216,19813],{"class":245},[200,121218,249],{"class":214},[200,121220,65004],{"class":401},[200,121222,121223,121225,121227,121229,121231,121233,121235,121237],{"class":202,"line":272},[200,121224,972],{"class":206},[200,121226,318],{"class":214},[200,121228,64270],{"class":210},[200,121230,980],{"class":214},[200,121232,983],{"class":206},[200,121234,242],{"class":214},[200,121236,64279],{"class":245},[200,121238,991],{"class":214},[200,121240,121241],{"class":202,"line":283},[200,121242,528],{"emptyLinePlaceholder":20},[200,121244,121245,121247,121249,121251,121253,121255,121257,121259,121261,121263],{"class":202,"line":300},[200,121246,1390],{"class":206},[200,121248,1393],{"class":206},[200,121250,38723],{"class":223},[200,121252,227],{"class":210},[200,121254,3726],{"class":379},[200,121256,1094],{"class":214},[200,121258,25426],{"class":386},[200,121260,2104],{"class":214},[200,121262,393],{"class":379},[200,121264,280],{"class":214},[200,121266,121267,121269,121271,121273,121275,121277,121279,121281,121283,121285,121287,121289,121291],{"class":202,"line":310},[200,121268,3738],{"class":379},[200,121270,40809],{"class":210},[200,121272,509],{"class":214},[200,121274,452],{"class":206},[200,121276,40951],{"class":223},[200,121278,227],{"class":235},[200,121280,25426],{"class":210},[200,121282,549],{"class":214},[200,121284,3180],{"class":210},[200,121286,215],{"class":214},[200,121288,293],{"class":223},[200,121290,227],{"class":235},[200,121292,230],{"class":214},[200,121294,121295,121297,121299,121301,121303,121305,121307,121309,121311,121313],{"class":202,"line":336},[200,121296,5207],{"class":235},[200,121298,239],{"class":214},[200,121300,3180],{"class":210},[200,121302,215],{"class":214},[200,121304,328],{"class":223},[200,121306,1549],{"class":235},[200,121308,215],{"class":214},[200,121310,2035],{"class":223},[200,121312,1549],{"class":235},[200,121314,252],{"class":214},[200,121316,121317,121319,121321,121323,121325],{"class":202,"line":342},[200,121318,5095],{"class":214},[200,121320,2104],{"class":235},[200,121322,215],{"class":214},[200,121324,40994],{"class":210},[200,121326,416],{"class":235},[200,121328,121329],{"class":202,"line":365},[200,121330,528],{"emptyLinePlaceholder":20},[200,121332,121333,121335,121337,121339,121341,121343,121345],{"class":202,"line":371},[200,121334,3738],{"class":379},[200,121336,65121],{"class":210},[200,121338,509],{"class":214},[200,121340,46636],{"class":223},[200,121342,227],{"class":235},[200,121344,25426],{"class":210},[200,121346,416],{"class":235},[200,121348,121349,121351,121353,121355,121357,121359,121361,121363,121365],{"class":202,"line":398},[200,121350,3738],{"class":379},[200,121352,3239],{"class":210},[200,121354,509],{"class":214},[200,121356,56790],{"class":223},[200,121358,227],{"class":235},[200,121360,23914],{"class":210},[200,121362,215],{"class":214},[200,121364,2035],{"class":210},[200,121366,416],{"class":235},[200,121368,121369,121371,121373,121375,121377,121379,121381,121383,121385,121387,121389,121391,121393,121395,121397,121399],{"class":202,"line":405},[200,121370,3738],{"class":379},[200,121372,65158],{"class":210},[200,121374,509],{"class":214},[200,121376,13007],{"class":214},[200,121378,65165],{"class":223},[200,121380,227],{"class":235},[200,121382,3333],{"class":214},[200,121384,2898],{"class":245},[200,121386,4868],{"class":214},[200,121388,3295],{"class":210},[200,121390,23045],{"class":214},[200,121392,549],{"class":214},[200,121394,65121],{"class":210},[200,121396,2104],{"class":235},[200,121398,215],{"class":214},[200,121400,46647],{"class":210},[200,121402,121403],{"class":202,"line":410},[200,121404,528],{"emptyLinePlaceholder":20},[200,121406,121407,121409,121411,121413,121415,121417,121419,121421,121423,121425,121427,121429,121431,121433,121435,121437],{"class":202,"line":860},[200,121408,6177],{"class":206},[200,121410,65198],{"class":223},[200,121412,227],{"class":235},[200,121414,249],{"class":214},[200,121416,12736],{"class":245},[200,121418,249],{"class":214},[200,121420,2104],{"class":235},[200,121422,215],{"class":214},[200,121424,65213],{"class":223},[200,121426,227],{"class":235},[200,121428,3295],{"class":210},[200,121430,549],{"class":214},[200,121432,40809],{"class":210},[200,121434,215],{"class":214},[200,121436,2035],{"class":210},[200,121438,416],{"class":235},[200,121440,121441],{"class":202,"line":889},[200,121442,528],{"emptyLinePlaceholder":20},[200,121444,121445,121447,121449,121451,121453,121455],{"class":202,"line":899},[200,121446,3738],{"class":379},[200,121448,64309],{"class":210},[200,121450,509],{"class":214},[200,121452,3729],{"class":214},[200,121454,393],{"class":379},[200,121456,280],{"class":214},[200,121458,121459,121461],{"class":202,"line":904},[200,121460,3311],{"class":206},[200,121462,64324],{"class":235},[200,121464,121465,121467,121469],{"class":202,"line":930},[200,121466,22071],{"class":214},[200,121468,11400],{"class":235},[200,121470,647],{"class":214},[200,121472,121473,121475,121477],{"class":202,"line":3655},[200,121474,49441],{"class":214},[200,121476,64339],{"class":1364},[200,121478,647],{"class":214},[200,121480,121481,121483,121485,121487,121489,121491,121493],{"class":202,"line":3660},[200,121482,49468],{"class":214},[200,121484,3581],{"class":235},[200,121486,674],{"class":214},[200,121488,65278],{"class":210},[200,121490,680],{"class":214},[200,121492,3581],{"class":235},[200,121494,647],{"class":214},[200,121496,121497,121499,121501],{"class":202,"line":4035},[200,121498,49549],{"class":214},[200,121500,64339],{"class":1364},[200,121502,647],{"class":214},[200,121504,121505,121507,121509,121511,121513,121515,121517],{"class":202,"line":4490},[200,121506,49441],{"class":214},[200,121508,51],{"class":235},[200,121510,674],{"class":214},[200,121512,65303],{"class":210},[200,121514,680],{"class":214},[200,121516,51],{"class":235},[200,121518,647],{"class":214},[200,121520,121521,121523],{"class":202,"line":4496},[200,121522,49441],{"class":214},[200,121524,65316],{"class":235},[200,121526,121527,121529,121531,121533,121535],{"class":202,"line":4502},[200,121528,4929],{"class":379},[200,121530,449],{"class":214},[200,121532,610],{"class":214},[200,121534,697],{"class":245},[200,121536,616],{"class":214},[200,121538,121539,121541,121543,121545],{"class":202,"line":4508},[200,121540,65333],{"class":379},[200,121542,65336],{"class":214},[200,121544,65339],{"class":210},[200,121546,1129],{"class":214},[200,121548,121549],{"class":202,"line":5469},[200,121550,65346],{"class":379},[200,121552,121553],{"class":202,"line":5474},[200,121554,64789],{"class":214},[200,121556,121557,121559,121561],{"class":202,"line":13237},[200,121558,43803],{"class":214},[200,121560,11400],{"class":235},[200,121562,647],{"class":214},[200,121564,121565],{"class":202,"line":13263},[200,121566,3869],{"class":235},[200,121568,121569],{"class":202,"line":13279},[200,121570,4505],{"class":214},[200,121572,121573],{"class":202,"line":13323},[200,121574,528],{"emptyLinePlaceholder":20},[200,121576,121577,121579,121581,121583,121585,121587,121589,121591,121593],{"class":202,"line":13329},[200,121578,3738],{"class":379},[200,121580,14334],{"class":210},[200,121582,509],{"class":214},[200,121584,64247],{"class":223},[200,121586,227],{"class":235},[200,121588,599],{"class":214},[200,121590,64531],{"class":1364},[200,121592,22099],{"class":214},[200,121594,416],{"class":235},[200,121596,121597,121599,121601,121603,121605,121607,121609,121611,121613,121615,121617,121619,121621,121623,121625],{"class":202,"line":13334},[200,121598,3738],{"class":379},[200,121600,318],{"class":214},[200,121602,40809],{"class":235},[200,121604,239],{"class":214},[200,121606,65403],{"class":210},[200,121608,549],{"class":214},[200,121610,64548],{"class":210},[200,121612,980],{"class":214},[200,121614,509],{"class":214},[200,121616,64242],{"class":210},[200,121618,215],{"class":214},[200,121620,14496],{"class":223},[200,121622,227],{"class":235},[200,121624,26785],{"class":210},[200,121626,416],{"class":235},[200,121628,121629],{"class":202,"line":13360},[200,121630,528],{"emptyLinePlaceholder":20},[200,121632,121633,121635,121637,121639],{"class":202,"line":13365},[200,121634,3758],{"class":206},[200,121636,64270],{"class":223},[200,121638,227],{"class":235},[200,121640,230],{"class":214},[200,121642,121643,121645,121647,121649],{"class":202,"line":13385},[200,121644,18788],{"class":235},[200,121646,239],{"class":214},[200,121648,65403],{"class":210},[200,121650,252],{"class":214},[200,121652,121653,121655],{"class":202,"line":13405},[200,121654,64593],{"class":210},[200,121656,252],{"class":214},[200,121658,121659,121661],{"class":202,"line":13410},[200,121660,5095],{"class":214},[200,121662,416],{"class":235},[200,121664,121665,121667],{"class":202,"line":13418},[200,121666,413],{"class":214},[200,121668,416],{"class":210},[35,121670,65468,121671,24175,121673,65474,121675,65477],{},[156,121672,40889],{},[6862,121674,65473],{},[156,121676,2035],{},[35,121678,65480,121679,65484,121681,17891,121683,215],{},[6862,121680,65483],{},[156,121682,65487],{},[156,121684,1929],{},[35,121686,19439,121687,65494,121689,17891,121691,65499],{},[6862,121688,52842],{},[156,121690,52842],{},[6862,121692,64065],{},[35,121694,65502,121695,65484,121697,17891,121699,65510,121701,65513],{},[6862,121696,64021],{},[156,121698,65507],{},[6862,121700,64049],{},[156,121702,12736],{},[7918,121704,121705,121707,121711,121799,121813],{"text":65516,"href":65517},[35,121706,65520],{},[35,121708,65523,121709,65527],{},[156,121710,65526],{},[192,121712,121713],{"className":1380,"code":65530,"filename":65526,"language":1383,"meta":13,"style":13},[156,121714,121715,121727,121741,121749,121789,121793],{"__ignoreMap":13},[200,121716,121717,121719,121721,121723,121725],{"class":202,"line":203},[200,121718,1390],{"class":206},[200,121720,1393],{"class":206},[200,121722,65541],{"class":223},[200,121724,227],{"class":210},[200,121726,230],{"class":214},[200,121728,121729,121731,121733,121735,121737,121739],{"class":202,"line":14},[200,121730,65550],{"class":235},[200,121732,239],{"class":214},[200,121734,242],{"class":214},[200,121736,3123],{"class":245},[200,121738,249],{"class":214},[200,121740,252],{"class":214},[200,121742,121743,121745,121747],{"class":202,"line":255},[200,121744,65565],{"class":235},[200,121746,239],{"class":214},[200,121748,280],{"class":214},[200,121750,121751,121753,121755,121757,121759,121761,121763,121765,121767,121769,121771,121773,121775,121777,121779,121781,121783,121785,121787],{"class":202,"line":272},[200,121752,65574],{"class":235},[200,121754,239],{"class":214},[200,121756,318],{"class":214},[200,121758,5056],{"class":235},[200,121760,239],{"class":214},[200,121762,318],{"class":214},[200,121764,65587],{"class":235},[200,121766,239],{"class":214},[200,121768,242],{"class":214},[200,121770,65594],{"class":245},[200,121772,249],{"class":214},[200,121774,549],{"class":214},[200,121776,65601],{"class":235},[200,121778,239],{"class":214},[200,121780,242],{"class":214},[200,121782,65608],{"class":245},[200,121784,249],{"class":214},[200,121786,980],{"class":214},[200,121788,333],{"class":214},[200,121790,121791],{"class":202,"line":283},[200,121792,368],{"class":214},[200,121794,121795,121797],{"class":202,"line":300},[200,121796,413],{"class":214},[200,121798,416],{"class":210},[35,121800,65627,121801,65630,121803,65633,121805,65636,121807,65640,121809,65644,121811,215],{},[156,121802,12736],{},[6862,121804,65594],{},[156,121806,65608],{},[156,121808,65639],{},[6862,121810,65643],{},[6862,121812,38550],{},[7918,121814,121815,121817,121823,122003,122015,122017,122023],{"text":65649,"href":65650},[1929,121816,65654],{"id":65653},[35,121818,65657,121819,65661,121821,215],{},[156,121820,65660],{},[156,121822,64215],{},[192,121824,121825],{"className":1380,"code":65666,"filename":65667,"language":1383,"meta":13,"style":13},[156,121826,121827,121849,121873,121877,121915,121919,121933,121943,121953,121967,121973,121977,121981,121997],{"__ignoreMap":13},[200,121828,121829,121831,121833,121835,121837,121839,121841,121843,121845,121847],{"class":202,"line":203},[200,121830,1390],{"class":206},[200,121832,1393],{"class":206},[200,121834,38723],{"class":223},[200,121836,227],{"class":210},[200,121838,3726],{"class":379},[200,121840,1094],{"class":214},[200,121842,25426],{"class":386},[200,121844,2104],{"class":214},[200,121846,393],{"class":379},[200,121848,280],{"class":214},[200,121850,121851,121853,121855,121857,121859,121861,121863,121865,121867,121869,121871],{"class":202,"line":14},[200,121852,3738],{"class":379},[200,121854,3239],{"class":210},[200,121856,509],{"class":214},[200,121858,65702],{"class":223},[200,121860,227],{"class":235},[200,121862,25426],{"class":210},[200,121864,549],{"class":214},[200,121866,242],{"class":214},[200,121868,65713],{"class":245},[200,121870,249],{"class":214},[200,121872,416],{"class":235},[200,121874,121875],{"class":202,"line":255},[200,121876,528],{"emptyLinePlaceholder":20},[200,121878,121879,121881,121883,121885,121887,121889,121891,121893,121895,121897,121899,121901,121903,121905,121907,121909,121911,121913],{"class":202,"line":272},[200,121880,3738],{"class":379},[200,121882,809],{"class":210},[200,121884,509],{"class":214},[200,121886,452],{"class":206},[200,121888,65198],{"class":223},[200,121890,599],{"class":214},[200,121892,328],{"class":1364},[200,121894,674],{"class":214},[200,121896,227],{"class":235},[200,121898,249],{"class":214},[200,121900,12736],{"class":245},[200,121902,249],{"class":214},[200,121904,2104],{"class":235},[200,121906,215],{"class":214},[200,121908,14971],{"class":223},[200,121910,227],{"class":235},[200,121912,3295],{"class":210},[200,121914,416],{"class":235},[200,121916,121917],{"class":202,"line":283},[200,121918,528],{"emptyLinePlaceholder":20},[200,121920,121921,121923,121925,121927,121929,121931],{"class":202,"line":300},[200,121922,20098],{"class":206},[200,121924,1094],{"class":235},[200,121926,11852],{"class":214},[200,121928,15840],{"class":210},[200,121930,5282],{"class":235},[200,121932,230],{"class":214},[200,121934,121935,121937,121939,121941],{"class":202,"line":310},[200,121936,39200],{"class":206},[200,121938,39203],{"class":223},[200,121940,227],{"class":235},[200,121942,230],{"class":214},[200,121944,121945,121947,121949,121951],{"class":202,"line":336},[200,121946,39212],{"class":235},[200,121948,239],{"class":214},[200,121950,40347],{"class":2060},[200,121952,252],{"class":214},[200,121954,121955,121957,121959,121961,121963,121965],{"class":202,"line":342},[200,121956,65802],{"class":235},[200,121958,239],{"class":214},[200,121960,242],{"class":214},[200,121962,40360],{"class":245},[200,121964,249],{"class":214},[200,121966,252],{"class":214},[200,121968,121969,121971],{"class":202,"line":365},[200,121970,2111],{"class":214},[200,121972,416],{"class":235},[200,121974,121975],{"class":202,"line":371},[200,121976,4505],{"class":214},[200,121978,121979],{"class":202,"line":398},[200,121980,528],{"emptyLinePlaceholder":20},[200,121982,121983,121985,121987,121989,121991,121993,121995],{"class":202,"line":405},[200,121984,3758],{"class":206},[200,121986,46075],{"class":223},[200,121988,227],{"class":235},[200,121990,25426],{"class":210},[200,121992,549],{"class":214},[200,121994,809],{"class":210},[200,121996,416],{"class":235},[200,121998,121999,122001],{"class":202,"line":410},[200,122000,413],{"class":214},[200,122002,416],{"class":210},[35,122004,65851,122005,65854,122007,65857,122009,26733,122011,65863,122013,65867],{},[156,122006,3295],{},[156,122008,65507],{},[6862,122010,65860],{},[6862,122012,64021],{},[6862,122014,65866],{},[51,122016,65871],{"id":65870},[35,122018,65874,122019,65878,122021,215],{},[6862,122020,65877],{},[6862,122022,65881],{},[7918,122024,122025,122031,122041,122045,122055,122065,122071,122073,122079,122797,122801,122816,122818,122828,122834],{"text":65884,"href":65885},[938,122026,122027],{},[35,122028,122029,65892],{},[200,122030,29427],{},[35,122032,65895,122033,65899,122035,65902,122037,10703,122039,65909],{},[156,122034,65898],{},[156,122036,5154],{},[156,122038,65905],{},[156,122040,65908],{},[35,122042,65912,122043,215],{},[6862,122044,38535],{},[35,122046,122047,65920,122049,65924,122051,65928,122053,65931],{},[6862,122048,65919],{},[6862,122050,65923],{},[6862,122052,65927],{},[156,122054,65608],{},[938,122056,122057],{},[35,122058,122059,65938,122061,65941,122063,65945],{},[200,122060,944],{},[6862,122062,65923],{},[6862,122064,65944],{},[938,122066,122067],{},[35,122068,122069,65952],{},[200,122070,944],{},[51,122072,65956],{"id":65955},[35,122074,65959,122075,65963,122077,65966],{},[6862,122076,65962],{},[156,122078,64930],{},[192,122080,122081],{"className":64223,"code":65969,"filename":64937,"language":64226,"meta":13,"style":13},[156,122082,122083,122109,122127,122145,122163,122167,122179,122197,122217,122241,122245,122259,122269,122279,122293,122299,122303,122307,122323,122333,122343,122357,122363,122367,122371,122389,122417,122439,122451,122455,122471,122491,122525,122529,122563,122567,122581,122587,122595,122603,122619,122627,122643,122649,122661,122671,122675,122679,122687,122691,122695,122699,122719,122751,122755,122765,122775,122781,122787,122791],{"__ignoreMap":13},[200,122084,122085,122087,122089,122091,122093,122095,122097,122099,122101,122103,122105,122107],{"class":202,"line":203},[200,122086,972],{"class":206},[200,122088,318],{"class":214},[200,122090,64237],{"class":210},[200,122092,549],{"class":214},[200,122094,64242],{"class":210},[200,122096,549],{"class":214},[200,122098,64247],{"class":210},[200,122100,980],{"class":214},[200,122102,983],{"class":206},[200,122104,242],{"class":214},[200,122106,64256],{"class":245},[200,122108,991],{"class":214},[200,122110,122111,122113,122115,122117,122119,122121,122123,122125],{"class":202,"line":14},[200,122112,972],{"class":206},[200,122114,318],{"class":214},[200,122116,56790],{"class":210},[200,122118,980],{"class":214},[200,122120,983],{"class":206},[200,122122,242],{"class":214},[200,122124,64065],{"class":245},[200,122126,991],{"class":214},[200,122128,122129,122131,122133,122135,122137,122139,122141,122143],{"class":202,"line":255},[200,122130,972],{"class":206},[200,122132,318],{"class":214},[200,122134,3180],{"class":210},[200,122136,980],{"class":214},[200,122138,983],{"class":206},[200,122140,242],{"class":214},[200,122142,19813],{"class":245},[200,122144,991],{"class":214},[200,122146,122147,122149,122151,122153,122155,122157,122159,122161],{"class":202,"line":272},[200,122148,972],{"class":206},[200,122150,318],{"class":214},[200,122152,64270],{"class":210},[200,122154,980],{"class":214},[200,122156,983],{"class":206},[200,122158,242],{"class":214},[200,122160,64279],{"class":245},[200,122162,991],{"class":214},[200,122164,122165],{"class":202,"line":283},[200,122166,528],{"emptyLinePlaceholder":20},[200,122168,122169,122171,122173,122175,122177],{"class":202,"line":300},[200,122170,1390],{"class":206},[200,122172,1393],{"class":206},[200,122174,38723],{"class":223},[200,122176,227],{"class":210},[200,122178,230],{"class":214},[200,122180,122181,122183,122185,122187,122189,122191,122193,122195],{"class":202,"line":310},[200,122182,66072],{"class":223},[200,122184,239],{"class":214},[200,122186,380],{"class":379},[200,122188,1094],{"class":214},[200,122190,25426],{"class":386},[200,122192,2104],{"class":214},[200,122194,393],{"class":379},[200,122196,280],{"class":214},[200,122198,122199,122201,122203,122205,122207,122209,122211,122213,122215],{"class":202,"line":336},[200,122200,2014],{"class":379},[200,122202,65121],{"class":210},[200,122204,509],{"class":214},[200,122206,46636],{"class":223},[200,122208,227],{"class":235},[200,122210,25426],{"class":210},[200,122212,2104],{"class":235},[200,122214,215],{"class":214},[200,122216,66107],{"class":210},[200,122218,122219,122221,122223,122225,122227,122229,122231,122233,122235,122237,122239],{"class":202,"line":342},[200,122220,2014],{"class":379},[200,122222,66114],{"class":210},[200,122224,509],{"class":214},[200,122226,66119],{"class":223},[200,122228,227],{"class":235},[200,122230,25426],{"class":210},[200,122232,549],{"class":214},[200,122234,242],{"class":214},[200,122236,66130],{"class":245},[200,122238,249],{"class":214},[200,122240,416],{"class":235},[200,122242,122243],{"class":202,"line":365},[200,122244,528],{"emptyLinePlaceholder":20},[200,122246,122247,122249,122251,122253,122255,122257],{"class":202,"line":371},[200,122248,5267],{"class":206},[200,122250,1094],{"class":235},[200,122252,11852],{"class":214},[200,122254,66130],{"class":210},[200,122256,5282],{"class":235},[200,122258,230],{"class":214},[200,122260,122261,122263,122265,122267],{"class":202,"line":398},[200,122262,20250],{"class":206},[200,122264,39203],{"class":223},[200,122266,227],{"class":235},[200,122268,230],{"class":214},[200,122270,122271,122273,122275,122277],{"class":202,"line":405},[200,122272,66167],{"class":235},[200,122274,239],{"class":214},[200,122276,41198],{"class":2060},[200,122278,252],{"class":214},[200,122280,122281,122283,122285,122287,122289,122291],{"class":202,"line":410},[200,122282,66178],{"class":235},[200,122284,239],{"class":214},[200,122286,242],{"class":214},[200,122288,66185],{"class":245},[200,122290,249],{"class":214},[200,122292,252],{"class":214},[200,122294,122295,122297],{"class":202,"line":860},[200,122296,2647],{"class":214},[200,122298,416],{"class":235},[200,122300,122301],{"class":202,"line":889},[200,122302,4977],{"class":214},[200,122304,122305],{"class":202,"line":899},[200,122306,528],{"emptyLinePlaceholder":20},[200,122308,122309,122311,122313,122315,122317,122319,122321],{"class":202,"line":904},[200,122310,5267],{"class":206},[200,122312,1094],{"class":235},[200,122314,66130],{"class":210},[200,122316,13398],{"class":214},[200,122318,65121],{"class":210},[200,122320,5282],{"class":235},[200,122322,230],{"class":214},[200,122324,122325,122327,122329,122331],{"class":202,"line":930},[200,122326,20250],{"class":206},[200,122328,39203],{"class":223},[200,122330,227],{"class":235},[200,122332,230],{"class":214},[200,122334,122335,122337,122339,122341],{"class":202,"line":3655},[200,122336,66167],{"class":235},[200,122338,239],{"class":214},[200,122340,39226],{"class":2060},[200,122342,252],{"class":214},[200,122344,122345,122347,122349,122351,122353,122355],{"class":202,"line":3660},[200,122346,66178],{"class":235},[200,122348,239],{"class":214},[200,122350,242],{"class":214},[200,122352,39727],{"class":245},[200,122354,249],{"class":214},[200,122356,252],{"class":214},[200,122358,122359,122361],{"class":202,"line":4035},[200,122360,2647],{"class":214},[200,122362,416],{"class":235},[200,122364,122365],{"class":202,"line":4490},[200,122366,4977],{"class":214},[200,122368,122369],{"class":202,"line":4496},[200,122370,368],{"class":214},[200,122372,122373,122375,122377,122379,122381,122383,122385,122387],{"class":202,"line":4502},[200,122374,66272],{"class":223},[200,122376,239],{"class":214},[200,122378,380],{"class":379},[200,122380,1094],{"class":214},[200,122382,25426],{"class":386},[200,122384,2104],{"class":214},[200,122386,393],{"class":379},[200,122388,280],{"class":214},[200,122390,122391,122393,122395,122397,122399,122401,122403,122405,122407,122409,122411,122413,122415],{"class":202,"line":4508},[200,122392,2014],{"class":379},[200,122394,40809],{"class":210},[200,122396,509],{"class":214},[200,122398,452],{"class":206},[200,122400,40951],{"class":223},[200,122402,227],{"class":235},[200,122404,25426],{"class":210},[200,122406,549],{"class":214},[200,122408,3180],{"class":210},[200,122410,215],{"class":214},[200,122412,293],{"class":223},[200,122414,227],{"class":235},[200,122416,230],{"class":214},[200,122418,122419,122421,122423,122425,122427,122429,122431,122433,122435,122437],{"class":202,"line":5469},[200,122420,3620],{"class":235},[200,122422,239],{"class":214},[200,122424,3180],{"class":210},[200,122426,215],{"class":214},[200,122428,328],{"class":223},[200,122430,1549],{"class":235},[200,122432,215],{"class":214},[200,122434,2035],{"class":223},[200,122436,1549],{"class":235},[200,122438,252],{"class":214},[200,122440,122441,122443,122445,122447,122449],{"class":202,"line":5474},[200,122442,2111],{"class":214},[200,122444,2104],{"class":235},[200,122446,215],{"class":214},[200,122448,40994],{"class":210},[200,122450,416],{"class":235},[200,122452,122453],{"class":202,"line":13237},[200,122454,528],{"emptyLinePlaceholder":20},[200,122456,122457,122459,122461,122463,122465,122467,122469],{"class":202,"line":13263},[200,122458,2014],{"class":379},[200,122460,65121],{"class":210},[200,122462,509],{"class":214},[200,122464,46636],{"class":223},[200,122466,227],{"class":235},[200,122468,25426],{"class":210},[200,122470,416],{"class":235},[200,122472,122473,122475,122477,122479,122481,122483,122485,122487,122489],{"class":202,"line":13279},[200,122474,2014],{"class":379},[200,122476,3239],{"class":210},[200,122478,509],{"class":214},[200,122480,56790],{"class":223},[200,122482,227],{"class":235},[200,122484,23914],{"class":210},[200,122486,215],{"class":214},[200,122488,2035],{"class":210},[200,122490,416],{"class":235},[200,122492,122493,122495,122497,122499,122501,122503,122505,122507,122509,122511,122513,122515,122517,122519,122521,122523],{"class":202,"line":13323},[200,122494,2014],{"class":379},[200,122496,65158],{"class":210},[200,122498,509],{"class":214},[200,122500,13007],{"class":214},[200,122502,65165],{"class":223},[200,122504,227],{"class":235},[200,122506,3333],{"class":214},[200,122508,2898],{"class":245},[200,122510,4868],{"class":214},[200,122512,3295],{"class":210},[200,122514,23045],{"class":214},[200,122516,549],{"class":214},[200,122518,65121],{"class":210},[200,122520,2104],{"class":235},[200,122522,215],{"class":214},[200,122524,46647],{"class":210},[200,122526,122527],{"class":202,"line":13329},[200,122528,528],{"emptyLinePlaceholder":20},[200,122530,122531,122533,122535,122537,122539,122541,122543,122545,122547,122549,122551,122553,122555,122557,122559,122561],{"class":202,"line":13334},[200,122532,19231],{"class":206},[200,122534,65198],{"class":223},[200,122536,227],{"class":235},[200,122538,249],{"class":214},[200,122540,12736],{"class":245},[200,122542,249],{"class":214},[200,122544,2104],{"class":235},[200,122546,215],{"class":214},[200,122548,65213],{"class":223},[200,122550,227],{"class":235},[200,122552,3295],{"class":210},[200,122554,549],{"class":214},[200,122556,40809],{"class":210},[200,122558,215],{"class":214},[200,122560,2035],{"class":210},[200,122562,416],{"class":235},[200,122564,122565],{"class":202,"line":13360},[200,122566,528],{"emptyLinePlaceholder":20},[200,122568,122569,122571,122573,122575,122577,122579],{"class":202,"line":13365},[200,122570,2014],{"class":379},[200,122572,64309],{"class":210},[200,122574,509],{"class":214},[200,122576,3729],{"class":214},[200,122578,393],{"class":379},[200,122580,280],{"class":214},[200,122582,122583,122585],{"class":202,"line":13385},[200,122584,11730],{"class":206},[200,122586,64324],{"class":235},[200,122588,122589,122591,122593],{"class":202,"line":13405},[200,122590,49441],{"class":214},[200,122592,11400],{"class":235},[200,122594,647],{"class":214},[200,122596,122597,122599,122601],{"class":202,"line":13410},[200,122598,49468],{"class":214},[200,122600,64339],{"class":1364},[200,122602,647],{"class":214},[200,122604,122605,122607,122609,122611,122613,122615,122617],{"class":202,"line":13418},[200,122606,66505],{"class":214},[200,122608,3581],{"class":235},[200,122610,674],{"class":214},[200,122612,65278],{"class":210},[200,122614,680],{"class":214},[200,122616,3581],{"class":235},[200,122618,647],{"class":214},[200,122620,122621,122623,122625],{"class":202,"line":13436},[200,122622,49540],{"class":214},[200,122624,64339],{"class":1364},[200,122626,647],{"class":214},[200,122628,122629,122631,122633,122635,122637,122639,122641],{"class":202,"line":13466},[200,122630,49468],{"class":214},[200,122632,51],{"class":235},[200,122634,674],{"class":214},[200,122636,65303],{"class":210},[200,122638,680],{"class":214},[200,122640,51],{"class":235},[200,122642,647],{"class":214},[200,122644,122645,122647],{"class":202,"line":13472},[200,122646,49468],{"class":214},[200,122648,65316],{"class":235},[200,122650,122651,122653,122655,122657,122659],{"class":202,"line":13477},[200,122652,66552],{"class":379},[200,122654,449],{"class":214},[200,122656,610],{"class":214},[200,122658,697],{"class":245},[200,122660,616],{"class":214},[200,122662,122663,122665,122667,122669],{"class":202,"line":13482},[200,122664,66565],{"class":379},[200,122666,65336],{"class":214},[200,122668,65339],{"class":210},[200,122670,1129],{"class":214},[200,122672,122673],{"class":202,"line":13497},[200,122674,66576],{"class":379},[200,122676,122677],{"class":202,"line":13508},[200,122678,66581],{"class":214},[200,122680,122681,122683,122685],{"class":202,"line":13513},[200,122682,49549],{"class":214},[200,122684,11400],{"class":235},[200,122686,647],{"class":214},[200,122688,122689],{"class":202,"line":13518},[200,122690,46766],{"class":235},[200,122692,122693],{"class":202,"line":20247},[200,122694,4977],{"class":214},[200,122696,122697],{"class":202,"line":20269},[200,122698,528],{"emptyLinePlaceholder":20},[200,122700,122701,122703,122705,122707,122709,122711,122713,122715,122717],{"class":202,"line":20274},[200,122702,2014],{"class":379},[200,122704,14334],{"class":210},[200,122706,509],{"class":214},[200,122708,64247],{"class":223},[200,122710,227],{"class":235},[200,122712,599],{"class":214},[200,122714,64531],{"class":1364},[200,122716,22099],{"class":214},[200,122718,416],{"class":235},[200,122720,122721,122723,122725,122727,122729,122731,122733,122735,122737,122739,122741,122743,122745,122747,122749],{"class":202,"line":20279},[200,122722,2014],{"class":379},[200,122724,318],{"class":214},[200,122726,40809],{"class":235},[200,122728,239],{"class":214},[200,122730,65403],{"class":210},[200,122732,549],{"class":214},[200,122734,64548],{"class":210},[200,122736,980],{"class":214},[200,122738,509],{"class":214},[200,122740,64242],{"class":210},[200,122742,215],{"class":214},[200,122744,14496],{"class":223},[200,122746,227],{"class":235},[200,122748,26785],{"class":210},[200,122750,416],{"class":235},[200,122752,122753],{"class":202,"line":20284},[200,122754,528],{"emptyLinePlaceholder":20},[200,122756,122757,122759,122761,122763],{"class":202,"line":20305},[200,122758,3311],{"class":206},[200,122760,64270],{"class":223},[200,122762,227],{"class":235},[200,122764,230],{"class":214},[200,122766,122767,122769,122771,122773],{"class":202,"line":20312},[200,122768,41853],{"class":235},[200,122770,239],{"class":214},[200,122772,65403],{"class":210},[200,122774,252],{"class":214},[200,122776,122777,122779],{"class":202,"line":20317},[200,122778,66682],{"class":210},[200,122780,252],{"class":214},[200,122782,122783,122785],{"class":202,"line":20333},[200,122784,2111],{"class":214},[200,122786,416],{"class":235},[200,122788,122789],{"class":202,"line":46974},[200,122790,368],{"class":214},[200,122792,122793,122795],{"class":202,"line":46979},[200,122794,413],{"class":214},[200,122796,416],{"class":210},[35,122798,66703,122799,66707],{},[6862,122800,66706],{},[938,122802,122803],{},[35,122804,122805,66714,122807,66717,122809,66723,122812,66726,122814,66730],{},[200,122806,944],{},[6862,122808,65962],{},[39,122810,66722],{"href":66720,"rel":122811},[77],[6862,122813,66706],{},[6862,122815,66729],{},[51,122817,24146],{"id":24145},[35,122819,66735,122820,1655,122822,66740,122824,66743,122826,215],{},[6862,122821,36573],{},[6862,122823,38535],{},[6862,122825,64085],{},[6862,122827,64056],{},[35,122829,66748,122830,1655,122832,66753],{},[6862,122831,36573],{},[6862,122833,38535],{},[938,122835,122836],{},[35,122837,122838,66760,122840,215],{},[200,122839,944],{},[39,122841,66765],{"href":66763,"rel":122842},[77],[1170,122844,66768],{},{"title":13,"searchDepth":14,"depth":14,"links":122846},[122847,122848],{"id":64098,"depth":14,"text":64099},{"id":64178,"depth":14,"text":64179,"children":122849},[122850,122851],{"id":64202,"depth":255,"text":64203},{"id":64894,"depth":255,"text":64895},{},{"title":63974,"description":66777},{"loc":66779},1790086897824]